mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Implement LDAP bind grace period 389-ds plugin
Add support for bind grace limiting per https://datatracker.ietf.org/doc/html/draft-behera-ldap-password-policy-06 389-ds provides for alternative naming than the draft, using those instead: passwordGraceUserTime for pwdGraceUserTime and passwordGraceLimit for pwdGraceLoginLimit. passwordGraceLimit is a policy variable that an administrator sets to determine the maximum number of LDAP binds allowed when a password is marked as expired. This is suported for both the global and per-group password policies. passwordGraceUserTime is a count per-user of the number of binds. When the passwordGraceUserTime exceeds the passwordGraceLimit then all subsequent binds will be denied and an administrator will need to reset the user password. If passwordGraceLimit is less than 0 then grace limiting is disabled and unlimited binds are allowed. Grace login limitations only apply to entries with the objectclass posixAccount or simplesecurityobject in order to limit this to IPA users and system accounts. Some basic support for the LDAP ppolicy control is enabled such that if the ppolicy control is in the bind request then the number of remaining grace binds will be returned with the request. The passwordGraceUserTime attribute is reset to 0 upon a password reset. user-status has been extended to display the number of grace binds which is stored centrally and not per-server. Note that passwordGraceUserTime is an operational attribute. https://pagure.io/freeipa/issue/1539 Signed-off-by: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
committed by
Alexander Bokovoy
parent
0e6d9edd5d
commit
f347c3f230
4
ACI.txt
4
ACI.txt
@@ -241,9 +241,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")(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 = "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";)
|
||||
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")(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 || 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";)
|
||||
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
|
||||
|
||||
9
API.txt
9
API.txt
@@ -4058,7 +4058,7 @@ output: Entry('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: PrimaryKey('value')
|
||||
command: pwpolicy_add/1
|
||||
args: 1,18,3
|
||||
args: 1,19,3
|
||||
arg: Str('cn', cli_name='group')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -4075,6 +4075,7 @@ option: Int('krbpwdlockoutduration?', cli_name='lockouttime')
|
||||
option: Int('krbpwdmaxfailure?', cli_name='maxfail')
|
||||
option: Int('krbpwdmindiffchars?', cli_name='minclasses')
|
||||
option: Int('krbpwdminlength?', cli_name='minlength')
|
||||
option: Int('passwordgracelimit?', cli_name='gracelimit', default=0)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||
option: Str('setattr*', cli_name='setattr')
|
||||
option: Str('version?')
|
||||
@@ -4090,7 +4091,7 @@ output: Output('result', type=[<type 'dict'>])
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: ListOfPrimaryKeys('value')
|
||||
command: pwpolicy_find/1
|
||||
args: 1,20,4
|
||||
args: 1,21,4
|
||||
arg: Str('criteria?')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
option: Str('cn?', autofill=False, cli_name='group')
|
||||
@@ -4107,6 +4108,7 @@ option: Int('krbpwdlockoutduration?', autofill=False, cli_name='lockouttime')
|
||||
option: Int('krbpwdmaxfailure?', autofill=False, cli_name='maxfail')
|
||||
option: Int('krbpwdmindiffchars?', autofill=False, cli_name='minclasses')
|
||||
option: Int('krbpwdminlength?', autofill=False, cli_name='minlength')
|
||||
option: Int('passwordgracelimit?', autofill=False, cli_name='gracelimit', default=0)
|
||||
option: Flag('pkey_only?', autofill=True, default=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||
option: Int('sizelimit?', autofill=False)
|
||||
@@ -4117,7 +4119,7 @@ output: ListOfEntries('result')
|
||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||
output: Output('truncated', type=[<type 'bool'>])
|
||||
command: pwpolicy_mod/1
|
||||
args: 1,20,3
|
||||
args: 1,21,3
|
||||
arg: Str('cn?', cli_name='group')
|
||||
option: Str('addattr*', cli_name='addattr')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False)
|
||||
@@ -4135,6 +4137,7 @@ option: Int('krbpwdlockoutduration?', autofill=False, cli_name='lockouttime')
|
||||
option: Int('krbpwdmaxfailure?', autofill=False, cli_name='maxfail')
|
||||
option: Int('krbpwdmindiffchars?', autofill=False, cli_name='minclasses')
|
||||
option: Int('krbpwdminlength?', autofill=False, cli_name='minlength')
|
||||
option: Int('passwordgracelimit?', autofill=False, cli_name='gracelimit', default=0)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False)
|
||||
option: Flag('rights', autofill=True, default=False)
|
||||
option: Str('setattr*', cli_name='setattr')
|
||||
|
||||
@@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
|
||||
# #
|
||||
########################################################
|
||||
define(IPA_API_VERSION_MAJOR, 2)
|
||||
# Last change: add idp API
|
||||
define(IPA_API_VERSION_MINOR, 247)
|
||||
# Last change: add graceperiodlimit
|
||||
define(IPA_API_VERSION_MINOR, 248)
|
||||
|
||||
########################################################
|
||||
# Following values are auto-generated from values above
|
||||
|
||||
@@ -631,6 +631,7 @@ AC_CONFIG_FILES([
|
||||
daemons/ipa-slapi-plugins/ipa-cldap/Makefile
|
||||
daemons/ipa-slapi-plugins/ipa-dns/Makefile
|
||||
daemons/ipa-slapi-plugins/ipa-enrollment/Makefile
|
||||
daemons/ipa-slapi-plugins/ipa-graceperiod/Makefile
|
||||
daemons/ipa-slapi-plugins/ipa-lockout/Makefile
|
||||
daemons/ipa-slapi-plugins/ipa-otp-counter/Makefile
|
||||
daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile
|
||||
|
||||
@@ -5,6 +5,7 @@ SUBDIRS = \
|
||||
ipa-cldap \
|
||||
ipa-dns \
|
||||
ipa-enrollment \
|
||||
ipa-graceperiod \
|
||||
ipa-lockout \
|
||||
ipa-modrdn \
|
||||
ipa-otp-counter \
|
||||
|
||||
42
daemons/ipa-slapi-plugins/ipa-graceperiod/Makefile.am
Normal file
42
daemons/ipa-slapi-plugins/ipa-graceperiod/Makefile.am
Normal file
@@ -0,0 +1,42 @@
|
||||
NULL =
|
||||
|
||||
PLUGIN_COMMON_DIR = $(srcdir)/../common
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
-I$(srcdir) \
|
||||
-I$(PLUGIN_COMMON_DIR) \
|
||||
-DPREFIX=\""$(prefix)"\" \
|
||||
-DBINDIR=\""$(bindir)"\" \
|
||||
-DLIBDIR=\""$(libdir)"\" \
|
||||
-DLIBEXECDIR=\""$(libexecdir)"\" \
|
||||
-DDATADIR=\""$(datadir)"\" \
|
||||
-I$(top_srcdir)/util \
|
||||
$(DIRSRV_CFLAGS) \
|
||||
$(LDAP_CFLAGS) \
|
||||
$(WARN_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
plugindir = $(libdir)/dirsrv/plugins
|
||||
plugin_LTLIBRARIES = \
|
||||
libipa_graceperiod.la \
|
||||
$(NULL)
|
||||
|
||||
libipa_graceperiod_la_SOURCES = \
|
||||
ipa_graceperiod.c \
|
||||
$(NULL)
|
||||
|
||||
libipa_graceperiod_la_LDFLAGS = -avoid-version
|
||||
|
||||
libipa_graceperiod_la_LIBADD = \
|
||||
$(LDAP_LIBS) \
|
||||
$(top_builddir)/util/libutil.la \
|
||||
$(NULL)
|
||||
|
||||
appdir = $(IPA_DATA_DIR)
|
||||
app_DATA = \
|
||||
graceperiod-conf.ldif \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(app_DATA) \
|
||||
$(NULL)
|
||||
@@ -0,0 +1,15 @@
|
||||
dn: cn=IPA Graceperiod,cn=plugins,cn=config
|
||||
changetype: add
|
||||
objectclass: top
|
||||
objectclass: nsSlapdPlugin
|
||||
objectclass: extensibleObject
|
||||
cn: IPA Graceperiod
|
||||
nsslapd-pluginpath: libipa_graceperiod
|
||||
nsslapd-plugininitfunc: ipagraceperiod_init
|
||||
nsslapd-plugintype: object
|
||||
nsslapd-pluginenabled: on
|
||||
nsslapd-pluginid: ipagraceperiod_version
|
||||
nsslapd-pluginversion: 1.0
|
||||
nsslapd-pluginvendor: Red Hat, Inc.
|
||||
nsslapd-plugindescription: IPA Graceperiod plugin
|
||||
nsslapd-plugin-depends-on-type: database
|
||||
507
daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
Normal file
507
daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
Normal file
@@ -0,0 +1,507 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Additional permission under GPLv3 section 7:
|
||||
*
|
||||
* In the following paragraph, "GPL" means the GNU General Public
|
||||
* License, version 3 or any later version, and "Non-GPL Code" means
|
||||
* code that is governed neither by the GPL nor a license
|
||||
* compatible with the GPL.
|
||||
*
|
||||
* You may link the code of this Program with Non-GPL Code and convey
|
||||
* linked combinations including the two, provided that such Non-GPL
|
||||
* Code only links to the code of this Program through those well
|
||||
* defined interfaces identified in the file named EXCEPTION found in
|
||||
* the source code files (the "Approved Interfaces"). The files of
|
||||
* Non-GPL Code may instantiate templates or use macros or inline
|
||||
* functions from the Approved Interfaces without causing the resulting
|
||||
* work to be covered by the GPL. Only the copyright holders of this
|
||||
* Program may make changes or additions to the list of Approved
|
||||
* Interfaces.
|
||||
*
|
||||
* Copyright (C) 2022 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
* END COPYRIGHT BLOCK **/
|
||||
|
||||
/**
|
||||
* IPA Graceperiod plug-in
|
||||
*
|
||||
* Limit LDAP operations to password changes while in the grace period.
|
||||
*
|
||||
*/
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE 1
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include "slapi-plugin.h"
|
||||
#include "nspr.h"
|
||||
#include <krb5.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "ipa_pwd.h"
|
||||
|
||||
#define IPAGRACEPERIOD_PLUGIN_NAME "ipa-graceperiod-plugin"
|
||||
#define IPAGRACEPERIOD_PLUGIN_VERSION 0x00010000
|
||||
|
||||
#define IPA_PLUGIN_NAME IPAGRACEPERIOD_PLUGIN_NAME
|
||||
|
||||
#define IPAGRACEPERIOD_FEATURE_DESC "IPA Graceperiod"
|
||||
#define IPAGRACEPERIOD_PLUGIN_DESC "IPA Graceperiod plugin"
|
||||
#define IPAGRACEPERIOD_PREOP_DESC "IPA Graceperiod preop plugin"
|
||||
|
||||
static Slapi_PluginDesc pdesc = {
|
||||
IPAGRACEPERIOD_FEATURE_DESC,
|
||||
"Red Hat, Inc.",
|
||||
"1.0",
|
||||
IPAGRACEPERIOD_PLUGIN_DESC
|
||||
};
|
||||
|
||||
struct ipa_context {
|
||||
bool disable_last_success;
|
||||
bool disable_lockout;
|
||||
};
|
||||
|
||||
static void *_PluginID = NULL;
|
||||
|
||||
static int g_plugin_started = 0;
|
||||
|
||||
#if 0
|
||||
static struct ipa_context *global_ipactx = NULL;
|
||||
#endif
|
||||
|
||||
static char *ipa_global_policy = NULL;
|
||||
|
||||
int ipagraceperiod_getpolicy(Slapi_Entry *target_entry, Slapi_Entry **policy_entry,
|
||||
Slapi_ValueSet** values, char **actual_type_name,
|
||||
const char **policy_dn, int *attr_free_flags,
|
||||
char **errstr);
|
||||
int ipagraceperiod_version(void);
|
||||
|
||||
static void *getPluginID(void);
|
||||
static void setPluginID(void *pluginID);
|
||||
|
||||
#define GENERALIZED_TIME_LENGTH 15
|
||||
|
||||
/**
|
||||
*
|
||||
* management functions
|
||||
*
|
||||
*/
|
||||
int ipagraceperiod_init(Slapi_PBlock * pb);
|
||||
|
||||
static int ipagraceperiod_start(Slapi_PBlock * pb);
|
||||
static int ipagraceperiod_close(Slapi_PBlock * pb);
|
||||
static int ipagraceperiod_preop_init(Slapi_PBlock * pb);
|
||||
static int ipagraceperiod_get_global_config(void);
|
||||
|
||||
/**
|
||||
*
|
||||
* the ops (where the real work is done)
|
||||
*
|
||||
*/
|
||||
static int ipagraceperiod_preop(Slapi_PBlock *pb);
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the plug-in version
|
||||
*
|
||||
*/
|
||||
int ipagraceperiod_version(void)
|
||||
{
|
||||
return IPAGRACEPERIOD_PLUGIN_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin identity mgmt
|
||||
*/
|
||||
static void setPluginID(void *pluginID)
|
||||
{
|
||||
_PluginID = pluginID;
|
||||
}
|
||||
|
||||
static void *getPluginID(void)
|
||||
{
|
||||
return _PluginID;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ipagraceperiod_get_global_config(void)
|
||||
{
|
||||
char *dn = NULL;
|
||||
char *basedn = NULL;
|
||||
char *realm = NULL;
|
||||
Slapi_DN *sdn;
|
||||
Slapi_Entry *config_entry = NULL;
|
||||
krb5_context krbctx = NULL;
|
||||
krb5_error_code krberr;
|
||||
int ret;
|
||||
|
||||
/* Get cn=config so we can get the default naming context */
|
||||
sdn = slapi_sdn_new_dn_byref("cn=config");
|
||||
|
||||
ret = slapi_search_internal_get_entry(sdn, NULL, &config_entry,
|
||||
getPluginID());
|
||||
|
||||
slapi_sdn_free(&sdn);
|
||||
|
||||
if (ret) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
basedn = slapi_entry_attr_get_charptr(config_entry,
|
||||
"nsslapd-defaultnamingcontext");
|
||||
|
||||
slapi_entry_free(config_entry);
|
||||
config_entry = NULL;
|
||||
|
||||
if (!basedn) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
krberr = krb5_init_context(&krbctx);
|
||||
if (krberr) {
|
||||
LOG_FATAL("krb5_init_context failed (%d)\n", krberr);
|
||||
/* Yes, we failed, but it is because /etc/krb5.conf doesn't exist
|
||||
* or is misconfigured. Start up in a degraded mode.
|
||||
*/
|
||||
} else {
|
||||
krberr = krb5_get_default_realm(krbctx, &realm);
|
||||
if (krberr) {
|
||||
LOG_FATAL("Failed to get default realm (%d)\n", krberr);
|
||||
} else {
|
||||
ipa_global_policy =
|
||||
slapi_ch_smprintf("cn=global_policy,cn=%s,cn=kerberos,%s",
|
||||
realm, basedn);
|
||||
if (!ipa_global_policy) {
|
||||
LOG_OOM();
|
||||
ret = LDAP_OPERATIONS_ERROR;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
if (config_entry)
|
||||
slapi_entry_free(config_entry);
|
||||
free(realm);
|
||||
krb5_free_context(krbctx);
|
||||
free(dn);
|
||||
free(basedn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ipagraceperiod_getpolicy(Slapi_Entry *target_entry, Slapi_Entry **policy_entry,
|
||||
Slapi_ValueSet** values, char **actual_type_name,
|
||||
const char **policy_dn, int *attr_free_flags,
|
||||
char **errstr)
|
||||
{
|
||||
int ldrc = 0;
|
||||
int type_name_disposition = 0;
|
||||
Slapi_DN *pdn = NULL;
|
||||
|
||||
/* Only continue if there is a password policy */
|
||||
ldrc = slapi_vattr_values_get(target_entry, "krbPwdPolicyReference",
|
||||
values,
|
||||
&type_name_disposition, actual_type_name,
|
||||
SLAPI_VIRTUALATTRS_REQUEST_POINTERS,
|
||||
attr_free_flags);
|
||||
if (ldrc == 0) {
|
||||
Slapi_Value *sv = NULL;
|
||||
|
||||
if (values != NULL) {
|
||||
slapi_valueset_first_value(*values, &sv);
|
||||
*policy_dn = slapi_value_get_string(sv);
|
||||
}
|
||||
} else {
|
||||
*policy_dn = ipa_global_policy;
|
||||
}
|
||||
|
||||
if (*policy_dn == NULL) {
|
||||
LOG_TRACE("No kerberos password policy\n");
|
||||
return LDAP_SUCCESS;
|
||||
} else {
|
||||
pdn = slapi_sdn_new_dn_byref(*policy_dn);
|
||||
ldrc = slapi_search_internal_get_entry(pdn, NULL, policy_entry,
|
||||
getPluginID());
|
||||
slapi_sdn_free(&pdn);
|
||||
if (ldrc != LDAP_SUCCESS) {
|
||||
LOG_FATAL("Failed to retrieve entry \"%s\": %d\n", *policy_dn, ldrc);
|
||||
*errstr = "Failed to retrieve account policy.";
|
||||
return LDAP_OPERATIONS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
ipagraceperiod_init(Slapi_PBlock *pb)
|
||||
{
|
||||
int status = EOK;
|
||||
char *plugin_identity = NULL;
|
||||
|
||||
LOG_TRACE("--in-->\n");
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
|
||||
PR_ASSERT(plugin_identity);
|
||||
setPluginID(plugin_identity);
|
||||
|
||||
if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
|
||||
SLAPI_PLUGIN_VERSION_01) != 0 ||
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
|
||||
(void *) ipagraceperiod_start) != 0 ||
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
|
||||
(void *) ipagraceperiod_close) != 0 ||
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
|
||||
(void *) &pdesc) != 0 ||
|
||||
slapi_register_plugin("preoperation",
|
||||
1,
|
||||
"ipagraceperiod_init",
|
||||
ipagraceperiod_preop_init,
|
||||
IPAGRACEPERIOD_PREOP_DESC,
|
||||
NULL,
|
||||
plugin_identity
|
||||
)
|
||||
) {
|
||||
LOG_FATAL("failed to register plugin\n");
|
||||
status = EFAIL;
|
||||
}
|
||||
|
||||
LOG_TRACE("<--out--\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
ipagraceperiod_preop_init(Slapi_PBlock *pb)
|
||||
{
|
||||
int status = EOK;
|
||||
|
||||
if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
|
||||
SLAPI_PLUGIN_VERSION_01) != 0 ||
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
|
||||
(void *) &pdesc) != 0 ||
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN,
|
||||
(void *) ipagraceperiod_preop) != 0) {
|
||||
status = EFAIL;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
ipagraceperiod_start(Slapi_PBlock * pb)
|
||||
{
|
||||
LOG_TRACE("--in-->\n");
|
||||
|
||||
/* Check if we're already started */
|
||||
if (g_plugin_started) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
g_plugin_started = 1;
|
||||
|
||||
ipagraceperiod_get_global_config();
|
||||
|
||||
LOG("ready for service\n");
|
||||
|
||||
done:
|
||||
LOG_TRACE("<--out--\n");
|
||||
return EOK;
|
||||
}
|
||||
|
||||
static int
|
||||
ipagraceperiod_close(Slapi_PBlock * pb)
|
||||
{
|
||||
LOG_TRACE( "--in-->\n");
|
||||
|
||||
slapi_ch_free_string(&ipa_global_policy);
|
||||
|
||||
LOG_TRACE("<--out--\n");
|
||||
|
||||
return EOK;
|
||||
}
|
||||
|
||||
/*
|
||||
* In the pre-op stage the bind hasn't occurred yet. It is here that
|
||||
* we do the lockout enforcement.
|
||||
*/
|
||||
static int ipagraceperiod_preop(Slapi_PBlock *pb)
|
||||
{
|
||||
char *dn = NULL;
|
||||
const char *policy_dn = NULL;
|
||||
Slapi_Entry *target_entry = NULL;
|
||||
Slapi_Entry *policy_entry = NULL;
|
||||
Slapi_Value *objectclass = NULL;
|
||||
Slapi_DN *sdn = NULL;
|
||||
char *errstr = NULL;
|
||||
int ldrc = 0;
|
||||
int rc = 0;
|
||||
int ret = LDAP_SUCCESS;
|
||||
char *actual_type_name = NULL;
|
||||
int attr_free_flags = 0;
|
||||
Slapi_ValueSet *values = NULL;
|
||||
long grace_limit = 0;
|
||||
int grace_user_time;
|
||||
char *pwd_expiration = NULL;
|
||||
int pwresponse_requested = 0;
|
||||
Slapi_PBlock *pbtm = NULL;
|
||||
Slapi_Mods *smods = NULL;
|
||||
|
||||
LOG_TRACE("--in-->\n");
|
||||
|
||||
/* Just bail if we aren't ready to service requests yet. */
|
||||
if (!g_plugin_started) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn) != 0) {
|
||||
LOG_FATAL("Error retrieving target DN\n");
|
||||
ret = LDAP_OPERATIONS_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Client is anonymously bound */
|
||||
if (dn == NULL) {
|
||||
LOG_TRACE("anonymous bind\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Get the entry */
|
||||
sdn = slapi_sdn_new_dn_byref(dn);
|
||||
if (sdn == NULL) {
|
||||
LOG_OOM();
|
||||
errstr = "Out of memory.\n";
|
||||
ret = LDAP_OPERATIONS_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ldrc = slapi_search_internal_get_entry(sdn, NULL, &target_entry,
|
||||
getPluginID());
|
||||
|
||||
if (ldrc != LDAP_SUCCESS) {
|
||||
LOG_TRACE("Failed to retrieve entry \"%s\": %d\n", dn, ldrc);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Only deal with users and sysaccount entries */
|
||||
objectclass = slapi_value_new_string("posixAccount");
|
||||
if ((slapi_entry_attr_has_syntax_value(target_entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) != 1) {
|
||||
LOG_TRACE("Not a posix user\n");
|
||||
slapi_value_free(&objectclass);
|
||||
objectclass = slapi_value_new_string("simplesecurityobject");
|
||||
if ((slapi_entry_attr_has_syntax_value(target_entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) != 1) {
|
||||
LOG_TRACE("Not a sysaccount user\n");
|
||||
slapi_value_free(&objectclass);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
slapi_value_free(&objectclass);
|
||||
|
||||
pwd_expiration = slapi_entry_attr_get_charptr(target_entry, "krbPasswordExpiration");
|
||||
if (pwd_expiration == NULL) {
|
||||
/* No expiration means nothing to do */
|
||||
LOG_TRACE("No krbPasswordExpiration for %s, nothing to do\n", dn);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ldrc = ipagraceperiod_getpolicy(target_entry, &policy_entry,
|
||||
&values, &actual_type_name,
|
||||
&policy_dn, &attr_free_flags,
|
||||
&errstr);
|
||||
if (ldrc != LDAP_SUCCESS || policy_dn == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_PWPOLICY, &pwresponse_requested);
|
||||
|
||||
/* This returns 0 if the attribute doesn't exist, so no grace but
|
||||
* report that logins are not allowed.
|
||||
*/
|
||||
grace_limit = slapi_entry_attr_get_int(policy_entry, "passwordGraceLimit");
|
||||
|
||||
/* -1 means disable grace limit */
|
||||
if (grace_limit == -1) {
|
||||
LOG_TRACE("grace limit disabled, skipping\n");
|
||||
goto done;
|
||||
} else if (grace_limit < -1) {
|
||||
LOG_FATAL("Invalid passwordGraceLimit value %d\n", grace_limit);
|
||||
return LDAP_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
grace_user_time = slapi_entry_attr_get_int(target_entry, "passwordGraceUserTime");
|
||||
|
||||
if ((grace_limit > 0) && (grace_user_time < grace_limit)) {
|
||||
char graceUserTime[16] = {0};
|
||||
|
||||
grace_user_time++;
|
||||
sprintf(graceUserTime, "%d", grace_user_time);
|
||||
smods = slapi_mods_new();
|
||||
slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
|
||||
"passwordGraceUserTime", graceUserTime);
|
||||
|
||||
pbtm = slapi_pblock_new();
|
||||
slapi_modify_internal_set_pb(pbtm,
|
||||
slapi_entry_get_dn_const(target_entry),
|
||||
slapi_mods_get_ldapmods_byref(smods),
|
||||
NULL, NULL, getPluginID(), 0);
|
||||
|
||||
slapi_modify_internal_pb(pbtm);
|
||||
slapi_pblock_get(pbtm, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
|
||||
if (rc != LDAP_SUCCESS) {
|
||||
LOG_TRACE("WARNING: modify error %d on entry '%s'\n",
|
||||
rc, slapi_entry_get_dn_const(target_entry));
|
||||
}
|
||||
|
||||
if (pwresponse_requested) {
|
||||
slapi_pwpolicy_make_response_control(pb, -1, grace_limit - grace_user_time , -1);
|
||||
}
|
||||
} else if ((grace_limit > 0) && (grace_user_time >= grace_limit)) {
|
||||
LOG_TRACE("%s password is expired and out of grace limit\n", dn);
|
||||
errstr = "Password is expired.\n";
|
||||
ret = LDAP_INVALID_CREDENTIALS;
|
||||
|
||||
if (pwresponse_requested) {
|
||||
slapi_pwpolicy_make_response_control(pb, -1, 0, LDAP_PWPOLICY_PWDEXPIRED);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0);
|
||||
|
||||
done:
|
||||
slapi_pblock_destroy(pbtm);
|
||||
slapi_mods_free(&smods);
|
||||
slapi_entry_free(target_entry);
|
||||
slapi_entry_free(policy_entry);
|
||||
if (values != NULL) {
|
||||
slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
|
||||
}
|
||||
if (sdn) slapi_sdn_free(&sdn);
|
||||
|
||||
LOG("preop returning %d: %s\n", ret, errstr ? errstr : "success\n");
|
||||
|
||||
if (ret) {
|
||||
slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
|
||||
}
|
||||
|
||||
LOG_TRACE("<--out--\n");
|
||||
|
||||
return (ret == 0 ? EOK : EFAIL);
|
||||
}
|
||||
@@ -1071,6 +1071,7 @@ static int ipapwd_post_modadd(Slapi_PBlock *pb)
|
||||
struct ipapwd_krbcfg *krbcfg = NULL;
|
||||
char *principal = NULL;
|
||||
Slapi_Value *ipahost;
|
||||
Slapi_Value *zero;
|
||||
|
||||
LOG_TRACE("=>\n");
|
||||
|
||||
@@ -1167,6 +1168,13 @@ static int ipapwd_post_modadd(Slapi_PBlock *pb)
|
||||
}
|
||||
slapi_value_free(&ipahost);
|
||||
}
|
||||
zero = slapi_value_new_string("0");
|
||||
if (!slapi_entry_attr_has_syntax_value(pwdop->pwdata.target,
|
||||
"passwordgraceusertime", zero)) {
|
||||
/* Clear the passwordgraceusertime from the user entry */
|
||||
slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "passwordgraceusertime", "0");
|
||||
}
|
||||
slapi_value_free(&zero);
|
||||
|
||||
ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
|
||||
if (ret)
|
||||
|
||||
@@ -1067,6 +1067,7 @@ rm %{buildroot}/%{plugin_dir}/libipa_extdom_extop.la
|
||||
rm %{buildroot}/%{plugin_dir}/libipa_range_check.la
|
||||
rm %{buildroot}/%{plugin_dir}/libipa_otp_counter.la
|
||||
rm %{buildroot}/%{plugin_dir}/libipa_otp_lasttoken.la
|
||||
rm %{buildroot}/%{plugin_dir}/libipa_graceperiod.la
|
||||
rm %{buildroot}/%{plugin_dir}/libtopology.la
|
||||
rm %{buildroot}/%{_libdir}/krb5/plugins/kdb/ipadb.la
|
||||
rm %{buildroot}/%{_libdir}/samba/pdb/ipasam.la
|
||||
@@ -1426,6 +1427,7 @@ fi
|
||||
%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa_graceperiod.so
|
||||
%attr(755,root,root) %{_libdir}/krb5/plugins/kdb/ipadb.so
|
||||
%{_mandir}/man1/ipa-replica-conncheck.1*
|
||||
%{_mandir}/man1/ipa-replica-install.1*
|
||||
|
||||
@@ -58,4 +58,4 @@ 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) 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')
|
||||
|
||||
@@ -246,6 +246,8 @@ class DsInstance(service.Service):
|
||||
self.step("configuring DNS plugin", self.__config_dns_module)
|
||||
self.step("enabling entryUSN plugin", self.__enable_entryusn)
|
||||
self.step("configuring lockout plugin", self.__config_lockout_module)
|
||||
self.step("configuring graceperiod plugin",
|
||||
self.__config_graceperiod_module)
|
||||
self.step("configuring topology plugin", self.__config_topology_module)
|
||||
self.step("creating indices", self.__create_indices)
|
||||
self.step("enabling referential integrity plugin", self.__add_referint_module)
|
||||
@@ -751,6 +753,9 @@ class DsInstance(service.Service):
|
||||
def __config_lockout_module(self):
|
||||
self._ldap_mod("lockout-conf.ldif")
|
||||
|
||||
def __config_graceperiod_module(self):
|
||||
self._ldap_mod("graceperiod-conf.ldif")
|
||||
|
||||
def __config_topology_module(self):
|
||||
self._ldap_mod("ipa-topology-conf.ldif", self.sub_dict)
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ class pwpolicy(LDAPObject):
|
||||
'krbpwdmaxfailure', 'krbpwdfailurecountinterval',
|
||||
'krbpwdlockoutduration', 'ipapwdmaxrepeat',
|
||||
'ipapwdmaxsequence', 'ipapwddictcheck',
|
||||
'ipapwdusercheck',
|
||||
'ipapwdusercheck', 'passwordgracelimit',
|
||||
]
|
||||
managed_permissions = {
|
||||
'System: Read Group Password Policy': {
|
||||
@@ -257,7 +257,7 @@ class pwpolicy(LDAPObject):
|
||||
'krbpwdlockoutduration', 'krbpwdmaxfailure',
|
||||
'krbpwdmindiffchars', 'krbpwdminlength', 'objectclass',
|
||||
'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck',
|
||||
'ipapwdusercheck',
|
||||
'ipapwdusercheck', 'passwordgracelimit',
|
||||
},
|
||||
'default_privileges': {
|
||||
'Password Policy Readers',
|
||||
@@ -285,7 +285,7 @@ class pwpolicy(LDAPObject):
|
||||
'krbpwdhistorylength', 'krbpwdlockoutduration',
|
||||
'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength',
|
||||
'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck',
|
||||
'ipapwdusercheck',
|
||||
'ipapwdusercheck', 'passwordgracelimit',
|
||||
},
|
||||
'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";)',
|
||||
@@ -396,6 +396,15 @@ class pwpolicy(LDAPObject):
|
||||
doc=_('Check if the password contains the username'),
|
||||
default=False,
|
||||
),
|
||||
Int(
|
||||
'passwordgracelimit?',
|
||||
cli_name='gracelimit',
|
||||
label=_('Grace login limit'),
|
||||
doc=_('Number of LDAP authentications allowed after expiration'),
|
||||
minvalue=-1,
|
||||
maxvalue=Int.MAX_UINT32,
|
||||
default=0,
|
||||
),
|
||||
)
|
||||
|
||||
def get_dn(self, *keys, **options):
|
||||
|
||||
@@ -1189,6 +1189,9 @@ class userstatus(LDAPObject):
|
||||
label=_('Time now'),
|
||||
flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
|
||||
),
|
||||
Str('passwordgraceusertime',
|
||||
label=_('Password grace count'),
|
||||
flags={'no_create', 'no_update', 'no_search'},),
|
||||
)
|
||||
|
||||
|
||||
@@ -1230,7 +1233,9 @@ class user_status(LDAPQuery):
|
||||
def execute(self, *keys, **options):
|
||||
ldap = self.obj.backend
|
||||
dn, _oc = self.api.Object.user.get_either_dn(*keys, **options)
|
||||
attr_list = ['krbloginfailedcount', 'krblastsuccessfulauth', 'krblastfailedauth', 'nsaccountlock']
|
||||
attr_list = ['krbloginfailedcount', 'krblastsuccessfulauth',
|
||||
'krblastfailedauth', 'nsaccountlock',
|
||||
'passwordgraceusertime']
|
||||
|
||||
disabled = False
|
||||
masters = get_masters(ldap)
|
||||
@@ -1258,6 +1263,8 @@ class user_status(LDAPQuery):
|
||||
for attr in ['krblastsuccessfulauth', 'krblastfailedauth']:
|
||||
newresult[attr] = entry.get(attr, [u'N/A'])
|
||||
newresult['krbloginfailedcount'] = entry.get('krbloginfailedcount', u'0')
|
||||
newresult['passwordgraceusertime'] = \
|
||||
entry.get('passwordgraceusertime', u'0')
|
||||
if not options.get('raw', False):
|
||||
for attr in ['krblastsuccessfulauth', 'krblastfailedauth']:
|
||||
try:
|
||||
|
||||
@@ -20,6 +20,7 @@ class TestPWPolicy(IntegrationTest):
|
||||
"""
|
||||
Test password policy in action.
|
||||
"""
|
||||
num_replicas = 1
|
||||
|
||||
topology = 'line'
|
||||
|
||||
@@ -261,3 +262,92 @@ class TestPWPolicy(IntegrationTest):
|
||||
)
|
||||
assert result.returncode != 0
|
||||
assert 'minlength' in result.stderr_text
|
||||
|
||||
def test_graceperiod_expired(self):
|
||||
"""Test the LDAP bind grace period"""
|
||||
str(self.master.domain.basedn)
|
||||
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
|
||||
user=USER, base_dn=str(self.master.domain.basedn))
|
||||
|
||||
self.master.run_command(
|
||||
["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "3", ],
|
||||
)
|
||||
|
||||
# Resetting the password will mark it as expired
|
||||
self.reset_password(self.master)
|
||||
|
||||
for i in range(2, -1, -1):
|
||||
result = self.master.run_command(
|
||||
["ldapsearch", "-e", "ppolicy", "-D", dn,
|
||||
"-w", PASSWORD, "-b", dn], raiseonerr=False
|
||||
)
|
||||
# We're in grace, this will succeed
|
||||
assert result.returncode == 0
|
||||
|
||||
# verify that we get the expected ppolicy output
|
||||
assert 'Password expired, {} grace logins remain'.format(i) \
|
||||
in result.stderr_text
|
||||
|
||||
# Now grace is done and binds should fail.
|
||||
result = self.master.run_command(
|
||||
["ldapsearch", "-e", "ppolicy", "-D", dn,
|
||||
"-w", PASSWORD, "-b", dn], raiseonerr=False
|
||||
)
|
||||
assert result.returncode == 49
|
||||
|
||||
assert 'Password is expired' in result.stderr_text
|
||||
assert 'Password expired, 0 grace logins remain' in result.stderr_text
|
||||
|
||||
# Test that resetting the password resets the grace counter
|
||||
self.reset_password(self.master)
|
||||
result = tasks.ldapsearch_dm(
|
||||
self.master, dn, ['passwordgraceusertime',],
|
||||
)
|
||||
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
|
||||
def test_graceperiod_not_replicated(self):
|
||||
"""Test that the grace period is reset on password reset"""
|
||||
str(self.master.domain.basedn)
|
||||
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
|
||||
user=USER, base_dn=str(self.master.domain.basedn))
|
||||
|
||||
self.master.run_command(
|
||||
["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "3", ],
|
||||
)
|
||||
|
||||
# Resetting the password will mark it as expired
|
||||
self.reset_password(self.master)
|
||||
|
||||
# Generate some logins but don't exceed the limit
|
||||
for _i in range(2, -1, -1):
|
||||
result = self.master.run_command(
|
||||
["ldapsearch", "-e", "ppolicy", "-D", dn,
|
||||
"-w", PASSWORD, "-b", dn], raiseonerr=False
|
||||
)
|
||||
|
||||
# Verify that passwordgraceusertime is not replicated
|
||||
result = tasks.ldapsearch_dm(
|
||||
self.master, dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 2' in result.stdout_text.lower()
|
||||
|
||||
result = tasks.ldapsearch_dm(
|
||||
self.replicas[0], dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
|
||||
self.reset_password(self.master)
|
||||
|
||||
# Resetting the password should reset passwordgraceusertime
|
||||
result = tasks.ldapsearch_dm(
|
||||
self.master, dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
self.reset_password(self.master)
|
||||
|
||||
result = tasks.ldapsearch_dm(
|
||||
self.replicas[0], dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
self.reset_password(self.master)
|
||||
|
||||
@@ -222,6 +222,7 @@ class TestUser(XMLRPC_test):
|
||||
krblastfailedauth=[u'N/A'],
|
||||
krblastsuccessfulauth=[u'N/A'],
|
||||
krbloginfailedcount=u'0',
|
||||
passwordgraceusertime=u'0',
|
||||
now=isodate_re.match,
|
||||
server=api.env.host,
|
||||
), ],
|
||||
|
||||
Reference in New Issue
Block a user