From b20aaee8fb883894a288372f167076aeb4fb637f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:57:07 +0900
Subject: [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER
 ROLE

This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is.
---
 doc/src/sgml/ref/alter_role.sgml  |  2 ++
 doc/src/sgml/ref/create_role.sgml | 19 +++++++++++
 src/backend/commands/user.c       | 72 ++++++++++++++++++++++++++++++++++++---
 src/backend/parser/gram.y         |  7 ++++
 4 files changed, 95 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
     | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+    | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
 
 ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
       <term><literal>NOBYPASSRLS</literal></term>
       <term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
       <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+      <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
       <term><literal>ENCRYPTED</></term>
       <term><literal>UNENCRYPTED</></term>
       <term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 93f0763..fa74466 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
     | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+    | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
     | IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
     | IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,24 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
      </varlistentry>
 
      <varlistentry>
+      <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+      <listitem>
+       <para>
+        Sets the role's password using the requested protocol.  (A password
+        is only of use for roles having the <literal>LOGIN</literal>
+        attribute, but you can nonetheless define one for roles without it.)
+        If you do not plan to use password authentication you can omit this
+        option. The protocols supported are <literal>md5</> to enforce
+        a password to be MD5-encrypted, <literal>scram</> to enforce a password
+        to be encrypted with SCRAM-SHA256, or <literal>plain</> to use
+        an unencrypted password.   If the password string is already in
+        MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted
+        as-is.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
       <listitem>
        <para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c76d273..e7a4b8f 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -179,7 +179,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 
 		if (strcmp(defel->defname, "password") == 0 ||
 			strcmp(defel->defname, "encryptedPassword") == 0 ||
-			strcmp(defel->defname, "unencryptedPassword") == 0)
+			strcmp(defel->defname, "unencryptedPassword") == 0 ||
+			strcmp(defel->defname, "protocolPassword") == 0)
 		{
 			if (dpassword)
 				ereport(ERROR,
@@ -188,9 +189,41 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 						 parser_errposition(pstate, defel->location)));
 			dpassword = defel;
 			if (strcmp(defel->defname, "encryptedPassword") == 0)
+			{
 				password_type = PASSWORD_TYPE_MD5;
+				if (dpassword && dpassword->arg)
+					password = strVal(dpassword->arg);
+			}
 			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+			{
 				password_type = PASSWORD_TYPE_PLAINTEXT;
+				if (dpassword && dpassword->arg)
+					password = strVal(dpassword->arg);
+			}
+			else if (strcmp(defel->defname, "protocolPassword") == 0)
+			{
+				/*
+				 * This is a list of two elements, the password is first and
+				 * then there is the protocol wanted by caller.
+				 */
+				if (dpassword && dpassword->arg)
+				{
+					char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+					password = strVal(linitial((List *) dpassword->arg));
+
+					if (strcmp(protocol, "md5") == 0)
+						password_type = PASSWORD_TYPE_MD5;
+					else if (strcmp(protocol, "plain") == 0)
+						password_type = PASSWORD_TYPE_PLAINTEXT;
+					else if (strcmp(protocol, "scram") == 0)
+						password_type = PASSWORD_TYPE_SCRAM;
+					else
+						ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("unsupported password protocol %s", protocol)));
+				}
+			}
 		}
 		else if (strcmp(defel->defname, "sysid") == 0)
 		{
@@ -310,8 +343,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg) != 0;
 	if (dinherit)
@@ -586,6 +617,7 @@ AlterRole(AlterRoleStmt *stmt)
 
 		if (strcmp(defel->defname, "password") == 0 ||
 			strcmp(defel->defname, "encryptedPassword") == 0 ||
+			strcmp(defel->defname, "protocolPassword") == 0 ||
 			strcmp(defel->defname, "unencryptedPassword") == 0)
 		{
 			if (dpassword)
@@ -594,9 +626,41 @@ AlterRole(AlterRoleStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			dpassword = defel;
 			if (strcmp(defel->defname, "encryptedPassword") == 0)
+			{
 				password_type = PASSWORD_TYPE_MD5;
+				if (dpassword && dpassword->arg)
+					password = strVal(dpassword->arg);
+			}
 			else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+			{
 				password_type = PASSWORD_TYPE_PLAINTEXT;
+				if (dpassword && dpassword->arg)
+					password = strVal(dpassword->arg);
+			}
+			else if (strcmp(defel->defname, "protocolPassword") == 0)
+			{
+				/*
+				 * This is a list of two elements, the password is first and
+				 * then there is the protocol wanted by caller.
+				 */
+				if (dpassword && dpassword->arg)
+				{
+					char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+					if (strcmp(protocol, "md5") == 0)
+						password_type = PASSWORD_TYPE_MD5;
+					else if (strcmp(protocol, "plain") == 0)
+						password_type = PASSWORD_TYPE_PLAINTEXT;
+					else if (strcmp(protocol, "scram") == 0)
+						password_type = PASSWORD_TYPE_SCRAM;
+					else
+						ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("unsupported password protocol %s", protocol)));
+
+					password = strVal(linitial((List *) dpassword->arg));
+				}
+			}
 		}
 		else if (strcmp(defel->defname, "superuser") == 0)
 		{
@@ -684,8 +748,6 @@ AlterRole(AlterRoleStmt *stmt)
 				 defel->defname);
 	}
 
-	if (dpassword && dpassword->arg)
-		password = strVal(dpassword->arg);
 	if (dissuper)
 		issuper = intVal(dissuper->arg);
 	if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..bed09f4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,13 @@ AlterOptRoleElem:
 				{
 					$$ = makeDefElem("password", NULL, @1);
 				}
+			| PASSWORD Sconst USING Sconst
+				{
+					$$ = makeDefElem("protocolPassword",
+									 (Node *)list_make2(makeString($2),
+														makeString($4)),
+									 @1);
+				}
 			| ENCRYPTED PASSWORD Sconst
 				{
 					$$ = makeDefElem("encryptedPassword",
-- 
2.9.3

