diff --git a/ACI.txt b/ACI.txt index 50c8824d4..0b2cd0cfd 100644 --- a/ACI.txt +++ b/ACI.txt @@ -247,9 +247,9 @@ aci: (targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")( dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example aci: (targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=System: Delete Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example -aci: (targetattr = "ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || passwordgracelimit")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "ipapwddcredit || ipapwddictcheck || ipapwdlcredit || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdocredit || ipapwducredit || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || passwordgracelimit")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example -aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass || passwordgracelimit")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || ipapwddcredit || ipapwddictcheck || ipapwdlcredit || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdocredit || ipapwducredit || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass || passwordgracelimit")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=radiusproxy,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || description || entryusn || ipatokenradiusretries || ipatokenradiusserver || ipatokenradiustimeout || ipatokenusermapattribute || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipatokenradiusconfiguration)")(version 3.0;acl "permission:System: Read Radius Servers";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Radius Servers,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=Realm Domains,cn=ipa,cn=etc,dc=ipa,dc=example diff --git a/API.txt b/API.txt index f19e3bf34..9dee923d7 100644 --- a/API.txt +++ b/API.txt @@ -4134,14 +4134,18 @@ output: Entry('result') output: Output('summary', type=[, ]) output: PrimaryKey('value') command: pwpolicy_add/1 -args: 1,19,3 +args: 1,23,3 arg: Str('cn', cli_name='group') option: Str('addattr*', cli_name='addattr') option: Flag('all', autofill=True, cli_name='all', default=False) option: Int('cospriority', cli_name='priority') +option: Int('ipapwddcredit?', cli_name='dcredit', default=0) option: Bool('ipapwddictcheck?', cli_name='dictcheck', default=False) +option: Int('ipapwducredit?', cli_name='ucredit', default=0) +option: Int('ipapwdlcredit?', cli_name='lcredit', default=0) option: Int('ipapwdmaxrepeat?', cli_name='maxrepeat', default=0) option: Int('ipapwdmaxsequence?', cli_name='maxsequence', default=0) +option: Int('ipapwdocredit?', cli_name='ocredit', default=0) option: Bool('ipapwdusercheck?', cli_name='usercheck', default=False) option: Int('krbmaxpwdlife?', cli_name='maxlife') option: Int('krbminpwdlife?', cli_name='minlife') @@ -4167,14 +4171,18 @@ output: Output('result', type=[]) output: Output('summary', type=[, ]) output: ListOfPrimaryKeys('value') command: pwpolicy_find/1 -args: 1,21,4 +args: 1,25,4 arg: Str('criteria?') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('cn?', autofill=False, cli_name='group') option: Int('cospriority?', autofill=False, cli_name='priority') +option: Int('ipapwddcredit?', autofill=False, cli_name='dcredit', default=0) option: Bool('ipapwddictcheck?', autofill=False, cli_name='dictcheck', default=False) +option: Int('ipapwducredit?', autofill=False, cli_name='ucredit', default=0) +option: Int('ipapwdlcredit?', autofill=False, cli_name='lcredit', default=0) option: Int('ipapwdmaxrepeat?', autofill=False, cli_name='maxrepeat', default=0) option: Int('ipapwdmaxsequence?', autofill=False, cli_name='maxsequence', default=0) +option: Int('ipapwdocredit?', autofill=False, cli_name='ocredit', default=0) option: Bool('ipapwdusercheck?', autofill=False, cli_name='usercheck', default=False) option: Int('krbmaxpwdlife?', autofill=False, cli_name='maxlife') option: Int('krbminpwdlife?', autofill=False, cli_name='minlife') @@ -4195,15 +4203,19 @@ output: ListOfEntries('result') output: Output('summary', type=[, ]) output: Output('truncated', type=[]) command: pwpolicy_mod/1 -args: 1,21,3 +args: 1,25,3 arg: Str('cn?', cli_name='group') option: Str('addattr*', cli_name='addattr') option: Flag('all', autofill=True, cli_name='all', default=False) option: Int('cospriority?', autofill=False, cli_name='priority') option: Str('delattr*', cli_name='delattr') +option: Int('ipapwddcredit?', autofill=False, cli_name='dcredit', default=0) option: Bool('ipapwddictcheck?', autofill=False, cli_name='dictcheck', default=False) +option: Int('ipapwducredit?', autofill=False, cli_name='ucredit', default=0) +option: Int('ipapwdlcredit?', autofill=False, cli_name='lcredit', default=0) option: Int('ipapwdmaxrepeat?', autofill=False, cli_name='maxrepeat', default=0) option: Int('ipapwdmaxsequence?', autofill=False, cli_name='maxsequence', default=0) +option: Int('ipapwdocredit?', autofill=False, cli_name='ocredit', default=0) option: Bool('ipapwdusercheck?', autofill=False, cli_name='usercheck', default=False) option: Int('krbmaxpwdlife?', autofill=False, cli_name='maxlife') option: Int('krbminpwdlife?', autofill=False, cli_name='minlife') diff --git a/VERSION.m4 b/VERSION.m4 index 20aa1a241..6fa999510 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -# Last change: add keeponly option to batch command -define(IPA_API_VERSION_MINOR, 254) +# Last change: add pwpolicy credit options +define(IPA_API_VERSION_MINOR, 255) ######################################################## # Following values are auto-generated from values above diff --git a/daemons/ipa-kdb/ipa_kdb_passwords.c b/daemons/ipa-kdb/ipa_kdb_passwords.c index 3fc75bf1e..4ee9dfe6b 100644 --- a/daemons/ipa-kdb/ipa_kdb_passwords.c +++ b/daemons/ipa-kdb/ipa_kdb_passwords.c @@ -50,6 +50,10 @@ static krb5_error_code ipapwd_error_to_kerr(krb5_context context, krb5_set_error_message(context, kerr, "Password reuse not permitted"); break; case IPAPWD_POLICY_PWD_COMPLEXITY: + case IPAPWD_POLICY_PWD_MIN_DIGITS: + case IPAPWD_POLICY_PWD_MIN_UPPERS: + case IPAPWD_POLICY_PWD_MIN_LOWERS: + case IPAPWD_POLICY_PWD_MIN_OTHERS: kerr = KADM5_PASS_Q_CLASS; krb5_set_error_message(context, kerr, "Password is too simple"); break; diff --git a/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c b/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c index 6f21ef867..4df80f457 100644 --- a/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c +++ b/daemons/ipa-kdb/ipa_kdb_pwdpolicy.c @@ -38,6 +38,10 @@ char *std_pwdpolicy_attrs[] = { "ipapwdmaxsequence", "ipapwddictcheck", "ipapwdusercheck", + "ipapwddcredit", + "ipapwducredit", + "ipapwdlcredit", + "ipapwdocredit", NULL }; @@ -51,6 +55,7 @@ krb5_error_code ipadb_get_ipapwd_policy(struct ipadb_context *ipactx, LDAPMessage *res = NULL; LDAPMessage *lentry; uint32_t result; + int iresult; bool resbool; int ret; @@ -146,8 +151,28 @@ krb5_error_code ipadb_get_ipapwd_policy(struct ipadb_context *ipactx, pol->usercheck = 1; } + ret = ipadb_ldap_attr_to_int(ipactx->lcontext, lentry, + "ipaPwdDcredit", &iresult); if (ret == 0) { - pol->max_sequence = result; + pol->dcredit = iresult; + } + + ret = ipadb_ldap_attr_to_int(ipactx->lcontext, lentry, + "ipaPwdUcredit", &iresult); + if (ret == 0) { + pol->ucredit = iresult; + } + + ret = ipadb_ldap_attr_to_int(ipactx->lcontext, lentry, + "ipaPwdLcredit", &iresult); + if (ret == 0) { + pol->lcredit = iresult; + } + + ret = ipadb_ldap_attr_to_int(ipactx->lcontext, lentry, + "ipaPwdOcredit", &iresult); + if (ret == 0) { + pol->ocredit = iresult; } *_pol = pol; diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c index 1355f20d3..c85795c1e 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c @@ -333,7 +333,9 @@ int ipapwd_getPolicy(const char *dn, "krbPwdMinDiffChars", "krbPwdMinLength", "krbPwdHistoryLength", "ipaPwdMaxRepeat", "ipaPwdMaxSequence", "ipaPwdDictCheck", - "ipaPwdUserCheck", NULL}; + "ipaPwdUserCheck", "ipapwdDcredit", + "ipaPwdUcredit", "ipaPwdLcredit", + "ipaPwdOcredit", NULL}; Slapi_Entry **es = NULL; Slapi_Entry *pe = NULL; int ret, res, scope; @@ -418,6 +420,10 @@ int ipapwd_getPolicy(const char *dn, policy->max_sequence = slapi_entry_attr_get_int(pe, "ipaPwdMaxSequence"); policy->dictcheck = slapi_entry_attr_get_bool(pe, "ipaPwdDictCheck"); policy->usercheck = slapi_entry_attr_get_bool(pe, "ipaPwdUserCheck"); + policy->dcredit = slapi_entry_attr_get_int(pe, "ipaPwdDcredit"); + policy->ucredit = slapi_entry_attr_get_int(pe, "ipaPwdUcredit"); + policy->lcredit = slapi_entry_attr_get_int(pe, "ipaPwdLcredit"); + policy->ocredit = slapi_entry_attr_get_int(pe, "ipaPwdOcredit"); ret = 0; diff --git a/doc/api/pwpolicy_add.md b/doc/api/pwpolicy_add.md index 77319754a..2bf660b65 100644 --- a/doc/api/pwpolicy_add.md +++ b/doc/api/pwpolicy_add.md @@ -29,6 +29,14 @@ Add a new group password policy. * Default: False * ipapwdusercheck : :ref:`Bool` * Default: False +* ipapwddcredit : :ref:`Int` + * Default: 0 +* ipapwducredit : :ref:`Int` + * Default: 0 +* ipapwdlcredit : :ref:`Int` + * Default: 0 +* ipapwdocredit : :ref:`Int` + * Default: 0 * passwordgracelimit : :ref:`Int` * Default: -1 * setattr : :ref:`Str` @@ -47,4 +55,4 @@ Add a new group password policy. ### Notes -### Version differences \ No newline at end of file +### Version differences diff --git a/doc/api/pwpolicy_find.md b/doc/api/pwpolicy_find.md index 0c7ecb5d1..7ca80ac72 100644 --- a/doc/api/pwpolicy_find.md +++ b/doc/api/pwpolicy_find.md @@ -30,6 +30,14 @@ Search for group password policies. * Default: False * ipapwdusercheck : :ref:`Bool` * Default: False +* ipapwddcredit : :ref:`Int` + * Default: 0 +* ipapwducredit : :ref:`Int` + * Default: 0 +* ipapwdlcredit : :ref:`Int` + * Default: 0 +* ipapwdocredit : :ref:`Int` + * Default: 0 * passwordgracelimit : :ref:`Int` * Default: -1 * timelimit : :ref:`Int` @@ -51,4 +59,4 @@ Search for group password policies. ### Notes -### Version differences \ No newline at end of file +### Version differences diff --git a/doc/api/pwpolicy_mod.md b/doc/api/pwpolicy_mod.md index 723212278..2bc165045 100644 --- a/doc/api/pwpolicy_mod.md +++ b/doc/api/pwpolicy_mod.md @@ -31,6 +31,14 @@ Modify a group password policy. * Default: False * ipapwdusercheck : :ref:`Bool` * Default: False +* ipapwddcredit : :ref:`Int` + * Default: 0 +* ipapwducredit : :ref:`Int` + * Default: 0 +* ipapwdlcredit : :ref:`Int` + * Default: 0 +* ipapwdocredit : :ref:`Int` + * Default: 0 * passwordgracelimit : :ref:`Int` * Default: -1 * setattr : :ref:`Str` @@ -50,4 +58,4 @@ Modify a group password policy. ### Notes -### Version differences \ No newline at end of file +### Version differences diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif index 53c857d1b..dbaa7cac8 100644 --- a/install/share/60basev2.ldif +++ b/install/share/60basev2.ldif @@ -58,4 +58,8 @@ attributeTypes: (2.16.840.1.113730.3.8.23.2 NAME 'ipaPwdMaxRepeat' EQUALITY inte attributeTypes: (2.16.840.1.113730.3.8.23.3 NAME 'ipaPwdMaxSequence' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4') attributeTypes: (2.16.840.1.113730.3.8.23.4 NAME 'ipaPwdDictCheck' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4') attributeTypes: (2.16.840.1.113730.3.8.23.5 NAME 'ipaPwdUserCheck' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4') -objectClasses: (2.16.840.1.113730.3.8.24.1 NAME 'ipaPwdPolicy' DESC 'IPA Password policy object class' SUP top MAY (ipaPwdMaxRepeat $ ipaPwdMaxSequence $ ipaPwdDictCheck $ ipaPwdUserCheck $ passwordGraceLimit) X-ORIGIN 'IPA v4') +attributeTypes: (2.16.840.1.113730.3.8.23.28 NAME 'ipaPwdDcredit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4') +attributeTypes: (2.16.840.1.113730.3.8.23.29 NAME 'ipaPwdUcredit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4') +attributeTypes: (2.16.840.1.113730.3.8.23.30 NAME 'ipaPwdLcredit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4') +attributeTypes: (2.16.840.1.113730.3.8.23.31 NAME 'ipaPwdOcredit' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4') +objectClasses: (2.16.840.1.113730.3.8.24.1 NAME 'ipaPwdPolicy' DESC 'IPA Password policy object class' SUP top MAY (ipaPwdMaxRepeat $ ipaPwdMaxSequence $ ipaPwdDictCheck $ ipaPwdUserCheck $ ipaPwdDcredit $ ipaPwdUcredit $ ipaPwdLcredit $ ipaPwdOcredit $ passwordGraceLimit) X-ORIGIN 'IPA v4') diff --git a/ipaserver/plugins/pwpolicy.py b/ipaserver/plugins/pwpolicy.py index 15cfef45b..e49a2e1cd 100644 --- a/ipaserver/plugins/pwpolicy.py +++ b/ipaserver/plugins/pwpolicy.py @@ -66,6 +66,20 @@ Grace period defines the number of LDAP logins allowed after expiration. -1 means do not enforce expiration to match previous behavior. 0 allows no additional logins after expiration. +The pwquality options are either mutually exclusive, or take +precedence, over the standard password policy values. In the case +of minimum password length if any pwquality-based options are used then +the minimum length must be >= 6. + +The "credit" settings are used to adjust password complexity requirements. +- With a value of 0, the default, the option is ignored. +- With a positive value each character of that type in the password + contributes towards meeting the mininum length requirement. + For example, with a password policy of `minlength=6`, `dcredit=1`, + these passwords are valid: abcdef or abcd1 +- With a negative value signifies a minimum number of that character type + that must be present. + EXAMPLES: Modify the global policy: @@ -249,6 +263,7 @@ class pwpolicy(LDAPObject): 'krbpwdlockoutduration', 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', 'ipapwdusercheck', 'passwordgracelimit', + 'ipapwddcredit', 'ipapwducredit', 'ipapwdlcredit', 'ipapwdocredit', ] managed_permissions = { 'System: Read Group Password Policy': { @@ -261,7 +276,8 @@ class pwpolicy(LDAPObject): 'krbpwdlockoutduration', 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength', 'objectclass', 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', - 'ipapwdusercheck', 'passwordgracelimit', + 'ipapwdusercheck', 'passwordgracelimit', 'ipapwddcredit', + 'ipapwducredit', 'ipapwdlcredit', 'ipapwdocredit', }, 'default_privileges': { 'Password Policy Readers', @@ -289,7 +305,9 @@ class pwpolicy(LDAPObject): 'krbpwdhistorylength', 'krbpwdlockoutduration', 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength', 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', - 'ipapwdusercheck', 'passwordgracelimit', + 'ipapwdusercheck', 'passwordgracelimit', 'ipapwddcredit', + 'ipapwducredit', 'ipapwdlcredit', 'ipapwdocredit', + }, 'replaces': [ '(targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdhistorylength || krbpwdmindiffchars || krbpwdminlength || krbpwdmaxfailure || krbpwdfailurecountinterval || krbpwdlockoutduration")(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', @@ -400,6 +418,42 @@ class pwpolicy(LDAPObject): doc=_('Check if the password contains the username'), default=False, ), + Int( + 'ipapwddcredit?', + cli_name='dcredit', + label=_('Digit Credit'), + doc=_('The max credit for digits in the password.'), + minvalue=-256, + maxvalue=256, + default=0, + ), + Int( + 'ipapwducredit?', + cli_name='ucredit', + label=_('Uppercase Credit'), + doc=_('The max credit for uppercase characters in the password.'), + minvalue=-256, + maxvalue=256, + default=0, + ), + Int( + 'ipapwdlcredit?', + cli_name='lcredit', + label=_('Lowercase Credit'), + doc=_('The max credit for lowercase characters in the password.'), + minvalue=-256, + maxvalue=256, + default=0, + ), + Int( + 'ipapwdocredit?', + cli_name='ocredit', + label=_('Other Credit'), + doc=_('The max credit for other characters in the password.'), + minvalue=-256, + maxvalue=256, + default=0, + ), Int( 'passwordgracelimit?', cli_name='gracelimit', @@ -455,14 +509,26 @@ class pwpolicy(LDAPObject): def has_pwquality_set(entry): for attr in ['ipapwdmaxrepeat', 'ipapwdmaxsequence', - 'ipapwddictcheck', 'ipapwdusercheck']: + 'ipapwddictcheck', 'ipapwdusercheck', + 'ipapwddcredit', 'ipapwducredit', + 'ipapwdlcredit', 'ipapwdocredit',]: + val = get_val(entry, attr) + if val not in (False, 'FALSE', '0', 0, None): + return True + return False + + def has_credit_set(entry): + for attr in ['ipapwddcredit', 'ipapwducredit', + 'ipapwdlcredit', 'ipapwdocredit',]: val = get_val(entry, attr) if val not in (False, 'FALSE', '0', 0, None): return True return False has_pwquality_value = False + has_credit_value = False min_length = 0 + minclasses = False if not add: if len(keys) > 0: existing_entry = self.api.Command.pwpolicy_show( @@ -473,11 +539,15 @@ class pwpolicy(LDAPObject): existing_entry.update(entry_attrs) if existing_entry.get('krbpwdminlength'): min_length = int(get_val(existing_entry, 'krbpwdminlength')) + if existing_entry.get('krbpwdmindiffchars'): + minclasses = int(get_val(existing_entry, 'krbpwdmindiffchars')) has_pwquality_value = has_pwquality_set(existing_entry) + has_credit_value = has_credit_set(existing_entry) else: if entry_attrs.get('krbpwdminlength'): min_length = int(get_val(entry_attrs, 'krbpwdminlength')) has_pwquality_value = has_pwquality_set(entry_attrs) + has_credit_value = has_credit_set(entry_attrs) if min_length < 6 and has_pwquality_value: raise errors.ValidationError( @@ -485,6 +555,12 @@ class pwpolicy(LDAPObject): error=_('Minimum length must be >= 6 if maxrepeat, ' 'maxsequence, dictcheck or usercheck are defined') ) + if minclasses > 0 and has_credit_value: + raise errors.ValidationError( + name='minclasses', + error=_('Minimum number of classes cannot be used when ' + 'character credit are defined') + ) def validate_lifetime(self, entry_attrs, add=False, *keys): """ diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py index 652c95e47..a627b66ce 100644 --- a/ipatests/test_integration/test_pwpolicy.py +++ b/ipatests/test_integration/test_pwpolicy.py @@ -55,15 +55,20 @@ class TestPWPolicy(IntegrationTest): ), ) - def reset_password(self, host, user=USER, password=PASSWORD): + def reset_password(self, host, user=USER, password=PASSWORD, + unlock=False): tasks.kinit_admin(host) host.run_command( ['ipa', 'passwd', user], stdin_text='{password}\n{password}\n'.format(password=password), ) + if unlock: + host.run_command(['ipa', 'user-unlock', user]) + tasks.kdestroy_all(host) def set_pwpolicy(self, minlength=None, maxrepeat=None, maxsequence=None, - dictcheck=None, usercheck=None, minclasses=None): + dictcheck=None, usercheck=None, minclasses=None, + dcredit=None, ucredit=None, lcredit=None, ocredit=None): tasks.kinit_admin(self.master) args = ["ipa", "pwpolicy-mod", POLICY] if minlength is not None: @@ -78,6 +83,14 @@ class TestPWPolicy(IntegrationTest): args.append("--usercheck={}".format(usercheck)) if minclasses is not None: args.append("--minclasses={}".format(minclasses)) + if dcredit is not None: + args.append("--dcredit={}".format(dcredit)) + if ucredit is not None: + args.append("--ucredit={}".format(ucredit)) + if lcredit is not None: + args.append("--lcredit={}".format(lcredit)) + if ocredit is not None: + args.append("--ocredit={}".format(ocredit)) self.master.run_command(args) self.reset_password(self.master) @@ -92,7 +105,11 @@ class TestPWPolicy(IntegrationTest): "--dictcheck" ,"false", "--minlife", "0", "--minlength", "0", - "--minclasses", "0",], + "--minclasses", "0", + "--dcredit", "0", + "--ucredit", "0", + "--lcredit", "0", + "--ocredit", "0",], ) # minlength => 6 is required for any of the libpwquality settings self.master.run_command( @@ -226,6 +243,89 @@ class TestPWPolicy(IntegrationTest): self.kinit_as_user(self.master, PASSWORD, valid) self.reset_password(self.master) + def test_credits(self, reset_pwpolicy): + """Test each credit individually. This would be all copy/paste + if done individually so provide a config and set of good + and bad passwords to test. + + Reminder: a positive credit reduces minlength + a zero credit is a no-op + a negative credit is the minimum required + """ + def test_policy(good, bad, msg, **options): + """Validate the configuration. + + Given the options in **options, the other arguments are: + good: tuple of valid passwords + bad: tuple of invalid passwords + msg: tuple of expected messages. LDAP may return a different + one than kinit. + """ + + tasks.kinit_admin(self.master) + self.clean_pwpolicy() + self.set_pwpolicy(**options) + + for password in good: + self.kinit_as_user(self.master, PASSWORD, password) + self.reset_password(self.master) + + for password in bad: + result = self.kinit_as_user(self.master, PASSWORD, password, + raiseonerr=False) + assert result.returncode == 1 + for message in msg: + if message in result.stdout_text: + break + else: + assert False, "%s not in %s" % ( + result.stdout_text, ', '.join(msg) + ) + result = tasks.ldappasswd_user_change(USER, PASSWORD, password, + self.master, + raiseonerr=False) + assert result.returncode == 1 + for message in msg: + if message in result.stdout_text: + break + else: + assert False, "%s not in %s" % ( + result.stdout_text, ', '.join(msg) + ) + + # hint: minlength == 6 + # dcredit + test_policy(('Pass12', 'password1'), ('123', 'passw'), + ('Password is too short',), **{'dcredit': 1}) + test_policy(('Passw0rd1', 'password12'), ('Passwd1', 'password1'), + ('Password does not contain enough character', + 'Password contains too few digits',), + **{'dcredit': -2}) + + # ucredit + test_policy(('Passw', 'PASSWORD'), ('PAS', 'passw'), + ('Password is too short',), **{'ucredit': 1}) + test_policy(('Passw0rD1', 'PasswordS'), ('Passwd1', 'password1'), + ('Password does not contain enough character', + 'Password contains too few upper characters',), + **{'ucredit': -2}) + + # lcredit + test_policy(('PASSw', 'password'), ('pas', 'PASSW'), + ('Password is too short',), **{'lcredit': 1}) + test_policy(('Passw0rD1', 'PasswordS'), ('PASSWd1', 'PASSWoRD1'), + ('Password does not contain enough character', + 'Password contains too few lower characters',), + **{'lcredit': -2}) + + # ocredit + test_policy(('passw!', 'password#'), ('pas#', 'PAS$'), + ('Password is too short',), **{'ocredit': 1}) + test_policy(('Passw@rD1$', 'P&sswordS%'), ('passwd*', 'password^'), + ('Password does not contain enough character', + 'Password contains too few special characters',), + **{'ocredit': -2}) + def test_minlength_mod(self, reset_pwpolicy): """Test that the pwpolicy minlength overrides our policy """ @@ -245,7 +345,7 @@ class TestPWPolicy(IntegrationTest): assert result.returncode != 0 assert 'minlength' in result.stderr_text - # With any pwq value set, setting minlife < 6 should fail + # With any pwq value set, setting minlength < 6 should fail for values in (('--maxrepeat', '4'), ('--maxsequence', '4'), ('--dictcheck', 'true'), @@ -388,6 +488,7 @@ class TestPWPolicy(IntegrationTest): dn = "uid={user},cn=users,cn=accounts,{base_dn}".format( user=USER, base_dn=str(self.master.domain.basedn)) + tasks.kinit_admin(self.master) self.master.run_command( ["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "0", ], ) diff --git a/util/ipa_pwd.c b/util/ipa_pwd.c index 2abc9d20c..ba6860106 100644 --- a/util/ipa_pwd.c +++ b/util/ipa_pwd.c @@ -393,6 +393,128 @@ cleanup: return history; } +/** +* @brief Returns True/False if any of the pwquality config is set +* +* A history element is a base64 string of a hash+salt buffer, prepended +* by the hash type enclosed within curly braces. +* +* @param policy The policy to check against +* +* @return a boolean +*/ +static bool has_pwquality_rules(struct ipapwd_policy *p) +{ + return (p->max_repeat || p->max_sequence || + p->dictcheck || p->usercheck || + p->dcredit || p->ucredit || + p->lcredit || p->ocredit ); +} + +/** +* @brief Runs the libpwquality checks as configured in the policy +* +* If any libpwquality policies are defined then the password is +* passed into libpwquality for validation. The result is converted +* into a IPA password policy response and returned. +* +* @param policy The policy to check against +* +* @return int of the result +*/ + +#if defined(USE_PWQUALITY) +static int call_pwquality(const struct ipapwd_policy *policy, + const char *password, + const char *user) +{ + int entropy = 0; + char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; + void *auxerror; + struct pwquality_settings *pwq = pwquality_default_settings(); + + if (!pwq) { + syslog(LOG_ERR, "pwquality defaults failed"); + return IPAPWD_POLICY_ERROR; + } + + /* Call libpwquality */ + openlog(NULL, LOG_CONS | LOG_NDELAY, LOG_DAEMON); + if (pwq == NULL) { + syslog(LOG_ERR, "Not able to set pwquality defaults\n"); + return IPAPWD_POLICY_ERROR; + } + if (policy->min_pwd_length < 6) + syslog(LOG_WARNING, "password policy min length is < 6. Will be enforced as 6\n"); + pwquality_set_int_value(pwq, PWQ_SETTING_MIN_LENGTH, policy->min_pwd_length); + pwquality_set_int_value(pwq, PWQ_SETTING_MAX_REPEAT, policy->max_repeat); + pwquality_set_int_value(pwq, PWQ_SETTING_MAX_SEQUENCE, policy->max_sequence); + pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, policy->dictcheck); + pwquality_set_int_value(pwq, PWQ_SETTING_USER_CHECK, policy->usercheck); + pwquality_set_int_value(pwq, PWQ_SETTING_DIG_CREDIT, policy->dcredit); + pwquality_set_int_value(pwq, PWQ_SETTING_UP_CREDIT, policy->ucredit); + pwquality_set_int_value(pwq, PWQ_SETTING_LOW_CREDIT, policy->lcredit); + pwquality_set_int_value(pwq, PWQ_SETTING_OTH_CREDIT, policy->ocredit); + + entropy = pwquality_check(pwq, password, NULL, user, &auxerror); + pwquality_free_settings(pwq); + +#ifdef TEST + if (user != NULL) { + fprintf(stderr, "Checking password for %s\n", user); + } else { + fprintf(stderr, "No user provided\n"); + } + + fprintf(stderr, "min length %d\n", policy->min_pwd_length); + fprintf(stderr, "max repeat %d\n", policy->max_repeat); + fprintf(stderr, "max sequence %d\n", policy->max_sequence); + fprintf(stderr, "dict check %d\n", policy->dictcheck); + fprintf(stderr, "user check %d\n", policy->usercheck); + fprintf(stderr, "digit credit %d\n", policy->dcredit); + fprintf(stderr, "upper credit %d\n", policy->ucredit); + fprintf(stderr, "lower credit %d\n", policy->lcredit); + fprintf(stderr, "other credit %d\n", policy->ocredit); +#endif + + if (entropy < 0) { +#ifdef TEST + fprintf(stderr, "Bad password '%s': %s\n", password, pwquality_strerror(buf, sizeof(buf), entropy, auxerror)); +#endif + syslog(LOG_ERR, "Password is rejected with error %d: %s\n", entropy, pwquality_strerror(buf, sizeof(buf), entropy, auxerror)); + switch (entropy) { + case PWQ_ERROR_MIN_LENGTH: + return IPAPWD_POLICY_PWD_TOO_SHORT; + case PWQ_ERROR_PALINDROME: + return IPAPWD_POLICY_PWD_PALINDROME; + case PWQ_ERROR_MAX_CONSECUTIVE: + return IPAPWD_POLICY_PWD_CONSECUTIVE; + case PWQ_ERROR_MAX_SEQUENCE: + return IPAPWD_POLICY_PWD_SEQUENCE; + case PWQ_ERROR_CRACKLIB_CHECK: + return IPAPWD_POLICY_PWD_DICT_WORD; + case PWQ_ERROR_USER_CHECK: + return IPAPWD_POLICY_PWD_USER; + case PWQ_ERROR_MIN_DIGITS: + return IPAPWD_POLICY_PWD_MIN_DIGITS; + case PWQ_ERROR_MIN_UPPERS: + return IPAPWD_POLICY_PWD_MIN_UPPERS; + case PWQ_ERROR_MIN_LOWERS: + return IPAPWD_POLICY_PWD_MIN_LOWERS; + case PWQ_ERROR_MIN_OTHERS: + return IPAPWD_POLICY_PWD_MIN_OTHERS; + default: + return IPAPWD_POLICY_PWD_COMPLEXITY; + } +#ifdef TEST + } else { + fprintf(stderr, "Password '%s' is ok, entropy is %d\n", password, entropy); +#endif + } + return IPAPWD_POLICY_OK; +} +#endif /* USE_PWQUALITY */ + /** * @brief Funtion used to check password policies on a password change. * @@ -417,13 +539,6 @@ int ipapwd_check_policy(struct ipapwd_policy *policy, { int pwdlen, blen; int ret; -#if defined(USE_PWQUALITY) - pwquality_settings_t *pwq; - int check_pwquality = 0; - int entropy = 0; - char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; - void *auxerror; -#endif if (!policy || !password) { return IPAPWD_POLICY_ERROR; @@ -453,12 +568,6 @@ int ipapwd_check_policy(struct ipapwd_policy *policy, pwdlen = strlen_utf8(password, &blen); - if (policy->min_pwd_length) { - if (pwdlen < policy->min_pwd_length) { - return IPAPWD_POLICY_PWD_TOO_SHORT; - } - } - if (policy->min_complexity) { int num_digits = 0; int num_alphas = 0; @@ -541,70 +650,19 @@ int ipapwd_check_policy(struct ipapwd_policy *policy, * because there are a number of checks that don't have knobs * so preserve the previous behavior. */ - check_pwquality = policy->max_repeat + policy->max_sequence + policy->dictcheck + policy->usercheck; - - if (check_pwquality > 0) { - /* Call libpwquality */ - openlog(NULL, LOG_CONS | LOG_NDELAY, LOG_DAEMON); - pwq = pwquality_default_settings(); - if (pwq == NULL) { - syslog(LOG_ERR, "Not able to set pwquality defaults\n"); - return IPAPWD_POLICY_ERROR; - } - if (policy->min_pwd_length < 6) - syslog(LOG_WARNING, "password policy min length is < 6. Will be enforced as 6\n"); - pwquality_set_int_value(pwq, PWQ_SETTING_MIN_LENGTH, policy->min_pwd_length); - pwquality_set_int_value(pwq, PWQ_SETTING_MAX_REPEAT, policy->max_repeat); - pwquality_set_int_value(pwq, PWQ_SETTING_MAX_SEQUENCE, policy->max_sequence); - pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, policy->dictcheck); - pwquality_set_int_value(pwq, PWQ_SETTING_USER_CHECK, policy->usercheck); - - entropy = pwquality_check(pwq, password, NULL, user, &auxerror); - pwquality_free_settings(pwq); - -#ifdef TEST - if (user != NULL) { - fprintf(stderr, "Checking password for %s\n", user); - } else { - fprintf(stderr, "No user provided\n"); - } - - fprintf(stderr, "min length %d\n", policy->min_pwd_length); - fprintf(stderr, "max repeat %d\n", policy->max_repeat); - fprintf(stderr, "max sequence %d\n", policy->max_sequence); - fprintf(stderr, "dict check %d\n", policy->dictcheck); - fprintf(stderr, "user check %d\n", policy->usercheck); -#endif - - if (entropy < 0) { -#ifdef TEST - fprintf(stderr, "Bad password '%s': %s\n", password, pwquality_strerror(buf, sizeof(buf), entropy, auxerror)); -#endif - syslog(LOG_ERR, "Password is rejected with error %d: %s\n", entropy, pwquality_strerror(buf, sizeof(buf), entropy, auxerror)); - switch (entropy) { - case PWQ_ERROR_MIN_LENGTH: + if (has_pwquality_rules(policy)) { + ret = call_pwquality(policy, password, user); + if (ret != IPAPWD_POLICY_OK) + return ret; + } else +#endif /* USE_PWQUALITY */ + { + if (policy->min_pwd_length) { + if (pwdlen < policy->min_pwd_length) { return IPAPWD_POLICY_PWD_TOO_SHORT; - case PWQ_ERROR_PALINDROME: - return IPAPWD_POLICY_PWD_PALINDROME; - case PWQ_ERROR_MAX_CONSECUTIVE: - return IPAPWD_POLICY_PWD_CONSECUTIVE; - case PWQ_ERROR_MAX_SEQUENCE: - return IPAPWD_POLICY_PWD_SEQUENCE; - case PWQ_ERROR_CRACKLIB_CHECK: - return IPAPWD_POLICY_PWD_DICT_WORD; - case PWQ_ERROR_USER_CHECK: - return IPAPWD_POLICY_PWD_USER; - default: - return IPAPWD_POLICY_PWD_COMPLEXITY; } - -#ifdef TEST - } else { - fprintf(stderr, "Password '%s' is ok, entropy is %d\n", password, entropy); -#endif } } -#endif /* USE_PWQUALITY */ if (pwd_history) { char *hash; @@ -634,13 +692,17 @@ char * IPAPWD_ERROR_STRINGS[] = { "Password contains a monotonic sequence", "Password is based on a dictionary word", "Password is a palindrone", - "Password contains username" + "Password contains username", + "Password contains too few digits", + "Password contains too few upper characters", + "Password contains too few lower characters", + "Password contains too few special characters" }; char * IPAPWD_ERROR_STRING_GENERAL = "Password does not meet the policy requirements"; char * ipapwd_error2string(enum ipapwd_error err) { - if (err < 0 || err > IPAPWD_POLICY_PWD_USER) { + if (err < 0 || err > IPAPWD_POLICY_PWD_MIN_OTHERS) { /* IPAPWD_POLICY_ERROR or out of boundary, return general error */ return IPAPWD_ERROR_STRING_GENERAL; } diff --git a/util/ipa_pwd.h b/util/ipa_pwd.h index 4bd3a70d4..aa2c6e978 100644 --- a/util/ipa_pwd.h +++ b/util/ipa_pwd.h @@ -46,7 +46,11 @@ enum ipapwd_error { IPAPWD_POLICY_PWD_SEQUENCE = 7, IPAPWD_POLICY_PWD_DICT_WORD = 8, IPAPWD_POLICY_PWD_PALINDROME = 9, - IPAPWD_POLICY_PWD_USER = 10 + IPAPWD_POLICY_PWD_USER = 10, + IPAPWD_POLICY_PWD_MIN_DIGITS = 11, + IPAPWD_POLICY_PWD_MIN_UPPERS = 12, + IPAPWD_POLICY_PWD_MIN_LOWERS = 13, + IPAPWD_POLICY_PWD_MIN_OTHERS = 14 }; struct ipapwd_policy { @@ -58,11 +62,17 @@ struct ipapwd_policy { int max_fail; int failcnt_interval; int lockout_duration; + /* begin libpwquality options */ int max_repeat; int max_sequence; int max_classrepeat; int dictcheck; int usercheck; + int dcredit; + int ucredit; + int lcredit; + int ocredit; + /* end libpwquality options */ }; time_t ipapwd_gentime_to_time_t(char *timestr); diff --git a/util/t_policy.c b/util/t_policy.c index 88ac1b53b..3a06498f6 100644 --- a/util/t_policy.c +++ b/util/t_policy.c @@ -16,7 +16,8 @@ static void set_policy(struct ipapwd_policy *policy, int min_pwd_length, int min_diff_chars, int max_repeat, int max_sequence, int max_class_repeat, int dict_check, - int user_check) + int user_check, int dcredit, int ucredit, int lcredit, + int ocredit) { /* defaults for things we aren't testing */ @@ -32,6 +33,10 @@ set_policy(struct ipapwd_policy *policy, policy->max_classrepeat = max_class_repeat; policy->dictcheck = dict_check; policy->usercheck = user_check; + policy->dcredit = dcredit; + policy->ucredit = ucredit; + policy->lcredit = lcredit; + policy->ocredit = ocredit; } int main(int argc, const char *argv[]) { @@ -41,7 +46,7 @@ int main(int argc, const char *argv[]) { struct ipapwd_policy policy = {0}; /* No policy applied */ - set_policy(&policy, 0, 0, 0, 0, 0, 0, 0); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); assert(ipapwd_check_policy(&policy, "password", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); assert(ipapwd_check_policy(&policy, "abcddcba", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); @@ -50,7 +55,7 @@ int main(int argc, const char *argv[]) { assert(ipapwd_check_policy(&policy, "abc", NULL, 3, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); /* Max repeats of 1 */ - set_policy(&policy, 0, 0, 1, 0, 0, 0, 0); + set_policy(&policy, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "password", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_CONSECUTIVE); assert(ipapwd_check_policy(&policy, "Assembly", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_CONSECUTIVE); @@ -58,37 +63,125 @@ int main(int argc, const char *argv[]) { assert(ipapwd_check_policy(&policy, "abc", NULL, 3, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_TOO_SHORT); /* Max repeats of 2 */ - set_policy(&policy, 0, 0, 2, 0, 0, 0, 0); + set_policy(&policy, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "password", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); assert(ipapwd_check_policy(&policy, "Assembly", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); assert(ipapwd_check_policy(&policy, "permisssive", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_CONSECUTIVE); /* Max sequence of 1 */ - set_policy(&policy, 0, 0, 0, 1, 0, 0, 0); + set_policy(&policy, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "abacab", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_SEQUENCE); assert(ipapwd_check_policy(&policy, "AbacAb", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_SEQUENCE); /* Max sequence of 2 */ - set_policy(&policy, 0, 0, 0, 2, 0, 0, 0); + set_policy(&policy, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "AbacAb", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); assert(ipapwd_check_policy(&policy, "abacabc", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_SEQUENCE); /* Palindrone */ - set_policy(&policy, 0, 0, 0, 0, 0, 0, 0); /* Note there is no policy */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); /* Note there is no policy */ assert(ipapwd_check_policy(&policy, "password", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); assert(ipapwd_check_policy(&policy, "abccba", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); - set_policy(&policy, 0, 0, 3, 0, 0, 0, 0); /* Set anything */ + set_policy(&policy, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0); /* Set anything */ assert(ipapwd_check_policy(&policy, "abccba", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_PALINDROME); /* Dictionary check */ - set_policy(&policy, 0, 0, 0, 0, 0, 1, 0); + set_policy(&policy, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "password", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_DICT_WORD); assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_DICT_WORD); /* User check */ assert(ipapwd_check_policy(&policy, "userPDQ123", "user", 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); - set_policy(&policy, 0, 0, 0, 0, 0, 0, 1); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0); assert(ipapwd_check_policy(&policy, "userPDQ123", "user", 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_USER); + /* Digit check. + * Negative == minimum # of digits required + * Zero == skip check + * Positive == amount to add towards min length + */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, -19, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_MIN_DIGITS); + + /* dcredit > 0 gives a "credit" to a shorter password for having + * complexity so allows a shorter than minimum length password. + * Reminder that with libpwquality the minimum password len is + * hardcoded at 6. + */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "abcd1", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "ab21", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + + /* Verify that no credits are added automatically. We need to set some + * pwquality option in order to validate it, so set length. + */ + set_policy(&policy, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "abcd1", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_TOO_SHORT); + + /* Upper check. + * Negative == minimum # of uppers required + * Zero == skip check + * Positive == amount to add towards min length + */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0); + assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, -19, 0, 0); + assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_MIN_UPPERS); + + /* ucredit > 0 gives a "credit" to a shorter password for having + * complexity so allows a shorter than minimum length password. + * Reminder that with libpwquality the minimum password len is + * hardcoded at 6. + */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0); + assert(ipapwd_check_policy(&policy, "abcdE", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0); + assert(ipapwd_check_policy(&policy, "abDE", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + + /* Verify that no credits are added automatically. We need to set some + * pwquality option in order to validate it, so set length. + */ + set_policy(&policy, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "abcdE", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_TOO_SHORT); + + /* Lower check. + * Negative == minimum # of uppers required + * Zero == skip check + * Positive == amount to add towards min length + */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0); + assert(ipapwd_check_policy(&policy, "SECREt123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 0, -19, 0); + assert(ipapwd_check_policy(&policy, "SECREt123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_MIN_LOWERS); + + /* lcredit > 0 gives a "credit" to a shorter password for having + * complexity so allows a shorter than minimum length password. + * Reminder that with libpwquality the minimum password len is + * hardcoded at 6. + */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0); + assert(ipapwd_check_policy(&policy, "ABCEe", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0); + assert(ipapwd_check_policy(&policy, "ABcd", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + + /* Verify that no credits are added automatically. We need to set some + * pwquality option in order to validate it, so set length. + */ + set_policy(&policy, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + assert(ipapwd_check_policy(&policy, "ABCDE", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_TOO_SHORT); + + /* Mixed credit checks */ + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, 0); + assert(ipapwd_check_policy(&policy, "SecreT123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_OK); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, 0); + assert(ipapwd_check_policy(&policy, "SECREt123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_MIN_LOWERS); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, 0); + assert(ipapwd_check_policy(&policy, "Secret123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_MIN_UPPERS); + set_policy(&policy, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1); + assert(ipapwd_check_policy(&policy, "SecreT123", NULL, 0, 0, 0, 0, NULL) == IPAPWD_POLICY_PWD_MIN_OTHERS); + return 0; }