diff --git a/.gitignore b/.gitignore
index a2799fefbe1..c5195b901a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,10 +72,5 @@ lib*.pc
/CMakeLists.txt
/compile_commands.json
/tmp_install/
-<<<<<<< HEAD
/.cache/
/install/
-=======
-/install/
-/.cache/
->>>>>>> 77c11b4beab (update .gitignore.)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 688e7781737..dc050a76f38 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6911,6 +6911,16 @@ SCRAM-SHA-256$<iteration count>:&l
+
+
+ SHARED_DEPENDENCY_PROFILE (t)
+
+
+ The referenced object (which must be a profile) is mentioned as
+ the profile for a role using.
+
+
+
Other dependency flavors might be needed in future. Note in particular
diff --git a/gpMgmt/bin/gpcheckcat b/gpMgmt/bin/gpcheckcat
index 3df54dec0ea..4eb5a04152f 100755
--- a/gpMgmt/bin/gpcheckcat
+++ b/gpMgmt/bin/gpcheckcat
@@ -1568,6 +1568,12 @@ def checkTableInconsistentEntry(cat):
if catname == "pg_authid":
columns.remove("rolpassword")
castcols.remove("rolpassword")
+ columns.remove("rolpasswordsetat")
+ castcols.remove("rolpasswordsetat")
+ columns.remove("rollockdate")
+ castcols.remove("rollockdate")
+ columns.remove("rolpasswordexpire")
+ castcols.remove("rolpasswordexpire")
if cat.tableHasConsistentOids():
qry = inconsistentEntryQuery(GV.max_content, catname, ['oid'], columns, castcols)
@@ -2277,6 +2283,7 @@ TableMainColumn['pg_proc_callback'] = ['profnoid', 'pg_proc']
TableMainColumn['pg_statistic_ext_data'] = ['stxoid', 'pg_statistic_ext']
TableMainColumn['pg_type_encoding'] = ['typid', 'pg_type']
TableMainColumn['pg_window'] = ['winfnoid', 'pg_proc']
+TableMainColumn['pg_password_history'] = ['passhistroleid', 'pg_authid']
# Table with OID (special case), these OIDs are known to be inconsistent
TableMainColumn['pg_attrdef'] = ['adrelid', 'pg_class']
@@ -2915,7 +2922,7 @@ class GPObject:
# Report missing issues
if len(self.missingIssues):
- omitlist = ['pg_attribute', 'pg_attribute_encoding', 'pg_type', 'pg_appendonly', 'pg_index']
+ omitlist = ['pg_attribute', 'pg_attribute_encoding', 'pg_type', 'pg_appendonly', 'pg_index', 'pg_password_history']
if 'pg_class' in self.missingIssues:
myprint(' Name of test which found this issue: missing_extraneous_pg_class')
for name in omitlist:
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 6b800053526..e8b1560acc8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -91,7 +91,8 @@ CATALOG_HEADERS := \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
- pg_subscription_rel.h gp_partition_template.h pg_task.h pg_task_run_history.h
+ pg_subscription_rel.h gp_partition_template.h pg_task.h pg_task_run_history.h \
+ pg_profile.h pg_password_history.h
USE_INTERNAL_FTS_FOUND := $(if $(findstring USE_INTERNAL_FTS,$(CFLAGS)),true,false)
@@ -117,7 +118,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
pg_proc.dat pg_range.dat pg_tablespace.dat \
pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
- pg_ts_template.dat pg_type.dat \
+ pg_ts_template.dat pg_type.dat pg_profile.dat \
)
POSTGRES_BKI_DATA += $(addprefix $(top_srcdir)/src/include/catalog/,\
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 0250ff24d99..0f61d9dfeda 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3790,6 +3790,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
+ case OBJECT_PROFILE:
elog(ERROR, "unsupported object type %d", objtype);
}
@@ -3931,6 +3932,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
case OBJECT_USER_MAPPING:
+ case OBJECT_PROFILE:
elog(ERROR, "unsupported object type %d", objtype);
}
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 814188d1ade..c5cd2252c84 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -67,6 +67,8 @@
#include "catalog/pg_resqueuecapability.h"
#include "catalog/pg_resgroup.h"
#include "catalog/pg_resgroupcapability.h"
+#include "catalog/pg_profile.h"
+#include "catalog/pg_password_history.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_stat_last_operation.h"
#include "catalog/pg_stat_last_shoperation.h"
@@ -419,7 +421,10 @@ IsSharedRelation(Oid relationId)
#ifdef USE_INTERNAL_FTS
relationId == GpSegmentConfigRelationId ||
#endif
- relationId == AuthTimeConstraintRelationId)
+ relationId == AuthTimeConstraintRelationId ||
+
+ relationId == ProfileRelationId ||
+ relationId == PasswordHistoryRelationId)
return true;
/* These are their indexes */
@@ -463,7 +468,13 @@ IsSharedRelation(Oid relationId)
relationId == GpSegmentConfigContentPreferred_roleWarehouseIndexId ||
relationId == GpSegmentConfigDbidWarehouseIndexId ||
#endif
- relationId == AuthTimeConstraintAuthIdIndexId)
+ relationId == AuthTimeConstraintAuthIdIndexId ||
+ relationId == AuthIdRolProfileIndexId ||
+ relationId == ProfilePrfnameIndexId ||
+ relationId == ProfileOidIndexId ||
+ relationId == ProfileVerifyFunctionIndexId ||
+ relationId == PasswordHistoryRolePasswordIndexId ||
+ relationId == PasswordHistoryRolePasswordsetatIndexId)
{
return true;
}
@@ -484,7 +495,9 @@ IsSharedRelation(Oid relationId)
relationId == PgSubscriptionToastTable ||
relationId == PgSubscriptionToastIndex ||
relationId == PgTablespaceToastTable ||
- relationId == PgTablespaceToastIndex)
+ relationId == PgTablespaceToastIndex ||
+ relationId == PgPasswordHistoryToastTable ||
+ relationId == PgPasswordHistoryToastIndex)
return true;
#ifdef USE_INTERNAL_FTS
/* GPDB added toast tables and their indexes */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d9b20c21f6c..80a29c0841d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -94,6 +94,8 @@
#include "commands/tablespace.h"
#include "cdb/cdbvars.h"
#include "commands/extprotocolcmds.h"
+#include "catalog/pg_profile.h"
+#include "catalog/pg_password_history.h"
#include "commands/tablecmds.h"
@@ -198,6 +200,8 @@ static const Oid object_classes[] = {
TransformRelationId, /* OCLASS_TRANSFORM */
/* GPDB additions */
+ ProfileRelationId, /* OCLASS_PROFILE */
+ PasswordHistoryRelationId, /* OCLASS_PASSWORDHISTORY */
ExtprotocolRelationId, /* OCLASS_EXTPROTOCOL */
TaskRelationId /* OCLASS_TASK */
};
@@ -1573,6 +1577,8 @@ doDeletion(const ObjectAddress *object, int flags)
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_SUBSCRIPTION:
+ case OCLASS_PROFILE:
+ case OCLASS_PASSWORDHISTORY:
elog(ERROR, "global objects cannot be deleted by doDeletion");
break;
@@ -2931,6 +2937,12 @@ getObjectClass(const ObjectAddress *object)
Assert(object->objectSubId == 0);
return OCLASS_EXTPROTOCOL;
+ case ProfileRelationId:
+ return OCLASS_PROFILE;
+
+ case PasswordHistoryRelationId:
+ return OCLASS_PASSWORDHISTORY;
+
case PolicyRelationId:
return OCLASS_POLICY;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7b45f5d77c6..b3695086ed3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -47,6 +47,8 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_password_history.h"
+#include "catalog/pg_profile.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
@@ -379,6 +381,20 @@ static const ObjectPropertyType ObjectProperty[] =
OBJECT_OPFAMILY,
true
},
+ {
+ "profile",
+ ProfileRelationId,
+ ProfileOidIndexId,
+ PROFILEID,
+ PROFILENAME,
+ Anum_pg_profile_oid,
+ Anum_pg_profile_prfname,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ OBJECT_PROFILE,
+ true
+ },
{
"role",
AuthIdRelationId,
@@ -879,6 +895,10 @@ static const struct object_type_map
/* OCLASS_STATISTIC_EXT */
{
"statistics object", OBJECT_STATISTIC_EXT
+ },
+ /* OCLASS_PROFILE */
+ {
+ "profile", OBJECT_PROFILE
}
};
@@ -1052,6 +1072,7 @@ get_object_address(ObjectType objtype, Node *object,
case OBJECT_SUBSCRIPTION:
case OBJECT_RESQUEUE:
case OBJECT_RESGROUP:
+ case OBJECT_PROFILE:
address = get_object_address_unqualified(objtype,
(Value *) object, missing_ok);
break;
@@ -1371,6 +1392,11 @@ get_object_address_unqualified(ObjectType objtype,
address.objectId = get_resgroup_oid(name, missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_PROFILE:
+ address.classId = ProfileRelationId;
+ address.objectId = get_profile_oid(name, missing_ok);
+ address.objectSubId = 0;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
/* placate compiler, which doesn't know elog won't return */
@@ -2341,6 +2367,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
case OBJECT_EXTPROTOCOL:
case OBJECT_RESGROUP:
case OBJECT_RESQUEUE:
+ case OBJECT_PROFILE:
if (list_length(name) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2641,6 +2668,13 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
NameListToString(castNode(List, object)));
break;
+ case OBJECT_PROFILE:
+ /* We treat these object types as being owned by superusers */
+ if (!superuser_arg(roleid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser")));
+ break;
default:
elog(ERROR, "unrecognized object type: %d",
(int) objtype);
@@ -4026,6 +4060,25 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfo(&buffer, _("task %s"), taskname);
break;
}
+
+ case OCLASS_PROFILE:
+ {
+ char *profilename = ProfileGetNameByOid(object->objectId,
+ missing_ok);
+
+ if (profilename)
+ appendStringInfo(&buffer, _("profile %s"), profilename);
+ break;
+ }
+ case OCLASS_PASSWORDHISTORY:
+ {
+ char *username = GetUserNameFromId(object->objectId,
+ missing_ok);
+
+ if (username)
+ appendStringInfo(&buffer, _("history password for role %s"), username);
+ break;
+ }
}
/* an empty buffer is equivalent to no object found */
@@ -4589,6 +4642,14 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
appendStringInfoString(&buffer, "task");
break;
+ case OCLASS_PROFILE:
+ appendStringInfoString(&buffer, "profile");
+ break;
+
+ case OCLASS_PASSWORDHISTORY:
+ appendStringInfoString(&buffer, "password_history");
+ break;
+
/*
* There's intentionally no default: case here; we want the
* compiler to warn if a new OCLASS hasn't been handled above.
@@ -5897,6 +5958,35 @@ getObjectIdentityParts(const ObjectAddress *object,
}
}
break;
+
+ case OCLASS_PROFILE:
+ {
+ char *prfname;
+
+ prfname = ProfileGetNameByOid(object->objectId, missing_ok);
+ if (!prfname)
+ break;
+ if (objname)
+ *objname = list_make1(prfname);
+ appendStringInfoString(&buffer,
+ quote_identifier(prfname));
+ break;
+ }
+
+ case OCLASS_PASSWORDHISTORY:
+ {
+ char *username;
+
+ username = GetUserNameFromId(object->objectId, missing_ok);
+ if (!username)
+ break;
+ if (objname)
+ *objname = list_make1(username);
+ appendStringInfo(&buffer,
+ "history password for role %s: ", quote_identifier(username));
+
+ break;
+ }
/*
* There's intentionally no default: case here; we want the
diff --git a/src/backend/catalog/oid_dispatch.c b/src/backend/catalog/oid_dispatch.c
index 2f49300230f..8147323aadf 100644
--- a/src/backend/catalog/oid_dispatch.c
+++ b/src/backend/catalog/oid_dispatch.c
@@ -104,6 +104,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_policy.h"
+#include "catalog/pg_profile.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
@@ -727,6 +728,22 @@ GetNewOidForExtprotocol(Relation relation, Oid indexId, AttrNumber oidcolumn,
return GetNewOrPreassignedOid(relation, indexId, oidcolumn, &key);
}
+Oid
+GetNewOidForProfile(Relation relation, Oid indexId, AttrNumber oidcolumn,
+ char *prfname)
+{
+ OidAssignment key;
+
+ Assert(RelationGetRelid(relation) == ProfileRelationId);
+ Assert(indexId == ProfileOidIndexId);
+ Assert(oidcolumn == Anum_pg_profile_oid);
+
+ memset(&key, 0, sizeof(OidAssignment));
+ key.type = T_OidAssignment;
+ key.objname = prfname;
+ return GetNewOrPreassignedOid(relation, indexId, oidcolumn, &key);
+}
+
Oid
GetNewOidForForeignDataWrapper(Relation relation, Oid indexId, AttrNumber oidcolumn,
char *fdwname)
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index e9b37383acf..b7d9a696d11 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -38,6 +38,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_profile.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -1209,6 +1210,16 @@ shdepLockAndCheckObject(Oid classId, Oid objectId)
break;
}
+ case ProfileRelationId:
+ {
+ if (!SearchSysCacheExists1(PROFILEID, ObjectIdGetDatum(objectId)))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile %u was concurrently dropped",
+ objectId)));
+ break;
+ }
+
default:
elog(ERROR, "unrecognized shared classId: %u", classId);
@@ -1261,6 +1272,8 @@ storeObjectDescription(StringInfo descs,
appendStringInfo(descs, _("target of %s"), objdesc);
else if (deptype == SHARED_DEPENDENCY_TABLESPACE)
appendStringInfo(descs, _("tablespace for %s"), objdesc);
+ else if (deptype == SHARED_DEPENDENCY_PROFILE)
+ appendStringInfo(descs, _("profile of %s"), objdesc);
else
elog(ERROR, "unrecognized dependency type: %d",
(int) deptype);
@@ -1683,3 +1696,50 @@ shdepReassignOwned(List *roleids, Oid newrole)
table_close(sdepRel, RowExclusiveLock);
}
+
+/*
+ * recordProfileDependency
+ *
+ * A convenient wrapper of recordSharedDependencyOn -- register the specified
+ * roles of attached to profile.
+ */
+void
+recordProfileDependency(Oid roleId, Oid profileId)
+{
+ ObjectAddress myself,
+ referenced;
+
+ myself.classId = AuthIdRelationId;
+ myself.objectId = roleId;
+ myself.objectSubId = 0;
+
+ referenced.classId = ProfileRelationId;
+ referenced.objectId = profileId;
+ referenced.objectSubId = 0;
+
+ recordSharedDependencyOn(&myself, &referenced, SHARED_DEPENDENCY_PROFILE);
+}
+
+/*
+ * changeProfileDependency
+ *
+ * Update the shared dependencies to account for the new profile.
+ *
+ * Note: we don't need an objsubid argument because only whole objects
+ * have owners.
+ */
+void
+changeProfileDependency(Oid roleId, Oid newprofileId)
+{
+ Relation sdepRel;
+
+ sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);
+
+ /* Adjust the SHARED_DEPENDENCY_PROFILE entry */
+ shdepChangeDep(sdepRel,
+ AuthIdRelationId, roleId, 0,
+ ProfileRelationId, newprofileId,
+ SHARED_DEPENDENCY_PROFILE);
+
+ table_close(sdepRel, RowExclusiveLock);
+}
\ No newline at end of file
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 44dc6fe714f..ead1aac2ad2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -26,8 +26,14 @@ CREATE VIEW pg_roles AS
rolcanlogin,
rolreplication,
rolconnlimit,
+ rolenableprofile,
+ pg_profile.prfname AS rolprofile,
+ rolaccountstatus,
+ rolfailedlogins,
'********'::text as rolpassword,
rolvaliduntil,
+ rollockdate,
+ rolpasswordexpire,
rolbypassrls,
setconfig as rolconfig,
rolresqueue,
@@ -36,8 +42,9 @@ CREATE VIEW pg_roles AS
rolcreaterexthttp,
rolcreatewextgpfd,
rolresgroup
- FROM pg_authid LEFT JOIN pg_db_role_setting s
- ON (pg_authid.oid = setrole AND setdatabase = 0);
+ FROM pg_profile, pg_authid LEFT JOIN pg_db_role_setting s
+ ON (pg_authid.oid = setrole AND setdatabase = 0)
+ WHERE pg_profile.oid = pg_authid.rolprofile;
CREATE VIEW pg_shadow AS
SELECT
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 613886dce77..94e0485ac13 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -63,7 +63,8 @@ OBJS = \
user.o \
vacuum.o \
variable.o \
- view.o
+ view.o \
+ pg_profile.o
OBJS += analyzefuncs.o analyzeutils.o extprotocolcmds.o exttablecmds.o queue.o
OBJS += resgroupcmds.o tablecmds_gp.o vacuum_ao.o taskcmds.o
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index dad0bcf9fef..cb6f6d6addb 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -33,6 +33,7 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_profile.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
@@ -379,6 +380,9 @@ ExecRenameStmt_internal(RenameStmt *stmt)
case OBJECT_POLICY:
return rename_policy(stmt);
+ case OBJECT_PROFILE:
+ return rename_profile(stmt->subname, stmt->newname);
+
case OBJECT_DOMAIN:
case OBJECT_TYPE:
return RenameType(stmt);
@@ -726,6 +730,8 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
case OCLASS_TRANSFORM:
case OCLASS_EXTPROTOCOL:
case OCLASS_TASK:
+ case OCLASS_PROFILE:
+ case OCLASS_PASSWORDHISTORY:
/* ignore object types that don't have schema-qualified names */
break;
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 50b96977d02..b8e5f14ccbc 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -131,6 +131,7 @@ CommentObject(CommentStmt *stmt)
case OBJECT_ROLE:
case OBJECT_RESQUEUE:
case OBJECT_RESGROUP:
+ case OBJECT_PROFILE:
CreateSharedComments(address.objectId, address.classId, stmt->comment);
break;
default:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 72b3cb74c5e..94c312433fc 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -940,6 +940,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_DATABASE:
case OBJECT_TABLESPACE:
case OBJECT_ROLE:
+ case OBJECT_PROFILE:
/* no support for global objects */
return false;
case OBJECT_EVENT_TRIGGER:
@@ -1021,6 +1022,8 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_DATABASE:
case OCLASS_TBLSPACE:
case OCLASS_ROLE:
+ case OCLASS_PROFILE:
+ case OCLASS_PASSWORDHISTORY:
/* no support for global objects */
return false;
case OCLASS_EVENT_TRIGGER:
@@ -2156,6 +2159,7 @@ stringify_grant_objtype(ObjectType objtype)
case OBJECT_EXTPROTOCOL:
case OBJECT_RESQUEUE:
case OBJECT_RESGROUP:
+ case OBJECT_PROFILE:
elog(ERROR, "unsupported object type: %d", (int) objtype);
}
@@ -2241,6 +2245,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_EXTPROTOCOL:
case OBJECT_RESQUEUE:
case OBJECT_RESGROUP:
+ case OBJECT_PROFILE:
elog(ERROR, "unsupported object type: %d", (int) objtype);
}
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2f37cec0372..08c3a9b569a 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -3526,6 +3526,7 @@ ExecAlterExtensionContentsStmt_internal(AlterExtensionContentsStmt *stmt,
case OBJECT_STATISTIC_EXT:
case OBJECT_SUBSCRIPTION:
case OBJECT_TABLESPACE:
+ case OBJECT_PROFILE:
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot add an object of this type to an extension")));
diff --git a/src/backend/commands/pg_profile.c b/src/backend/commands/pg_profile.c
new file mode 100644
index 00000000000..4a88ba8eb81
--- /dev/null
+++ b/src/backend/commands/pg_profile.c
@@ -0,0 +1,831 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_profile.c
+ * routines to support manipulation of the pg_profile relation
+ *
+ *
+ * Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/command/pg_profile.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/heap.h"
+#include "catalog/oid_dispatch.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_profile.h"
+#include "cdb/cdbdisp_query.h"
+#include "cdb/cdbvars.h"
+#include "catalog/objectaccess.h"
+#include "postmaster/postmaster.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/syscache.h"
+
+PG_FUNCTION_INFO_V1(get_role_status);
+
+char *
+ProfileGetNameByOid(Oid profileOid, bool noerr)
+{
+ char *prfnamestr;
+ Relation rel;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple tup;
+ Form_pg_profile profile;
+
+ rel = table_open(ProfileRelationId, AccessShareLock);
+
+ /*
+ * Search pg_profile.
+ */
+ ScanKeyInit(&skey,
+ Anum_pg_profile_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(profileOid));
+ scan = systable_beginscan(rel, ProfileOidIndexId, true,
+ NULL, 1, &skey);
+ tup = systable_getnext(scan);
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!noerr)
+ elog(ERROR, "profile %u could not be found", profileOid);
+
+ prfnamestr = NULL;
+ }
+ else
+ {
+ profile = (Form_pg_profile) GETSTRUCT(tup);
+
+ prfnamestr = pstrdup(profile->prfname.data);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ return prfnamestr;
+}
+
+/*
+ * get_profile_oid - Given a profile name, look up the profile's OID.
+ *
+ * If missing_ok is false, throw an error if profile name not found. If
+ * true, just return InvalidOid.
+ */
+Oid get_profile_oid(const char* prfname, bool missing_ok)
+{
+ Oid oid;
+
+ oid = GetSysCacheOid1(PROFILENAME, Anum_pg_profile_oid,
+ CStringGetDatum(prfname));
+ if (!OidIsValid(oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%s\" does not exist", prfname)));
+ return oid;
+}
+
+/*
+ * rename_profile -
+ * change the name of a profile
+ */
+ObjectAddress
+rename_profile(char *oldname, char *newname)
+{
+ HeapTuple oldtuple,
+ newtuple;
+ TupleDesc dsc;
+ Relation rel;
+ Datum repl_val[Natts_pg_profile];
+ bool repl_null[Natts_pg_profile];
+ bool repl_repl[Natts_pg_profile];
+ Oid profileid;
+ ObjectAddress address;
+ Form_pg_profile profileform;
+
+ /* Only when enable_password_profile is true, can RENAME PROFILE. */
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't RENAME PROFILE for enable_password_profile is not open")));
+
+ /* Only super user can RENMAE PROFILE. */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to rename profile, must be superuser")));
+
+ /* pg_default profile can't be renamed */
+ if (strcmp(oldname, "pg_default") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("can't RENAME \"pg_default\" profile")));
+
+ rel = table_open(ProfileRelationId, RowExclusiveLock);
+ dsc = RelationGetDescr(rel);
+
+ oldtuple = SearchSysCache1(PROFILENAME, CStringGetDatum(oldname));
+ if (!HeapTupleIsValid(oldtuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%s\" does not exist", oldname)));
+
+ profileform = (Form_pg_profile) GETSTRUCT(oldtuple);
+ profileid = profileform->oid;
+
+ /* make sure the new name doesn't exist */
+ if (SearchSysCacheExists1(PROFILENAME, CStringGetDatum(newname)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("profile \"%s\" already exists", newname)));
+
+ /* Build up updated catalog tuple */
+ MemSet(repl_repl, false, sizeof(repl_repl));
+
+ repl_repl[Anum_pg_profile_prfname - 1] = true;
+ repl_val[Anum_pg_profile_prfname - 1] = DirectFunctionCall1(namein,CStringGetDatum(newname));
+ repl_null[Anum_pg_profile_prfname - 1] = false;
+
+ newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
+ CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
+
+ InvokeObjectPostAlterHook(ProfileRelationId, profileid, 0);
+
+ ObjectAddressSet(address, ProfileRelationId, profileid);
+
+ ReleaseSysCache(oldtuple);
+
+ /*
+ * Close pg_profile, keep lock until transaction commit.
+ */
+ table_close(rel, NoLock);
+
+ /* MPP-6929: metadata tracking */
+ if (Gp_role == GP_ROLE_DISPATCH)
+ MetaTrackUpdObject(ProfileRelationId,
+ profileid,
+ GetUserId(),
+ "ALTER", "RENAME"
+ );
+
+ return address;
+}
+
+/*
+ * CREATE PROFILE
+ */
+Oid
+CreateProfile(ParseState *pstate, CreateProfileStmt *stmt)
+{
+ Relation pg_profile_rel;
+ TupleDesc pg_profile_dsc;
+ HeapTuple tuple;
+ Datum new_record[Natts_pg_profile];
+ bool new_record_nulls[Natts_pg_profile];
+ Oid profileid;
+ ListCell *option;
+ int failed_login_attempts = -1;
+ int password_lock_time = -1;
+ int password_life_time = -1;
+ int password_grace_time = -1;
+ int password_reuse_time = -1;
+ int password_reuse_max = -1;
+ int password_allow_hashed = -1;
+ DefElem *dfailedloginattempts = NULL;
+ DefElem *dpasswordlocktime = NULL;
+ DefElem *dpasswordreusemax = NULL;
+
+ /* Only when enable_password_profile is true, can CREATE PROFILE. */
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't CREATE PROFILE for enable_password_profile is not open")));
+
+ /* Only super user can CREATE PROFILE. */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create profile, must be superuser")));
+
+ /* Extract options from the statement node tree */
+ foreach(option, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(option);
+
+ if (strcmp(defel->defname, "failed_login_attempts") == 0)
+ {
+ if (dfailedloginattempts)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options"),
+ parser_errposition(pstate, defel->location)));
+ dfailedloginattempts = defel;
+ }
+ else if (strcmp(defel->defname, "password_lock_time") == 0)
+ {
+ if (dpasswordlocktime)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options"),
+ parser_errposition(pstate, defel->location)));
+ dpasswordlocktime = defel;
+ }
+ else if (strcmp(defel->defname, "password_reuse_max") == 0)
+ {
+ if (dpasswordreusemax)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options"),
+ parser_errposition(pstate, defel->location)));
+ dpasswordreusemax = defel;
+ }
+ }
+
+ if (dfailedloginattempts && dfailedloginattempts->arg)
+ {
+ failed_login_attempts = intVal(dfailedloginattempts->arg);
+ if (failed_login_attempts == 0 ||
+ failed_login_attempts < PROFILE_UNLIMITED ||
+ failed_login_attempts > PROFILE_MAX_VALID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid failed login attempts: %d", failed_login_attempts)));
+ }
+
+ if (dpasswordlocktime && dpasswordlocktime->arg)
+ {
+ password_lock_time = intVal(dpasswordlocktime->arg);
+ if (password_lock_time < PROFILE_UNLIMITED ||
+ password_lock_time > PROFILE_MAX_VALID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid password lock time: %d", password_lock_time)));
+ }
+
+ if (dpasswordreusemax && dpasswordreusemax->arg)
+ {
+ password_reuse_max = intVal(dpasswordreusemax->arg);
+ if (password_reuse_max < PROFILE_UNLIMITED ||
+ password_reuse_max > PROFILE_MAX_VALID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid password reuse max: %d", password_reuse_max)));
+ }
+
+ /*
+ * Check the pg_profile relation to be certain the profile doesn't already
+ * exist.
+ */
+ pg_profile_rel = table_open(ProfileRelationId, RowExclusiveLock);
+ pg_profile_dsc = RelationGetDescr(pg_profile_rel);
+
+ if (OidIsValid(get_profile_oid(stmt->profile_name, true)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("profile \"%s\" already exists",
+ stmt->profile_name)));
+
+ /*
+ * Build a tuple to insert
+ */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+ new_record[Anum_pg_profile_prfname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(stmt->profile_name));
+
+ new_record[Anum_pg_profile_prffailedloginattempts - 1] =
+ Int32GetDatum(failed_login_attempts);
+ new_record[Anum_pg_profile_prfpasswordlocktime - 1] =
+ Int32GetDatum(password_lock_time);
+ new_record[Anum_pg_profile_prfpasswordlifetime - 1] =
+ Int32GetDatum(password_life_time);
+ new_record[Anum_pg_profile_prfpasswordgracetime - 1] =
+ Int32GetDatum(password_grace_time);
+ new_record[Anum_pg_profile_prfpasswordreusetime - 1] =
+ Int32GetDatum(password_reuse_time);
+ new_record[Anum_pg_profile_prfpasswordreusemax - 1] =
+ Int32GetDatum(password_reuse_max);
+ new_record[Anum_pg_profile_prfpasswordallowhashed - 1] =
+ Int32GetDatum(password_allow_hashed);
+ new_record_nulls[Anum_pg_profile_prfpasswordverifyfuncdb -1] =
+ true;
+ new_record_nulls[Anum_pg_profile_prfpasswordverifyfunc - 1] =
+ true;
+
+ /*
+ * GetNewOidForProfile() / GetNewOrPreassignedOid() will return the
+ * pre-assigned OID, if any, and error out if there was no pre-assigned
+ * values in binary upgrade mode.
+ */
+ profileid = GetNewOidForProfile(pg_profile_rel, ProfileOidIndexId,
+ Anum_pg_profile_oid,
+ stmt->profile_name);
+
+ new_record[Anum_pg_profile_oid - 1] =
+ ObjectIdGetDatum(profileid);
+
+ tuple = heap_form_tuple(pg_profile_dsc, new_record, new_record_nulls);
+
+ /*
+ * Insert new record in the pg_profile table
+ */
+ CatalogTupleInsert(pg_profile_rel, tuple);
+
+ InvokeObjectPostCreateHook(ProfileRelationId, profileid, 0);
+
+ heap_freetuple(tuple);
+
+ /*
+ * Close pg_profile, but keep lock till commit
+ */
+ table_close(pg_profile_rel, NoLock);
+
+ if (Gp_role == GP_ROLE_DISPATCH)
+ {
+ Assert(stmt->type == T_CreateProfileStmt);
+ CdbDispatchUtilityStatement((Node *) stmt,
+ DF_CANCEL_ON_ERROR|
+ DF_WITH_SNAPSHOT|
+ DF_NEED_TWO_PHASE,
+ GetAssignedOidsForDispatch(),
+ NULL);
+
+ /* MPP-6929: metadata tracking */
+ MetaTrackAddObject(ProfileRelationId,
+ profileid,
+ GetUserId(),
+ "CREATE", "PROFILE");
+ }
+
+ return profileid;
+}
+
+/*
+ * ALTER PROFILE
+ */
+Oid
+AlterProfile(AlterProfileStmt *stmt)
+{
+ Datum new_record[Natts_pg_profile];
+ bool new_record_nulls[Natts_pg_profile];
+ bool new_record_repl[Natts_pg_profile];
+ Relation pg_profile_rel;
+ TupleDesc pg_profile_dsc;
+ HeapTuple tuple,
+ new_tuple;
+ Form_pg_profile profileform;
+ Oid profileid;
+ char *profile_name;
+ ListCell *option;
+ int failed_login_attempts = -1;
+ int password_lock_time = -1;
+ int password_life_time = -1;
+ int password_grace_time = -1;
+ int password_reuse_time = -1;
+ int password_reuse_max = -1;
+ int password_allow_hashed = -1;
+ Oid password_verify_funcdb = 0;
+ Oid password_verify_func = 0;
+ DefElem *dfailedloginattempts = NULL;
+ DefElem *dpasswordlocktime = NULL;
+ DefElem *dpasswordreusemax = NULL;
+ int numopts = 0;
+
+ numopts = list_length(stmt->options);
+
+ /* Only when enable_password_profile is true, can ALTER PROFILE. */
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't ALTER PROFILE for enable_password_profile is not open")));
+
+ /* Only super user can ALTER PROFILE */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to alter profile, must be superuser")));
+
+ profile_name = stmt->profile_name;
+ Assert(profile_name);
+
+ /* Extract options from the statement node tree */
+ foreach(option, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(option);
+
+ if (strcmp(defel->defname, "failed_login_attempts") == 0)
+ {
+ if (dfailedloginattempts)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dfailedloginattempts = defel;
+ }
+ else if (strcmp(defel->defname, "password_lock_time") == 0)
+ {
+ if (dpasswordlocktime)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dpasswordlocktime = defel;
+ }
+ else if (strcmp(defel->defname, "password_reuse_max") == 0)
+ {
+ if (dpasswordreusemax)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dpasswordreusemax = defel;
+ }
+ }
+
+ if (dfailedloginattempts && dfailedloginattempts->arg)
+ {
+ failed_login_attempts = intVal(dfailedloginattempts->arg);
+ if (failed_login_attempts == 0 ||
+ failed_login_attempts < PROFILE_UNLIMITED ||
+ failed_login_attempts > PROFILE_MAX_VALID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid failed login attempts: %d", failed_login_attempts)));
+ else if (failed_login_attempts == PROFILE_DEFAULT &&
+ strcmp(profile_name, "pg_default") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("can't set failed login attempts to -1(DEFAULT) of pg_default")));
+ }
+
+ if (dpasswordlocktime && dpasswordlocktime->arg)
+ {
+ password_lock_time = intVal(dpasswordlocktime->arg);
+ if (password_lock_time < PROFILE_UNLIMITED ||
+ password_lock_time > PROFILE_MAX_VALID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid password lock time: %d", password_lock_time)));
+ else if (password_lock_time == PROFILE_DEFAULT &&
+ strcmp(profile_name, "pg_default") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("can't set password lock time to -1(DEFAULT) of pg_default")));
+ }
+
+ if (dpasswordreusemax && dpasswordreusemax->arg)
+ {
+ password_reuse_max = intVal(dpasswordreusemax->arg);
+ if (password_reuse_max < PROFILE_UNLIMITED ||
+ password_reuse_max > PROFILE_MAX_VALID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid password reuse max: %d", password_reuse_max)));
+ else if (password_reuse_max == PROFILE_DEFAULT &&
+ strcmp(profile_name, "pg_default") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("can't set password reuse max to -1(DEFAULT) of pg_default")));
+ }
+
+ /*
+ * Scan the pg_profile relation to be certain the profile exists.
+ */
+ pg_profile_rel = table_open(ProfileRelationId, RowExclusiveLock);
+ pg_profile_dsc = RelationGetDescr(pg_profile_rel);
+
+ tuple = SearchSysCache1(PROFILENAME, CStringGetDatum(profile_name));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%s\" does not exist", profile_name)));
+
+ profileform = (Form_pg_profile) GETSTRUCT(tuple);
+ profileid = profileform->oid;
+
+ /*
+ * If all altered profile parameters are same with existing parameters, don't need
+ * to update tuple and return directly.
+ */
+ if (failed_login_attempts == profileform->prffailedloginattempts &&
+ password_lock_time == profileform->prfpasswordlocktime &&
+ password_reuse_max == profileform->prfpasswordreusemax)
+ {
+ ReleaseSysCache(tuple);
+ table_close(pg_profile_rel, NoLock);
+
+ return profileid;
+ }
+
+ /*
+ * Build an updated tuple, perusing the information just obtained
+ */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+ MemSet(new_record_repl, false, sizeof(new_record_repl));
+
+ if (failed_login_attempts != -1 &&
+ failed_login_attempts != profileform->prffailedloginattempts)
+ {
+ new_record[Anum_pg_profile_prffailedloginattempts - 1] =
+ Int32GetDatum(failed_login_attempts);
+ new_record_repl[Anum_pg_profile_prffailedloginattempts - 1] =
+ true;
+ }
+
+ if (password_lock_time != -1 &&
+ password_lock_time != profileform->prfpasswordlocktime)
+ {
+ new_record[Anum_pg_profile_prfpasswordlocktime - 1] =
+ Int32GetDatum(password_lock_time);
+ new_record_repl[Anum_pg_profile_prfpasswordlocktime - 1] =
+ true;
+ }
+
+ if (password_reuse_max != -1 &&
+ password_reuse_max != profileform->prfpasswordreusemax)
+ {
+ new_record[Anum_pg_profile_prfpasswordreusemax - 1] =
+ Int32GetDatum(password_reuse_max);
+ new_record_repl[Anum_pg_profile_prfpasswordreusemax - 1] =
+ true;
+ }
+
+ /*
+ * Now, we just support failed_login_attempts, password_lock_time and
+ * password_reuse_max, we need to set other fields' values to default.
+ */
+ new_record[Anum_pg_profile_prfpasswordlifetime - 1] =
+ Int32GetDatum(password_life_time);
+ new_record[Anum_pg_profile_prfpasswordgracetime - 1] =
+ Int32GetDatum(password_grace_time);
+ new_record[Anum_pg_profile_prfpasswordreusetime - 1] =
+ Int32GetDatum(password_reuse_time);
+ new_record[Anum_pg_profile_prfpasswordallowhashed - 1] =
+ Int32GetDatum(password_allow_hashed);
+ new_record[Anum_pg_profile_prfpasswordverifyfuncdb -1] =
+ Int32GetDatum(password_verify_funcdb);
+ new_record[Anum_pg_profile_prfpasswordverifyfunc - 1] =
+ Int32GetDatum(password_verify_func);
+
+ new_tuple = heap_modify_tuple(tuple, pg_profile_dsc,
+ new_record, new_record_nulls, new_record_repl);
+ CatalogTupleUpdate(pg_profile_rel, &tuple->t_self, new_tuple);
+
+ InvokeObjectPostAlterHook(ProfileRelationId, profileid, 0);
+
+ ReleaseSysCache(tuple);
+ heap_freetuple(new_tuple);
+
+ /* MPP-6929: metadata tracking */
+ if (Gp_role == GP_ROLE_DISPATCH)
+ MetaTrackUpdObject(ProfileRelationId,
+ profileid,
+ GetUserId(),
+ "ALTER", "PROFILE");
+
+ /*
+ * Close pg_profile, but keep lock untill commit.
+ */
+ table_close(pg_profile_rel, NoLock);
+
+ if (Gp_role == GP_ROLE_DISPATCH)
+ {
+ CdbDispatchUtilityStatement((Node *) stmt,
+ DF_CANCEL_ON_ERROR|
+ DF_WITH_SNAPSHOT|
+ DF_NEED_TWO_PHASE,
+ NIL,
+ NULL);
+ }
+
+ return profileid;
+}
+
+/*
+ * DROP PROFILE
+ */
+void
+DropProfile(DropProfileStmt *stmt)
+{
+ Relation pg_profile_rel;
+ ListCell *cell;
+
+ /* Only when enable_password_profile is true, can ALTER PROFILE. */
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't DROP PROFILE for enable_password_profile is not open")));
+
+ /* Only super user can DROP PROFILE */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to drop profile, must be superuser")));
+
+ /*
+ * Scan the pg_profile relation to find the Oid of the Profile(s) to be
+ * deleted.
+ */
+ pg_profile_rel = table_open(ProfileRelationId, RowExclusiveLock);
+
+ foreach(cell, stmt->profiles)
+ {
+ HeapTuple tuple;
+ Form_pg_profile profileform;
+ char *detail;
+ char *detail_log;
+ Oid profileid;
+
+ char *profile_name = strVal(lfirst(cell));
+ if (strcmp(profile_name, "pg_default") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Disallow to drop default profile")));
+
+ tuple = SearchSysCache1(PROFILENAME, PointerGetDatum(profile_name));
+ if (!HeapTupleIsValid(tuple))
+ {
+ if (!stmt->missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%s\" does not exist", profile_name)));
+ }
+ if (Gp_role != GP_ROLE_EXECUTE)
+ {
+ ereport(NOTICE,
+ (errmsg("profile \"%s\" does not exist", profile_name)));
+ }
+
+ continue;
+ }
+
+ profileform = (Form_pg_profile) GETSTRUCT(tuple);
+ profileid = profileform->oid;
+
+ /* DROP hook for the profile being removed */
+ InvokeObjectDropHook(ProfileRelationId, profileid, 0);
+
+ /*
+ * Lock the profile, so nobody can add dependencies to her while we drop
+ * her. We keep the lock until the end of transaction.
+ */
+ LockSharedObject(ProfileRelationId, profileid, 0, AccessExclusiveLock);
+
+ /* Check for pg_shdepend entries depending on this profile */
+ if (checkSharedDependencies(ProfileRelationId, profileid,
+ &detail, &detail_log))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("profile \"%s\" cannot be dropped because some objects depend on it",
+ profile_name),
+ errdetail_internal("%s", detail),
+ errdetail_log("%s", detail_log)));
+
+ /*
+ * Remove the profile from the pg_profile table
+ */
+ CatalogTupleDelete(pg_profile_rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+
+ /* metadata track */
+ if (Gp_role == GP_ROLE_DISPATCH)
+ MetaTrackDropObject(ProfileRelationId,
+ profileid);
+ }
+
+ /*
+ * Now we can clean up; but keep locks until commit.
+ */
+ table_close(pg_profile_rel, NoLock);
+
+ if (Gp_role == GP_ROLE_DISPATCH)
+ {
+ CdbDispatchUtilityStatement((Node *) stmt,
+ DF_CANCEL_ON_ERROR|
+ DF_WITH_SNAPSHOT|
+ DF_NEED_TWO_PHASE,
+ NIL,
+ NULL);
+ }
+}
+
+/*
+ * As pg_profile fields' values may be PROFILE_UNLIMITED(-2) or
+ * PROFILE_DEFAULT(-1), transform them to normal values.
+ */
+int32
+tranformProfileValueToNormal(int32 profile_val, int attoff)
+{
+ HeapTuple profile_tuple;
+ int32 normal_profile_val;
+ bool isnull;
+ Datum datum;
+ Relation rel;
+
+ /*
+ * If current value is PROFILE_DEFAULT(-1), we need to search
+ * pg_profile catalog to get default profile's value.
+ */
+ if (profile_val == PROFILE_DEFAULT)
+ {
+ rel = table_open(ProfileRelationId, AccessShareLock);
+
+ profile_tuple = SearchSysCache1(PROFILEID, ObjectIdGetDatum(DefaultProfileOID));
+ Assert(HeapTupleIsValid(profile_tuple));
+
+ datum = SysCacheGetAttr(PROFILEID, profile_tuple, attoff, &isnull);
+ Assert(!isnull);
+
+ normal_profile_val = DatumGetInt32(datum);
+
+ if (normal_profile_val == PROFILE_UNLIMITED)
+ normal_profile_val = PROFILE_MAX_VALID;
+
+ ReleaseSysCache(profile_tuple);
+ table_close(rel, AccessShareLock);
+ }
+ /*
+ * Currently, the profile field's max valid value is PROFILE_MAX_VALID(9999).
+ */
+ else if (profile_val == PROFILE_UNLIMITED)
+ normal_profile_val = PROFILE_MAX_VALID;
+ else
+ normal_profile_val = profile_val;
+
+ return normal_profile_val;
+}
+
+/*
+ * Function get_role_status()
+ */
+Datum
+get_role_status(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ char *user_name;
+ Datum datum;
+ bool isnull;
+ int16 account_status;
+ char *ret = NULL;
+
+ user_name = PG_GETARG_CSTRING(0);
+
+ /* Only when enable_password_profile is true, can ALTER PROFILE. */
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't call get_role_status for enable_password_profile is not open")));
+
+ /* Only super users has the privilege */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to call get_role_status, must be superuser")));
+
+ tuple = SearchSysCache1(AUTHNAME, CStringGetDatum(user_name));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("user \"%s\" does not exist", user_name)));
+
+ datum = SysCacheGetAttr(AUTHNAME, tuple, Anum_pg_authid_rolaccountstatus, &isnull);
+ Assert(!isnull);
+
+ account_status = DatumGetInt16(datum);
+
+ ReleaseSysCache(tuple);
+
+ switch(account_status)
+ {
+ case ROLE_ACCOUNT_STATUS_OPEN:
+ ret = "OPEN";
+ break;
+ case ROLE_ACCOUNT_STATUS_LOCKED_TIMED:
+ ret = "LOCKED(TIMED)";
+ break;
+ case ROLE_ACCOUNT_STATUS_LOCKED:
+ ret = "LOCKED";
+ break;
+ case ROLE_ACCOUNT_STATUS_EXPIRED_GRACE:
+ ret = "EXPIRED(GRACE)";
+ break;
+ case ROLE_ACCOUNT_STATUS_EXPIRED:
+ ret = "EXPIRED";
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("user %s's account_status %d not recognized",
+ user_name, account_status)));
+ }
+
+ PG_RETURN_CSTRING(ret);
+}
\ No newline at end of file
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index c6adb01f8d9..487ef4417b2 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -59,6 +59,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
case OBJECT_TABLESPACE:
case OBJECT_TYPE:
case OBJECT_VIEW:
+ case OBJECT_PROFILE:
return true;
case OBJECT_ACCESS_METHOD:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ab15d32e47c..8d796cc2b70 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -14000,6 +14000,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
case OCLASS_TRANSFORM:
case OCLASS_EXTPROTOCOL:
case OCLASS_TASK:
+ case OCLASS_PROFILE:
+ case OCLASS_PASSWORDHISTORY:
/*
* We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 5c3db9714b5..69cf69fd261 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -28,12 +28,15 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_password_history.h"
+#include "catalog/pg_profile.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
#include "libpq/crypt.h"
#include "miscadmin.h"
+#include "postmaster/postmaster.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -112,6 +115,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
+ char *profilename = NULL; /* profile name the role be attached */
+ Oid profileId = DefaultProfileOID; /* default profile oid */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -133,6 +138,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
bool validUntil_null;
char *resqueue = NULL; /* resource queue for this role */
char *resgroup = NULL; /* resource group for this role */
+ bool account_is_lock = false; /* whether the account will be locked/unlocked */
+ bool enable_profile = false; /* whether user can use password profile */
+ int16 account_status = ROLE_ACCOUNT_STATUS_OPEN; /* default accountstatus is 'OPEN' */
+ TimestampTz now = 0; /* current timestamp with time zone */
List *addintervals = NIL; /* list of time intervals for which login should be denied */
DefElem *dpassword = NULL;
DefElem *dresqueue = NULL;
@@ -149,6 +158,11 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dprofile = NULL;
+ DefElem *daccountIsLock = NULL;
+ DefElem *denableProfile = NULL;
+
+ now = GetCurrentTimestamp();
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -333,6 +347,31 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
parser_errposition(pstate, defel->location)));
dbypassRLS = defel;
}
+
+ else if (strcmp(defel->defname, "profile") == 0)
+ {
+ if (dprofile)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dprofile = defel;
+ }
+ else if (strcmp(defel->defname, "accountislock") == 0)
+ {
+ if (daccountIsLock)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ daccountIsLock = defel;
+ }
+ else if (strcmp(defel->defname, "enableProfile") == 0)
+ {
+ if (denableProfile)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ denableProfile = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -374,6 +413,55 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
resgroup = strVal(linitial((List *) dresgroup->arg));
if (dbypassRLS)
bypassrls = intVal(dbypassRLS->arg) != 0;
+ if (dprofile)
+ profilename = strVal(dprofile->arg);
+ if (daccountIsLock)
+ account_is_lock = intVal(daccountIsLock->arg) != 0;
+ if (denableProfile)
+ enable_profile = intVal(denableProfile->arg) != 0;
+
+ /*
+ * Only the super user has the privileges of profile.
+ */
+ if (dprofile)
+ {
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't CREATE USER ... PROFILE for enable_password_profile is not open")));
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create role attached to profile")));
+ }
+
+ if (daccountIsLock)
+ {
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't CREATE USER ... ACCOUNT LOCK/UNLOCK for enable_password_profile is not open")));
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create role account lock/unlock")));
+ }
+
+ if (denableProfile)
+ {
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't CREATE USER ... ENABLE/DISABLE PROFILE for enable_password_profile is not open")));
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create role enable/disable profile")));
+ }
+
/* Check some permissions first */
if (issuper)
@@ -479,6 +567,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
+ new_record[Anum_pg_authid_rolenableprofile - 1] = BoolGetDatum(enable_profile);
+
+ new_record[Anum_pg_authid_rolprofile - 1] = ObjectIdGetDatum(profileId);
+ new_record[Anum_pg_authid_rolaccountstatus - 1] = Int16GetDatum(account_status);
+ new_record[Anum_pg_authid_rolfailedlogins - 1] = Int32GetDatum(0);
+ new_record_nulls[Anum_pg_authid_rolpasswordsetat - 1] = true;
+ new_record_nulls[Anum_pg_authid_rollockdate - 1] = true;
+ new_record_nulls[Anum_pg_authid_rolpasswordexpire - 1] = true;
/* Set the CREATE EXTERNAL TABLE permissions for this role */
if (exttabcreate || exttabnocreate)
@@ -520,6 +616,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
password);
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
+ new_record[Anum_pg_authid_rolpasswordsetat - 1] =
+ Int64GetDatum(now);
+ new_record_nulls[Anum_pg_authid_rolpasswordsetat - 1] =
+ false;
}
}
else
@@ -631,6 +731,60 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
new_record[Anum_pg_authid_oid - 1] = ObjectIdGetDatum(roleid);
+ /*
+ * Change accountstatus and lockdate if lock account
+ */
+ if (account_is_lock)
+ {
+ new_record[Anum_pg_authid_rolaccountstatus - 1] =
+ Int16GetDatum(ROLE_ACCOUNT_STATUS_LOCKED);
+ new_record[Anum_pg_authid_rollockdate - 1] =
+ Int64GetDatum(now);
+ new_record_nulls[Anum_pg_authid_rollockdate - 1] =
+ false;
+ }
+
+ if (enable_profile)
+ {
+ new_record[Anum_pg_authid_rolenableprofile - 1] =
+ BoolGetDatum(enable_profile);
+ }
+
+ if (profilename)
+ {
+ /* Scan the pg_profile relation to be certain the profile exists. */
+ Relation pg_profile_rel;
+ TupleDesc pg_profile_dsc;
+ HeapTuple tuple;
+ Form_pg_profile profileform;
+ Oid profileid;
+
+ pg_profile_rel = table_open(ProfileRelationId, AccessShareLock);
+ pg_profile_dsc = RelationGetDescr(pg_profile_rel);
+
+ tuple = SearchSysCache1(PROFILENAME, CStringGetDatum(profilename));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%s\" does not exist", profilename)));
+
+ profileform = (Form_pg_profile) GETSTRUCT(tuple);
+ profileid = profileform->oid;
+
+ new_record[Anum_pg_authid_rolprofile - 1] =
+ ObjectIdGetDatum(profileid);
+ new_record_nulls[Anum_pg_authid_rolprofile - 1] =
+ false;
+
+ ReleaseSysCache(tuple);
+ table_close(pg_profile_rel, NoLock);
+
+ /*
+ * Add profile dependency
+ */
+ recordProfileDependency(roleid, profileid);
+ }
+
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
@@ -747,9 +901,15 @@ AlterRole(AlterRoleStmt *stmt)
HeapTuple tuple,
new_tuple;
Form_pg_authid authform;
+ Relation pg_profile_rel;
+ TupleDesc pg_profile_dsc;
+ Form_pg_profile profileform;
+ Oid profileid;
+ HeapTuple profile_tuple;
ListCell *option;
char *rolename = NULL;
char *password = NULL; /* user password */
+ char *profilename = NULL; /* profile name the role be attached */
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -757,6 +917,7 @@ AlterRole(AlterRoleStmt *stmt)
int canlogin = -1; /* Can this user login? */
int isreplication = -1; /* Is this a replication role? */
int connlimit = -1; /* maximum connections allowed */
+ bool enable_profile = false; /* whether user can use password profile */
char *resqueue = NULL; /* resource queue for this role */
char *resgroup = NULL; /* resource group for this role */
List *exttabcreate = NIL; /* external table create privileges being added */
@@ -766,6 +927,8 @@ AlterRole(AlterRoleStmt *stmt)
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
int bypassrls = -1;
+ int account_is_lock = -1; /* whether the account will be locked/unlocked */
+ TimestampTz now = 0; /* current timestamp with time zone */
DefElem *dpassword = NULL;
DefElem *dresqueue = NULL;
DefElem *dresgroup = NULL;
@@ -779,6 +942,9 @@ AlterRole(AlterRoleStmt *stmt)
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dprofile = NULL;
+ DefElem *daccountIsLock = NULL;
+ DefElem *denableProfile = NULL;
Oid roleid;
bool bWas_super = false; /* Was the user a superuser? */
int numopts = 0;
@@ -809,6 +975,8 @@ AlterRole(AlterRoleStmt *stmt)
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
+ now = GetCurrentTimestamp();
+
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
@@ -968,6 +1136,30 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "profile") == 0)
+ {
+ if (dprofile)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dprofile = defel;
+ }
+ else if (strcmp(defel->defname, "accountislock") == 0)
+ {
+ if (daccountIsLock)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ daccountIsLock = defel;
+ }
+ else if (strcmp(defel->defname, "enableProfile") == 0)
+ {
+ if (denableProfile)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ denableProfile = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -1005,6 +1197,54 @@ AlterRole(AlterRoleStmt *stmt)
resgroup = strVal(linitial((List *) dresgroup->arg));
if (dbypassRLS)
bypassrls = intVal(dbypassRLS->arg);
+ if (dprofile)
+ profilename = strVal(dprofile->arg);
+ if (daccountIsLock)
+ account_is_lock = intVal(daccountIsLock->arg);
+ if (denableProfile)
+ enable_profile = intVal(denableProfile->arg) != 0;
+
+ /*
+ * Only the super user has the privileges of profile.
+ */
+ if (dprofile)
+ {
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't ALTER USER ... PROFILE for enable_password_profile is not open")));
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter role attached to profile")));
+ }
+
+ if (daccountIsLock)
+ {
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't ALTER USER ... ACCOUNT LOCK/UNLOCK for enable_password_profile is not open")));
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter role account lock/unlock")));
+ }
+
+ if (denableProfile)
+ {
+ if (!enable_password_profile)
+ ereport(ERROR,
+ (errcode(ERRCODE_GP_FEATURE_NOT_CONFIGURED),
+ errmsg("can't ALTER USER ... ENABLE/DISABLE PROFILE for enable_password_profile is not open")));
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter role enable/disable profile")));
+ }
/*
* Scan the pg_authid relation to be certain the user exists.
@@ -1171,11 +1411,85 @@ AlterRole(AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
+ if (denableProfile)
+ {
+ new_record[Anum_pg_authid_rolenableprofile - 1] = BoolGetDatum(enable_profile);
+ new_record_repl[Anum_pg_authid_rolenableprofile - 1] = true;
+ }
+
+ /*
+ * Change accountstatus and lockdate when superuser alter user to lock/unlock
+ */
+ if (account_is_lock >= 0)
+ {
+ if (account_is_lock == 0)
+ {
+ new_record[Anum_pg_authid_rolaccountstatus - 1] =
+ Int16GetDatum(ROLE_ACCOUNT_STATUS_OPEN);
+ new_record_repl[Anum_pg_authid_rolaccountstatus - 1] = true;
+
+ new_record[Anum_pg_authid_rolfailedlogins - 1] =
+ Int32GetDatum(0);
+ new_record_repl[Anum_pg_authid_rolfailedlogins - 1] = true;
+
+ new_record_nulls[Anum_pg_authid_rollockdate - 1] = true;
+ new_record_repl[Anum_pg_authid_rollockdate - 1] = true;
+ }
+ else
+ {
+ new_record[Anum_pg_authid_rolaccountstatus - 1] =
+ Int16GetDatum(ROLE_ACCOUNT_STATUS_LOCKED);
+ new_record_repl[Anum_pg_authid_rolaccountstatus - 1] = true;
+
+ new_record[Anum_pg_authid_rollockdate - 1] = Int64GetDatum(now);
+ new_record_repl[Anum_pg_authid_rollockdate - 1] = true;
+ }
+ }
+
/* password */
if (password)
{
char *shadow_pass;
char *logdetail;
+ Datum datum;
+ bool isnull;
+ bool setat_isnull;
+ TimestampTz password_set_at = 0;
+ int32 profile_reuse_max = 0;
+ SysScanDesc password_history_scan;
+ HeapTuple profiletuple;
+
+ pg_profile_rel = table_open(ProfileRelationId, AccessShareLock);
+ pg_profile_dsc = RelationGetDescr(pg_profile_rel);
+
+ datum = SysCacheGetAttr(AUTHNAME, tuple,
+ Anum_pg_authid_rolprofile, &isnull);
+ Assert(!isnull);
+
+ profileid = DatumGetObjectId(datum);
+ profiletuple = SearchSysCache1(PROFILEID, ObjectIdGetDatum(profileid));
+ if (!HeapTupleIsValid(profiletuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%d\" does not exist", profileid)));
+
+ /* Get PASSWORD_REUSE_MAX from profile tuple and transform it to normal value */
+ profileform = (Form_pg_profile) GETSTRUCT(profiletuple);
+ profile_reuse_max = tranformProfileValueToNormal(profileform->prfpasswordreusemax,
+ Anum_pg_profile_prfpasswordreusemax);
+
+ ReleaseSysCache(profiletuple);
+
+ if (profile_reuse_max == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PASSWORD),
+ errmsg("can't alter user password for profile's password_reuse_max is zero.")));
+
+ /*
+ * Get shadow password from pg_authid
+ */
+ datum = SysCacheGetAttr(AUTHNAME, tuple,
+ Anum_pg_authid_rolpassword, &isnull);
/* Like in CREATE USER, don't allow an empty password. */
if (password[0] == '\0' ||
@@ -1193,7 +1507,107 @@ AlterRole(AlterRoleStmt *stmt)
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
}
+
+ /*
+ * Disallow to use recently passwords which controlled by
+ * profile's PASSWORD_REUSE_MAX.
+ */
+ if (!isnull)
+ {
+ Relation pg_password_history_rel;
+ Relation pg_password_history_passwordsetat_idx;
+ TupleDesc pg_password_history_dsc;
+ char *history_shadow_pass = NULL;
+ Datum password_history_record[Natts_pg_password_history];
+ bool password_nulls[Natts_pg_password_history];
+ TimestampTz history_password_set_at = 0;
+ HeapTuple password_history_tuple;
+ ScanKeyData skey;
+ int i;
+
+ pg_password_history_rel = table_open(PasswordHistoryRelationId, RowExclusiveLock);
+ pg_password_history_passwordsetat_idx = index_open(PasswordHistoryRolePasswordsetatIndexId, RowExclusiveLock);
+ pg_password_history_dsc = RelationGetDescr(pg_password_history_rel);
+
+ MemSet(password_history_record, 0, sizeof(password_history_record));
+ MemSet(password_nulls, false, sizeof(password_nulls));
+
+ history_shadow_pass = TextDatumGetCString(datum);
+
+ datum = SysCacheGetAttr(AUTHNAME, tuple,
+ Anum_pg_authid_rolpasswordsetat, &setat_isnull);
+ Assert(!setat_isnull);
+ history_password_set_at = DatumGetInt64(datum);
+
+ /*
+ * When current password is not null in pg_authid, we need to record
+ * it into pg_password_history table every time.
+ */
+ password_history_record[Anum_pg_password_history_passhistroleid - 1] =
+ ObjectIdGetDatum(roleid);
+ password_history_record[Anum_pg_password_history_passhistpasswordsetat - 1] =
+ Int64GetDatum(history_password_set_at);
+ password_history_record[Anum_pg_password_history_passhistpassword - 1] =
+ CStringGetTextDatum(history_shadow_pass);
+
+ /* Form the insert tuple */
+ password_history_tuple = heap_form_tuple(pg_password_history_dsc,
+ password_history_record, password_nulls);
+
+ /* Insert new record into the pg_password_history table */
+ CatalogTupleInsert(pg_password_history_rel, password_history_tuple);
+
+ /* Advance command counter so we can see new record */
+ CommandCounterIncrement();
+
+ /* form a scan key */
+ ScanKeyInit(&skey,
+ Anum_pg_password_history_passhistroleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ /*
+ * Get only recently PASSWORD_REUSE_MAX tuples.
+ */
+ password_history_scan = systable_beginscan_ordered(pg_password_history_rel,
+ pg_password_history_passwordsetat_idx,
+ NULL, 1, &skey);
+ for (i = 0; i < profile_reuse_max; i++)
+ {
+ password_history_tuple = systable_getnext_ordered(password_history_scan, BackwardScanDirection);
+
+ if (!HeapTupleIsValid(password_history_tuple))
+ break;
+
+ datum = heap_getattr(password_history_tuple, Anum_pg_password_history_passhistpassword,
+ pg_password_history_dsc, &isnull);
+ Assert(!isnull);
+ history_shadow_pass = text_to_cstring(DatumGetTextP(datum));
+
+ /*
+ * Use password verify function to check whether password
+ * has been recorded in pg_password_history.
+ */
+ if (plain_crypt_verify(rolename, history_shadow_pass, password, &logdetail) == STATUS_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PASSWORD),
+ errmsg("The new password should not be the same with latest %d history password",
+ profile_reuse_max)));
+ }
+
+ systable_endscan_ordered(password_history_scan);
+
+ index_close(pg_password_history_passwordsetat_idx, NoLock);
+ table_close(pg_password_history_rel, NoLock);
+ }
+
+ password_set_at = now;
+ new_record[Anum_pg_authid_rolpasswordsetat - 1] =
+ Int64GetDatum(password_set_at);
+ new_record_repl[Anum_pg_authid_rolpasswordsetat - 1] = true;
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+
+ table_close(pg_profile_rel, NoLock);
}
/* unset password */
@@ -1208,6 +1622,32 @@ AlterRole(AlterRoleStmt *stmt)
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
+ /* profile name */
+ if (profilename)
+ {
+ /* Scan the pg_profile relation to be certain the profile exists. */
+ pg_profile_rel = table_open(ProfileRelationId, RowExclusiveLock);
+ pg_profile_dsc = RelationGetDescr(pg_profile_rel);
+
+ profile_tuple = SearchSysCache1(PROFILENAME, CStringGetDatum(profilename));
+ if (!HeapTupleIsValid(profile_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%s\" does not exist", profilename)));
+
+ profileform = (Form_pg_profile) GETSTRUCT(profile_tuple);
+ profileid = profileform->oid;
+
+ new_record[Anum_pg_authid_rolprofile - 1] = PointerGetDatum(profileid);
+ new_record_repl[Anum_pg_authid_rolprofile - 1] = true;
+
+ ReleaseSysCache(profile_tuple);
+ table_close(pg_profile_rel, NoLock);
+
+ /* set up dependencies for the new role */
+ changeProfileDependency(roleid, profileid);
+ }
+
/* Set the CREATE EXTERNAL TABLE permissions for this role, if specified in ALTER */
if (exttabcreate || exttabnocreate)
{
@@ -1514,7 +1954,8 @@ void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
- pg_auth_members_rel;
+ pg_auth_members_rel,
+ pg_password_history_rel;
ListCell *item;
if (!have_createrole_privilege())
@@ -1528,6 +1969,7 @@ DropRole(DropRoleStmt *stmt)
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
+ pg_password_history_rel = table_open(PasswordHistoryRelationId, RowExclusiveLock);
foreach(item, stmt->roles)
{
@@ -1655,6 +2097,29 @@ DropRole(DropRoleStmt *stmt)
systable_endscan(sscan);
+ /*
+ * Remove all role history passwords from pg_password_history.
+ */
+ ScanKeyInit(&scankey,
+ Anum_pg_password_history_passhistroleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_password_history_rel, PasswordHistoryRolePasswordIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ CatalogTupleDelete(pg_password_history_rel, &tmp_tuple->t_self);
+ }
+
+ systable_endscan(sscan);
+
+ /*
+ * Delete shared dependency references related to this role object.
+ */
+ deleteSharedDependencyRecordsFor(AuthIdRelationId, roleid, 0);
+
/*
* Remove any time constraints on this role.
*/
@@ -1690,6 +2155,7 @@ DropRole(DropRoleStmt *stmt)
/*
* Now we can clean up; but keep locks until commit.
*/
+ table_close(pg_password_history_rel, NoLock);
table_close(pg_auth_members_rel, NoLock);
table_close(pg_authid_rel, NoLock);
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index ae465b8fda0..7937b05e510 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -45,11 +45,14 @@
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_time_constraint.h"
+#include "catalog/pg_profile.h"
#include "cdb/cdbendpoint.h"
#include "cdb/cdbvars.h"
#include "pgtime.h"
+#include "postmaster/loginmonitor.h"
#include "postmaster/postmaster.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -286,6 +289,12 @@ auth_failed(Port *port, int status, char *logdetail)
const char *errstr;
char *cdetail;
int errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;
+ Relation pg_authid_rel;
+ TupleDesc pg_authid_dsc;
+ HeapTuple auth_tuple;
+ Form_pg_authid authform;
+ bool account_status_isnull;
+ int16 account_status;
/*
* If we failed due to EOF from client, just quit; there's no point in
@@ -364,6 +373,32 @@ auth_failed(Port *port, int status, char *logdetail)
else
logdetail = cdetail;
+ /* Send signal to login monitor */
+ pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);
+ pg_authid_dsc = RelationGetDescr(pg_authid_rel);
+
+ auth_tuple = SearchSysCache1(AUTHNAME,
+ CStringGetDatum(port->user_name));
+
+ if (!HeapTupleIsValid(auth_tuple))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("user \"%s\" does not exist",
+ port->user_name)));
+ }
+
+ /* Send signal only when user is not a super user and
+ * user is enable to use profile.
+ */
+ authform = (Form_pg_authid) GETSTRUCT(auth_tuple);
+ account_status = DatumGetInt16(SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rolaccountstatus, &account_status_isnull));
+ if (enable_password_profile && !authform->rolsuper && authform->rolenableprofile &&
+ (account_status != ROLE_ACCOUNT_STATUS_LOCKED ||
+ account_status != ROLE_ACCOUNT_STATUS_LOCKED_TIMED))
+ SendLoginFailedSignal(port->user_name);
+
ereport(FATAL,
(errcode(errcode_return),
errmsg(errstr, port->user_name),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 755c64e0999..bfccb567432 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -5155,6 +5155,17 @@ _copyCreateRoleStmt(const CreateRoleStmt *from)
return newnode;
}
+static CreateProfileStmt *
+_copyCreateProfileStmt(const CreateProfileStmt *from)
+{
+ CreateProfileStmt *newnode = makeNode(CreateProfileStmt);
+
+ COPY_STRING_FIELD(profile_name);
+ COPY_NODE_FIELD(options);
+
+ return newnode;
+}
+
static DenyLoginInterval *
_copyDenyLoginInterval(const DenyLoginInterval *from)
{
@@ -5189,6 +5200,17 @@ _copyAlterRoleStmt(const AlterRoleStmt *from)
return newnode;
}
+static AlterProfileStmt *
+_copyAlterProfileStmt(const AlterProfileStmt *from)
+{
+ AlterProfileStmt *newnode = makeNode(AlterProfileStmt);
+
+ COPY_STRING_FIELD(profile_name);
+ COPY_NODE_FIELD(options);
+
+ return newnode;
+}
+
static AlterRoleSetStmt *
_copyAlterRoleSetStmt(const AlterRoleSetStmt *from)
{
@@ -5212,6 +5234,17 @@ _copyDropRoleStmt(const DropRoleStmt *from)
return newnode;
}
+static DropProfileStmt *
+_copyDropProfileStmt(const DropProfileStmt *from)
+{
+ DropProfileStmt *newnode = makeNode(DropProfileStmt);
+
+ COPY_NODE_FIELD(profiles);
+ COPY_SCALAR_FIELD(missing_ok);
+
+ return newnode;
+}
+
static LockStmt *
_copyLockStmt(const LockStmt *from)
{
@@ -6753,6 +6786,15 @@ copyObjectImpl(const void *from)
case T_DropRoleStmt:
retval = _copyDropRoleStmt(from);
break;
+ case T_CreateProfileStmt:
+ retval = _copyCreateProfileStmt(from);
+ break;
+ case T_AlterProfileStmt:
+ retval = _copyAlterProfileStmt(from);
+ break;
+ case T_DropProfileStmt:
+ retval = _copyDropProfileStmt(from);
+ break;
case T_LockStmt:
retval = _copyLockStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8568c69dca9..3bb25eb1d21 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2286,6 +2286,15 @@ _equalCreateRoleStmt(const CreateRoleStmt *a, const CreateRoleStmt *b)
return true;
}
+static bool
+_equalCreateProfileStmt(const CreateProfileStmt *a, const CreateProfileStmt *b)
+{
+ COMPARE_STRING_FIELD(profile_name);
+ COMPARE_NODE_FIELD(options);
+
+ return true;
+}
+
static bool
_equalDenyLoginInterval(const DenyLoginInterval *a, const DenyLoginInterval *b)
{
@@ -2324,6 +2333,15 @@ _equalAlterRoleSetStmt(const AlterRoleSetStmt *a, const AlterRoleSetStmt *b)
return true;
}
+static bool
+_equalAlterProfileStmt(const AlterProfileStmt *a, const AlterProfileStmt *b)
+{
+ COMPARE_STRING_FIELD(profile_name);
+ COMPARE_NODE_FIELD(options);
+
+ return true;
+}
+
static bool
_equalDropRoleStmt(const DropRoleStmt *a, const DropRoleStmt *b)
{
@@ -2333,6 +2351,15 @@ _equalDropRoleStmt(const DropRoleStmt *a, const DropRoleStmt *b)
return true;
}
+static bool
+_equalDropProfileStmt(const DropProfileStmt *a, const DropProfileStmt *b)
+{
+ COMPARE_NODE_FIELD(profiles);
+ COMPARE_SCALAR_FIELD(missing_ok);
+
+ return true;
+}
+
static bool
_equalLockStmt(const LockStmt *a, const LockStmt *b)
{
@@ -3948,6 +3975,15 @@ equal(const void *a, const void *b)
case T_DropRoleStmt:
retval = _equalDropRoleStmt(a, b);
break;
+ case T_CreateProfileStmt:
+ retval = _equalCreateProfileStmt(a, b);
+ break;
+ case T_AlterProfileStmt:
+ retval = _equalAlterProfileStmt(a, b);
+ break;
+ case T_DropProfileStmt:
+ retval = _equalDropProfileStmt(a, b);
+ break;
case T_LockStmt:
retval = _equalLockStmt(a, b);
break;
diff --git a/src/backend/nodes/outfast.c b/src/backend/nodes/outfast.c
index 706d2f8e21f..a8ab42e8a0d 100644
--- a/src/backend/nodes/outfast.c
+++ b/src/backend/nodes/outfast.c
@@ -1341,6 +1341,17 @@ _outNode(StringInfo str, void *obj)
case T_AlterRoleSetStmt:
_outAlterRoleSetStmt(str, obj);
break;
+
+ case T_CreateProfileStmt:
+ _outCreateProfileStmt(str, obj);
+ break;
+ case T_AlterProfileStmt:
+ _outAlterProfileStmt(str, obj);
+ break;
+ case T_DropProfileStmt:
+ _outDropProfileStmt(str, obj);
+ break;
+
case T_AlterSystemStmt:
_outAlterSystemStmt(str, obj);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 43dd8d7b9a5..cfff625e491 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3276,6 +3276,10 @@ _outQuery(StringInfo str, const Query *node)
case T_AlterRoleSetStmt:
case T_DropRoleStmt:
+ case T_CreateProfileStmt:
+ case T_AlterProfileStmt:
+ case T_DropProfileStmt:
+
case T_CreateSchemaStmt:
case T_CreatePLangStmt:
case T_AlterOwnerStmt:
@@ -4758,6 +4762,16 @@ outNode(StringInfo str, const void *obj)
_outAlterRoleSetStmt(str, obj);
break;
+ case T_CreateProfileStmt:
+ _outCreateProfileStmt(str, obj);
+ break;
+ case T_AlterProfileStmt:
+ _outAlterProfileStmt(str, obj);
+ break;
+ case T_DropProfileStmt:
+ _outDropProfileStmt(str, obj);
+ break;
+
case T_AlterSystemStmt:
_outAlterSystemStmt(str, obj);
break;
diff --git a/src/backend/nodes/outfuncs_common.c b/src/backend/nodes/outfuncs_common.c
index 9de97f138ca..f777d6de2e7 100644
--- a/src/backend/nodes/outfuncs_common.c
+++ b/src/backend/nodes/outfuncs_common.c
@@ -836,6 +836,15 @@ _outCreateRoleStmt(StringInfo str, const CreateRoleStmt *node)
WRITE_NODE_FIELD(options);
}
+static void
+_outCreateProfileStmt(StringInfo str, const CreateProfileStmt *node)
+{
+ WRITE_NODE_TYPE("CREATEPROFILESTMT");
+
+ WRITE_STRING_FIELD(profile_name);
+ WRITE_NODE_FIELD(options);
+}
+
static void
_outDenyLoginInterval(StringInfo str, const DenyLoginInterval *node)
{
@@ -863,6 +872,15 @@ _outDropRoleStmt(StringInfo str, const DropRoleStmt *node)
WRITE_BOOL_FIELD(missing_ok);
}
+static void
+_outDropProfileStmt(StringInfo str, const DropProfileStmt *node)
+{
+ WRITE_NODE_TYPE("DROPPROFILESTMT");
+
+ WRITE_NODE_FIELD(profiles);
+ WRITE_BOOL_FIELD(missing_ok);
+}
+
static void
_outAlterObjectSchemaStmt(StringInfo str, const AlterObjectSchemaStmt *node)
{
@@ -905,6 +923,15 @@ _outAlterRoleStmt(StringInfo str, const AlterRoleStmt *node)
WRITE_INT_FIELD(action);
}
+static void
+_outAlterProfileStmt(StringInfo str, const AlterProfileStmt *node)
+{
+ WRITE_NODE_TYPE("ALTERPROFILESTMT");
+
+ WRITE_STRING_FIELD(profile_name);
+ WRITE_NODE_FIELD(options);
+}
+
static void
_outAlterSystemStmt(StringInfo str, const AlterSystemStmt *node)
{
diff --git a/src/backend/nodes/readfast.c b/src/backend/nodes/readfast.c
index 889ae04f200..d1efd231c53 100644
--- a/src/backend/nodes/readfast.c
+++ b/src/backend/nodes/readfast.c
@@ -2247,6 +2247,16 @@ readNodeBinary(void)
return_value = _readAlterRoleSetStmt();
break;
+ case T_CreateProfileStmt:
+ return_value = _readCreateProfileStmt();
+ break;
+ case T_AlterProfileStmt:
+ return_value = _readAlterProfileStmt();
+ break;
+ case T_DropProfileStmt:
+ return_value = _readDropProfileStmt();
+ break;
+
case T_AlterObjectDependsStmt:
return_value = _readAlterObjectDependsStmt();
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 028da201b8f..7e8ead1e3ff 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -3124,6 +3124,8 @@ parseNodeString(void)
return_value = _readAlterSystemStmt();
else if (MATCHX("ALTERROLESTMT"))
return_value = _readAlterRoleStmt();
+ else if (MATCHX("ALTERPROFILESTMT"))
+ return_value = _readAlterProfileStmt();
else if (MATCHX("ALTERSEQSTMT"))
return_value = _readAlterSeqStmt();
else if (MATCHX("ALTERTABLECMD"))
@@ -3192,6 +3194,8 @@ parseNodeString(void)
return_value = _readCreatePolicyStmt();
else if (MATCHX("CREATEROLESTMT"))
return_value = _readCreateRoleStmt();
+ else if (MATCHX("CREATEPROFILESTMT"))
+ return_value = _readCreateProfileStmt();
else if (MATCHX("CREATESCHEMASTMT"))
return_value = _readCreateSchemaStmt();
else if (MATCHX("CREATESEQSTMT"))
@@ -3212,6 +3216,8 @@ parseNodeString(void)
return_value = _readDropdbStmt();
else if (MATCHX("DROPROLESTMT"))
return_value = _readDropRoleStmt();
+ else if (MATCHX("DROPPROFILESTMT"))
+ return_value = _readDropProfileStmt();
else if (MATCHX("DROPSTMT"))
return_value = _readDropStmt();
else if (MATCHX("DISTRIBUTIONKEYELEM"))
diff --git a/src/backend/nodes/readfuncs_common.c b/src/backend/nodes/readfuncs_common.c
index 43e02b4188f..f23687143e3 100644
--- a/src/backend/nodes/readfuncs_common.c
+++ b/src/backend/nodes/readfuncs_common.c
@@ -389,6 +389,17 @@ _readAlterRoleStmt(void)
READ_DONE();
}
+static AlterProfileStmt *
+_readAlterProfileStmt(void)
+{
+ READ_LOCALS(AlterProfileStmt);
+
+ READ_STRING_FIELD(profile_name);
+ READ_NODE_FIELD(options);
+
+ READ_DONE();
+}
+
static AlterSeqStmt *
_readAlterSeqStmt(void)
{
@@ -870,6 +881,17 @@ _readCreateRoleStmt(void)
READ_DONE();
}
+static CreateProfileStmt *
+_readCreateProfileStmt(void)
+{
+ READ_LOCALS(CreateProfileStmt);
+
+ READ_STRING_FIELD(profile_name);
+ READ_NODE_FIELD(options);
+
+ READ_DONE();
+}
+
static CreateSchemaStmt *
_readCreateSchemaStmt(void)
{
@@ -1031,6 +1053,17 @@ _readDropRoleStmt(void)
READ_DONE();
}
+static DropProfileStmt *
+_readDropProfileStmt(void)
+{
+ READ_LOCALS(DropProfileStmt);
+
+ READ_NODE_FIELD(profiles);
+ READ_BOOL_FIELD(missing_ok);
+
+ READ_DONE();
+}
+
static DropStmt *
_readDropStmt(void)
{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 45e22f74bab..b6ac3a5462b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -318,10 +318,10 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
RetrieveStmt CreateTaskStmt AlterTaskStmt DropTaskStmt
/* GPDB-specific commands */
-%type AlterQueueStmt AlterResourceGroupStmt
+%type AlterProfileStmt AlterQueueStmt AlterResourceGroupStmt
CreateExternalStmt
- CreateQueueStmt CreateResourceGroupStmt
- DropQueueStmt DropResourceGroupStmt
+ CreateProfileStmt CreateQueueStmt CreateResourceGroupStmt
+ DropProfileStmt DropQueueStmt DropResourceGroupStmt
ExtTypedesc OptSingleRowErrorHandling ExtSingleRowErrorHandling
%type deny_login_role deny_interval deny_point deny_day_specifier
@@ -373,8 +373,10 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
%type opt_nowait_or_skip
%type OptRoleList AlterOptRoleList
+%type OptProfileList
%type CreateOptRoleElem AlterOptRoleElem
%type AlterOnlyOptRoleElem
+%type OptProfileElem
%type opt_type
%type foreign_server_version opt_foreign_server_version
@@ -831,7 +833,7 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
/* GPDB-added keywords, in alphabetical order */
%token
- ACTIVE
+ ACCOUNT ACTIVE
CONTAINS COORDINATOR CPUSET CPU_RATE_LIMIT
@@ -841,7 +843,7 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
ERRORS EVERY EXCHANGE EXPAND
- FIELDS FILL FORMAT
+ FAILED_LOGIN_ATTEMPTS FIELDS FILL FORMAT
FULLSCAN
@@ -859,7 +861,7 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
ORDERED OVERCOMMIT
- PARTITIONS PERCENT PERSISTENTLY PROTOCOL
+ PARTITIONS PASSWORD_LOCK_TIME PASSWORD_REUSE_MAX PERCENT PERSISTENTLY PROFILE PROTOCOL
QUEUE
@@ -872,6 +874,8 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
THRESHOLD
+ UNLOCK_P
+
VALIDATION
WAREHOUSE
@@ -1191,6 +1195,7 @@ static void check_expressions_in_partition_key(PartitionSpec *spec, core_yyscan_
%nonassoc UNCOMMITTED
%nonassoc UNENCRYPTED
%nonassoc UNLISTEN
+ %nonassoc UNLOCK_P
%nonassoc UNTIL
%nonassoc UPDATE
%nonassoc VACUUM
@@ -1386,6 +1391,7 @@ stmt:
| AlterTaskStmt
| AlterTypeStmt
| AlterPolicyStmt
+ | AlterProfileStmt
| AlterQueueStmt
| AlterResourceGroupStmt
| AlterSeqStmt
@@ -1429,6 +1435,7 @@ stmt:
| CreateWarehouseStmt
| AlterOpFamilyStmt
| CreatePolicyStmt
+ | CreateProfileStmt
| CreatePLangStmt
| CreateQueueStmt
| CreateResourceGroupStmt
@@ -1456,6 +1463,7 @@ stmt:
| DropOpClassStmt
| DropOpFamilyStmt
| DropOwnedStmt
+ | DropProfileStmt
| DropQueueStmt
| DropResourceGroupStmt
| DropStmt
@@ -1853,6 +1861,26 @@ AlterOptRoleElem:
{
$$ = makeDefElem("deny", (Node *) $1, @1);
}
+ | PROFILE name
+ {
+ $$ = makeDefElem("profile", (Node *)makeString($2), @1);
+ }
+ | ACCOUNT LOCK_P
+ {
+ $$ = makeDefElem("accountislock", (Node *) makeInteger(true), @1);
+ }
+ | ACCOUNT UNLOCK_P
+ {
+ $$ = makeDefElem("accountislock", (Node*) makeInteger(false), @1);
+ }
+ | ENABLE_P PROFILE
+ {
+ $$ = makeDefElem("enableProfile", (Node *) makeInteger(true), @1);
+ }
+ | DISABLE_P PROFILE
+ {
+ $$ = makeDefElem("enableProfile", (Node *) makeInteger(false), @1);
+ }
| IDENT
{
/*
@@ -2112,6 +2140,99 @@ DropRoleStmt:
;
+/*****************************************************************************
+ *
+ * Create a new Postgres DBMS Profile
+ *
+ *****************************************************************************/
+
+CreateProfileStmt:
+ CREATE PROFILE name
+ {
+ CreateProfileStmt *n = makeNode(CreateProfileStmt);
+ n->profile_name = $3;
+ $$ = (Node *)n;
+ }
+ | CREATE PROFILE name LIMIT OptProfileList
+ {
+ CreateProfileStmt *n = makeNode(CreateProfileStmt);
+ n->profile_name = $3;
+ n->options = $5;
+ $$ = (Node *)n;
+ }
+ ;
+
+/*
+ * Options for CREATE PROFILE and ALTER PROFILE.
+ */
+OptProfileList:
+ OptProfileList OptProfileElem { $$ = lappend($1, $2); }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+OptProfileElem:
+ FAILED_LOGIN_ATTEMPTS SignedIconst
+ {
+ $$ = makeDefElem("failed_login_attempts",
+ (Node *)makeInteger($2), @1);
+ }
+ | PASSWORD_LOCK_TIME SignedIconst
+ {
+ $$ = makeDefElem("password_lock_time",
+ (Node *)makeInteger($2), @1);
+ }
+ | PASSWORD_REUSE_MAX SignedIconst
+ {
+ $$ = makeDefElem("password_reuse_max",
+ (Node *)makeInteger($2), @1);
+ }
+ ;
+
+
+/*****************************************************************************
+ *
+ * Alter a postgresql DBMS profile
+ *
+ *****************************************************************************/
+
+AlterProfileStmt:
+ ALTER PROFILE name LIMIT OptProfileList
+ {
+ AlterProfileStmt *n = makeNode(AlterProfileStmt);
+ n->profile_name = $3;
+ n->options = $5;
+ $$ = (Node *)n;
+ }
+ ;
+
+
+/*****************************************************************************
+ *
+ * Drop a postgresql DBMS profile
+ *
+ * XXX Ideally this would have CASCADE/RESTRICT options, but a profile
+ * might be attached by users in multiple databases, using CASCADE will drop
+ * users meanwhile which is unreasonable. So we always behave as RESTRICT.
+ *****************************************************************************/
+
+DropProfileStmt:
+ DROP PROFILE name_list
+ {
+ DropProfileStmt *n = makeNode(DropProfileStmt);
+ n->profiles = $3;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | DROP PROFILE IF_P EXISTS name_list
+ {
+ DropProfileStmt *n = makeNode(DropProfileStmt);
+ n->profiles = $5;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
+ ;
+
+
/*****************************************************************************
*
* Create a postgresql group (role without login ability)
@@ -8969,6 +9090,7 @@ object_type_name:
drop_type_name { $$ = $1; }
| DATABASE { $$ = OBJECT_DATABASE; }
| ROLE { $$ = OBJECT_ROLE; }
+ | PROFILE { $$ = OBJECT_PROFILE; }
| SUBSCRIPTION { $$ = OBJECT_SUBSCRIPTION; }
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
| RESOURCE QUEUE { $$ = OBJECT_RESQUEUE; }
@@ -11698,6 +11820,15 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER PROFILE name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_PROFILE;
+ n->subname = $3;
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
;
opt_column: COLUMN
@@ -18501,6 +18632,7 @@ unreserved_keyword:
ABORT_P
| ABSOLUTE_P
| ACCESS
+ | ACCOUNT
| ACTION
| ACTIVE
| ADD_P
@@ -18601,6 +18733,7 @@ unreserved_keyword:
| EXPRESSION
| EXTENSION
| EXTERNAL
+ | FAILED_LOGIN_ATTEMPTS
| FAMILY
| FIELDS
| FILL
@@ -18717,6 +18850,8 @@ unreserved_keyword:
| PARTITIONS
| PASSING
| PASSWORD
+ | PASSWORD_LOCK_TIME
+ | PASSWORD_REUSE_MAX
| PERCENT
| PERSISTENTLY
| PLANS
@@ -18729,6 +18864,7 @@ unreserved_keyword:
| PROCEDURAL
| PROCEDURE
| PROCEDURES
+ | PROFILE
| PROGRAM
| PROTOCOL
| PUBLICATION
@@ -18831,6 +18967,7 @@ unreserved_keyword:
| UNENCRYPTED
| UNKNOWN
| UNLISTEN
+ | UNLOCK_P
| UNLOGGED
| UNTIL
| UPDATE
@@ -19378,6 +19515,7 @@ bare_label_keyword:
ABORT_P
| ABSOLUTE_P
| ACCESS
+ | ACCOUNT
| ACTION
| ACTIVE
| ADD_P
@@ -19522,6 +19660,7 @@ bare_label_keyword:
| EXTENSION
| EXTERNAL
| EXTRACT
+ | FAILED_LOGIN_ATTEMPTS
| FALSE_P
| FAMILY
| FIELDS
@@ -19675,6 +19814,8 @@ bare_label_keyword:
| PARTITIONS
| PASSING
| PASSWORD
+ | PASSWORD_LOCK_TIME
+ | PASSWORD_REUSE_MAX
| PERCENT
| PERSISTENTLY
| PLACING
@@ -19692,6 +19833,7 @@ bare_label_keyword:
| PROCEDURAL
| PROCEDURE
| PROCEDURES
+ | PROFILE
| PROGRAM
| PROTOCOL
| PUBLICATION
@@ -19816,6 +19958,7 @@ bare_label_keyword:
| UNIQUE
| UNKNOWN
| UNLISTEN
+ | UNLOCK_P
| UNLOGGED
| UNTIL
| UPDATE
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 917c60577b6..5ba0602a9fa 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -25,7 +25,8 @@ OBJS = \
postmaster.o \
startup.o \
syslogger.o \
- walwriter.o
+ walwriter.o \
+ loginmonitor.o
OBJS += backoff.o autostats.o
diff --git a/src/backend/postmaster/loginmonitor.c b/src/backend/postmaster/loginmonitor.c
new file mode 100644
index 00000000000..d67fcc818cf
--- /dev/null
+++ b/src/backend/postmaster/loginmonitor.c
@@ -0,0 +1,893 @@
+/*-------------------------------------------------------------------------
+ *
+ * loginmonitor.c
+ *
+ * PostgreSQL Integrated Login Monitor Daemon
+ *
+ * Like autovacuum, the login monitor is structured in two different
+ * kinds of processes: the login monitor launcher and the login monitor
+ * worker. The launcher is an always-running process, started by the
+ * postmaster. It is mainly used to process user's failed login
+ * authentication. It will always running in loop waiting for failed
+ * login signal. The launcher will signal postmaster to fork the worker
+ * process when it receives failed authentication signal from the postgres
+ * process. The worker process is the process which doing the actual
+ * working; as the worker process is only forked when authenticate failed,
+ * one worker process is enough to finish that. It will be forked from
+ * the postmaster as needed. Like normal postgres process, login monitor
+ * worker is equipped with locks, transactions, read/write catalog table
+ * and other functionalities.
+ *
+ * The login monitor launcher cannot start the worker process by itself,
+ * as doing so would cause robustness issues (namely, failure to shut
+ * them down on exceptional conditions, and also, since the launcher is
+ * connected to shared memory and is thus subject to corruption there,
+ * it is not as robust as the postmaster). So it leaves that task to the
+ * postmaster.
+ *
+ * There is a login monitor shared memory area, where the launcher
+ * stores information about its pid and latch. What's more, the worker
+ * stores the current login user's name and it's latch to shared memory.
+ * There is also a flag between launcher and worker to indicate whether
+ * the failed login authentication signal has been processed.
+ *
+ * When there is a failed login authentication, the postgres process will
+ * send the signal to the postmaster and wait the shared memory latch.
+ * The launcher received signal from postgres process, it will set the
+ * flag to true to indicate the signal is processing. And it will resend
+ * a signal to postmaster and wait the latch of lm_latch in shared memory.
+ * After receiving the signal from the launcher, the postmaster will fork
+ * the worker to do the actual working. After the worker finishes work,
+ * it will notify postgres process and the launcher by setting the latch
+ * and the lm_latch in shared memory. Moreover, it will reset the flag to
+ * false to indicate the work is finished.
+ *
+ * Only when the user is able to use profile, process will send signal to
+ * login monitor and wait for the completion of worker process. Otherwise,
+ * the failed login authentication of the user will be ignored and doesn't
+ * need to send signal to the launcher.
+ *
+ * Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/loginmonitor.c
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/reloptions.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_profile.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/loginmonitor.h"
+#include "postmaster/postmaster.h"
+#include "storage/ipc.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/smgr.h"
+#include "tcop/tcopprot.h"
+#include "utils/ps_status.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+#include "utils/timeout.h"
+
+#include "cdb/cdbvars.h"
+
+/* Memory context for login monitor rewrites catalogs */
+static MemoryContext LoginMonitorMemCxt;
+static pid_t LoginMonitorPID;
+
+/*
+ * The main Login Monitor shmem struct. On shared memory we store it,
+ * which will be set by failed proc and reset by login monitor
+ * post-processing. This struct keeps:
+ *
+ * lm_pid Login Monitor Launcher Process pid
+ * lm_latch Login Monitor Launcher Latch pointer
+ * curr_user_name current failed user name
+ * latch pointer of current failed proc's latch
+ * login_failed_requested whether current is handling a failed login
+ *
+ * curr_user_name, latch and login_failed_requested are protected by LWLock LoginFailedSharedMemoryLock
+ */
+typedef struct {
+ pid_t lm_pid;
+ Latch *lm_latch;
+ char curr_user_name[NAMEDATALEN];
+ Latch *latch;
+ sig_atomic_t login_failed_requested;
+} LoginMonitorShmemStruct;
+
+static LoginMonitorShmemStruct *LoginMonitorShmem;
+
+/* Flags to tell if we are in an login monitor process */
+static bool am_login_monitor_launcher = false;
+static bool am_login_monitor_worker = false;
+
+static void LoginMonitorShutdown(void);
+
+static void HandleLoginMonitorInterrupts(void);
+
+static void LoginMonitorShmemReset(void);
+
+#ifdef EXEC_BACKEND
+static pid_t lmlauncher_forkexec(void);
+static pid_t lmworker_forkexec(void);
+#endif
+
+NON_EXEC_STATIC void LoginMonitorLauncherMain(int argc, char *argv[]);
+
+NON_EXEC_STATIC void LoginMonitorWorkerMain(int argc, char *argv[]) pg_attribute_noreturn();
+
+static void record_failed_login(void);
+
+
+/********************************************************************
+ * LOGIN MONITOR CODE
+ ********************************************************************/
+
+
+#ifdef EXEC_BACKEND
+/*
+ * forkexec routine for the login monitor launcher process.
+ *
+ * Format up the arglist, then fork and exec.
+ */
+static pid_t
+lmlauncher_forkexec(void)
+{
+ char *lm[10];
+ int lc = 0;
+
+ lm[lc++] = "postgres";
+ lm[lc++] = "--forkloginmonitor";
+ lm[lc++] = NULL;
+ lm[lc] = NULL;
+
+ Assert(lc < lengthof(lm));
+
+ return postmaster_forkexec(lc, lm);
+}
+#endif
+
+/*
+ * Main entry point for login monitor launcher process, to be called from
+ * the postmaster.
+ */
+int
+StartLoginMonitorLauncher(void) {
+#ifdef EXEC_BACKEND
+ switch ((LoginMonitorPID = lmlauncher_forkexec()))
+#else
+ switch ((LoginMonitorPID = fork_process()))
+#endif
+ {
+ case -1:
+ ereport(LOG,
+ (errmsg("could not fork login monitor process: %m")));
+ return 0;
+
+#ifndef EXEC_BACKEND
+ case 0:
+ /* in postgresmaster child ... */
+ InitPostmasterChild();
+
+ /* Close the postmaster's sockets */
+ ClosePostmasterPorts(false);
+
+ LoginMonitorLauncherMain(0, NULL);
+ break;
+#endif
+ default:
+ return (int) LoginMonitorPID;
+ }
+
+ /* shouldn't get here */
+ return 0;
+}
+
+/*
+ * Main loop for the login monitor process.
+ */
+NON_EXEC_STATIC void
+LoginMonitorLauncherMain(int argc, char *argv[]) {
+ sigjmp_buf local_sigjmp_buf;
+
+ am_login_monitor_launcher = true;
+
+ MyBackendType = B_LOGIN_MONITOR;
+ init_ps_display(NULL);
+
+ ereport(DEBUG1,
+ (errmsg_internal("login monitor launcher started")));
+
+ SetProcessingMode(InitProcessing);
+
+ /*
+ * Set up signal handler. We operate on databases much like a regular
+ * backend, so we use the same signal handling. See equivalent code in
+ * tcop/postgres.c.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, StatementCancelHandler);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+
+ InitializeTimeouts(); /* established SIGALRM handler */
+
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+ pqsignal(SIGFPE, FloatExceptionHandler);
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /* Early initialization */
+ BaseInit();
+
+ /*
+ * Create a per-backend PGPROC struct in shared memory, except in the
+ * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
+ * this before we can use LWLocks (and in the EXEC_BACKEND case we already
+ * had to do some stuff with LWLocks).
+ */
+#ifndef EXEC_BACKEND
+ InitProcess();
+#endif
+
+ InitPostgres(NULL, InvalidOid, NULL, InvalidOid, NULL, false);
+
+ SetProcessingMode(NormalProcessing);
+
+ /*
+ * Create a memory context that we will do all our work in. We do this so
+ * that we can reset the context during error recovery and thereby avoid
+ * possible memory leaks.
+ */
+ LoginMonitorMemCxt = AllocSetContextCreate(TopMemoryContext,
+ "Login Monitor",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(LoginMonitorMemCxt);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * This code is a stripped down version of PostgresMain error recovery.
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
+ /* since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevents interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Forget any pending QueryCancel or timeout request */
+ disable_all_timeouts(false);
+ QueryCancelPending = false; /* second to avoid race condition */
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /* Abort the current transaction in order to recover */
+ AbortCurrentTransaction();
+
+ /*
+ * Release any other resources, for the case where we were not in a
+ * transaction.
+ */
+ LWLockReleaseAll();
+ AbortBufferIO();
+ UnlockBuffers();
+ /* this is probably dead code, but let's be safe: */
+ if (AuxProcessResourceOwner)
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Buffers(false);
+ AtEOXact_SMgr();
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ /*
+ * Now return to normal top-level context and clear ErrorContext for
+ * next time.
+ */
+ MemoryContextSwitchTo(LoginMonitorMemCxt);
+ FlushErrorState();
+
+ /* Flush any leaked data in the top-level context */
+ MemoryContextResetAndDeleteChildren(LoginMonitorMemCxt);
+
+ /* Now we can allow interrupts again */
+ RESUME_INTERRUPTS();
+
+ /* reset and notify process by latch */
+ SetLatch(LoginMonitorShmem->latch);
+ ResetLatch(LoginMonitorShmem->lm_latch);
+ LoginMonitorShmemReset();
+
+ /* if in shutdown mode, no need for anything further; just go away */
+ if (ShutdownRequestPending)
+ LoginMonitorShutdown();
+
+ /*
+ * Sleep at least 1 milli second after any error. We don't want to be
+ * filling the error logs as fast as we can.
+ */
+ pg_usleep(1000L);
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ /* must unblock signals before calling rebuild */
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Set always-secure search path.
+ */
+ SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force zero_damaged_pages OFF in the login monitor process, even if it is set`
+ * in postgresql.conf. We don't really want such a dangerous option being applied
+ * non-interactively.
+ */
+ SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force settable timeouts off to avoid letting these settings prevent
+ * regular maintenance from being executed.
+ */
+ SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
+ SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
+ SetConfigOption("idle_in_transaction_session_timeout", "0",
+ PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force default_transaction_isolation to READ COMMITTED. We don't want
+ * to pay the overhead of serializable mode, nor add any risk of causing
+ * deadlocks or delaying other transactions.
+ */
+ SetConfigOption("default_transaction_isolation", "read committed",
+ PGC_SUSET, PGC_S_OVERRIDE);
+
+ LoginMonitorShmem->lm_pid = MyProcPid;
+ LoginMonitorShmem->lm_latch = MyLatch;
+
+ /*
+ * Main loop until shutdown request
+ */
+ while (!ShutdownRequestPending) {
+ /*
+ * Wait until naptime expires or we get some type of signal (all the
+ * signal handlers will wake us by calling SetLatch).
+ */
+ (void) WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ 5 * 1000L,
+ WAIT_EVENT_LOGIN_MONITOR_LAUNCHER_MAIN);
+
+ /* Clear any already-pending wakeups */
+ ResetLatch(MyLatch);
+
+ HandleLoginMonitorInterrupts();
+
+ /*
+ * As only one worker can run at any time, before start a worker,
+ * we should check whether there is already one worker running.
+ */
+ if (LoginMonitorShmem->login_failed_requested)
+ {
+ SendPostmasterSignal(PMSIGNAL_START_LOGIN_MONITOR_WORKER);
+ elog(DEBUG1, "Login monitor launcher is processing uesr %s and has sent starting"
+ "worker signal to postmaster.", LoginMonitorShmem->curr_user_name);
+ WaitLatch(LoginMonitorShmem->lm_latch,
+ WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
+ WAIT_EVENT_LOGINMONITOR_FINISH);
+ /* Clear any already-pending wakeups */
+ ResetLatch(LoginMonitorShmem->lm_latch);
+ }
+ }
+
+ LoginMonitorShutdown();
+}
+
+/********************************************************************
+ * LOGIN MONITOR WORKER CODE
+ ********************************************************************/
+/*
+ * Main entry point for login monitor process.
+ *
+ * This code is heavily based on pgarch.c, q.v.
+ */
+int
+StartLoginMonitorWorker(void) {
+ pid_t worker_pid;
+
+#ifdef EXEC_BACKEND
+ switch ((worker_pid = lmworker_forkexec()))
+#else
+ switch ((worker_pid = fork_process()))
+#endif
+ {
+ case -1:
+ ereport(LOG,
+ (errmsg("could not fork login monitor worker process: %m")));
+ return 0;
+
+#ifndef EXEC_BACKEND
+ case 0:
+ /* in postmaster child ... */
+ InitPostmasterChild();
+
+ /* Close the postmaster's sockets */
+ ClosePostmasterPorts(false);
+
+ LoginMonitorWorkerMain(0, NULL);
+ break;
+#endif
+ default:
+ return (int) worker_pid;
+ }
+
+ /* shouldn't get here */
+ return 0;
+}
+
+/*
+ * LoginMonitorWorkerMain
+ */
+NON_EXEC_STATIC void
+LoginMonitorWorkerMain(int argc, char *argv[]) {
+ sigjmp_buf local_sigjmp_buf;
+
+ am_login_monitor_worker = true;
+
+ /* MPP-4990: LoginMonitor always runs as utility-mode */
+ if (IS_QUERY_DISPATCHER())
+ Gp_role = GP_ROLE_DISPATCH;
+ else
+ Gp_role = GP_ROLE_UTILITY;
+
+ MyBackendType = B_LOGIN_MONITOR_WORKER;
+ init_ps_display(NULL);
+
+ SetProcessingMode(InitProcessing);
+
+ /*
+ * Set up signal handlers. We operate on databases much like a regular
+ * backend, so we use the same signal handling. See equivalent code in
+ * tcop/postgres.c.
+ */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGTERM, die);
+
+ /* SIGQUIT handler was already set up by InitPostmasterChild */
+ InitializeTimeouts(); /* establishes SIGALRM handler */
+
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGUSR2, SIG_IGN);
+ pqsignal(SIGFPE, FloatExceptionHandler);
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /* Early initialization */
+ BaseInit();
+
+ /*
+ * Create a per-backend PGPROC struct in shared memory, except in the
+ * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
+ * this before we can use LWLocks (and in the EXEC_BACKEND case we already
+ * had to do some stuff with LWLocks).
+ */
+#ifndef EXEC_BACKEND
+ InitProcess();
+#endif
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * This code is a stripped down version of PostgresMain error recovery.
+ *
+ * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
+ * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
+ * signals other than SIGQUIT will be blocked until we complete error
+ * recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
+ * call redundant, but it is not since InterruptPending might be set
+ * already.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
+ /* since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevents interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ SetLatch(LoginMonitorShmem->lm_latch);
+ /*
+ * We can now go away. Note that because we called InitProcess, a
+ * callback was registered to do ProcKill, which will clean up
+ * necessary state.
+ */
+ proc_exit(0);
+ }
+
+ /* We can now handle ereport(ERROR) */
+ PG_exception_stack = &local_sigjmp_buf;
+
+ PG_SETMASK(&UnBlockSig);
+
+ /*
+ * Set always-secure search path.
+ */
+ SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force zero_damaged_pages OFF in the login monitor process, even if it is set`
+ * in postgresql.conf. We don't really want such a dangerous option being applied
+ * non-interactively.
+ */
+ SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force settable timeouts off to avoid letting these settings prevent
+ * regular maintenance from being executed.
+ */
+ SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
+ SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
+ SetConfigOption("idle_in_transaction_session_timeout", "0",
+ PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force default_transaction_isolation to READ COMMITTED. We don't want
+ * to pay the overhead of serializable mode, nor add any risk of causing
+ * deadlocks or delaying other transactions.
+ */
+ SetConfigOption("default_transaction_isolation", "read committed",
+ PGC_SUSET, PGC_S_OVERRIDE);
+
+ /*
+ * Force synchronous replication off to allow regular maintenance even if
+ * we are waiting for standbys to connect. This is important to ensure we
+ * aren't blocked from performing anti-wraparound tasks.
+ */
+ if (synchronous_commit > SYNCHRONOUS_COMMIT_LOCAL_FLUSH)
+ SetConfigOption("synchronous_commit", "local",
+ PGC_SUSET, PGC_S_OVERRIDE);
+
+ if (LoginMonitorShmem->curr_user_name) {
+ InitPostgres(DB_FOR_COMMON_ACCESS, InvalidOid, NULL, InvalidOid, NULL, false);
+ SetProcessingMode(NormalProcessing);
+ set_ps_display(LoginMonitorShmem->curr_user_name);
+ ereport(DEBUG1,
+ (errmsg_internal("login monitor: processing user \"%s\" failed",
+ LoginMonitorShmem->curr_user_name)));
+
+ record_failed_login();
+ }
+
+ proc_exit(0);
+}
+
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleLoginMonitorInterrupts(void) {
+ /* the normal shutdown case */
+ if (ShutdownRequestPending)
+ LoginMonitorShutdown();
+
+ if (ConfigReloadPending) {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
+ /* Perform logging of memory contexts of this process */
+ if (LogMemoryContextPending)
+ ProcessLogMemoryContextInterrupt();
+
+ /* Process sinval catchup interrupts that happened while sleeping */
+ ProcessCatchupInterrupt();
+}
+
+/*
+ * Process a failed login authentication
+ *
+ * As this will only record the current failed login, we don't need
+ * CHECK_FOR_INTERRUPTS during process.
+ */
+static void
+record_failed_login(void) {
+ Datum new_record[Natts_pg_authid];
+ bool new_record_nulls[Natts_pg_authid];
+ bool new_record_repl[Natts_pg_authid];
+ Relation pg_authid_rel;
+ Relation pg_profile_rel;
+ TupleDesc pg_authid_dsc;
+ HeapTuple profile_tuple;
+ HeapTuple auth_tuple;
+ HeapTuple new_tuple;
+ Form_pg_profile profileform;
+ int32 failed_login_attempts;
+ int32 profile_failed_login_attempts;
+ bool isnull;
+ int32 profileid;
+ TimestampTz now;
+
+ /* Start a transaction so our commands have one to play into. */
+ StartTransactionCommand();
+
+ /* Acquire LWLock */
+ LWLockAcquire(LoginFailedSharedMemoryLock, LW_EXCLUSIVE);
+ elog(DEBUG1, "Login monitor worker has acquired LoginFailedSharedMemoryLock for process user %s.",
+ LoginMonitorShmem->curr_user_name);
+
+ /*
+ * Update related catalog and check whether the account need to be locked
+ */
+ pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
+ pg_authid_dsc = RelationGetDescr(pg_authid_rel);
+ pg_profile_rel = table_open(ProfileRelationId, AccessShareLock);
+
+ auth_tuple = SearchSysCache1(AUTHNAME,
+ CStringGetDatum(LoginMonitorShmem->curr_user_name));
+
+ Assert(HeapTupleIsValid(auth_tuple));
+
+ /* get current failed_login_attempts */
+ failed_login_attempts = SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rolfailedlogins, &isnull);
+ Assert(!isnull);
+
+ /* increase failed_login_attempts by one */
+ failed_login_attempts++;
+ elog(DEBUG1, "User %s FAILED LOGIN ATTEMPTS is %d in Login Monitor worker",
+ LoginMonitorShmem->curr_user_name, failed_login_attempts);
+ /*
+ * Build an updated tuple, perusing the information just obtained
+ */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, true, sizeof(new_record_nulls));
+ MemSet(new_record_repl, false, sizeof(new_record_repl));
+
+ new_record[Anum_pg_authid_rolfailedlogins - 1] =
+ Int32GetDatum(failed_login_attempts);
+ new_record_nulls[Anum_pg_authid_rolfailedlogins - 1] =
+ false;
+ new_record_repl[Anum_pg_authid_rolfailedlogins - 1] =
+ true;
+
+ /* get the user's current profile oid */
+ profileid = SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rolprofile, &isnull);
+ Assert(!isnull);
+
+ /* get user's current profile tuple */
+ profile_tuple = SearchSysCache1(PROFILEID, ObjectIdGetDatum(profileid));
+ Assert(HeapTupleIsValid(profile_tuple));
+ profileform = (Form_pg_profile) GETSTRUCT(profile_tuple);
+
+ /*
+ * Transform failed_login_attempts to normal value if it's
+ * PROFILE_DEFAULT or PROFILE_UNLIMITED.
+ */
+ profile_failed_login_attempts =
+ tranformProfileValueToNormal(profileform->prffailedloginattempts,
+ Anum_pg_profile_prffailedloginattempts);
+
+ /*
+ * If user's failed_login_attempts is bigger equal than current
+ * profile's failed_login_attempts, update account status to
+ * locked and lockdate to now.
+ */
+ if (failed_login_attempts >= profile_failed_login_attempts) {
+ new_record[Anum_pg_authid_rolaccountstatus - 1] =
+ Int32GetDatum(ROLE_ACCOUNT_STATUS_LOCKED_TIMED);
+ new_record_nulls[Anum_pg_authid_rolaccountstatus - 1] =
+ false;
+ new_record_repl[Anum_pg_authid_rolaccountstatus - 1] =
+ true;
+
+ now = GetCurrentTimestamp();
+ new_record[Anum_pg_authid_rollockdate - 1] =
+ Int64GetDatum(now);
+ new_record_nulls[Anum_pg_authid_rollockdate - 1] =
+ false;
+ new_record_repl[Anum_pg_authid_rollockdate - 1] =
+ true;
+ }
+
+ new_tuple = heap_modify_tuple(auth_tuple, pg_authid_dsc,
+ new_record, new_record_nulls, new_record_repl);
+ CatalogTupleUpdate(pg_authid_rel, &auth_tuple->t_self, new_tuple);
+
+ InvokeObjectPostAlterHook(AuthIdRelationId, profileid, 0);
+
+ ReleaseSysCache(auth_tuple);
+ ReleaseSysCache(profile_tuple);
+ table_close(pg_profile_rel, NoLock);
+ table_close(pg_authid_rel, NoLock);
+
+ /* reset login_failed_requested to false */
+ LoginMonitorShmem->login_failed_requested = false;
+
+ /* notify process by setting latch */
+ SetLatch(LoginMonitorShmem->lm_latch);
+ SetLatch(LoginMonitorShmem->latch);
+ LWLockRelease(LoginFailedSharedMemoryLock);
+ elog(DEBUG1, "Login monitor worker has released LoginFailedSharedMemoryLock for process user %s.",
+ LoginMonitorShmem->curr_user_name);
+
+ CommitTransactionCommand();
+}
+
+/*
+ * Notify Login Monitor Launcher by LoginMonitorShmem
+ */
+void
+LoginMonitorWorkerFailed(void) {
+ SetLatch(LoginMonitorShmem->lm_latch);
+}
+
+/*
+ * Perform a normal exit from the login monitor.
+ */
+static void
+LoginMonitorShutdown(void) {
+ ereport(DEBUG1,
+ (errmsg_internal("login monitor shutting down")));
+
+ LoginMonitorShmem->lm_pid = 0;
+ LoginMonitorShmem->lm_latch = NULL;
+
+ proc_exit(0);
+}
+
+/*
+ * SIGUSR1: a login password authentication failed
+ */
+void
+HandleLoginFailed(void) {
+ int save_errno = errno;
+
+ LoginMonitorShmem->login_failed_requested = true;
+ SetLatch(MyLatch);
+
+ errno = save_errno;
+}
+
+/*
+ * IsLoginMonitorLauncher functions
+ * Return whether this is a login monitor launcher process.
+ */
+bool
+IsLoginMonitorLauncherProcess(void) {
+ return am_login_monitor_launcher;
+}
+
+/*
+ * IsLoginMonitorWorker functions
+ * Return whether this is a login monitor worker process.
+ */
+bool
+IsLoginMonitorWorkerProcess(void) {
+ return am_login_monitor_worker;
+}
+
+/*
+ * LoginMonitorShmemSize
+ * Compute space needed for login monitor-related shared memory
+ */
+Size
+LoginMonitorShmemSize(void) {
+ Size size;
+
+ size = sizeof(LoginMonitorShmemStruct);
+
+ return size;
+}
+
+/*
+ * LoginMonitorShmemInit
+ * Allocate and initialize login monitor-related shared memory
+ */
+void
+LoginMonitorShmemInit(void) {
+ bool found;
+
+ LoginMonitorShmem = (LoginMonitorShmemStruct *)
+ ShmemInitStruct("Login Monitor Data",
+ LoginMonitorShmemSize(),
+ &found);
+
+ if (!IsUnderPostmaster) {
+ Assert(!found);
+ LoginMonitorShmem->lm_pid = 0;
+ LoginMonitorShmem->lm_latch = NULL;
+ memset(LoginMonitorShmem->curr_user_name, 0, NAMEDATALEN);
+ LoginMonitorShmem->latch = NULL;
+ LoginMonitorShmem->login_failed_requested = false;
+ } else
+ Assert(found);
+}
+
+/*
+ * SendLoginFailedSignal - signal the postmaster after failed authentication.
+ */
+void
+SendLoginFailedSignal(const char *curr_user_name) {
+ int rc;
+
+ /* If called in a standalone backend or Login Monitor PID is 0, do nothing */
+ if (!IsUnderPostmaster || !LoginMonitorPID)
+ return;
+ /*
+ * Before sending signal, we need to acquire lock in shared memory and set
+ * current user oid. Only when login monitor backend process has solved the
+ * current signal, the lock will be released. By this way, the login monitor
+ * solve the postgres signal serially which will avoid dead lock.
+ */
+ LWLockAcquire(LoginFailedControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(LoginFailedSharedMemoryLock, LW_EXCLUSIVE);
+ elog(DEBUG1, "User %s has acquire LoginFailedControlLock and LoginFailedSharedMemoryLock", curr_user_name);
+
+ /* Reset login monitor shmem user name */
+ memset(LoginMonitorShmem->curr_user_name, 0, NAMEDATALEN);
+ /* Set current user name */
+ strcpy(LoginMonitorShmem->curr_user_name, curr_user_name);
+
+ /* Set latch to pointer to MyLatch */
+ LoginMonitorShmem->latch = &MyProc->procLatch;
+ ResetLatch(LoginMonitorShmem->latch);
+
+ /* Send signal to PostMaster */
+ SendPostmasterSignal(PMSIGNAL_FAILED_LOGIN);
+
+ elog(DEBUG1, "User %s has sent failed login signal to postmaster", curr_user_name);
+
+ LWLockRelease(LoginFailedSharedMemoryLock);
+ elog(DEBUG1, "User %s has released LoginFailedSharedMemoryLock and wait latch", curr_user_name);
+
+ rc = WaitLatch(LoginMonitorShmem->latch,
+ WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
+ WAIT_EVENT_LOGINMONITOR_FINISH);
+
+ LWLockRelease(LoginFailedControlLock);
+ elog(DEBUG1, "User %s has release LoginFailedControlLock", curr_user_name);
+}
+
+static void
+LoginMonitorShmemReset(void) {
+ Assert(LoginMonitorShmem);
+
+ LWLockAcquire(LoginFailedSharedMemoryLock, LW_EXCLUSIVE);
+
+ memset(LoginMonitorShmem->curr_user_name, 0, NAMEDATALEN);
+ LoginMonitorShmem->latch = NULL;
+ LoginMonitorShmem->login_failed_requested = false;
+
+ LWLockRelease(LoginFailedSharedMemoryLock);
+
+ return;
+}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3d9a199c8cb..c9c8b2dec9e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -130,6 +130,7 @@
#include "postmaster/syslogger.h"
#include "postmaster/backoff.h"
#include "postmaster/bgworker.h"
+#include "postmaster/loginmonitor.h"
#include "replication/logicallauncher.h"
#include "replication/walsender.h"
#include "storage/fd.h"
@@ -278,6 +279,7 @@ bool enable_bonjour = false;
char *bonjour_name;
bool restart_after_crash = true;
bool remove_temp_files_after_crash = true;
+bool enable_password_profile = true;
/* Hook for plugins to start background workers */
start_bgworkers_hook_type start_bgworkers_hook = NULL;
@@ -295,7 +297,8 @@ static pid_t StartupPID = 0,
AutoVacPID = 0,
PgArchPID = 0,
PgStatPID = 0,
- SysLoggerPID = 0;
+ SysLoggerPID = 0,
+ LoginMonitorPID = 0;
/* Startup process's status */
typedef enum
@@ -541,6 +544,7 @@ static bool CreateOptsFile(int argc, char *argv[], char *fullprogname);
static bool do_start_bgworker(RegisteredBgWorker *rw);
static pid_t StartChildProcess(AuxProcType type);
static void StartAutovacuumWorker(void);
+static void StartLMWorker(void);
static void MaybeStartWalReceiver(void);
static void InitPostmasterDeathWatchHandle(void);
@@ -2089,6 +2093,9 @@ ServerLoop(void)
if (PgArchPID == 0 && PgArchStartupAllowed())
PgArchPID = StartArchiver();
+ if (enable_password_profile && LoginMonitorPID == 0 && pmState == PM_RUN)
+ LoginMonitorPID = StartLoginMonitorLauncher();
+
/* If we need to signal the autovacuum launcher, do so now */
if (avlauncher_needs_signal)
{
@@ -3192,6 +3199,8 @@ SIGHUP_handler(SIGNAL_ARGS)
signal_child(SysLoggerPID, SIGHUP);
if (PgStatPID != 0)
signal_child(PgStatPID, SIGHUP);
+ if (enable_password_profile && LoginMonitorPID != 0)
+ signal_child(LoginMonitorPID, SIGHUP);
/* Reload authentication config files too */
if (!load_hba())
@@ -3522,6 +3531,8 @@ reaper(SIGNAL_ARGS)
PgArchPID = StartArchiver();
if (PgStatPID == 0)
PgStatPID = pgstat_start();
+ if (enable_password_profile && LoginMonitorPID == 0)
+ LoginMonitorPID = StartLoginMonitorLauncher();
/* workers may be scheduled to start now */
maybe_start_bgworkers();
@@ -3668,6 +3679,21 @@ reaper(SIGNAL_ARGS)
continue;
}
+ /*
+ * Was it the login monitor? If exit status is zero (normal) or one
+ * (FATAL exit), we assume everything is all right just like normal
+ * backends and just try to restart a new one. Any other exit condition
+ * is treated as a crash.
+ */
+ if (enable_password_profile && pid == LoginMonitorPID)
+ {
+ LoginMonitorPID = 0;
+ if (!EXIT_STATUS_0(exitstatus) && !EXIT_STATUS_1(exitstatus))
+ HandleChildCrash(pid, exitstatus,
+ _("login monitor process"));
+ continue;
+ }
+
/*
* Was it the archiver? If exit status is zero (normal) or one (FATAL
* exit), we assume everything is all right just like normal backends
@@ -4138,6 +4164,21 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT));
}
+ /* Take care of the login monitor too */
+ if (enable_password_profile)
+ {
+ if (pid == LoginMonitorPID)
+ LoginMonitorPID = 0;
+ else if (LoginMonitorPID != 0 && take_action)
+ {
+ ereport(DEBUG2,
+ (errmsg_internal("sending %s to process %d",
+ (SendStop ? "SIGSTOP" : "SIGQUIT"),
+ (int) LoginMonitorPID)));
+ signal_child(LoginMonitorPID, (SendStop ? SIGSTOP : SIGQUIT));
+ }
+ }
+
/* Take care of the archiver too */
if (pid == PgArchPID)
PgArchPID = 0;
@@ -4313,6 +4354,9 @@ PostmasterStateMachine(void)
signal_child(StartupPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ /* stop login monitor */
+ if (enable_password_profile && LoginMonitorPID != 0)
+ signal_child(LoginMonitorPID, SIGTERM);
/* checkpointer, archiver, stats, and syslogger may continue for now */
/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -4343,7 +4387,8 @@ PostmasterStateMachine(void)
(CheckpointerPID == 0 ||
(!FatalError && Shutdown < ImmediateShutdown)) &&
WalWriterPID == 0 &&
- AutoVacPID == 0)
+ AutoVacPID == 0 &&
+ LoginMonitorPID == 0)
{
if (Shutdown >= ImmediateShutdown || FatalError)
{
@@ -4437,6 +4482,7 @@ PostmasterStateMachine(void)
Assert(CheckpointerPID == 0);
Assert(WalWriterPID == 0);
Assert(AutoVacPID == 0);
+ Assert(LoginMonitorPID == 0);
/* syslogger is not considered here */
pmState = PM_NO_CHILDREN;
}
@@ -4658,6 +4704,8 @@ TerminateChildren(int signal)
signal_child(PgArchPID, signal);
if (PgStatPID != 0)
signal_child(PgStatPID, signal);
+ if (enable_password_profile && LoginMonitorPID != 0)
+ signal_child(LoginMonitorPID, signal);
}
/*
@@ -5820,6 +5868,13 @@ sigusr1_handler(SIGNAL_ARGS)
StartAutovacuumWorker();
}
+ if (CheckPostmasterSignal(PMSIGNAL_START_LOGIN_MONITOR_WORKER) &&
+ Shutdown <= SmartShutdown && pmState < PM_STOP_BACKENDS)
+ {
+ /* The login monitor wants us to start a worker process. */
+ StartLMWorker();
+ }
+
if (CheckPostmasterSignal(PMSIGNAL_START_WALRECEIVER))
{
/* Startup Process wants us to start the walreceiver process. */
@@ -5864,6 +5919,16 @@ sigusr1_handler(SIGNAL_ARGS)
PostmasterStateMachine();
}
+ /*
+ * Postmaster send signal to login monitor, notifying login monitor to
+ * process failed login.
+ */
+ if (LoginMonitorPID != 0 &&
+ pmState == PM_RUN && CheckPostmasterSignal(PMSIGNAL_FAILED_LOGIN))
+ {
+ SendProcSignal(LoginMonitorPID, PROCSIG_FAILED_LOGIN, InvalidBackendId);
+ }
+
if (StartupPID != 0 &&
(pmState == PM_STARTUP || pmState == PM_RECOVERY ||
pmState == PM_HOT_STANDBY) &&
@@ -6174,6 +6239,91 @@ StartAutovacuumWorker(void)
}
}
+/*
+ * StartLMWorker
+ * Start a login monitor worker process.
+ *
+ * Like StartAutovacuumWorker, this function is here. As login monitor
+ * worker process is used like normal process, we set backend_type to
+ * NORMAL.
+ */
+static void
+StartLMWorker(void)
+{
+ Backend *bn;
+
+ /*
+ * If not in condition to run a process, don't try, but handle it like a
+ * fork failure. This does not normally happen, since the signal is only
+ * supposed to be sent by login monitor launcher when it's OK to do it,
+ * but we have to check to avoid race-condition problems during DB state
+ * changes.
+ */
+ if (canAcceptConnections(BACKEND_TYPE_NORMAL) == CAC_OK)
+ {
+ /*
+ * Compute the cancel key that will be assigned to this session. We
+ * probably don't need cancel keys for autovac workers, but we'd
+ * better have something random in the field to prevent unfriendly
+ * people from sending cancels to them。
+ */
+ if (!RandomCancelKey(&MyCancelKey))
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not generate random cancel key")));
+ return;
+ }
+
+ bn = (Backend *) malloc(sizeof(Backend));
+ if (bn)
+ {
+ bn->cancel_key = MyCancelKey;
+
+ /* Login monitor workers are not dead_end and need a child slot */
+ bn->dead_end = false;
+ bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
+ bn->bgworker_notify = false;
+
+ bn->pid = StartLoginMonitorWorker();
+ if (bn->pid > 0)
+ {
+ bn->bkend_type = BACKEND_TYPE_NORMAL;
+ dlist_push_head(&BackendList, &bn->elem);
+#ifdef EXEC_BACKEND
+ ShmemBackendArrayAdd(bn);
+#endif
+
+ /* all OK */
+ return;
+ }
+
+ /*
+ * fork failed, fall through to report -- actual error message was
+ * logged by StartLoginMonitorWorker
+ */
+ (void) ReleasePostmasterChildSlot(bn->child_slot);
+ free(bn);
+ }
+ else
+ ereport(LOG,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+
+ /*
+ * Notify failure to the launcher by latch, if it's running. (If it's not,
+ * we might not even be connected to shared memory, so don't try to call
+ * LoginMonitorWorkerFailed.) Note that we also need to notify it so that
+ * it responds to the condition, but we don't do that here, instead waiting
+ * for ServerLoop to do it.
+ */
+ if (LoginMonitorPID != 0)
+ {
+ LoginMonitorWorkerFailed();
+ }
+}
+
/*
* MaybeStartWalReceiver
* Start the WAL receiver process, if not running and our state allows.
@@ -6258,7 +6408,7 @@ int
MaxLivePostmasterChildren(void)
{
return 2 * (MaxConnections + autovacuum_max_workers + 1 +
- max_wal_senders + max_worker_processes);
+ login_monitor_max_processes /* Login Monitor */ + max_wal_senders + max_worker_processes);
}
/*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0ea4e823d69..11324d659a3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -39,6 +39,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/fts.h"
+#include "postmaster/loginmonitor.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
#include "replication/slot.h"
@@ -185,6 +186,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, ProcSignalShmemSize());
size = add_size(size, CheckpointerShmemSize());
size = add_size(size, AutoVacuumShmemSize());
+ size = add_size(size, LoginMonitorShmemSize());
size = add_size(size, ReplicationSlotsShmemSize());
size = add_size(size, ReplicationOriginShmemSize());
size = add_size(size, WalSndShmemSize());
@@ -365,6 +367,7 @@ CreateSharedMemoryAndSemaphores(void)
ProcSignalShmemInit();
CheckpointerShmemInit();
AutoVacuumShmemInit();
+ LoginMonitorShmemInit();
ReplicationSlotsShmemInit();
ReplicationOriginShmemInit();
WalSndShmemInit();
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 191f9a5115c..669b5465d73 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -22,6 +22,7 @@
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/loginmonitor.h"
#include "replication/walsender.h"
#include "storage/condition_variable.h"
#include "storage/ipc.h"
@@ -704,6 +705,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_RESOURCE_GROUP_MOVE_QUERY))
HandleMoveResourceGroup();
+ if (CheckProcSignal(PROCSIG_FAILED_LOGIN))
+ HandleLoginFailed();
+
SetLatch(MyLatch);
errno = save_errno;
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index c83f1588529..13d8980d1a3 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -71,3 +71,5 @@ DistributedLogControlLock 61
CdbConfigCacheLock 62
KmgrFileLock 63
GpParallelDSMHashLock 64
+LoginFailedControlLock 65
+LoginFailedSharedMemoryLock 66
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index a9a6699408f..768b585863e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -46,6 +46,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/loginmonitor.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
#include "replication/walsender.h"
@@ -203,6 +204,7 @@ InitProcGlobal(void)
ProcGlobal->spins_per_delay = DEFAULT_SPINS_PER_DELAY;
ProcGlobal->freeProcs = NULL;
ProcGlobal->autovacFreeProcs = NULL;
+ ProcGlobal->lmFreeProcs = NULL;
ProcGlobal->bgworkerFreeProcs = NULL;
ProcGlobal->walsenderFreeProcs = NULL;
ProcGlobal->startupProc = NULL;
@@ -291,7 +293,14 @@ InitProcGlobal(void)
ProcGlobal->autovacFreeProcs = &procs[i];
procs[i].procgloballist = &ProcGlobal->autovacFreeProcs;
}
- else if (i < MaxConnections + autovacuum_max_workers + 1 + max_worker_processes)
+ else if (i < MaxConnections + autovacuum_max_workers + 1 + login_monitor_max_processes)
+ {
+ /* PGPROC for login monitor, add to lmFreeProcs list */
+ procs[i].links.next = (SHM_QUEUE *) ProcGlobal->lmFreeProcs;
+ ProcGlobal->lmFreeProcs = &procs[i];
+ procs[i].procgloballist = &ProcGlobal->lmFreeProcs;
+ }
+ else if (i < MaxConnections + autovacuum_max_workers + 1 + login_monitor_max_processes + max_worker_processes)
{
/* PGPROC for bgworker, add to bgworkerFreeProcs list */
procs[i].links.next = (SHM_QUEUE *) ProcGlobal->bgworkerFreeProcs;
@@ -371,6 +380,8 @@ InitProcess(void)
/* Decide which list should supply our PGPROC. */
if (IsAnyAutoVacuumProcess())
procgloballist = &ProcGlobal->autovacFreeProcs;
+ else if (IsAnyLoginMonitorProcess())
+ procgloballist = &ProcGlobal->lmFreeProcs;
else if (IsBackgroundWorker)
procgloballist = &ProcGlobal->bgworkerFreeProcs;
else if (am_walsender)
@@ -444,11 +455,13 @@ InitProcess(void)
* cleaning up. (XXX autovac launcher currently doesn't participate in
* this; it probably should.)
*
+ * Like autovac launcher, login monitor doesn't participate in this.
+ *
* Ideally, we should create functions similar to IsAutoVacuumLauncherProcess()
* for ftsProber, etc who call InitProcess().
* But MyPMChildSlot helps to get away with it.
*/
- if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess()
+ if (IsUnderPostmaster && !(IsAutoVacuumLauncherProcess() || IsLoginMonitorLauncherProcess())
&& MyPMChildSlot > 0)
MarkPostmasterChildActive();
@@ -1155,9 +1168,9 @@ ProcKill(int code, Datum arg)
/*
* This process is no longer present in shared memory in any meaningful
* way, so tell the postmaster we've cleaned up acceptably well. (XXX
- * autovac launcher should be included here someday)
+ * autovac launcher and login monitor should be included here someday)
*/
- if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess()
+ if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess() && !IsLoginMonitorLauncherProcess()
&& MyPMChildSlot > 0)
MarkPostmasterChildInactive();
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6d5c8b5f5f6..998d3efc02e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -79,6 +79,7 @@
#include "access/table.h"
#include "catalog/oid_dispatch.h"
+#include "catalog/pg_profile.h"
#include "cdb/cdbdisp_query.h"
#include "cdb/cdbendpoint.h"
#include "cdb/cdbvars.h"
@@ -232,13 +233,16 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_ViewStmt:
/* fallthrough */
/* GPDB specific commands */
+ case T_AlterProfileStmt:
case T_AlterQueueStmt:
case T_AlterResourceGroupStmt:
+ case T_CreateProfileStmt:
case T_CreateQueueStmt:
case T_CreateResourceGroupStmt:
case T_CreateTaskStmt:
case T_AlterTaskStmt:
case T_DropTaskStmt:
+ case T_DropProfileStmt:
case T_DropQueueStmt:
case T_DropResourceGroupStmt:
case T_DropWarehouseStmt:
@@ -1104,6 +1108,18 @@ standard_ProcessUtility(PlannedStmt *pstmt,
DropRole((DropRoleStmt *) parsetree);
break;
+ case T_CreateProfileStmt:
+ CreateProfile(pstate, (CreateProfileStmt *) parsetree);
+ break;
+
+ case T_AlterProfileStmt:
+ AlterProfile((AlterProfileStmt *) parsetree);
+ break;
+
+ case T_DropProfileStmt:
+ DropProfile((DropProfileStmt *) parsetree);
+ break;
+
case T_ReassignOwnedStmt:
/* no event triggers for global objects */
ReassignOwnedObjects((ReassignOwnedStmt *) parsetree);
@@ -2744,6 +2760,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_ROLE:
tag = CMDTAG_ALTER_ROLE;
break;
+ case OBJECT_PROFILE:
+ tag = CMDTAG_ALTER_PROFILE;
+ break;
case OBJECT_ROUTINE:
tag = CMDTAG_ALTER_ROUTINE;
break;
@@ -3491,6 +3510,18 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_DROP_ROLE;
break;
+ case T_CreateProfileStmt:
+ tag = CMDTAG_CREATE_PROFILE;
+ break;
+
+ case T_AlterProfileStmt:
+ tag = CMDTAG_ALTER_PROFILE;
+ break;
+
+ case T_DropProfileStmt:
+ tag = CMDTAG_DROP_PROFILE;
+ break;
+
case T_DropOwnedStmt:
tag = CMDTAG_DROP_OWNED;
break;
@@ -4123,6 +4154,18 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateProfileStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_AlterProfileStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_DropProfileStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_DropOwnedStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index 17cad37c7f6..d041ac05e2c 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -279,6 +279,9 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_GLOBAL_DEADLOCK_DETECTOR_MAIN:
event_name = "GlobalDeadLockDetectorMain";
break;
+ case WAIT_EVENT_LOGIN_MONITOR_LAUNCHER_MAIN:
+ event_name = "LoginMonitorLauncherMain";
+ break;
/* no default case, so that compiler will warn */
}
@@ -488,6 +491,9 @@ pgstat_get_wait_ipc(WaitEventIPC w)
case WAIT_EVENT_SHAREINPUT_SCAN:
event_name = "ShareInputScan";
break;
+ case WAIT_EVENT_LOGINMONITOR_FINISH:
+ event_name = "LoginMonitorFinish";
+ break;
case WAIT_EVENT_DTX_RECOVERY:
event_name = "DtxRecovery";
/* no default case, so that compiler will warn */
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index a51e88eb8d9..60f643c2d87 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1113,6 +1113,8 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHOID:
case AUTHMEMMEMROLE:
case DATABASEOID:
+ case PROFILEID:
+ case PROFILENAME:
/*
* Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 65d341c6806..851ee051ce1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -58,7 +58,9 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_password_history.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_profile.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -128,6 +130,8 @@ static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
static const FormData_pg_attribute Desc_pg_auth_time_constraint_members[Natts_pg_auth_time_constraint] = {Schema_pg_auth_time_constraint};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
+static const FormData_pg_attribute Desc_pg_password_history[Natts_pg_password_history] = {Schema_pg_password_history};
+static const FormData_pg_attribute Desc_pg_profile[Natts_pg_profile] = {Schema_pg_profile};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
static const FormData_pg_attribute Desc_pg_subscription[Natts_pg_subscription] = {Schema_pg_subscription};
@@ -4096,6 +4100,10 @@ RelationCacheInitializePhase2(void)
Natts_pg_database, Desc_pg_database);
formrdesc("pg_authid", AuthIdRelation_Rowtype_Id, true,
Natts_pg_authid, Desc_pg_authid);
+ formrdesc("pg_password_history", PasswordHistoryRelation_Rowtype_Id, true,
+ Natts_pg_password_history, Desc_pg_password_history);
+ formrdesc("pg_profile", ProfileRelation_Rowtype_Id, true,
+ Natts_pg_profile, Desc_pg_profile);
formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
Natts_pg_auth_members, Desc_pg_auth_members);
formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
@@ -4105,7 +4113,7 @@ RelationCacheInitializePhase2(void)
formrdesc("pg_auth_time_constraint", AuthTimeConstraint_Rowtype_Id, true,
Natts_pg_auth_time_constraint, Desc_pg_auth_time_constraint_members);
-#define NUM_CRITICAL_SHARED_RELS 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 8 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -4249,6 +4257,12 @@ RelationCacheInitializePhase3(void)
AuthIdRelationId);
load_critical_index(AuthIdOidIndexId,
AuthIdRelationId);
+ load_critical_index(ProfilePrfnameIndexId,
+ ProfileRelationId);
+ load_critical_index(ProfileOidIndexId,
+ ProfileRelationId);
+ load_critical_index(ProfileVerifyFunctionIndexId,
+ ProfileRelationId);
load_critical_index(AuthMemMemRoleIndexId,
AuthMemRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
@@ -4256,7 +4270,7 @@ RelationCacheInitializePhase3(void)
load_critical_index(AuthTimeConstraintAuthIdIndexId,
AuthTimeConstraintRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 10 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 558fb6c39ef..3f39c506a76 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -51,6 +51,8 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_password_history.h"
+#include "catalog/pg_profile.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_rel.h"
@@ -638,6 +640,28 @@ static const struct cachedesc cacheinfo[] = {
},
32
},
+ {ProfileRelationId, /* PROFILEID */
+ ProfileOidIndexId,
+ 1,
+ {
+ Anum_pg_profile_oid,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
+ {ProfileRelationId, /* PROFILENAME */
+ ProfilePrfnameIndexId,
+ 1,
+ {
+ Anum_pg_profile_prfname,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 5fafe83ca4c..b187da7e849 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -166,6 +166,7 @@ int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
double VacuumCostDelay = 0;
+int login_monitor_max_processes = 2; /* login monitor launcher and 1 worker */
int64 VacuumPageHit = 0;
int64 VacuumPageMiss = 0;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 9a006c282aa..f2344d0c370 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -38,6 +38,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/loginmonitor.h"
#include "postmaster/fts.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
@@ -287,6 +288,12 @@ GetBackendTypeDesc(BackendType backendType)
case B_LOGGER:
backendDesc = "logger";
break;
+ case B_LOGIN_MONITOR:
+ backendDesc = "login monitor";
+ break;
+ case B_LOGIN_MONITOR_WORKER:
+ backendDesc = "login monitor worker";
+ break;
}
return backendDesc;
@@ -820,9 +827,10 @@ InitializeSessionUserIdStandalone(void)
{
/*
* This function should only be called in single-user mode, in autovacuum
- * workers, and in background workers.
+ * workers, login monitor, and in background workers.
*/
AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker
+ || IsAnyLoginMonitorProcess()
|| am_startup
|| (am_faulthandler && am_mirror)
|| (am_ftshandler && am_mirror));
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 7a8cace8384..f6980d14982 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -30,13 +30,16 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "catalog/pg_tablespace.h"
#include "catalog/indexing.h"
#include "catalog/storage_tablespace.h"
+#include "catalog/pg_profile.h"
#include "commands/tablespace.h"
+#include "datatype/timestamp.h"
#include "libpq/auth.h"
#include "libpq/hba.h"
@@ -49,6 +52,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/loginmonitor.h"
#include "postmaster/fts.h"
#include "postmaster/postmaster.h"
#include "replication/walsender.h"
@@ -388,9 +392,10 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
* a way to recover from disabling all access to all databases, for
* example "UPDATE pg_database SET datallowconn = false;".
*
- * We do not enforce them for autovacuum worker processes either.
+ * We do not enforce them for autovacuum worker and login monitor processes
+ * either.
*/
- if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess())
+ if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess() && !IsLoginMonitorWorkerProcess())
{
/*
* Check that the database is currently allowing connections.
@@ -572,9 +577,9 @@ InitializeMaxBackends(void)
{
Assert(MaxBackends == 0);
- /* the extra unit accounts for the autovacuum launcher */
+ /* the extra unit accounts for the autovacuum launcher and login monitor */
MaxBackends = MaxConnections + autovacuum_max_workers + 1 +
- max_worker_processes + max_wal_senders;
+ login_monitor_max_processes /* Login Monitor */ + max_worker_processes + max_wal_senders;
/* internal error because the values were all checked previously */
if (MaxBackends > MAX_BACKENDS)
@@ -787,8 +792,8 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
*/
before_shmem_exit(ShutdownPostgres, 0);
- /* The autovacuum launcher is done here */
- if (IsAutoVacuumLauncherProcess())
+ /* The autovacuum launcher and login monitor launcher is done here */
+ if (IsAutoVacuumLauncherProcess() || IsLoginMonitorLauncherProcess())
{
/* report this backend in the PgBackendStatus array */
pgstat_bestart();
@@ -833,10 +838,11 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
* Perform client authentication if necessary, then figure out our
* postgres user ID, and see if we are a superuser.
*
- * In standalone mode and in autovacuum worker processes, we use a fixed
- * ID, otherwise we figure it out from the authenticated user name.
+ * In standalone mode, login monitor and in autovacuum worker processes,
+ * we use a fixed ID, otherwise we figure it out from the authenticated
+ * user name.
*/
- if (bootstrap || IsAutoVacuumWorkerProcess())
+ if (bootstrap || IsAutoVacuumWorkerProcess() || IsLoginMonitorWorkerProcess())
{
InitializeSessionUserIdStandalone();
am_superuser = true;
@@ -1205,7 +1211,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
* process_startup_options parses the GUC.
*/
if (gp_maintenance_mode && Gp_role == GP_ROLE_DISPATCH &&
- !(am_superuser && gp_maintenance_conn))
+ !(am_superuser && gp_maintenance_conn) && !IsLoginMonitorWorkerProcess())
ereport(FATAL,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("maintenance mode: connected by superuser only")));
@@ -1252,7 +1258,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
* This is SKIPPED when the database is in bootstrap mode or
* Is not UnderPostmaster.
*/
- if (!bootstrap && IsUnderPostmaster)
+ if (!bootstrap && IsUnderPostmaster && !IsLoginMonitorWorkerProcess())
{
cdb_setup();
on_proc_exit( cdb_cleanup, 0 );
@@ -1291,9 +1297,183 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
}
/*
- * Initialize resource manager.
+ * Initialize resource manager. Login Monitor doesn't need to do.
*/
- InitResManager();
+ if (!IsLoginMonitorWorkerProcess())
+ InitResManager();
+
+ if (enable_password_profile &&
+ !(bootstrap || IsBackgroundWorker || IsLoginMonitorWorkerProcess() ||
+ IsAutoVacuumWorkerProcess() ||
+ am_mirror || !IsUnderPostmaster))
+ {
+ Datum new_record[Natts_pg_authid];
+ bool new_record_nulls[Natts_pg_authid];
+ bool new_record_repl[Natts_pg_authid];
+ Relation pg_authid_rel;
+ Relation pg_profile_rel;
+ TupleDesc pg_authid_dsc;
+ HeapTuple auth_tuple;
+ HeapTuple profile_tuple;
+ HeapTuple new_tuple;
+ Form_pg_authid authidform;
+ Form_pg_profile profileform;
+ bool role_enable_profile;
+ bool account_status_isnull;
+ bool role_lock_date_isnull;
+ bool profile_isnull;
+ bool failed_attempts_isnull;
+ int16_t account_status;
+ TimestampTz role_lock_date;
+ int32 prf_password_lock_time;
+ int32 failed_login_attempts;
+ int32 prf_failed_login_attempts;
+ TimestampTz password_lock_time_usec;
+ Oid profileid;
+
+ /*
+ * Check whether the account is locked and update related catalog.
+ */
+ pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);
+ pg_authid_dsc = RelationGetDescr(pg_authid_rel);
+
+ auth_tuple = SearchSysCache1(AUTHNAME, CStringGetDatum(username));
+
+ if (!HeapTupleIsValid(auth_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("user \"%s\" does not exist", username)));
+
+ authidform = (Form_pg_authid) GETSTRUCT(auth_tuple);
+ role_enable_profile = authidform->rolenableprofile;
+
+ /*
+ * Only when role is enable to use profile, we should process that when login successful.
+ */
+ if (role_enable_profile)
+ {
+ account_status = SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rolaccountstatus, &account_status_isnull);
+ Assert(!account_status_isnull);
+
+ role_lock_date = SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rollockdate, &role_lock_date_isnull);
+
+ profileid = SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rolprofile, &profile_isnull);
+ Assert(!profile_isnull);
+
+ failed_login_attempts = SysCacheGetAttr(AUTHNAME, auth_tuple,
+ Anum_pg_authid_rolfailedlogins, &failed_attempts_isnull);
+ Assert(!failed_attempts_isnull);
+
+ /*
+ * If last login is successful, we don't need to update catalog table.
+ */
+ if (!(failed_login_attempts == 0 && account_status == ROLE_ACCOUNT_STATUS_OPEN))
+ {
+ /*
+ * In here, to avoid updating table concurrently which will cause
+ * connection failed when connect to database concurrently, we use
+ * self exclusive lock ShareUpdateExclusiveLock.
+ */
+ pg_authid_rel = table_open(AuthIdRelationId, ShareUpdateExclusiveLock);
+ pg_profile_rel = table_open(ProfileRelationId, AccessShareLock);
+ profile_tuple = SearchSysCache1(PROFILEID, ObjectIdGetDatum(profileid));
+
+ /*
+ * Build an updated tuple, perusing the information just obtained
+ */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, true, sizeof(new_record_nulls));
+ MemSet(new_record_repl, false, sizeof(new_record_repl));
+
+ if (!HeapTupleIsValid(profile_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("profile \"%d\" does not exist", profileid)));
+ profileform = (Form_pg_profile) GETSTRUCT(profile_tuple);
+
+ /*
+ * Transform password_lock_time and failed_login_attempts to
+ * normal value if it's PROFILE_DEFAULT or PROFILE_UNLIMITED.
+ */
+ prf_password_lock_time = tranformProfileValueToNormal(profileform->prfpasswordlocktime,
+ Anum_pg_profile_prfpasswordlocktime);
+ prf_failed_login_attempts = tranformProfileValueToNormal(profileform->prffailedloginattempts,
+ Anum_pg_profile_prffailedloginattempts);
+
+ /*
+ * Transform lock time format from days to milliseconds
+ */
+ password_lock_time_usec = prf_password_lock_time * USECS_PER_HOUR;
+
+ if ((account_status == ROLE_ACCOUNT_STATUS_OPEN &&
+ failed_login_attempts < prf_failed_login_attempts) ||
+ (!role_lock_date_isnull && account_status == ROLE_ACCOUNT_STATUS_LOCKED_TIMED &&
+ role_lock_date + password_lock_time_usec < GetCurrentTimestamp()))
+ {
+ /* update pg_authid.rolfailedlogins to 0 */
+ new_record[Anum_pg_authid_rolfailedlogins - 1] = Int32GetDatum(0);
+ new_record_nulls[Anum_pg_authid_rolfailedlogins - 1] = false;
+ new_record_repl[Anum_pg_authid_rolfailedlogins - 1] = true;
+
+
+ if (account_status == ROLE_ACCOUNT_STATUS_LOCKED_TIMED)
+ {
+ /* update pg_authid.rolaccountstatus to 'OPEN' */
+ new_record[Anum_pg_authid_rolaccountstatus - 1] =
+ Int32GetDatum(ROLE_ACCOUNT_STATUS_OPEN);
+ new_record_nulls[Anum_pg_authid_rolaccountstatus - 1] = false;
+ new_record_repl[Anum_pg_authid_rolaccountstatus - 1] = true;
+ }
+
+ new_tuple = heap_modify_tuple(auth_tuple, pg_authid_dsc, new_record,
+ new_record_nulls, new_record_repl);
+ CatalogTupleUpdate(pg_authid_rel, &auth_tuple->t_self, new_tuple);
+
+ InvokeObjectPostAlterHook(AuthIdRelationId, profileid, 0);
+ }
+ else if (account_status == ROLE_ACCOUNT_STATUS_OPEN ||
+ account_status == ROLE_ACCOUNT_STATUS_LOCKED_TIMED)
+ {
+ /* Update pg_authid.rolaccountstatus to ROLE_ACCOUNT_STATUS_LOCKED.
+ * This will occur when ALTER PROFILE change failed_login_attempts.
+ */
+ if (account_status == ROLE_ACCOUNT_STATUS_OPEN)
+ {
+ new_record[Anum_pg_authid_rolaccountstatus - 1] =
+ Int32GetDatum(ROLE_ACCOUNT_STATUS_LOCKED);
+ new_record_nulls[Anum_pg_authid_rolaccountstatus - 1] =
+ false;
+ new_record_repl[Anum_pg_authid_rolaccountstatus - 1] =
+ true;
+
+ new_tuple = heap_modify_tuple(auth_tuple, pg_authid_dsc, new_record,
+ new_record_nulls, new_record_repl);
+ CatalogTupleUpdate(pg_authid_rel, &auth_tuple->t_self, new_tuple);
+
+ InvokeObjectPostAlterHook(AuthIdRelationId, profileid, 0);
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("Can't login in role \"%s\", the account is locking.",
+ username)));
+ }
+
+ table_close(pg_profile_rel, NoLock);
+ table_close(pg_authid_rel, NoLock);
+ ReleaseSysCache(profile_tuple);
+ }
+ }
+
+ table_close(pg_authid_rel, NoLock);
+ ReleaseSysCache(auth_tuple);
+
+ CommitTransactionCommand();
+ return;
+ }
/* close the transaction we started above */
if (!bootstrap)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e9881d2bc7d..c6b413aa70d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -46,6 +46,7 @@
#include "access/xlog_internal.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_profile.h"
#include "catalog/storage.h"
#include "commands/async.h"
#include "commands/prepare.h"
@@ -74,6 +75,7 @@
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
#include "postmaster/fts.h"
+#include "postmaster/loginmonitor.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "postmaster/walwriter.h"
@@ -1275,6 +1277,16 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
+ {
+ {"enable_password_profile", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+ gettext_noop("Use profile for password authentication security."),
+ NULL
+ },
+ &enable_password_profile,
+ true,
+ NULL, NULL, NULL
+ },
+
{
{"fsync", PGC_SIGHUP, WAL_SETTINGS,
gettext_noop("Forces synchronization of updates to disk."),
@@ -12457,7 +12469,7 @@ show_tcp_user_timeout(void)
static bool
check_maxconnections(int *newval, void **extra, GucSource source)
{
- if (*newval + autovacuum_max_workers + 1 +
+ if (*newval + autovacuum_max_workers + 1 + login_monitor_max_processes /* Login Monitor */ +
max_worker_processes + max_wal_senders > MAX_BACKENDS)
return false;
return true;
@@ -12475,7 +12487,7 @@ check_autovacuum_max_workers(int *newval, void **extra, GucSource source)
static bool
check_max_wal_senders(int *newval, void **extra, GucSource source)
{
- if (MaxConnections + autovacuum_max_workers + 1 +
+ if (MaxConnections + autovacuum_max_workers + 1 + login_monitor_max_processes /* Login Monitor */ +
max_worker_processes + *newval > MAX_BACKENDS)
return false;
return true;
@@ -12507,7 +12519,7 @@ check_autovacuum_work_mem(int *newval, void **extra, GucSource source)
static bool
check_max_worker_processes(int *newval, void **extra, GucSource source)
{
- if (MaxConnections + autovacuum_max_workers + 1 +
+ if (MaxConnections + autovacuum_max_workers + 1 + login_monitor_max_processes /* Login Monitor */ +
*newval + max_wal_senders > MAX_BACKENDS)
return false;
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 9b29fc73778..a80f80aef6e 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -282,6 +282,7 @@ static void setup_privileges(FILE *cmdfd);
static void set_info_version(void);
static void setup_schema(FILE *cmdfd);
static void setup_cdb_schema(FILE *cmdfd);
+static void setup_password_history(FILE *cmdfd);
static void load_plpgsql(FILE *cmdfd);
static void vacuum_db(FILE *cmdfd);
static void make_template0(FILE *cmdfd);
@@ -1679,6 +1680,8 @@ setup_depend(FILE *cmdfd)
"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
" FROM pg_foreign_server;\n\n",
"INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
+ " FROM pg_profile;\n\n",
+ "INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
" FROM pg_resgroup;\n\n",
"INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
" FROM pg_resourcetype;\n\n",
@@ -1974,6 +1977,26 @@ setup_schema(FILE *cmdfd)
escape_quotes(features_file));
}
+/*
+ * set up the password history table
+ */
+static void
+setup_password_history(FILE *cmdfd)
+{
+ const char *const *line;
+ static const char *const pg_password_history_setup[] = {
+ /*
+ * The password history table shouldn't be readable except through views, to
+ * ensure passwords are not publicly visible.
+ */
+ "REVOKE ALL ON pg_password_history FROM public;\n\n",
+ NULL
+ };
+
+ for (line = pg_password_history_setup; *line != NULL; line++)
+ PG_CMD_PUTS(*line);
+}
+
static int
cmpstringp(const void *p1, const void *p2)
{
@@ -3208,6 +3231,8 @@ initialize_data_directory(void)
setup_run_file(cmdfd, dictionary_file);
+ setup_password_history(cmdfd);
+
setup_privileges(cmdfd);
setup_schema(cmdfd);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 0ccba3d8f63..38e6d321b1c 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -43,6 +43,8 @@ static void dumpRoleConstraints(PGconn *conn);
static void dropRoles(PGconn *conn);
static void dumpRoles(PGconn *conn);
static void dumpRoleMembership(PGconn *conn);
+static void dumpProfiles(PGconn *conn);
+static void dumpPasswordHistory(PGconn *conn);
static void dropTablespaces(PGconn *conn);
static void dumpTablespaces(PGconn *conn);
static void dropDBs(PGconn *conn);
@@ -96,8 +98,12 @@ static int load_via_partition_root = 0;
static int on_conflict_do_nothing = 0;
static char role_catalog[10];
+static char profile_catalog[12];
+static char password_history_catalog[20];
#define PG_AUTHID "pg_authid"
#define PG_ROLES "pg_roles "
+#define PG_PROFILES "pg_profile"
+#define PG_PASSWORD_HISTORY "pg_password_history"
static FILE *OPF;
static char *filename = NULL;
@@ -457,6 +463,9 @@ main(int argc, char *argv[])
else
sprintf(role_catalog, "%s", PG_AUTHID);
+ sprintf(profile_catalog, "%s", PG_PROFILES);
+ sprintf(password_history_catalog, "%s", PG_PASSWORD_HISTORY);
+
/* Add long options to the pg_dump argument list */
if (binary_upgrade)
appendPQExpBufferStr(pgdumpopts, " --binary-upgrade");
@@ -652,6 +661,9 @@ main(int argc, char *argv[])
if (resource_groups)
dumpResGroups(conn);
+ /* Dump role profile */
+ dumpProfiles(conn);
+
/* Dump roles (users) */
dumpRoles(conn);
@@ -660,6 +672,9 @@ main(int argc, char *argv[])
/* Dump role constraints */
dumpRoleConstraints(conn);
+
+ /* Dump role password history */
+ dumpPasswordHistory(conn);
}
/* Dump tablespaces */
@@ -1182,6 +1197,13 @@ dumpRoles(PGconn *conn)
i_rolvaliduntil,
i_rolreplication,
i_rolbypassrls,
+ i_rolenableprofile,
+ i_rolprofile,
+ i_rolaccountstatus,
+ i_rolfailedlogins,
+ i_rolpasswordsetat,
+ i_rollockdate,
+ i_rolpasswordexpire,
i_rolcomment,
i_rolqueuename = -1, /* keep compiler quiet */
i_rolgroupname = -1, /* keep compiler quiet */
@@ -1210,7 +1232,24 @@ dumpRoles(PGconn *conn)
*/
/* note: rolconfig is dumped later */
- if (server_version >= 90600)
+ if (server_version >= 140000)
+ {
+ printfPQExpBuffer(buf,
+ "SELECT %s.oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, rolpassword, "
+ "rolvaliduntil, rolreplication, rolbypassrls, "
+ "rolenableprofile, prfname, rolaccountstatus, rolfailedlogins, "
+ "rolpasswordsetat, rollockdate, rolpasswordexpire, "
+ "pg_catalog.shobj_description(%s.oid, '%s') as rolcomment, "
+ "rolname = current_user AS is_current_user "
+ " %s %s %s %s"
+ "FROM %s, %s "
+ "WHERE rolname !~ '^pg_' and %s.rolprofile = %s.oid "
+ "ORDER BY 2", role_catalog, role_catalog, role_catalog, resq_col, resgroup_col, extauth_col,
+ hdfs_col, role_catalog, profile_catalog, role_catalog, profile_catalog);
+ }
+ else if (server_version >= 90600)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
@@ -1317,6 +1356,13 @@ dumpRoles(PGconn *conn)
i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
i_rolreplication = PQfnumber(res, "rolreplication");
i_rolbypassrls = PQfnumber(res, "rolbypassrls");
+ i_rolprofile = PQfnumber(res, "prfname");
+ i_rolenableprofile = PQfnumber(res, "rolenableprofile");
+ i_rolaccountstatus = PQfnumber(res, "rolaccountstatus");
+ i_rolfailedlogins = PQfnumber(res, "rolfailedlogins");
+ i_rolpasswordsetat = PQfnumber(res, "rolpasswordsetat");
+ i_rollockdate = PQfnumber(res, "rollockdate");
+ i_rolpasswordexpire = PQfnumber(res, "rolpasswordexpire");
i_rolcomment = PQfnumber(res, "rolcomment");
i_is_current_user = PQfnumber(res, "is_current_user");
@@ -1418,13 +1464,6 @@ dumpRoles(PGconn *conn)
appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
PQgetvalue(res, i, i_rolconnlimit));
-
- if (!PQgetisnull(res, i, i_rolpassword) && !no_role_passwords)
- {
- appendPQExpBufferStr(buf, " PASSWORD ");
- appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolpassword), conn);
- }
-
if (!PQgetisnull(res, i, i_rolvaliduntil))
appendPQExpBuffer(buf, " VALID UNTIL '%s'",
PQgetvalue(res, i, i_rolvaliduntil));
@@ -1484,6 +1523,48 @@ dumpRoles(PGconn *conn)
"ROLE", rolename,
buf);
+ appendPQExpBuffer(buf, "ALTER ROLE %s WITH ", fmtId(rolename));
+ appendPQExpBuffer(buf, "PROFILE %s; \n", fmtId(PQgetvalue(res, i, i_rolprofile)));
+
+ appendPQExpBuffer(buf, "SET allow_system_table_mods = true;\n");
+
+ appendPQExpBuffer(buf, "UPDATE pg_authid SET "
+ "rolenableprofile = \'%s\', "
+ "rolaccountstatus = %s, "
+ "rolfailedlogins = %s",
+ PQgetvalue(res, i, i_rolenableprofile),
+ PQgetvalue(res, i, i_rolaccountstatus),
+ PQgetvalue(res, i, i_rolfailedlogins));
+
+ if (!PQgetisnull(res, i, i_rolpassword) && !no_role_passwords)
+ {
+ appendPQExpBuffer(buf, ", rolpassword = \'%s\'",
+ PQgetvalue(res, i, i_rolpassword));
+ }
+
+ if (!PQgetisnull(res, i, i_rolpasswordsetat))
+ {
+ appendPQExpBuffer(buf, ", rolpasswordsetat = \'%s\'",
+ PQgetvalue(res, i, i_rolpasswordsetat));
+ }
+
+ if (!PQgetisnull(res, i, i_rollockdate))
+ {
+ appendPQExpBuffer(buf, ", rollockdate = \'%s\'",
+ PQgetvalue(res, i, i_rollockdate));
+ }
+
+ if (!PQgetisnull(res, i, i_rolpasswordexpire))
+ {
+ appendPQExpBuffer(buf, ", rolpasswordexpire = \'%s\'",
+ PQgetvalue(res, i, i_rolpasswordexpire));
+ }
+
+ appendPQExpBuffer(buf, " WHERE oid = %s; \n",
+ PQgetvalue(res, i, i_oid));
+
+ appendPQExpBuffer(buf, "RESET allow_system_table_mods;\n");
+
fprintf(OPF, "%s", buf->data);
}
@@ -1908,6 +1989,271 @@ dumpUserConfig(PGconn *conn, const char *username)
destroyPQExpBuffer(buf);
}
+/*
+ * Dump profiles
+ */
+static void
+dumpProfiles(PGconn *conn)
+{
+ PQExpBuffer buf = createPQExpBuffer();
+ PGresult *res;
+ int i_oid,
+ i_prfname,
+ i_prffailedloginattempts,
+ i_prfpasswordlocktime,
+ i_prfpasswordlifetime,
+ i_prfpasswordgracetime,
+ i_prfpasswordreusetime,
+ i_prfpasswordreusemax,
+ i_prfpasswordallowhashed,
+ i_prfpasswordverifyfuncdb,
+ i_prfpasswordverifyfunc,
+ i_prfcomment;
+ int i;
+
+ if (server_version < 90600)
+ return;
+
+ printfPQExpBuffer(buf,
+ "SELECT oid, prfname, prffailedloginattempts, "
+ "prfpasswordlocktime, prfpasswordlifetime, prfpasswordgracetime, "
+ "prfpasswordreusetime, prfpasswordreusemax, prfpasswordallowhashed, "
+ "prfpasswordverifyfuncdb, prfpasswordverifyfunc, "
+ "pg_catalog.shobj_description(oid, '%s') as prfcomment "
+ "FROM %s "
+ "ORDER BY 2", profile_catalog, profile_catalog);
+
+ res = executeQuery(conn, buf->data);
+
+ i_oid = PQfnumber(res, "oid");
+ i_prfname = PQfnumber(res, "prfname");
+ i_prffailedloginattempts = PQfnumber(res, "prffailedloginattempts");
+ i_prfpasswordlocktime = PQfnumber(res, "prfpasswordlocktime");
+ i_prfpasswordlifetime = PQfnumber(res, "prfpasswordlifetime");
+ i_prfpasswordgracetime = PQfnumber(res, "prfpasswordgracetime");
+ i_prfpasswordreusetime = PQfnumber(res, "prfpasswordreusetime");
+ i_prfpasswordreusemax = PQfnumber(res, "prfpasswordreusemax");
+ i_prfpasswordallowhashed = PQfnumber(res, "prfpasswordallowhashed");
+ i_prfpasswordverifyfuncdb = PQfnumber(res, "prfpasswordverifyfuncdb");
+ i_prfpasswordverifyfunc = PQfnumber(res, "prfpasswordverifyfunc");
+ i_prfcomment = PQfnumber(res, "prfcomment");
+
+ if (PQntuples(res) > 0)
+ fprintf(OPF, "--\n-- Profiles\n--\n\n");
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ const char *prfname;
+ Oid profile_oid;
+
+ profile_oid = atooid(PQgetvalue(res, i, i_oid));
+ prfname = PQgetvalue(res, i, i_prfname);
+
+ resetPQExpBuffer(buf);
+
+ /*
+ * We dump CREATE PROFILE followed by ALTER PROFILE to ensure that the
+ * profile acquire the right properties even if it already exists(ie,
+ * it won't hurt for the CREATE to fail). Notice, for default profife,
+ * we don't need to CREATE PROFILE while ALTER PROFILE is still needed.
+ */
+ if (profile_oid != 10140 && !binary_upgrade)
+ appendPQExpBuffer(buf, "CREATE PROFILE %s;\n", fmtId(prfname));
+ appendPQExpBuffer(buf, "ALTER PROFILE %s LIMIT", fmtId(prfname));
+
+ if (profile_oid != 10140)
+ {
+ if (strcmp(PQgetvalue(res, i, i_prffailedloginattempts), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " FAILED_LOGIN_ATTEMPTS %s",
+ PQgetvalue(res, i, i_prffailedloginattempts));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordlocktime), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_LOCK_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordlocktime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordlifetime), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_LIFE_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordlifetime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordgracetime), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_GRACE_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordgracetime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordreusetime), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_REUSE_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordreusetime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordreusemax), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_REUSE_MAX %s",
+ PQgetvalue(res, i, i_prfpasswordreusemax));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordallowhashed), "-1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_ALLOW_HASHED %s",
+ PQgetvalue(res, i, i_prfpasswordallowhashed));
+ }
+
+ if (!PQgetisnull(res, i, i_prfpasswordverifyfuncdb))
+ {
+ appendPQExpBuffer(buf, " PASSWORD_VERIFY_FUNCDB %s",
+ PQgetvalue(res, i, i_prfpasswordverifyfuncdb));
+ }
+
+ if (!PQgetisnull(res, i, i_prfpasswordverifyfunc))
+ {
+ appendPQExpBuffer(buf, " PASSWORD_VERIFY_FUNC %s",
+ PQgetvalue(res, i, i_prfpasswordverifyfunc));
+ }
+ }
+ else
+ {
+ if (strcmp(PQgetvalue(res, i, i_prffailedloginattempts), "-2") != 0)
+ {
+ appendPQExpBuffer(buf, " FAILED_LOGIN_ATTEMPTS %s",
+ PQgetvalue(res, i, i_prffailedloginattempts));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordlocktime), "-2") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_LOCK_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordlocktime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordlifetime), "-2") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_LIFE_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordlifetime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordgracetime), "-2") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_GRACE_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordgracetime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordreusetime), "-2") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_REUSE_TIME %s",
+ PQgetvalue(res, i, i_prfpasswordreusetime));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordreusemax), "-2") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_REUSE_MAX %s",
+ PQgetvalue(res, i, i_prfpasswordreusemax));
+ }
+
+ if (strcmp(PQgetvalue(res, i, i_prfpasswordallowhashed), "1") != 0)
+ {
+ appendPQExpBuffer(buf, " PASSWORD_ALLOW_HASHED %s",
+ PQgetvalue(res, i, i_prfpasswordallowhashed));
+ }
+
+ if (!PQgetisnull(res, i, i_prfpasswordverifyfuncdb))
+ {
+ appendPQExpBuffer(buf, " PASSWORD_VERIFY_FUNCDB %s",
+ PQgetvalue(res, i, i_prfpasswordverifyfuncdb));
+ }
+
+ if (!PQgetisnull(res, i, i_prfpasswordverifyfunc))
+ {
+ appendPQExpBuffer(buf, " PASSWORD_VERIFY_FUNC %s",
+ PQgetvalue(res, i, i_prfpasswordverifyfunc));
+ }
+ }
+
+ appendPQExpBufferStr(buf, ";\n");
+
+ if (!no_comments && !PQgetisnull(res, i, i_prfcomment))
+ {
+ appendPQExpBuffer(buf, "COMMENT ON PROFILE %s IS ", fmtId(prfname));
+ appendStringLiteralConn(buf, PQgetvalue(res, i, i_prfcomment), conn);
+ appendPQExpBufferStr(buf, ";\n");
+ }
+
+ fprintf(OPF, "%s", buf->data);;
+ }
+
+ PQclear(res);
+
+ fprintf(OPF, "\n\n");
+
+ destroyPQExpBuffer(buf);
+}
+
+/*
+ * Dump role password history
+ */
+static void
+dumpPasswordHistory(PGconn *conn)
+{
+ PQExpBuffer buf = createPQExpBuffer();
+ PGresult *res;
+ int i_passhistroleid,
+ i_passhistpasswordsetat,
+ i_passhistpassword;
+ int i;
+
+ if (server_version < 90600)
+ return;
+
+ printfPQExpBuffer(buf,
+ "SELECT passhistroleid, passhistpasswordsetat, passhistpassword "
+ "FROM %s "
+ "ORDER BY 1, 2; \n", password_history_catalog);
+
+ res = executeQuery(conn, buf->data);
+
+ i_passhistroleid = PQfnumber(res, "passhistroleid");
+ i_passhistpasswordsetat = PQfnumber(res, "passhistpasswordsetat");
+ i_passhistpassword = PQfnumber(res, "passhistpassword");
+
+ resetPQExpBuffer(buf);
+
+ if (PQntuples(res) > 0)
+ {
+ fprintf(OPF, "--\n-- Password Histories\n--\n\n");
+ fprintf(OPF, "SET allow_system_table_mods = true;\n");
+ }
+
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ appendPQExpBuffer(buf, "INSERT INTO pg_catalog.pg_password_history VALUES( "
+ "%s, \'%s\', \'%s\');\n",
+ PQgetvalue(res, i, i_passhistroleid),
+ PQgetvalue(res, i, i_passhistpasswordsetat),
+ PQgetvalue(res, i, i_passhistpassword));
+
+
+ fprintf(OPF, "%s", buf->data);
+
+ resetPQExpBuffer(buf);
+ }
+
+ appendPQExpBuffer(buf, "RESET allow_system_table_mods;\n");
+
+ fprintf(OPF, "%s", buf->data);
+
+ PQclear(res);
+
+ fprintf(OPF, "\n\n");
+
+ destroyPQExpBuffer(buf);
+}
+
/*
* Find a list of database names that match the given patterns.
* See also expand_table_name_patterns() in pg_dump.c
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 054f1a70eb8..ac531610557 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4688,6 +4688,11 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
}
+ if (isGPDB7000OrLater())
+ {
+ appendPQExpBufferStr(&buf, "\n, r.rolenableprofile");
+ }
+
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
if (!showSystem && !pattern)
@@ -4777,6 +4782,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
add_role_attribute(&buf, _("Bypass RLS"));
+ if (isGPDB7000OrLater())
+ if (strcmp(PQgetvalue(res, i, (verbose ? 15 : 14)), "t") == 0)
+ add_role_attribute(&buf, _("Enable Profile"));
+
conns = atoi(PQgetvalue(res, i, 6));
if (conns >= 0)
{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 68f13533d6b..b3a6a16ae17 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -961,6 +961,10 @@ static const SchemaQuery Query_for_list_of_collations = {
" FROM pg_catalog.pg_extension "\
" WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'"
+#define Query_for_list_of_profiles \
+"SELECT pg_catalog.quote_ident(profilename) FROM pg_catalog.pg_profile "\
+" WHERE substring(pg_catalog.quote_ident(profilename),1,%d)='%s'"
+
#define Query_for_list_of_available_extensions \
" SELECT pg_catalog.quote_ident(name) "\
" FROM pg_catalog.pg_available_extensions "\
@@ -1113,6 +1117,7 @@ static const pgsql_thing_t words_after_create[] = {
{"PARSER", Query_for_list_of_ts_parsers, NULL, NULL, THING_NO_SHOW},
{"POLICY", NULL, NULL, NULL},
{"PROCEDURE", NULL, NULL, Query_for_list_of_procedures},
+ {"PROFILE", Query_for_list_of_profiles},
{"PUBLICATION", NULL, Query_for_list_of_publications},
{"RESOURCE", NULL},
{"ROLE", Query_for_list_of_roles},
@@ -1670,6 +1675,12 @@ psql_completion(const char *text, int start, int end)
/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE */
else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
COMPLETE_WITH("(");
+ /* ALTER PROFILE */
+ else if (Matches("ALTER", "PROFILE", MatchAny))
+ COMPLETE_WITH("LIMIT", "RENAME TO");
+ /* ALTER PROFILE LIMIT */
+ else if (HeadMatches("ALTER", "PROFILE", MatchAny) && TailMatches("LIMIT"))
+ COMPLETE_WITH("FAILED_LOGIN_ATTEMPTS", "PASSWORD_REUSE_MAX", "PASSWORD_LOCK_TIME");
/* ALTER AGGREGATE (...) */
else if (Matches("ALTER", "AGGREGATE", MatchAny, MatchAny))
{
@@ -1876,7 +1887,8 @@ psql_completion(const char *text, int start, int end)
"NOCREATEDB", "NOCREATEROLE", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "PASSWORD",
"RENAME TO", "REPLICATION", "RESET", "SET", "SUPERUSER",
- "VALID UNTIL", "WITH");
+ "VALID UNTIL", "WITH", "PROFILE", "ENABLE PROFILE",
+ "DISABLE PROFILE", "ACCOUNT");
/* ALTER USER,ROLE WITH */
else if (Matches("ALTER", "USER|ROLE", MatchAny, "WITH"))
@@ -1886,8 +1898,11 @@ psql_completion(const char *text, int start, int end)
"NOCREATEDB", "NOCREATEROLE", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "PASSWORD",
"RENAME TO", "REPLICATION", "RESET", "SET", "SUPERUSER",
- "VALID UNTIL");
+ "VALID UNTIL", "PROFILE", "ENABLE PROFILE",
+ "DISABLE PROFILE", "ACCOUNT");
+ else if (Matches("ALTER", "USER|ROLE", MatchAny) && TailMatches("ACCOUNT"))
+ COMPLETE_WITH("LOCK", "UNLOCK");
/* ALTER DEFAULT PRIVILEGES */
else if (Matches("ALTER", "DEFAULT", "PRIVILEGES"))
COMPLETE_WITH("FOR ROLE", "IN SCHEMA");
@@ -2403,7 +2418,7 @@ psql_completion(const char *text, int start, int end)
"SCHEMA", "SEQUENCE", "STATISTICS", "SUBSCRIPTION",
"TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW",
"COLUMN", "AGGREGATE", "FUNCTION",
- "PROCEDURE", "ROUTINE",
+ "PROCEDURE", "PROFILE", "ROUTINE",
"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN",
"LARGE OBJECT", "TABLESPACE", "TEXT SEARCH", "ROLE");
else if (Matches("COMMENT", "ON", "ACCESS", "METHOD"))
@@ -2993,7 +3008,8 @@ psql_completion(const char *text, int start, int end)
"NOCREATEDB", "NOCREATEROLE", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "PASSWORD",
"REPLICATION", "ROLE", "SUPERUSER", "SYSID",
- "VALID UNTIL", "WITH");
+ "VALID UNTIL", "WITH", "PROFILE", "ENABLE PROFILE",
+ "DISABLE PROFILE", "ACCOUNT");
/* CREATE ROLE,USER,GROUP WITH */
else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
@@ -3004,7 +3020,8 @@ psql_completion(const char *text, int start, int end)
"NOCREATEDB", "NOCREATEROLE", "NOINHERIT",
"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "PASSWORD",
"REPLICATION", "ROLE", "SUPERUSER", "SYSID",
- "VALID UNTIL");
+ "VALID UNTIL", "PROFILE", "ENABLE PROFILE",
+ "DISABLE PROFILE", "ACCOUNT");
/* complete CREATE ROLE,USER,GROUP IN with ROLE,GROUP */
else if (Matches("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
@@ -3018,6 +3035,13 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_LIST(list_CREATERESOURCEGROUP);
}
+
+ /* CREATE PROFILE LIMIT */
+ else if (Matches("CREATE", "PROFILE", MatchAny))
+ COMPLETE_WITH("LIMIT");
+ else if (Matches("CREATE", "PROFILE", MatchAny, "LIMIT"))
+ COMPLETE_WITH("FAILED_LOGIN_ATTEMPTS", "PASSWORD_REUSE_MAX", "PASSWORD_LOCK_TIME");
+
/* CREATE/DROP RESOURCE GROUP */
else if (TailMatches("CREATE|DROP", "RESOURCE", "GROUP"))
COMPLETE_WITH_QUERY(Query_for_list_of_resgroups);
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 0df4b38a5c2..52e7825ba27 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -73,6 +73,10 @@ typedef enum DependencyType
* storage don't need this: they are protected by the existence of a physical
* file in the tablespace.)
*
+ * (f) a SHARED_DEPENDENCY_PROFILE entry means that the referenced object
+ * is a profile mentioned in a role object. The referenced object must be
+ * a pg_profile entry.
+ *
* SHARED_DEPENDENCY_INVALID is a value used as a parameter in internal
* routines, and is not valid in the catalog itself.
*/
@@ -83,6 +87,7 @@ typedef enum SharedDependencyType
SHARED_DEPENDENCY_ACL = 'a',
SHARED_DEPENDENCY_POLICY = 'r',
SHARED_DEPENDENCY_TABLESPACE = 't',
+ SHARED_DEPENDENCY_PROFILE = 'f',
SHARED_DEPENDENCY_INVALID = 0
} SharedDependencyType;
@@ -135,6 +140,8 @@ typedef enum ObjectClass
OCLASS_TRANSFORM, /* pg_transform */
/* GPDB additions */
+ OCLASS_PROFILE, /* pg_profile */
+ OCLASS_PASSWORDHISTORY, /* pg_password_history */
OCLASS_EXTPROTOCOL, /* pg_extprotocol */
OCLASS_TASK /* pg_task */
} ObjectClass;
@@ -278,4 +285,8 @@ extern void checkDependencies(const ObjectAddresses *objects,
const char *msg,
const char *hint);
+extern void recordProfileDependency(Oid roleId, Oid profileId);
+
+extern void changeProfileDependency(Oid roleId, Oid profileId);
+
#endif /* DEPENDENCY_H */
diff --git a/src/include/catalog/gp_indexing.h b/src/include/catalog/gp_indexing.h
index 642f695af6d..2adacd34cfd 100644
--- a/src/include/catalog/gp_indexing.h
+++ b/src/include/catalog/gp_indexing.h
@@ -22,6 +22,8 @@ DECLARE_INDEX(pg_authid_rolresqueue_index, 6029, on pg_authid using btree(rolres
#define AuthIdRolResQueueIndexId 6029
DECLARE_INDEX(pg_authid_rolresgroup_index, 6440, on pg_authid using btree(rolresgroup oid_ops));
#define AuthIdRolResGroupIndexId 6440
+DECLARE_INDEX(pg_authid_rolprofile_index, 6441, on pg_authid using btree(rolprofile oid_ops));
+#define AuthIdRolProfileIndexId 6441
DECLARE_INDEX(pg_auth_time_constraint_authid_index, 6449, on pg_auth_time_constraint using btree(authid oid_ops));
#define AuthTimeConstraintAuthIdIndexId 6449
diff --git a/src/include/catalog/oid_dispatch.h b/src/include/catalog/oid_dispatch.h
index 2baddd7e529..bd2fbc261fe 100644
--- a/src/include/catalog/oid_dispatch.h
+++ b/src/include/catalog/oid_dispatch.h
@@ -57,6 +57,8 @@ extern Oid GetNewOidForExtension(Relation relation, Oid indexId, AttrNumber oidc
char *extname);
extern Oid GetNewOidForExtprotocol(Relation relation, Oid indexId, AttrNumber oidcolumn,
char *ptcname);
+extern Oid GetNewOidForProfile(Relation relation, Oid indexId, AttrNumber oidcolumn,
+ char *prfname);
extern Oid GetNewOidForForeignDataWrapper(Relation relation, Oid indexId, AttrNumber oidcolumn,
char *fdwname);
extern Oid GetNewOidForForeignServer(Relation relation, Oid indexId, AttrNumber oidcolumn,
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 02bf0e91ba2..484181c581c 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -42,6 +42,7 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
bool rolreplication; /* role used for streaming replication */
bool rolbypassrls; /* bypasses row-level security? */
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
+ bool rolenableprofile BKI_DEFAULT(f) BKI_FORCE_NOT_NULL; /* whether user can use profile */
/* remaining fields may be null; use heap_getattr to read them! */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
@@ -52,6 +53,18 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
* GP added fields
*/
+ Oid rolprofile BKI_DEFAULT(10140) BKI_FORCE_NOT_NULL; /* name of profile */
+
+ int16 rolaccountstatus BKI_DEFAULT(0) BKI_FORCE_NOT_NULL; /* status of account */
+
+ int32 rolfailedlogins BKI_DEFAULT(0) BKI_FORCE_NOT_NULL; /* number of failed logins */
+
+ timestamptz rolpasswordsetat BKI_DEFAULT(_null_); /* password set time, if any */
+
+ timestamptz rollockdate BKI_DEFAULT(_null_) BKI_FORCE_NULL; /* account lock time, if any */
+
+ timestamptz rolpasswordexpire BKI_DEFAULT(_null_) BKI_FORCE_NULL; /* account password expire time, if any */
+
/* ID of resource queue for this role */
Oid rolresqueue BKI_DEFAULT(6055);
@@ -69,6 +82,15 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
#endif
} FormData_pg_authid;
+typedef enum
+{
+ ROLE_ACCOUNT_STATUS_OPEN,
+ ROLE_ACCOUNT_STATUS_LOCKED_TIMED,
+ ROLE_ACCOUNT_STATUS_LOCKED,
+ ROLE_ACCOUNT_STATUS_EXPIRED_GRACE,
+ ROLE_ACCOUNT_STATUS_EXPIRED
+} ROLE_ACCOUNT_STATUS;
+
/* GPDB added foreign key definitions for gpcheckcat. */
FOREIGN_KEY(rolresqueue REFERENCES pg_resqueue(oid));
FOREIGN_KEY(rolresgroup REFERENCES pg_resgroup(oid));
diff --git a/src/include/catalog/pg_password_history.h b/src/include/catalog/pg_password_history.h
new file mode 100644
index 00000000000..b2812964aa5
--- /dev/null
+++ b/src/include/catalog/pg_password_history.h
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_password_history.h
+ * definition of the "password history" system catalog (pg_password_history)
+ *
+ *
+ * Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+ *
+ * src/include/catalog/pg_password_history.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PASSWORD_HISTORY_H
+#define PG_PASSWROD_HISTORY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_password_history_d.h"
+
+/* ----------------
+ * pg_password_history definition. cpp turns this into
+ * typedef struct FormData_pg_password_history
+ * ----------------
+ */
+CATALOG(pg_password_history,10141,PasswordHistoryRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(10142,PasswordHistoryRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+{
+ Oid passhistroleid BKI_FORCE_NOT_NULL; /* oid of role */
+#ifdef CATALOG_VARLEN
+ timestamptz passhistpasswordsetat BKI_FORCE_NOT_NULL; /* password set time */
+ text passhistpassword BKI_FORCE_NOT_NULL; /* the history password */
+#endif
+} FormData_pg_password_history;
+
+/* ----------------
+ * Form_pg_password_history corresponds to a pointer to a tuple with
+ * the format of pg_password_history relation.
+ * ----------------
+ */
+typedef FormData_pg_password_history *Form_pg_password_history;
+
+DECLARE_TOAST(pg_password_history, 10143, 10144);
+#define PgPasswordHistoryToastTable 10143
+#define PgPasswordHistoryToastIndex 10144
+
+DECLARE_UNIQUE_INDEX(pg_password_history_role_password_index, 10145, on pg_password_history using btree(passhistroleid oid_ops, passhistpassword text_ops));
+#define PasswordHistoryRolePasswordIndexId 10145
+DECLARE_INDEX(pg_password_history_role_passwordsetat_index, 10146, on pg_password_history using btree(passhistroleid oid_ops, passhistpasswordsetat timestamptz_ops));
+#define PasswordHistoryRolePasswordsetatIndexId 10146
+
+#endif /* PG_PASSWORD_HISTORY_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e72142f0af7..63b49025869 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12476,5 +12476,7 @@
{ oid => 7077, descr => 'Update gp_segment_configuration mode and status by dbid', proname => 'gp_update_segment_configuration_mode_status',
proisstrict => 'f', provolatile => 'v', proparallel => 'r', prorettype => 'int2', proargtypes => 'int4 char char', prosrc => 'gp_update_segment_configuration_mode_status'},
+{ oid => 7060, descr => 'get user account status',
+ proname => 'get_role_status', prorettype => 'cstring', proargtypes => 'cstring', prosrc => 'get_role_status' },
]
diff --git a/src/include/catalog/pg_profile.dat b/src/include/catalog/pg_profile.dat
new file mode 100644
index 00000000000..d76b974e753
--- /dev/null
+++ b/src/include/catalog/pg_profile.dat
@@ -0,0 +1,28 @@
+#----------------------------------------------------------------------
+#
+# pg_profile.dat
+# Initial contents of the pg_profile system catalog.
+#
+# Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+#
+# src/include/catalog/pg_profile.dat
+#
+#----------------------------------------------------------------------
+
+[
+
+# The C code typically refers to these roles using the #define symbols,
+# so make sure every entry has an oid_symbol value.
+
+# The bootstrap superuser is named POSTGRES according to this data and
+# according to BKI_DEFAULT entries in other catalogs. However, initdb
+# will replace that at database initialization time.
+
+{ oid => '10140', oid_symbol => 'DEFAULT_PROFILE',
+ descr => 'default profile',
+ prfname => 'pg_default', prffailedloginattempts => '-2', prfpasswordlocktime => '-2',
+ prfpasswordlifetime => '-2', prfpasswordgracetime => '-2', prfpasswordreusetime => '-2',
+ prfpasswordreusemax => '-2', prfpasswordallowhashed => '1', prfpasswordverifyfuncdb => '_null_',
+ prfpasswordverifyfunc => '_null_' }
+
+]
diff --git a/src/include/catalog/pg_profile.h b/src/include/catalog/pg_profile.h
new file mode 100644
index 00000000000..8aba34fd091
--- /dev/null
+++ b/src/include/catalog/pg_profile.h
@@ -0,0 +1,78 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_profile.h
+ * definition of the "profile" system catalog (pg_profile)
+ *
+ *
+ * Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+ *
+ * src/include/catalog/pg_profile.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROFILE_H
+#define PG_PROFILE_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_profile_d.h"
+#include "parser/parse_node.h"
+
+/* ----------------
+ * pg_profile definition. cpp turns this into
+ * typedef struct FormData_pg_profile
+ * ----------------
+ */
+CATALOG(pg_profile,10135,ProfileRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(10136,ProfileRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+{
+ Oid oid BKI_FORCE_NOT_NULL; /* oid */
+ NameData prfname BKI_FORCE_NOT_NULL; /* name of profile */
+ int32 prffailedloginattempts BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* the number of failed login attempts */
+ int32 prfpasswordlocktime BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* the locking period */
+ int32 prfpasswordlifetime BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* the number of days that the current password is valid and usable */
+ int32 prfpasswordgracetime BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* the number of days an old password can still be used */
+ int32 prfpasswordreusetime BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* the number of days a user must wait before reusing a password */
+ int32 prfpasswordreusemax BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* the number of password changes that must occur before a password can be reused */
+ int32 prfpasswordallowhashed BKI_DEFAULT(-1) BKI_FORCE_NOT_NULL; /* whether an hash encrypted password is allowed to be used or not */
+ Oid prfpasswordverifyfuncdb BKI_FORCE_NULL; /* verify function for database when login */
+ Oid prfpasswordverifyfunc BKI_FORCE_NULL; /* verify function when login */
+} FormData_pg_profile;
+
+/* ----------------
+ * Form_pg_profile corresponds to a pointer to a tuple with
+ * the format of pg_profile relation.
+ * ----------------
+ */
+typedef FormData_pg_profile *Form_pg_profile;
+
+DECLARE_UNIQUE_INDEX(profile_name_index, 10137, on pg_profile using btree(prfname name_ops));
+#define ProfilePrfnameIndexId 10137
+DECLARE_UNIQUE_INDEX(profile_oid_index, 10138, on pg_profile using btree(oid oid_ops));
+#define ProfileOidIndexId 10138
+DECLARE_INDEX(profile_password_verify_function_index, 10139, on pg_profile using btree(prfpasswordverifyfuncdb oid_ops, prfpasswordverifyfunc oid_ops));
+#define ProfileVerifyFunctionIndexId 10139
+
+#define DefaultProfileOID 10140
+
+#define PROFILE_UNLIMITED -2
+#define PROFILE_DEFAULT -1
+#define PROFILE_MAX_VALID 9999
+
+extern char *
+ProfileGetNameByOid(Oid profileOid, bool noerr);
+extern Oid
+get_profile_oid(const char *prfname, bool missing_ok);
+extern ObjectAddress
+rename_profile(char *oldname, char *newname);
+extern Oid
+CreateProfile(ParseState *pstate, CreateProfileStmt *stmt);
+extern Oid
+AlterProfile(AlterProfileStmt *stmt);
+extern void
+DropProfile(DropProfileStmt *stmt);
+extern int32
+tranformProfileValueToNormal(int32 profile_val, int attoff);
+#endif /* PG_PROFILE_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 07e375dfea0..7447c3e0c7d 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -423,6 +423,8 @@ typedef enum BackendType
B_BG_WORKER,
B_BG_WRITER,
B_CHECKPOINTER,
+ B_LOGIN_MONITOR,
+ B_LOGIN_MONITOR_WORKER,
B_STARTUP,
B_WAL_RECEIVER,
B_WAL_SENDER,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 0912902eb21..e168fb85b73 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -445,6 +445,9 @@ typedef enum NodeTag
T_CreateRoleStmt,
T_AlterRoleStmt,
T_DropRoleStmt,
+ T_CreateProfileStmt,
+ T_AlterProfileStmt,
+ T_DropProfileStmt,
T_CreateQueueStmt,
T_AlterQueueStmt,
T_DropQueueStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 294e213ee1c..f2a7a520a03 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1931,6 +1931,7 @@ typedef enum ObjectType
OBJECT_OPERATOR,
OBJECT_OPFAMILY,
OBJECT_POLICY,
+ OBJECT_PROFILE,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
OBJECT_PUBLICATION_REL,
@@ -3130,6 +3131,27 @@ typedef struct DropRoleStmt
bool missing_ok; /* skip error if a role is missing? */
} DropRoleStmt;
+typedef struct CreateProfileStmt
+{
+ NodeTag type;
+ char *profile_name; /* profile name */
+ List *options; /* List of DefElem nodes */
+} CreateProfileStmt;
+
+typedef struct AlterProfileStmt
+{
+ NodeTag type;
+ char *profile_name; /* profile name */
+ List *options; /* List of DefElem nodes */
+} AlterProfileStmt;
+
+typedef struct DropProfileStmt
+{
+ NodeTag type;
+ List *profiles; /* List of profile name to remove */
+ bool missing_ok; /* skip error if a profile is missing? */
+} DropProfileStmt;
+
typedef struct DenyLoginPoint
{
NodeTag type;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a6e840fc4fe..a3fbbe72e33 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -29,6 +29,7 @@
PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("account", ACCOUNT, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("active", ACTIVE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -180,6 +181,7 @@ PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("failed_login_attempts", FAILED_LOGIN_ATTEMPTS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD, AS_LABEL)
@@ -354,6 +356,8 @@ PG_KEYWORD("partition", PARTITION, RESERVED_KEYWORD, AS_LABEL) /* GPDB *
PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("password_lock_time", PASSWORD_LOCK_TIME, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
+PG_KEYWORD("password_reuse_max", PASSWORD_REUSE_MAX, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("percent", PERCENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("persistently", PERSISTENTLY, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
@@ -371,6 +375,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("profile", PROFILE, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("protocol", PROTOCOL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -500,6 +505,7 @@ PG_KEYWORD("union", UNION, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("unknown", UNKNOWN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("unlisten", UNLISTEN, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("unlock", UNLOCK_P, UNRESERVED_KEYWORD, BARE_LABEL) /* GPDB */
PG_KEYWORD("unlogged", UNLOGGED, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("until", UNTIL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("update", UPDATE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/postmaster/loginmonitor.h b/src/include/postmaster/loginmonitor.h
new file mode 100644
index 00000000000..7834d894964
--- /dev/null
+++ b/src/include/postmaster/loginmonitor.h
@@ -0,0 +1,36 @@
+/*-------------------------------------------------------------------------
+ *
+ * loginmonitor.h
+ * header file for integrated loginmonitor daemon
+ *
+ *
+ * Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+ *
+ * src/include/postmaster/loginmonitor.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGINMONITOR_H
+#define LOGINMONITOR_H
+
+#include "storage/block.h"
+
+extern int StartLoginMonitorLauncher(void);
+extern int StartLoginMonitorWorker(void);
+extern Size LoginMonitorShmemSize(void);
+extern void LoginMonitorShmemInit(void);
+extern void SendLoginFailedSignal(const char *curr_user_name);
+extern bool IsLoginMonitorWorkerProcess(void);
+extern bool IsLoginMonitorLauncherProcess(void);
+extern void HandleLoginFailed(void);
+extern void LoginMonitorWorkerFailed(void);
+
+#define IsAnyLoginMonitorProcess() \
+ (IsLoginMonitorLauncherProcess() || IsLoginMonitorWorkerProcess())
+
+extern int login_monitor_max_processes;
+#endif /* LOGINMONITOR_H */
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 8d4d84582da..f9b48b36f32 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -32,6 +32,7 @@ extern bool enable_bonjour;
extern char *bonjour_name;
extern bool restart_after_crash;
extern bool remove_temp_files_after_crash;
+extern bool enable_password_profile;
extern int terminal_fd;
diff --git a/src/include/storage/pmsignal.h b/src/include/storage/pmsignal.h
index b46abda3a92..fab2192d877 100644
--- a/src/include/storage/pmsignal.h
+++ b/src/include/storage/pmsignal.h
@@ -45,6 +45,9 @@ typedef enum
PMSIGNAL_WAKEN_DTX_RECOVERY, /* wake up dtx recovery to abort dtx xacts */
PMSIGNAL_DTM_RECOVERED, /* distributed recovery completed */
+ PMSIGNAL_FAILED_LOGIN, /* send signal SIGUSR1 to login monitor launcher */
+ PMSIGNAL_START_LOGIN_MONITOR_WORKER, /* start a login monitor worker */
+
NUM_PMSIGNALS /* Must be last value of enum! */
} PMSignalReason;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 59cabdf7c60..dd6d7126e4e 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -414,6 +414,8 @@ typedef struct PROC_HDR
PGPROC *freeProcs;
/* Head of list of autovacuum's free PGPROC structures */
PGPROC *autovacFreeProcs;
+ /* Head of list of login monitor free PGPROC structures */
+ PGPROC *lmFreeProcs;
/* Head of list of bgworker free PGPROC structures */
PGPROC *bgworkerFreeProcs;
/* Head of list of walsender free PGPROC structures */
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 57862ae57db..0815460c72f 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -47,6 +47,8 @@ typedef enum
PROCSIG_QUERY_FINISH, /* query finish */
PROCSIG_RESOURCE_GROUP_MOVE_QUERY, /* move query to a new resource group */
+ PROCSIG_FAILED_LOGIN, /* failed login */
+
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 61469142633..74285522d99 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -49,6 +49,7 @@ PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, fals
PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_PROFILE, "ALTER PROFILE", false, false, false)
PG_CMDTAG(CMDTAG_ALTER_PROTOCOL, "ALTER PROTOCOL", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
PG_CMDTAG(CMDTAG_ALTER_QUEUE, "ALTER QUEUE", false, false, false)
@@ -110,6 +111,7 @@ PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, fa
PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_PROFILE, "CREATE PROFILE", false, false, false)
PG_CMDTAG(CMDTAG_CREATE_PROTOCOL, "CREATE PROTOCOL", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
PG_CMDTAG(CMDTAG_CREATE_QUEUE, "CREATE QUEUE", false, false, false)
@@ -175,6 +177,7 @@ PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, fals
PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_PROFILE, "DROP PROFILE", false, false, false)
PG_CMDTAG(CMDTAG_DROP_PROTOCOL, "DROP PROTOCOL", true, false, false)
PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index c2ba8a84b2f..f705c1756b6 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -77,6 +77,8 @@ enum SysCacheIdentifier
OPFAMILYAMNAMENSP,
OPFAMILYOID,
PARTRELID,
+ PROFILEID,
+ PROFILENAME,
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
diff --git a/src/include/utils/unsync_guc_name.h b/src/include/utils/unsync_guc_name.h
index 67483db6481..d8593b503c4 100644
--- a/src/include/utils/unsync_guc_name.h
+++ b/src/include/utils/unsync_guc_name.h
@@ -144,6 +144,7 @@
"enable_partition_pruning",
"enable_partitionwise_aggregate",
"enable_partitionwise_join",
+ "enable_password_profile",
"enable_seqscan",
"enable_sort",
"enable_tidscan",
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 295f48791bf..863ed3a2871 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -61,6 +61,7 @@ typedef enum
#ifdef USE_INTERNAL_FTS
WAIT_EVENT_FTS_PROBE_MAIN,
#endif
+ WAIT_EVENT_LOGIN_MONITOR_LAUNCHER_MAIN,
WAIT_EVENT_GLOBAL_DEADLOCK_DETECTOR_MAIN
} WaitEventActivity;
@@ -145,7 +146,8 @@ typedef enum
,
WAIT_EVENT_DTX_RECOVERY,
WAIT_EVENT_SHAREINPUT_SCAN,
- WAIT_EVENT_INTERCONNECT
+ WAIT_EVENT_INTERCONNECT,
+ WAIT_EVENT_LOGINMONITOR_FINISH
} WaitEventIPC;
/* ----------
diff --git a/src/test/authentication/t/003_profile.pl b/src/test/authentication/t/003_profile.pl
new file mode 100644
index 00000000000..33e0a7b2f9f
--- /dev/null
+++ b/src/test/authentication/t/003_profile.pl
@@ -0,0 +1,812 @@
+
+# Copyright (c) 2023, Cloudberry Database, HashData Technology Limited.
+
+# Test password profile feature.
+#
+# This test can only run with Unix-domain sockets.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More;
+if (!$use_unix_sockets)
+{
+ plan skip_all =>
+ "authentication tests cannot run without Unix-domain sockets";
+}
+else
+{
+ plan tests => 96;
+}
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ $node->append_conf('pg_hba.conf', "local all all $hba_method");
+ $node->reload;
+ return;
+}
+
+# Test access for a single role, useful to wrap all tests into one.
+sub test_login
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $node = shift;
+ my $role = shift;
+ my $password = shift;
+ my $expected_res = shift;
+ my $status_string = 'failed';
+
+ $status_string = 'success' if ($expected_res eq 0);
+
+ my $connstr = "user=$role";
+ my $testname =
+ "authentication $status_string for role $role with password $password";
+
+ $ENV{"PGPASSWORD"} = $password;
+ if ($expected_res eq 0)
+ {
+ $node->connect_ok($connstr, $testname);
+ }
+ else
+ {
+ # No checks of the error message, only the status code.
+ $node->connect_fails($connstr, $testname);
+ }
+}
+
+# Initialize primary node. Force UTF-8 encoding, so that we can use non-ASCII
+# characters in the passwords below.
+my $node = get_new_node('primary');
+my ($ret, $stdout, $stderr);
+$node->init(extra => [ '--locale=C', '--encoding=UTF8' ]);
+$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->start;
+
+# Create test profiles.
+$node->safe_psql(
+ 'postgres',
+ "CREATE PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 1;
+CREATE PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS 2 PASSWORD_REUSE_MAX 2;
+CREATE PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS -1 PASSWORD_REUSE_MAX 0;
+CREATE PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 1 PASSWORD_LOCK_TIME 2 PASSWORD_REUSE_MAX 1;",
+"");
+
+# Create test roles.
+$node->safe_psql(
+ 'postgres',
+ "SET password_encryption='scram-sha-256';
+SET client_encoding='utf8';
+CREATE USER profile_user1 LOGIN PASSWORD 'IX' ENABLE PROFILE;
+CREATE USER profile_user2 LOGIN PASSWORD 'a' DISABLE PROFILE;
+CREATE USER profile_user3 LOGIN PASSWORD E'\\xc2\\xaa' ENABLE PROFILE;
+CREATE USER profile_user4 LOGIN PASSWORD E'foo\\x07bar' DISABLE PROFILE;
+");
+
+
+# Test only super users have privileges of manipulating profile
+$node->safe_psql(
+ 'postgres',
+ "SET password_encryption='scram-sha-256';
+SET client_encoding='utf8';
+CREATE USER super_user SUPERUSER;
+CREATE USER normal_user;"
+);
+
+# Test CREATE PROFILE
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE PROFILE test_profile1 LIMIT FAILED_LOGIN_ATTEMPTS 2;"
+);
+is($ret, 0, 'create profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE PROFILE test_profile2 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 2;"
+);
+is($ret, 0, 'create profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "CREATE PROFILE test_profile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 2;"
+);
+is($ret, 3, 'non-super user can not create profile');
+like(
+ $stderr,
+ qr/permission denied to create profile, must be superuser/,
+ 'expected error from non-super user can not create profile'
+);
+
+# Test ALTER PROFILE
+$node->safe_psql(
+ 'postgres',
+ "ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS 4;"
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER PROFILE test_profile1 LIMIT FAILED_LOGIN_ATTEMPTS 3;"
+);
+is($ret, 0, 'alter profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER PROFILE test_profile2 LIMIT FAILED_LOGIN_ATTEMPTS 2 PASSWORD_LOCK_TIME 1 PASSWORD_REUSE_MAX 2;"
+);
+is($ret, 0, 'alter profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER PROFILE test_profile1 LIMIT FAILED_LOGIN_ATTEMPTS 3;"
+);
+is($ret, 3, 'non-super user can not alter profile');
+like(
+ $stderr,
+ qr/permission denied to alter profile, must be superuser/,
+ 'expected error from non-super user can not alter profile'
+);
+
+# Test DROP PROFILE
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "DROP PROFILE test_profile1;"
+);
+is($ret, 0, 'drop profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "DROP PROFILE test_profile2;"
+);
+is($ret, 3, 'non-super user can not drop profile');
+like(
+ $stderr,
+ qr/permission denied to drop profile, must be superuser/,
+ 'expected error from non-super user can not drop profile'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "DROP PROFILE pg_default;"
+);
+is($ret, 3, 'can not drop default profile pg_default');
+like(
+ $stderr,
+ qr/Disallow to drop default profile/,
+ 'expected error from can not drop default profile pg_default'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "DROP PROFILE test_profile2;"
+);
+is($ret, 0, 'drop profile succeed');
+
+# Test CREATE USER ... PROFILE
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE USER test_user1 PROFILE myprofile1;"
+);
+is($ret, 0, 'create user ... profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE USER test_user2 PROFILE myprofile2;"
+);
+is($ret, 0, 'create user ... profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "CREATE USER test_user3 PROFILE myprofile3;"
+);
+is($ret, 3, 'non-super user can not create user ... profile');
+like(
+ $stderr,
+ qr/must be superuser to create role attached to profile/,
+ 'expected error from non-super user can not create user ... profile'
+);
+
+# Test CREATE USER ... ACCOUNT LOCK/UNLOCK
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE USER test_user4 ACCOUNT LOCK;"
+);
+is($ret, 0, 'create user ... account lock succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE USER test_user5 ACCOUNT UNLOCK;"
+);
+is($ret, 0, 'create user ... account unlock succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "CREATE USER test_user6 ACCOUNT LOCK;"
+);
+is($ret, 3, 'non-super user can not create user ... account lock');
+like(
+ $stderr,
+ qr/must be superuser to create role account lock\/unlock/,
+ 'expected error from non-super user can not create user ... account lock'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "CREATE USER test_user7 ACCOUNT UNLOCK;"
+);
+is($ret, 3, 'non-super user can not create user ... account unlock');
+like(
+ $stderr,
+ qr/must be superuser to create role account lock\/unlock/,
+ 'expected error from non-super user can not create user ... account unlock'
+);
+
+# Test CREATE USER ... ENABLE/DISABLE PROFILE
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE USER test_user8 ENABLE PROFILE PROFILE myprofile2;"
+);
+is($ret, 0, 'create user ... enable profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "CREATE USER test_user9 DISABLE PROFILE;"
+);
+is($ret, 0, 'create user ... disable profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "CREATE USER test_user10 ENABLE PROFILE;"
+);
+is($ret, 3, 'non-super user can not create user ... enable profile');
+like(
+ $stderr,
+ qr/must be superuser to create role enable\/disable profile/,
+ 'expected error from non-super user can not create user ... enable profile'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "CREATE USER test_user11 DISABLE PROFILE;"
+);
+is($ret, 3, 'non-super user can not create user ... disable profile');
+like(
+ $stderr,
+ qr/must be superuser to create role enable\/disable profile/,
+ 'expected error from non-super user can not create user ... disable profile'
+);
+
+# Test ALTER USER ... PROFILE
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER USER test_user1 PROFILE myprofile1;"
+);
+is($ret, 0, 'alter user ... profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER USER test_user2 PROFILE myprofile2;"
+);
+is($ret, 0, 'alter user ... profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER USER test_user4 PROFILE myprofile4;"
+);
+is($ret, 3, 'non-super user can not alter user ... profile');
+like(
+ $stderr,
+ qr/must be superuser to alter role attached to profile/,
+ 'expected error from non-super user can not alter user ... profile'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER USER test_user4 PROFILE myprofile4;"
+);
+is($ret, 3, 'non-super user can not alter user ... profile');
+like(
+ $stderr,
+ qr/must be superuser to alter role attached to profile/,
+ 'expected error from non-super user can not alter user ... profile'
+);
+
+# Test ALTER USER ... ENABLE/DISABLE PROFILE
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER USER test_user1 ENABLE PROFILE;"
+);
+is($ret, 0, 'alter user ... enable profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER USER test_user2 DISABLE PROFILE;"
+);
+is($ret, 0, 'alter user ... disable profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER USER test_user4 ENABLE PROFILE;"
+);
+is($ret, 3, 'non-super user can not alter user ... enable profile');
+like(
+ $stderr,
+ qr/must be superuser to alter role enable\/disable profile/,
+ 'expected error from non-super user can not alter user ... enable profile'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER USER test_user5 DISABLE PROFILE;"
+);
+is($ret, 3, 'non-super user can not alter user ... disable profile');
+like(
+ $stderr,
+ qr/must be superuser to alter role enable\/disable profile/,
+ 'expected error from non-super user can not alter user ... disable profile'
+);
+
+# Test ALTER USER ... ACCOUNT
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER USER test_user1 ACCOUNT LOCK;"
+);
+is($ret, 0, 'alter user ... account lock succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "ALTER USER test_user2 ACCOUNT UNLOCK;"
+);
+is($ret, 0, 'alter user ... account unlock succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER USER test_user1 ACCOUNT LOCK;"
+);
+is($ret, 3, 'non-super user can not alter user ... account lock');
+like(
+ $stderr,
+ qr/must be superuser to alter role account lock\/unlock/,
+ 'expected error from non-super user can not alter user ... account lock'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "ALTER USER test_user1 ACCOUNT UNLOCK;"
+);
+is($ret, 3, 'non-super user can not alter user ... account unlock');
+like(
+ $stderr,
+ qr/must be superuser to alter role account lock\/unlock/,
+ 'expected error from non-super user can not alter user ... account unlock'
+);
+
+# Test DROP USER
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "DROP USER test_user1;"
+);
+is($ret, 0, 'drop user succeed');
+
+# Test privileges of SELECT pg_authid, pg_profile, pg_password_history
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname like '%test_user%';"
+);
+is($ret, 0, 'select pg_authid succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname like '%test_user%';"
+);
+is($ret, 3, 'non-super user can not select pg_authid');
+like(
+ $stderr,
+ qr/permission denied for table pg_authid/,
+ 'expected error from non-super user can not select pg_authid'
+);
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+ FROM pg_profile;"
+);
+is($ret, 0, 'select pg_profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+ FROM pg_profile;"
+);
+is($ret, 0, 'select pg_profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'super_user',
+ 'postgres',
+ "SELECT COUNT(*)
+ FROM pg_password_history;"
+);
+is($ret, 0, 'select pg_profile succeed');
+
+($ret, $stdout, $stderr) =
+$node->role_psql(
+ 'normal_user',
+ 'postgres',
+ "SELECT COUNT(*)
+ FROM pg_password_history;"
+);
+is($ret, 3, 'non-super user can not select pg_password_history');
+like(
+ $stderr,
+ qr/permission denied for table pg_password_history/,
+ 'expected error from non-super user can not select pg_password_history'
+);
+
+# Test Login Successful
+$node->safe_psql(
+ 'postgres',
+ "ALTER USER profile_user1 PROFILE myprofile1 ENABLE PROFILE;
+ ALTER USER profile_user2 PROFILE myprofile2 ENABLE PROFILE;
+ ALTER USER profile_user3 PROFILE myprofile3 ENABLE PROFILE;
+ ALTER USER profile_user4 PROFILE myprofile4 ENABLE PROFILE;
+ ALTER USER super_user PROFILE pg_default ENABLE PROFILE PASSWORD 'a_nice_word';"
+);
+
+# Require password from now on.
+reset_pg_hba($node, 'scram-sha-256');
+
+# Test passwords work OK
+test_login($node, 'profile_user1', "I\xc2\xadX", 0);
+test_login($node, 'profile_user1', "\xe2\x85\xa8", 0);
+
+test_login($node, 'profile_user2', "a", 0);
+test_login($node, 'profile_user2', "\xc2\xaa", 0);
+
+test_login($node, 'profile_user3', "a", 0);
+test_login($node, 'profile_user3', "\xc2\xaa", 0);
+
+test_login($node, 'profile_user4', "foo\x07bar", 0);
+
+test_login($node, 'profile_user1', 'IX', 0);
+
+# Test Login Successful and Failed
+# Test Account Unlocked
+test_login($node, 'profile_user1', "random", 2);
+test_login($node, 'profile_user1', "random", 2);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user1';"
+);
+like(
+ $stdout,
+ qr/profile_user1|0|2|t/,
+ 'expected results from select pg_authid'
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user1', "IX", 0);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user1';"
+);
+like(
+ $stdout,
+ qr/profile_user1|0|0|t/,
+ 'expected results from select pg_authid'
+);
+
+# Test Account Locked
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user2', "random", 2);
+test_login($node, 'profile_user2', "random", 2);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user2';"
+);
+like(
+ $stdout,
+ qr/profile_user2|2|2|t/,
+ 'expected results from select pg_authid'
+);
+
+$node->safe_psql(
+ 'postgres',
+ "ALTER USER profile_user2 ACCOUNT UNLOCK"
+);
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user2';"
+);
+like(
+ $stdout,
+ qr/profile_user2|0|2|t/,
+ 'expected results from select pg_authid'
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user2', "a", 0);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user2';"
+);
+like(
+ $stdout,
+ qr/profile_user2|0|0|t/,
+ 'expected results from select pg_authid'
+);
+
+# Test Default Profile Value
+reset_pg_hba($node, 'scram-sha-256');
+
+test_login($node, 'profile_user3', "random", 2);
+test_login($node, 'profile_user3', "random", 2);
+test_login($node, 'profile_user3', "random", 2);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user3';"
+);
+like(
+ $stdout,
+ qr/profile_user3|0|3|t/,
+ 'expected results from select pg_authid'
+);
+
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT prfname, prffailedloginattempts
+ FROM pg_profile
+ WHERE prfname = 'myprofile3';"
+);
+like(
+ $stdout,
+ qr/myprofile3|-1/,
+ 'expected results from select pg_profile'
+);
+
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT prfname, prffailedloginattempts
+ FROM pg_profile
+ WHERE prfname = 'pg_default';"
+);
+like(
+ $stdout,
+ qr/pg_default|4/,
+ 'expected results from select pg_profile'
+);
+
+$node->safe_psql(
+ 'postgres',
+ "ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS 2;"
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user3', "a", 2);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user3';"
+);
+like(
+ $stdout,
+ qr/profile_user3|2|3|t/,
+ 'expected results from select pg_authid'
+);
+
+# Test Alter User ... Enable/Disable Profile
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user4', "random", 2);
+test_login($node, 'profile_user4', "random", 2);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user4';"
+);
+like(
+ $stdout,
+ qr/profile_user4|2|2|t/,
+ 'expected results from select pg_authid'
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user4', "foo\x07bar", 2);
+
+reset_pg_hba($node, 'trust');
+$node->safe_psql(
+ 'postgres',
+ "ALTER USER profile_user4 DISABLE PROFILE;"
+);
+
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'profile_user4';"
+);
+like(
+ $stdout,
+ qr/profile_user4|2|2|f/,
+ 'expected results from select pg_authid'
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user4', "foo\x07bar", 0);
+
+reset_pg_hba($node, 'trust');
+$node->safe_psql(
+ 'postgres',
+ "ALTER USER profile_user4 ENABLE PROFILE;"
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'profile_user4', "foo\x07bar", 2);
+
+# Test Superuser Will Never Locked
+test_login($node, 'super_user', "random", 2);
+test_login($node, 'super_user', "random", 2);
+test_login($node, 'super_user', "random", 2);
+
+reset_pg_hba($node, 'trust');
+($ret, $stdout, $stderr) =
+$node->psql(
+ 'postgres',
+ "SELECT rolname, rolaccountstatus, rolfailedlogins, rolenableprofile
+ FROM pg_authid
+ WHERE rolname = 'super_user';"
+);
+like(
+ $stdout,
+ qr/super_user|0|0|t/,
+ 'expected results from select pg_authid'
+);
+
+reset_pg_hba($node, 'scram-sha-256');
+test_login($node, 'super_user', "a_nice_word", 0);
+
+# cleanup
+reset_pg_hba($node, 'trust');
+$node->safe_psql(
+ 'postgres',
+ "DROP USER test_user2;
+ DROP USER test_user4;
+ DROP USER test_user5;
+ DROP USER test_user8;
+ DROP USER test_user9;
+ DROP USER super_user;
+ DROP USER normal_user;
+ DROP USER profile_user1;
+ DROP USER profile_user2;
+ DROP USER profile_user3;
+ DROP USER profile_user4;
+ DROP PROFILE myprofile1;
+ DROP PROFILE myprofile2;
+ DROP PROFILE myprofile3;
+ DROP PROFILE myprofile4;
+ ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS -2 PASSWORD_LOCK_TIME -2 PASSWORD_REUSE_MAX -2;"
+);
\ No newline at end of file
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 647d0b8e088..1e2d7a065a3 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -2085,6 +2085,163 @@ sub _pgbench_make_files
return @file_opts;
}
+# Test SQL in specific role
+sub role_psql
+{
+ my ($self, $role, $dbname, $sql, %params) = @_;
+
+ local %ENV = $self->_get_env();
+
+ my $stdout = $params{stdout};
+ my $stderr = $params{stderr};
+ my $replication = $params{replication};
+ my $timeout = undef;
+ my $timeout_exception = 'psql timed out';
+
+
+ local $ENV{PGOPTIONS} = '-c gp_role=utility';
+
+ # Build the connection string.
+ my $psql_connstr;
+ if (defined $params{connstr})
+ {
+ $psql_connstr = $params{connstr};
+ }
+ else
+ {
+ $psql_connstr = $self->connstr($dbname);
+ }
+ $psql_connstr .= defined $replication ? " replication=$replication" : "";
+
+ my @psql_params = (
+ $self->installed_command('psql'),
+ '-XAtq', '-U', $role, '-d', $psql_connstr, '-f', '-');
+
+
+ # If the caller wants an array and hasn't passed stdout/stderr
+ # references, allocate temporary ones to capture them so we
+ # can return them. Otherwise we won't redirect them at all.
+ if (wantarray)
+ {
+ if (!defined($stdout))
+ {
+ my $temp_stdout = "";
+ $stdout = \$temp_stdout;
+ }
+ if (!defined($stderr))
+ {
+ my $temp_stderr = "";
+ $stderr = \$temp_stderr;
+ }
+ }
+
+ $params{on_error_stop} = 1 unless defined $params{on_error_stop};
+ $params{on_error_die} = 0 unless defined $params{on_error_die};
+
+ push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop};
+ push @psql_params, @{ $params{extra_params} }
+ if defined $params{extra_params};
+
+ $timeout =
+ IPC::Run::timeout($params{timeout}, exception => $timeout_exception)
+ if (defined($params{timeout}));
+
+ ${ $params{timed_out} } = 0 if defined $params{timed_out};
+
+ # IPC::Run would otherwise append to existing contents:
+ $$stdout = "" if ref($stdout);
+ $$stderr = "" if ref($stderr);
+
+ my $ret;
+
+ # Run psql and capture any possible exceptions. If the exception is
+ # because of a timeout and the caller requested to handle that, just return
+ # and set the flag. Otherwise, and for any other exception, rethrow.
+ #
+ # For background, see
+ # https://metacpan.org/pod/release/ETHER/Try-Tiny-0.24/lib/Try/Tiny.pm
+ do
+ {
+ local $@;
+ eval {
+ my @ipcrun_opts = (\@psql_params, '<', \$sql);
+ push @ipcrun_opts, '>', $stdout if defined $stdout;
+ push @ipcrun_opts, '2>', $stderr if defined $stderr;
+ push @ipcrun_opts, $timeout if defined $timeout;
+
+ IPC::Run::run @ipcrun_opts;
+ $ret = $?;
+ };
+ my $exc_save = $@;
+ if ($exc_save)
+ {
+
+ # IPC::Run::run threw an exception. re-throw unless it's a
+ # timeout, which we'll handle by testing is_expired
+ die $exc_save
+ if (blessed($exc_save)
+ || $exc_save !~ /^\Q$timeout_exception\E/);
+
+ $ret = undef;
+
+ die "Got timeout exception '$exc_save' but timer not expired?!"
+ unless $timeout->is_expired;
+
+ if (defined($params{timed_out}))
+ {
+ ${ $params{timed_out} } = 1;
+ }
+ else
+ {
+ die "psql timed out: stderr: '$$stderr'\n"
+ . "while running '@psql_params'";
+ }
+ }
+ };
+
+ if (defined $$stdout)
+ {
+ chomp $$stdout;
+ }
+
+ if (defined $$stderr)
+ {
+ chomp $$stderr;
+ }
+
+ # See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
+ # We don't use IPC::Run::Simple to limit dependencies.
+ #
+ # We always die on signal.
+ my $core = $ret & 128 ? " (core dumped)" : "";
+ die "psql exited with signal "
+ . ($ret & 127)
+ . "$core: '$$stderr' while running '@psql_params'"
+ if $ret & 127;
+ $ret = $ret >> 8;
+
+ if ($ret && $params{on_error_die})
+ {
+ die "psql error: stderr: '$$stderr'\nwhile running '@psql_params'"
+ if $ret == 1;
+ die "connection error: '$$stderr'\nwhile running '@psql_params'"
+ if $ret == 2;
+ die
+ "error running SQL: '$$stderr'\nwhile running '@psql_params' with sql '$sql'"
+ if $ret == 3;
+ die "psql returns $ret: '$$stderr'\nwhile running '@psql_params'";
+ }
+
+ if (wantarray)
+ {
+ return ($ret, $$stdout, $$stderr);
+ }
+ else
+ {
+ return $ret;
+ }
+}
+
=pod
=item $node->pgbench($opts, $stat, $out, $err, $name, $files, @args)
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index 4f7ef1d12b7..7e1df68863b 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -161,7 +161,9 @@ ORDER BY 1;
pg_compression
pg_depend
pg_extprotocol
+ pg_password_history
pg_proc_callback
+ pg_profile
pg_resgroup
pg_resgroupcapability
pg_resourcetype
@@ -171,7 +173,7 @@ ORDER BY 1;
pg_stat_last_operation
pg_stat_last_shoperation
pg_type_encoding
-(23 rows)
+(25 rows)
-- system catalog unique indexes not wrapped in a constraint
-- (There should be none.)
diff --git a/src/test/regress/expected/misc_sanity_external_fts.out b/src/test/regress/expected/misc_sanity_external_fts.out
index 06af9c3fed1..c5115a88617 100644
--- a/src/test/regress/expected/misc_sanity_external_fts.out
+++ b/src/test/regress/expected/misc_sanity_external_fts.out
@@ -158,7 +158,9 @@ ORDER BY 1;
pg_compression
pg_depend
pg_extprotocol
+ pg_password_history
pg_proc_callback
+ pg_profile
pg_resgroup
pg_resgroupcapability
pg_resourcetype
@@ -168,7 +170,7 @@ ORDER BY 1;
pg_stat_last_operation
pg_stat_last_shoperation
pg_type_encoding
-(22 rows)
+(24 rows)
-- system catalog unique indexes not wrapped in a constraint
-- (There should be none.)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index bf4635ee996..fbac24e2306 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -130,10 +130,10 @@ WHERE p1.oid < p2.oid AND
p1.pronargs != p2.pronargs);
oid | proname | oid | proname
------+-------------------+------+---------------------
- 6212 | int2_matrix_accum | 6214 | int8_matrix_accum
6212 | int2_matrix_accum | 6215 | float8_matrix_accum
- 6213 | int4_matrix_accum | 6214 | int8_matrix_accum
+ 6212 | int2_matrix_accum | 6214 | int8_matrix_accum
6213 | int4_matrix_accum | 6215 | float8_matrix_accum
+ 6213 | int4_matrix_accum | 6214 | int8_matrix_accum
(4 rows)
-- Look for uses of different type OIDs in the argument/result type fields
@@ -402,11 +402,12 @@ WHERE 'cstring'::regtype = ANY (p1.proargtypes)
AND NOT EXISTS(SELECT 1 FROM pg_conversion WHERE conproc = p1.oid)
AND p1.oid != 'shell_in(cstring)'::regprocedure
ORDER BY 1;
- oid | proname
-------+--------------
+ oid | proname
+------+-----------------
2293 | cstring_out
2501 | cstring_send
-(2 rows)
+ 7060 | get_role_status
+(3 rows)
-- Likewise, look for functions that return cstring and aren't datatype output
-- functions nor typmod output functions.
@@ -419,11 +420,12 @@ WHERE p1.prorettype = 'cstring'::regtype
AND NOT EXISTS(SELECT 1 FROM pg_type WHERE typmodout = p1.oid)
AND p1.oid != 'shell_out(void)'::regprocedure
ORDER BY 1;
- oid | proname
-------+--------------
+ oid | proname
+------+-----------------
2292 | cstring_in
2500 | cstring_recv
-(2 rows)
+ 7060 | get_role_status
+(3 rows)
-- Check for length inconsistencies between the various argument-info arrays.
SELECT p1.oid, p1.proname
diff --git a/src/test/regress/expected/profile.out b/src/test/regress/expected/profile.out
new file mode 100644
index 00000000000..9541ced3c6d
--- /dev/null
+++ b/src/test/regress/expected/profile.out
@@ -0,0 +1,689 @@
+--
+-- Test for PROFILE
+--
+-- Display pg_stas_activity to check the login monitor process
+SELECT COUNT(*) FROM pg_stat_activity;
+ count
+-------
+ 8
+(1 row)
+
+-- Display pg_authid, pg_roles, pg_profile and pg_password_history catalog
+\d+ pg_authid;
+ Table "pg_catalog.pg_authid"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+-------------------+--------------------------+-----------+----------+---------+----------+--------------+-------------
+ oid | oid | | not null | | plain | |
+ rolname | name | | not null | | plain | |
+ rolsuper | boolean | | not null | | plain | |
+ rolinherit | boolean | | not null | | plain | |
+ rolcreaterole | boolean | | not null | | plain | |
+ rolcreatedb | boolean | | not null | | plain | |
+ rolcanlogin | boolean | | not null | | plain | |
+ rolreplication | boolean | | not null | | plain | |
+ rolbypassrls | boolean | | not null | | plain | |
+ rolconnlimit | integer | | not null | | plain | |
+ rolenableprofile | boolean | | not null | | plain | |
+ rolpassword | text | C | | | extended | |
+ rolvaliduntil | timestamp with time zone | | | | plain | |
+ rolprofile | oid | | not null | | plain | |
+ rolaccountstatus | smallint | | not null | | plain | |
+ rolfailedlogins | integer | | not null | | plain | |
+ rolpasswordsetat | timestamp with time zone | | | | plain | |
+ rollockdate | timestamp with time zone | | | | plain | |
+ rolpasswordexpire | timestamp with time zone | | | | plain | |
+ rolresqueue | oid | | | | plain | |
+ rolcreaterextgpfd | boolean | | | | plain | |
+ rolcreaterexthttp | boolean | | | | plain | |
+ rolcreatewextgpfd | boolean | | | | plain | |
+ rolresgroup | oid | | | | plain | |
+Indexes:
+ "pg_authid_oid_index" PRIMARY KEY, btree (oid), tablespace "pg_global"
+ "pg_authid_rolname_index" UNIQUE CONSTRAINT, btree (rolname), tablespace "pg_global"
+ "pg_authid_rolprofile_index" btree (rolprofile), tablespace "pg_global"
+ "pg_authid_rolresgroup_index" btree (rolresgroup), tablespace "pg_global"
+ "pg_authid_rolresqueue_index" btree (rolresqueue), tablespace "pg_global"
+Tablespace: "pg_global"
+
+\d+ pg_roles;
+ View "pg_catalog.pg_roles"
+ Column | Type | Collation | Nullable | Default | Storage | Description
+-------------------+--------------------------+-----------+----------+---------+----------+-------------
+ rolname | name | | | | plain |
+ rolsuper | boolean | | | | plain |
+ rolinherit | boolean | | | | plain |
+ rolcreaterole | boolean | | | | plain |
+ rolcreatedb | boolean | | | | plain |
+ rolcanlogin | boolean | | | | plain |
+ rolreplication | boolean | | | | plain |
+ rolconnlimit | integer | | | | plain |
+ rolenableprofile | boolean | | | | plain |
+ rolprofile | name | | | | plain |
+ rolaccountstatus | smallint | | | | plain |
+ rolfailedlogins | integer | | | | plain |
+ rolpassword | text | | | | extended |
+ rolvaliduntil | timestamp with time zone | | | | plain |
+ rollockdate | timestamp with time zone | | | | plain |
+ rolpasswordexpire | timestamp with time zone | | | | plain |
+ rolbypassrls | boolean | | | | plain |
+ rolconfig | text[] | C | | | extended |
+ rolresqueue | oid | | | | plain |
+ oid | oid | | | | plain |
+ rolcreaterextgpfd | boolean | | | | plain |
+ rolcreaterexthttp | boolean | | | | plain |
+ rolcreatewextgpfd | boolean | | | | plain |
+ rolresgroup | oid | | | | plain |
+View definition:
+ SELECT pg_authid.rolname,
+ pg_authid.rolsuper,
+ pg_authid.rolinherit,
+ pg_authid.rolcreaterole,
+ pg_authid.rolcreatedb,
+ pg_authid.rolcanlogin,
+ pg_authid.rolreplication,
+ pg_authid.rolconnlimit,
+ pg_authid.rolenableprofile,
+ pg_profile.prfname AS rolprofile,
+ pg_authid.rolaccountstatus,
+ pg_authid.rolfailedlogins,
+ '********'::text AS rolpassword,
+ pg_authid.rolvaliduntil,
+ pg_authid.rollockdate,
+ pg_authid.rolpasswordexpire,
+ pg_authid.rolbypassrls,
+ s.setconfig AS rolconfig,
+ pg_authid.rolresqueue,
+ pg_authid.oid,
+ pg_authid.rolcreaterextgpfd,
+ pg_authid.rolcreaterexthttp,
+ pg_authid.rolcreatewextgpfd,
+ pg_authid.rolresgroup
+ FROM pg_profile,
+ pg_authid
+ LEFT JOIN pg_db_role_setting s ON pg_authid.oid = s.setrole AND s.setdatabase = 0::oid
+ WHERE pg_profile.oid = pg_authid.rolprofile;
+
+\d+ pg_profile;
+ Table "pg_catalog.pg_profile"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+-------------------------+---------+-----------+----------+---------+---------+--------------+-------------
+ oid | oid | | not null | | plain | |
+ prfname | name | | not null | | plain | |
+ prffailedloginattempts | integer | | not null | | plain | |
+ prfpasswordlocktime | integer | | not null | | plain | |
+ prfpasswordlifetime | integer | | not null | | plain | |
+ prfpasswordgracetime | integer | | not null | | plain | |
+ prfpasswordreusetime | integer | | not null | | plain | |
+ prfpasswordreusemax | integer | | not null | | plain | |
+ prfpasswordallowhashed | integer | | not null | | plain | |
+ prfpasswordverifyfuncdb | oid | | | | plain | |
+ prfpasswordverifyfunc | oid | | | | plain | |
+Indexes:
+ "profile_name_index" UNIQUE CONSTRAINT, btree (prfname), tablespace "pg_global"
+ "profile_oid_index" UNIQUE CONSTRAINT, btree (oid), tablespace "pg_global"
+ "profile_password_verify_function_index" btree (prfpasswordverifyfuncdb, prfpasswordverifyfunc), tablespace "pg_global"
+Tablespace: "pg_global"
+
+\d+ pg_password_history;
+ Table "pg_catalog.pg_password_history"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+-----------------------+--------------------------+-----------+----------+---------+----------+--------------+-------------
+ passhistroleid | oid | | not null | | plain | |
+ passhistpasswordsetat | timestamp with time zone | | not null | | plain | |
+ passhistpassword | text | C | not null | | extended | |
+Indexes:
+ "pg_password_history_role_password_index" UNIQUE CONSTRAINT, btree (passhistroleid, passhistpassword), tablespace "pg_global"
+ "pg_password_history_role_passwordsetat_index" btree (passhistroleid, passhistpasswordsetat), tablespace "pg_global"
+Tablespace: "pg_global"
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------+---------+------------------+-----------------+------------------
+(0 rows)
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ pg_default | -2 | -2 | -2
+(1 row)
+
+-- Test CREATE PROFILE
+CREATE PROFILE myprofile1;
+CREATE PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS -1 PASSWORD_LOCK_TIME -2;
+CREATE PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 1;
+CREATE PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 9999 PASSWORD_REUSE_MAX 3;
+CREATE PROFILE myprofile4; -- Failed for myprofile4 already exists
+ERROR: profile "myprofile4" already exists
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ pg_default | -2 | -2 | -2
+ myprofile1 | -1 | -1 | -1
+ myprofile2 | -1 | -2 | -1
+ myprofile3 | 4 | 1 | -1
+ myprofile4 | 5 | 9999 | 3
+(5 rows)
+
+-- Failed for invalid parameters
+CREATE PROFILE myprofile5 LIMIT FAILED_LOGIN_ATTEMPTS -3;
+ERROR: invalid failed login attempts: -3
+CREATE PROFILE myprofile6 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME -5;
+ERROR: invalid password lock time: -5
+CREATE PROFILE myprofile7 LIMIT FAILED_LOGIN_ATTEMPTS -2 PASSWORD_LOCK_TIME -1 PASSWORD_REUSE_MAX -9999;
+ERROR: invalid password reuse max: -9999
+CREATE PROFILE myprofile8 LIMIT FAILED_LOGIN_ATTEMPTS 10000;
+ERROR: invalid failed login attempts: 10000
+CREATE PROFILE myprofile9 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 10000;
+ERROR: invalid password lock time: 10000
+CREATE PROFILE myprofile10 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME -1 PASSWORD_REUSE_MAX 99999;
+ERROR: invalid password reuse max: 99999
+CREATE PROFILE myprofile11 LIMIT FAILED_LOGIN_ATTEMPTS 9999 FAILED_LOGIN_ATTEMPTS 2;
+ERROR: conflicting or redundant options
+LINE 1: ...FILE myprofile11 LIMIT FAILED_LOGIN_ATTEMPTS 9999 FAILED_LOG...
+ ^
+CREATE PROFILE myprofile12 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 4 PASSWORD_LOCK_TIME 3;
+ERROR: conflicting or redundant options
+LINE 1: ...IMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 4 PASSWORD_L...
+ ^
+CREATE PROFILE myprofile13 LIMIT FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 3 PASSWORD_REUSE_MAX 2 PASSWORD_REUSE_MAX 2;
+ERROR: conflicting or redundant options
+LINE 1: ...EMPTS 4 PASSWORD_LOCK_TIME 3 PASSWORD_REUSE_MAX 2 PASSWORD_R...
+ ^
+-- Failed for syntax error
+CREATE PROFILE myprofile14 FAILED_LOGIN_ATTEMPTS 1;
+ERROR: syntax error at or near "FAILED_LOGIN_ATTEMPTS"
+LINE 1: CREATE PROFILE myprofile14 FAILED_LOGIN_ATTEMPTS 1;
+ ^
+CREATE PROFILE myprofile15 PASSWORD_LOCK_TIME -2;
+ERROR: syntax error at or near "PASSWORD_LOCK_TIME"
+LINE 1: CREATE PROFILE myprofile15 PASSWORD_LOCK_TIME -2;
+ ^
+CREATE PROFILE myprofile16 PASSWORD_RESUE_MAX -1;
+ERROR: syntax error at or near "PASSWORD_RESUE_MAX"
+LINE 1: CREATE PROFILE myprofile16 PASSWORD_RESUE_MAX -1;
+ ^
+CREATE PROFILE myprofile17 FAILED_LOGIN_ATTEMPTS 0;
+ERROR: syntax error at or near "FAILED_LOGIN_ATTEMPTS"
+LINE 1: CREATE PROFILE myprofile17 FAILED_LOGIN_ATTEMPTS 0;
+ ^
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ pg_default | -2 | -2 | -2
+ myprofile1 | -1 | -1 | -1
+ myprofile2 | -1 | -2 | -1
+ myprofile3 | 4 | 1 | -1
+ myprofile4 | 5 | 9999 | 3
+(5 rows)
+
+-- Test CREATE USER ... PROFILE
+CREATE USER profile_user1 PROFILE test; -- failed
+NOTICE: resource queue required -- using default resource queue "pg_default"
+ERROR: profile "test" does not exist
+CREATE USER profile_user1 PROFILE pg_default;
+NOTICE: resource queue required -- using default resource queue "pg_default"
+CREATE USER profile_user2 PASSWORD 'a_nice_long_password_123';
+NOTICE: resource queue required -- using default resource queue "pg_default"
+CREATE USER profile_user3 PASSWORD 'a_nice_long_password_456' PROFILE myprofile3;
+NOTICE: resource queue required -- using default resource queue "pg_default"
+CREATE USER profile_user4 ACCOUNT LOCK PROFILE myprofile4;
+NOTICE: resource queue required -- using default resource queue "pg_default"
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user1 | pg_default | 0 | 0 | f
+ profile_user2 | pg_default | 0 | 0 | f
+ profile_user3 | myprofile3 | 0 | 0 | f
+ profile_user4 | myprofile4 | 2 | 0 | f
+(4 rows)
+
+-- Test CREATE USER ... ENABLE/DISABLE PROFILE
+CREATE USER profile_user5 ENABLE PROFILE PROFILE pg_default;
+NOTICE: resource queue required -- using default resource queue "pg_default"
+CREATE USER profile_user6 ENABLE PROFILE PROFILE; -- failed
+ERROR: syntax error at or near ";"
+LINE 1: CREATE USER profile_user6 ENABLE PROFILE PROFILE;
+ ^
+CREATE USER profile_user7 DISABLE PROFILE PROFILE pg_default;
+NOTICE: resource queue required -- using default resource queue "pg_default"
+CREATE USER profile_user8 DISABLE PROFILE PROFILE; -- failed
+ERROR: syntax error at or near ";"
+LINE 1: CREATE USER profile_user8 DISABLE PROFILE PROFILE;
+ ^
+CREATE USER profile_user9 SUPERUSER;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user1 | pg_default | 0 | 0 | f
+ profile_user2 | pg_default | 0 | 0 | f
+ profile_user3 | myprofile3 | 0 | 0 | f
+ profile_user4 | myprofile4 | 2 | 0 | f
+ profile_user5 | pg_default | 0 | 0 | t
+ profile_user7 | pg_default | 0 | 0 | f
+ profile_user9 | pg_default | 0 | 0 | f
+(7 rows)
+
+-- Test ALTER PROFILE
+ALTER USER profile_user1 PROFILE myprofile1;
+ALTER USER profile_user2 PROFILE myprofile2;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user3 | myprofile3 | 0 | 0 | f
+ profile_user4 | myprofile4 | 2 | 0 | f
+ profile_user5 | pg_default | 0 | 0 | t
+ profile_user7 | pg_default | 0 | 0 | f
+ profile_user9 | pg_default | 0 | 0 | f
+ profile_user1 | myprofile1 | 0 | 0 | f
+ profile_user2 | myprofile2 | 0 | 0 | f
+(7 rows)
+
+ALTER USER profile_user10 PROFILE myprofile2; -- failed
+ERROR: role "profile_user10" does not exist
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname = 'profile_user9';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user9 | pg_default | 0 | 0 | f
+(1 row)
+
+ALTER USER profile_user9 PROFILE pg_default;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname = 'profile_user9';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user9 | pg_default | 0 | 0 | f
+(1 row)
+
+ALTER PROFILE myprofile1 LIMIT; -- OK
+ALTER PROFILE myprofile1 LIMIT PASSWORD_LOCK_TIME 1;
+ALTER PROFILE myprofile2 PASSWORD_LOCK_TIME 3; -- syntax error
+ERROR: syntax error at or near "PASSWORD_LOCK_TIME"
+LINE 1: ALTER PROFILE myprofile2 PASSWORD_LOCK_TIME 3;
+ ^
+ALTER PROFILE myprofile2 LIMIT PASSWORD_LOCK_TIME 3; -- OK
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_REUSE_MAX 2;
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_REUSE_MAX 2; -- ALTER PROFILE the same values
+ALTER PROFILE myprofile4 LIMIT PASSWORD_LOCK_TIME 10 PASSWORD_REUSE_MAX -1;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 9999 PASSWORD_REUSE_MAX 9999;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 0 PASSWORD_REUSE_MAX 0;
+ALTER PROFILE myprofile5 LIMIT FAILED_LOGIN_ATTEMPTS 3;
+ERROR: profile "myprofile5" does not exist
+ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS 2 PASSWORD_LOCK_TIME 1 PASSWORD_REUSE_MAX 3;
+ALTER PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 1 FAILED_LOGIN_ATTEMPTS 2;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile2 LIMIT PASSWORD_LOCK_TIME 2 PASSWORD_LOCK_TIME 3;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile3 LIMIT PASSWORD_REUSE_MAX -1 PASSWORD_REUSE_MAX -2;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 1 FAILED_LOGIN_ATTEMPTS 2;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS -2 PASSWORD_LOCK_TIME 2 PASSWORD_LOCK_TIME -2;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME -1 PASSWORD_REUSE_MAX 2 PASSWORD_REUSE_MAX 2;
+ERROR: conflicting or redundant options
+-- Failed for pg_default value can not be -1
+ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS -1;
+ERROR: can't set failed login attempts to -1(DEFAULT) of pg_default
+ALTER PROFILE pg_default LIMIT PASSWORD_LOCK_TIME -1;
+ERROR: can't set password lock time to -1(DEFAULT) of pg_default
+ALTER PROFILE pg_default LIMIT PASSWORD_REUSE_MAX -1;
+ERROR: can't set password reuse max to -1(DEFAULT) of pg_default
+ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS 2 PASSWORD_LOCK_TIME 2 PASSWORD_REUSE_MAX -1;
+ERROR: can't set password reuse max to -1(DEFAULT) of pg_default
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ myprofile1 | -1 | 1 | -1
+ myprofile2 | -1 | 3 | -1
+ myprofile3 | 3 | 1 | 2
+ myprofile4 | 4 | 0 | 0
+ pg_default | 2 | 1 | 3
+(5 rows)
+
+-- Test ALTER PROFILE ... RENAME TO
+ALTER PROFILE pg_default RENAME TO anyname; -- failed for pg_default profile can't be renamed
+ERROR: can't RENAME "pg_default" profile
+ALTER PROFILE myprofile1 RENAME TO myprofile2; -- failed for myprofile2 already exists
+ERROR: profile "myprofile2" already exists
+ALTER PROFILE myprofile1 RENAME TO pg_default; -- failed for pg_default already exists
+ERROR: profile "pg_default" already exists
+ALTER PROFILE myprofile1 RENAME TO tempname; -- OK
+ALTER PROFILE myprofile2 RENAME TO myprofile1; -- OK
+ALTER PROFILE myprofile5 RENAME TO tempname2; -- failed for myprofile5 doesn't exists
+ERROR: profile "myprofile5" does not exist
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ myprofile3 | 3 | 1 | 2
+ myprofile4 | 4 | 0 | 0
+ pg_default | 2 | 1 | 3
+ tempname | -1 | 1 | -1
+ myprofile1 | -1 | 3 | -1
+(5 rows)
+
+ALTER PROFILE tempname RENAME TO myprofile2; -- OK
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ myprofile3 | 3 | 1 | 2
+ myprofile4 | 4 | 0 | 0
+ pg_default | 2 | 1 | 3
+ myprofile1 | -1 | 3 | -1
+ myprofile2 | -1 | 1 | -1
+(5 rows)
+
+-- Failed for invalid parameters
+ALTER PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 10000;
+ERROR: invalid failed login attempts: 10000
+ALTER PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 10000;
+ERROR: invalid password lock time: 10000
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 9999 PASSWORD_REUSE_MAX 10000;
+ERROR: invalid password reuse max: 10000
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 0;
+ERROR: invalid failed login attempts: 0
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 0 PASSWORD_LOCK_TIME 0 PASSWORD_REUSE_MAX 3;
+ERROR: invalid failed login attempts: 0
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 9999 FAILED_LOGIN_ATTEMPTS 3;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 1 PASSWORD_LOCK_TIME 2;
+ERROR: conflicting or redundant options
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3 PASSWORD_REUSE_MAX 4 PASSWORD_REUSE_MAX 3;
+ERROR: conflicting or redundant options
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ myprofile3 | 3 | 1 | 2
+ myprofile4 | 4 | 0 | 0
+ pg_default | 2 | 1 | 3
+ myprofile1 | -1 | 3 | -1
+ myprofile2 | -1 | 1 | -1
+(5 rows)
+
+-- Failed for syntax error
+ALTER PROFILE myprofile1 FAILED_LOGIN_ATTEMPTS 5;
+ERROR: syntax error at or near "FAILED_LOGIN_ATTEMPTS"
+LINE 1: ALTER PROFILE myprofile1 FAILED_LOGIN_ATTEMPTS 5;
+ ^
+ALTER PROFILE myprofile2 PASSWORD_LOCK_TIME -2;
+ERROR: syntax error at or near "PASSWORD_LOCK_TIME"
+LINE 1: ALTER PROFILE myprofile2 PASSWORD_LOCK_TIME -2;
+ ^
+ALTER PROFILE myprofile3 PASSWORD_RESUE_MAX -1;
+ERROR: syntax error at or near "PASSWORD_RESUE_MAX"
+LINE 1: ALTER PROFILE myprofile3 PASSWORD_RESUE_MAX -1;
+ ^
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ myprofile3 | 3 | 1 | 2
+ myprofile4 | 4 | 0 | 0
+ pg_default | 2 | 1 | 3
+ myprofile1 | -1 | 3 | -1
+ myprofile2 | -1 | 1 | -1
+(5 rows)
+
+DELETE FROM pg_profile; -- failed for catalog can't be deleted
+ERROR: permission denied: "pg_profile" is a system catalog
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ myprofile3 | 3 | 1 | 2
+ myprofile4 | 4 | 0 | 0
+ pg_default | 2 | 1 | 3
+ myprofile1 | -1 | 3 | -1
+ myprofile2 | -1 | 1 | -1
+(5 rows)
+
+-- Test ALTER USER ... PROFILE
+ALTER USER profile_user2 PROFILE myprofile3;
+ALTER USER profile_user3 PROFILE myprofile2;
+ALTER USER profile_user1 PROFILE myprofile1;
+ALTER USER profile_user4 PROFILE myprofile4;
+ALTER USER profile_user9 PROFILE myprofile3;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user5 | pg_default | 0 | 0 | t
+ profile_user7 | pg_default | 0 | 0 | f
+ profile_user2 | myprofile3 | 0 | 0 | f
+ profile_user3 | myprofile2 | 0 | 0 | f
+ profile_user1 | myprofile1 | 0 | 0 | f
+ profile_user4 | myprofile4 | 2 | 0 | f
+ profile_user9 | myprofile3 | 0 | 0 | f
+(7 rows)
+
+-- Test ALTER USER ... ENABLE/DISABLE PROFILE
+ALTER USER profile_user5 DISABLE PROFILE PROFILE myprofile3;
+ALTER USER profile_user5 ENABLE PROFILE PROFILE;
+ERROR: syntax error at or near ";"
+LINE 1: ALTER USER profile_user5 ENABLE PROFILE PROFILE;
+ ^
+ALTER USER profile_user7 ENABLE PROFILE PROFILE myprofile4;
+ALTER USER profile_user7 DISABLE PROFILE PROFILE;
+ERROR: syntax error at or near ";"
+LINE 1: ALTER USER profile_user7 DISABLE PROFILE PROFILE;
+ ^
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user2 | myprofile3 | 0 | 0 | f
+ profile_user3 | myprofile2 | 0 | 0 | f
+ profile_user1 | myprofile1 | 0 | 0 | f
+ profile_user4 | myprofile4 | 2 | 0 | f
+ profile_user9 | myprofile3 | 0 | 0 | f
+ profile_user5 | myprofile3 | 0 | 0 | f
+ profile_user7 | myprofile4 | 0 | 0 | t
+(7 rows)
+
+-- Test ALTER USER ... PASSWORD
+ALTER USER profile_user1 PASSWORD 'test';
+ALTER USER profile_user1 PASSWORD 'a_nice_long_password_123';
+ALTER USER profile_user1 PASSWORD 'a_new_password';
+ALTER USER profile_user1 PASSWORD 'test';
+ERROR: The new password should not be the same with latest 3 history password
+ALTER USER profile_user1 PASSWORD 'a_nice_long_password_123';
+ERROR: The new password should not be the same with latest 3 history password
+ALTER USER profile_user1 PASSWORD 'a_new_password';
+ERROR: The new password should not be the same with latest 3 history password
+ALTER USER profile_user1 PASSWORD 'ABCD';
+ALTER USER profile_user1 PASSWORD 'test';
+ALTER PROFILE pg_default LIMIT PASSWORD_REUSE_MAX 4;
+ALTER USER profile_user1 PASSWORD 'a_nice_long_password_123';
+ERROR: The new password should not be the same with latest 4 history password
+ALTER USER profile_user2 PASSWORD 'test2';
+ALTER USER profile_user2 PASSWORD 'a_bad_password';
+ALTER USER profile_user2 PASSWORD 'test2';
+ERROR: The new password should not be the same with latest 2 history password
+ALTER USER profile_user2 PASSWORD 'a_bad_password';
+ERROR: The new password should not be the same with latest 2 history password
+ALTER USER profile_user2 PASSWORD 'a_nice_password';
+ALTER USER profile_user2 PASSWORD 'a_bad_password';
+ERROR: The new password should not be the same with latest 2 history password
+ALTER USER profile_user2 PASSWORD 'test2';
+ALTER PROFILE myprofile3 LIMIT PASSWORD_REUSE_MAX 1;
+ALTER USER profile_user2 PASSWORD 'a_bad_password'; -- OK
+ALTER USER profile_user2 PASSWORD 'test2'; -- OK
+ALTER USER profile_user4 PASSWORD 'test3'; -- failed
+ERROR: can't alter user password for profile's password_reuse_max is zero.
+DELETE FROM pg_password_history; -- failed for catalog can't be deleted
+ERROR: permission denied: "pg_password_history" is a system catalog
+-- Test ALTER USER ... ACCOUNT LOCK/UNLOCK
+ALTER USER profile_user1 ACCOUNT LOCK;
+ALTER USER profile_user2 ACCOUNT UNLOCK;
+ALTER USER profile_user3 ACCOUNT LOCK;
+ALTER USER profile_user4 ACCOUNT UNLOCK;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user9 | myprofile3 | 0 | 0 | f
+ profile_user5 | myprofile3 | 0 | 0 | f
+ profile_user7 | myprofile4 | 0 | 0 | t
+ profile_user1 | myprofile1 | 2 | 0 | f
+ profile_user2 | myprofile3 | 0 | 0 | f
+ profile_user3 | myprofile2 | 2 | 0 | f
+ profile_user4 | myprofile4 | 0 | 0 | f
+(7 rows)
+
+-- Test for get_role_status()
+SELECT get_role_status('profile_user1');
+ get_role_status
+-----------------
+ LOCKED
+(1 row)
+
+SELECT get_role_status('profile_user2');
+ get_role_status
+-----------------
+ OPEN
+(1 row)
+
+SELECT get_role_status('profile_user3');
+ get_role_status
+-----------------
+ LOCKED
+(1 row)
+
+SELECT get_role_status('profile_user4');
+ get_role_status
+-----------------
+ OPEN
+(1 row)
+
+SELECT get_role_status('profile_user5'); -- failed for user does not exist
+ get_role_status
+-----------------
+ OPEN
+(1 row)
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user9 | myprofile3 | 0 | 0 | f
+ profile_user5 | myprofile3 | 0 | 0 | f
+ profile_user7 | myprofile4 | 0 | 0 | t
+ profile_user1 | myprofile1 | 2 | 0 | f
+ profile_user2 | myprofile3 | 0 | 0 | f
+ profile_user3 | myprofile2 | 2 | 0 | f
+ profile_user4 | myprofile4 | 0 | 0 | f
+(7 rows)
+
+-- Test update pg_password_history
+UPDATE pg_password_history SET passhistpassword = 'random'; -- permission denied
+ERROR: permission denied: "pg_password_history" is a system catalog
+-- Test DROP PROFILE
+-- Failed for profile is using by user
+DROP PROFILE myprofile1;
+ERROR: profile "myprofile1" cannot be dropped because some objects depend on it
+DETAIL: profile of role profile_user1
+DROP PROFILE myprofile2;
+ERROR: profile "myprofile2" cannot be dropped because some objects depend on it
+DETAIL: profile of role profile_user3
+DROP PROFILE myprofile3;
+ERROR: profile "myprofile3" cannot be dropped because some objects depend on it
+DETAIL: profile of role profile_user2
+profile of role profile_user5
+profile of role profile_user9
+DROP PROFILE myprofile4;
+ERROR: profile "myprofile4" cannot be dropped because some objects depend on it
+DETAIL: profile of role profile_user4
+profile of role profile_user7
+DROP PROFILE pg_default; -- failed, can't drop pg_default profile
+ERROR: Disallow to drop default profile
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user9 | myprofile3 | 0 | 0 | f
+ profile_user5 | myprofile3 | 0 | 0 | f
+ profile_user7 | myprofile4 | 0 | 0 | t
+ profile_user1 | myprofile1 | 2 | 0 | f
+ profile_user2 | myprofile3 | 0 | 0 | f
+ profile_user3 | myprofile2 | 2 | 0 | f
+ profile_user4 | myprofile4 | 0 | 0 | f
+(7 rows)
+
+-- cleanup
+DROP USER profile_user1;
+DROP USER profile_user2;
+DROP USER profile_user3;
+DROP USER profile_user4;
+DROP USER profile_user5;
+DROP USER profile_user7;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+ rolname | prfname | rolaccountstatus | rolfailedlogins | rolenableprofile
+---------------+------------+------------------+-----------------+------------------
+ profile_user9 | myprofile3 | 0 | 0 | f
+(1 row)
+
+-- Successful
+DROP PROFILE myprofile1, myprofile2;
+DROP PROFILE myprofile1; -- failed
+ERROR: profile "myprofile1" does not exist
+DROP PROFILE IF EXISTS myprofile2; -- OK
+NOTICE: profile "myprofile2" does not exist
+DROP PROFILE myprofile3; -- failed
+ERROR: profile "myprofile3" cannot be dropped because some objects depend on it
+DETAIL: profile of role profile_user9
+DROP PROFILE myprofile4, pg_default; -- failed
+ERROR: Disallow to drop default profile
+DROP PROFILE IF EXISTS myprofile4; -- OK
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ pg_default | 2 | 1 | 4
+ myprofile3 | 3 | 1 | 1
+(2 rows)
+
+DROP USER profile_user9;
+DROP PROFILE myprofile3; -- OK
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+ prfname | prffailedloginattempts | prfpasswordlocktime | prfpasswordreusemax
+------------+------------------------+---------------------+---------------------
+ pg_default | 2 | 1 | 4
+(1 row)
+
diff --git a/src/test/regress/expected/rangefuncs_cdb.out b/src/test/regress/expected/rangefuncs_cdb.out
index 296c7d22e32..31956c8d8ca 100644
--- a/src/test/regress/expected/rangefuncs_cdb.out
+++ b/src/test/regress/expected/rangefuncs_cdb.out
@@ -23,10 +23,11 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' and name != 'ena
enable_partition_pruning | on
enable_partitionwise_aggregate | off
enable_partitionwise_join | off
+ enable_password_profile | on
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(21 rows)
+(22 rows)
-- start_ignore
create schema rangefuncs_cdb;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d79ae943abc..e70e4f17fa8 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -146,9 +146,11 @@ pg_opclass|t
pg_operator|t
pg_opfamily|t
pg_partitioned_table|t
+pg_password_history|t
pg_policy|t
pg_proc|t
pg_proc_callback|t
+pg_profile|t
pg_publication|t
pg_publication_rel|t
pg_range|t
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 22a68a9e289..e1766efe7df 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -121,10 +121,11 @@ select name, setting from pg_settings where name like 'enable%' and name != 'ena
enable_partition_pruning | on
enable_partitionwise_aggregate | off
enable_partitionwise_join | off
+ enable_password_profile | on
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(21 rows)
+(22 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
diff --git a/src/test/regress/greenplum_schedule b/src/test/regress/greenplum_schedule
index c5bd9fd5b54..9f5d4d4bf71 100755
--- a/src/test/regress/greenplum_schedule
+++ b/src/test/regress/greenplum_schedule
@@ -298,4 +298,7 @@ test: run_utility_gpexpand_phase1
# check correct error message when create extension error on segment
test: create_extension_fail
+# check profile feature
+test: profile
+
# end of tests
diff --git a/src/test/regress/sql/profile.sql b/src/test/regress/sql/profile.sql
new file mode 100644
index 00000000000..18435149afe
--- /dev/null
+++ b/src/test/regress/sql/profile.sql
@@ -0,0 +1,286 @@
+--
+-- Test for PROFILE
+--
+
+-- Display pg_stas_activity to check the login monitor process
+SELECT COUNT(*) FROM pg_stat_activity;
+
+-- Display pg_authid, pg_roles, pg_profile and pg_password_history catalog
+\d+ pg_authid;
+\d+ pg_roles;
+\d+ pg_profile;
+\d+ pg_password_history;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Test CREATE PROFILE
+CREATE PROFILE myprofile1;
+CREATE PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS -1 PASSWORD_LOCK_TIME -2;
+CREATE PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 1;
+CREATE PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 9999 PASSWORD_REUSE_MAX 3;
+CREATE PROFILE myprofile4; -- Failed for myprofile4 already exists
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Failed for invalid parameters
+CREATE PROFILE myprofile5 LIMIT FAILED_LOGIN_ATTEMPTS -3;
+CREATE PROFILE myprofile6 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME -5;
+CREATE PROFILE myprofile7 LIMIT FAILED_LOGIN_ATTEMPTS -2 PASSWORD_LOCK_TIME -1 PASSWORD_REUSE_MAX -9999;
+
+CREATE PROFILE myprofile8 LIMIT FAILED_LOGIN_ATTEMPTS 10000;
+CREATE PROFILE myprofile9 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 10000;
+CREATE PROFILE myprofile10 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME -1 PASSWORD_REUSE_MAX 99999;
+
+CREATE PROFILE myprofile11 LIMIT FAILED_LOGIN_ATTEMPTS 9999 FAILED_LOGIN_ATTEMPTS 2;
+CREATE PROFILE myprofile12 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 4 PASSWORD_LOCK_TIME 3;
+CREATE PROFILE myprofile13 LIMIT FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 3 PASSWORD_REUSE_MAX 2 PASSWORD_REUSE_MAX 2;
+
+-- Failed for syntax error
+CREATE PROFILE myprofile14 FAILED_LOGIN_ATTEMPTS 1;
+CREATE PROFILE myprofile15 PASSWORD_LOCK_TIME -2;
+CREATE PROFILE myprofile16 PASSWORD_RESUE_MAX -1;
+CREATE PROFILE myprofile17 FAILED_LOGIN_ATTEMPTS 0;
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Test CREATE USER ... PROFILE
+CREATE USER profile_user1 PROFILE test; -- failed
+CREATE USER profile_user1 PROFILE pg_default;
+CREATE USER profile_user2 PASSWORD 'a_nice_long_password_123';
+CREATE USER profile_user3 PASSWORD 'a_nice_long_password_456' PROFILE myprofile3;
+CREATE USER profile_user4 ACCOUNT LOCK PROFILE myprofile4;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Test CREATE USER ... ENABLE/DISABLE PROFILE
+CREATE USER profile_user5 ENABLE PROFILE PROFILE pg_default;
+CREATE USER profile_user6 ENABLE PROFILE PROFILE; -- failed
+CREATE USER profile_user7 DISABLE PROFILE PROFILE pg_default;
+CREATE USER profile_user8 DISABLE PROFILE PROFILE; -- failed
+CREATE USER profile_user9 SUPERUSER;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Test ALTER PROFILE
+ALTER USER profile_user1 PROFILE myprofile1;
+ALTER USER profile_user2 PROFILE myprofile2;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+ALTER USER profile_user10 PROFILE myprofile2; -- failed
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname = 'profile_user9';
+
+ALTER USER profile_user9 PROFILE pg_default;
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname = 'profile_user9';
+
+ALTER PROFILE myprofile1 LIMIT; -- OK
+ALTER PROFILE myprofile1 LIMIT PASSWORD_LOCK_TIME 1;
+ALTER PROFILE myprofile2 PASSWORD_LOCK_TIME 3; -- syntax error
+ALTER PROFILE myprofile2 LIMIT PASSWORD_LOCK_TIME 3; -- OK
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_REUSE_MAX 2;
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_REUSE_MAX 2; -- ALTER PROFILE the same values
+ALTER PROFILE myprofile4 LIMIT PASSWORD_LOCK_TIME 10 PASSWORD_REUSE_MAX -1;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 9999 PASSWORD_REUSE_MAX 9999;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 4 PASSWORD_LOCK_TIME 0 PASSWORD_REUSE_MAX 0;
+ALTER PROFILE myprofile5 LIMIT FAILED_LOGIN_ATTEMPTS 3;
+ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS 2 PASSWORD_LOCK_TIME 1 PASSWORD_REUSE_MAX 3;
+
+ALTER PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 1 FAILED_LOGIN_ATTEMPTS 2;
+ALTER PROFILE myprofile2 LIMIT PASSWORD_LOCK_TIME 2 PASSWORD_LOCK_TIME 3;
+ALTER PROFILE myprofile3 LIMIT PASSWORD_REUSE_MAX -1 PASSWORD_REUSE_MAX -2;
+ALTER PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 1 FAILED_LOGIN_ATTEMPTS 2;
+ALTER PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS -2 PASSWORD_LOCK_TIME 2 PASSWORD_LOCK_TIME -2;
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME -1 PASSWORD_REUSE_MAX 2 PASSWORD_REUSE_MAX 2;
+
+-- Failed for pg_default value can not be -1
+ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS -1;
+ALTER PROFILE pg_default LIMIT PASSWORD_LOCK_TIME -1;
+ALTER PROFILE pg_default LIMIT PASSWORD_REUSE_MAX -1;
+ALTER PROFILE pg_default LIMIT FAILED_LOGIN_ATTEMPTS 2 PASSWORD_LOCK_TIME 2 PASSWORD_REUSE_MAX -1;
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Test ALTER PROFILE ... RENAME TO
+ALTER PROFILE pg_default RENAME TO anyname; -- failed for pg_default profile can't be renamed
+ALTER PROFILE myprofile1 RENAME TO myprofile2; -- failed for myprofile2 already exists
+ALTER PROFILE myprofile1 RENAME TO pg_default; -- failed for pg_default already exists
+ALTER PROFILE myprofile1 RENAME TO tempname; -- OK
+ALTER PROFILE myprofile2 RENAME TO myprofile1; -- OK
+ALTER PROFILE myprofile5 RENAME TO tempname2; -- failed for myprofile5 doesn't exists
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+ALTER PROFILE tempname RENAME TO myprofile2; -- OK
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Failed for invalid parameters
+ALTER PROFILE myprofile1 LIMIT FAILED_LOGIN_ATTEMPTS 10000;
+ALTER PROFILE myprofile2 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 10000;
+ALTER PROFILE myprofile3 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 9999 PASSWORD_REUSE_MAX 10000;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 0;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 0 PASSWORD_LOCK_TIME 0 PASSWORD_REUSE_MAX 3;
+
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 9999 FAILED_LOGIN_ATTEMPTS 3;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 9999 PASSWORD_LOCK_TIME 1 PASSWORD_LOCK_TIME 2;
+ALTER PROFILE myprofile4 LIMIT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3 PASSWORD_REUSE_MAX 4 PASSWORD_REUSE_MAX 3;
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Failed for syntax error
+ALTER PROFILE myprofile1 FAILED_LOGIN_ATTEMPTS 5;
+ALTER PROFILE myprofile2 PASSWORD_LOCK_TIME -2;
+ALTER PROFILE myprofile3 PASSWORD_RESUE_MAX -1;
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+DELETE FROM pg_profile; -- failed for catalog can't be deleted
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+-- Test ALTER USER ... PROFILE
+ALTER USER profile_user2 PROFILE myprofile3;
+ALTER USER profile_user3 PROFILE myprofile2;
+ALTER USER profile_user1 PROFILE myprofile1;
+ALTER USER profile_user4 PROFILE myprofile4;
+ALTER USER profile_user9 PROFILE myprofile3;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Test ALTER USER ... ENABLE/DISABLE PROFILE
+ALTER USER profile_user5 DISABLE PROFILE PROFILE myprofile3;
+ALTER USER profile_user5 ENABLE PROFILE PROFILE;
+ALTER USER profile_user7 ENABLE PROFILE PROFILE myprofile4;
+ALTER USER profile_user7 DISABLE PROFILE PROFILE;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Test ALTER USER ... PASSWORD
+ALTER USER profile_user1 PASSWORD 'test';
+ALTER USER profile_user1 PASSWORD 'a_nice_long_password_123';
+ALTER USER profile_user1 PASSWORD 'a_new_password';
+ALTER USER profile_user1 PASSWORD 'test';
+ALTER USER profile_user1 PASSWORD 'a_nice_long_password_123';
+ALTER USER profile_user1 PASSWORD 'a_new_password';
+ALTER USER profile_user1 PASSWORD 'ABCD';
+ALTER USER profile_user1 PASSWORD 'test';
+ALTER PROFILE pg_default LIMIT PASSWORD_REUSE_MAX 4;
+ALTER USER profile_user1 PASSWORD 'a_nice_long_password_123';
+
+ALTER USER profile_user2 PASSWORD 'test2';
+ALTER USER profile_user2 PASSWORD 'a_bad_password';
+ALTER USER profile_user2 PASSWORD 'test2';
+ALTER USER profile_user2 PASSWORD 'a_bad_password';
+ALTER USER profile_user2 PASSWORD 'a_nice_password';
+ALTER USER profile_user2 PASSWORD 'a_bad_password';
+ALTER USER profile_user2 PASSWORD 'test2';
+ALTER PROFILE myprofile3 LIMIT PASSWORD_REUSE_MAX 1;
+ALTER USER profile_user2 PASSWORD 'a_bad_password'; -- OK
+ALTER USER profile_user2 PASSWORD 'test2'; -- OK
+
+ALTER USER profile_user4 PASSWORD 'test3'; -- failed
+
+DELETE FROM pg_password_history; -- failed for catalog can't be deleted
+
+-- Test ALTER USER ... ACCOUNT LOCK/UNLOCK
+ALTER USER profile_user1 ACCOUNT LOCK;
+ALTER USER profile_user2 ACCOUNT UNLOCK;
+ALTER USER profile_user3 ACCOUNT LOCK;
+ALTER USER profile_user4 ACCOUNT UNLOCK;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Test for get_role_status()
+SELECT get_role_status('profile_user1');
+SELECT get_role_status('profile_user2');
+SELECT get_role_status('profile_user3');
+SELECT get_role_status('profile_user4');
+SELECT get_role_status('profile_user5'); -- failed for user does not exist
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Test update pg_password_history
+UPDATE pg_password_history SET passhistpassword = 'random'; -- permission denied
+
+-- Test DROP PROFILE
+-- Failed for profile is using by user
+DROP PROFILE myprofile1;
+DROP PROFILE myprofile2;
+DROP PROFILE myprofile3;
+DROP PROFILE myprofile4;
+DROP PROFILE pg_default; -- failed, can't drop pg_default profile
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- cleanup
+DROP USER profile_user1;
+DROP USER profile_user2;
+DROP USER profile_user3;
+DROP USER profile_user4;
+DROP USER profile_user5;
+DROP USER profile_user7;
+
+SELECT rolname, prfname, rolaccountstatus, rolfailedlogins, rolenableprofile
+FROM pg_authid, pg_profile
+WHERE pg_authid.rolprofile = pg_profile.oid
+AND rolname like '%profile_user%';
+
+-- Successful
+DROP PROFILE myprofile1, myprofile2;
+DROP PROFILE myprofile1; -- failed
+DROP PROFILE IF EXISTS myprofile2; -- OK
+DROP PROFILE myprofile3; -- failed
+DROP PROFILE myprofile4, pg_default; -- failed
+DROP PROFILE IF EXISTS myprofile4; -- OK
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
+
+DROP USER profile_user9;
+DROP PROFILE myprofile3; -- OK
+
+SELECT prfname, prffailedloginattempts, prfpasswordlocktime, prfpasswordreusemax
+FROM pg_profile;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d71d1adbecd..7a0216ff100 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -80,6 +80,7 @@ AlterOpFamilyStmt
AlterOperatorStmt
AlterOwnerStmt
AlterPolicyStmt
+AlterProfileStmt
AlterPublicationStmt
AlterRoleSetStmt
AlterRoleStmt
@@ -472,6 +473,7 @@ CreateOpClassStmt
CreateOpFamilyStmt
CreatePLangStmt
CreatePolicyStmt
+CreateProfileStmt
CreatePublicationStmt
CreateRangeStmt
CreateReplicationSlotCmd
@@ -566,6 +568,7 @@ DomainConstraintType
DomainIOData
DropBehavior
DropOwnedStmt
+DropProfileStmt
DropReplicationSlotCmd
DropRoleStmt
DropStmt