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