# Third party modules
# from ldap3 import MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE
+from ldap3 import MODIFY_REPLACE
from ldap3.core.exceptions import LDAPBindError
import passlib.apps
+HAS_CRACKLIB = False
+try:
+ import cracklib
+ HAS_CRACKLIB = True
+except ImportError:
+ pass
+
# Own modules
# from fb_tools.common import to_bool, is_sequence, pp
from fb_tools.common import is_sequence, pp
from ..xlate import XLATOR
-from .ldap import LdapAppError
+from .ldap import LdapAppError, FatalLDAPError
from .ldap import BaseLdapApplication
from .ldap import PasswordFileOptionAction
-__version__ = '0.5.1'
+__version__ = '0.6.1'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
}
passlib_context = None
- default_schema = 'ldap_salted_sha256'
- default_schema_id = 'SSHA256'
+ default_schema = 'ldap_sha512_crypt'
+ default_schema_id = 'CRYPT-SHA512'
default_pbkdf2_rounds = 30000
# -------------------------------------------------------------------------
self.user_dn = None
self.schema = self.default_schema
self.schema_id = self.default_schema_id
+ self.no_cracklib = False
+
+ self.user_connection = None
my_appname = self.get_generic_appname(appname)
"asked for it.").format(_("PASSWORD")),
)
+ if HAS_CRACKLIB:
+ app_group.add_argument(
+ '-N', '--no-cracklib', action="store_true", dest="no_cracklib",
+ help=_(
+ "Do not check the quality of the new password with the "
+ "{} library.").format('cracklib'),
+ )
+
schema_list = []
def_schema = ''
for method in self.available_schemes:
"The schema (hashing method) to use to hash the new password. "
"It is possible to give here the value {val_list!r}, then all possible schemes "
"are shown and exit. Default: {default!r}.").format(
- val_list='list', default=def_schema)
+ val_list='list', default=def_schema) + ' ' + _("If you are not using an "
+ "admin account, then the password will hashed only by the default schema.")
)
user_help = _(
msg = "Given args:\n" + pp(self.args.__dict__)
LOG.debug(msg)
+ self.no_cracklib = getattr(self.args, 'no_cracklib', False)
+
given_schema = getattr(self.args, 'schema', None)
if given_schema:
if given_schema == 'list':
else:
if self.current_user:
given_user = self.current_user
+ if self.verbose > 1:
+ LOG.debug("User bind: no explicit user given.")
self.do_user_bind = True
self.user_uid = given_user
else:
self.current_password = self.args.current_pw
self.do_user_bind = True
elif self.args.current_pw_prompt:
+ if self.verbose > 1:
+ LOG.debug("User bind: Password input demanded.")
self.do_user_bind = True
elif self.args.current_pw_file:
+ if self.verbose > 1:
+ LOG.debug("User bind: Password file given.")
self.current_password = self.read_password_file(self.args.current_pw_file)
self.do_user_bind = True
inst = self.ldap_instances[0]
ldap = self.cfg.ldap_connection[inst]
if not ldap.is_admin or ldap.readonly:
+ if self.verbose > 1:
+ LOG.debug("User bind: LDAP instance is readonly or not as admin.")
self.do_user_bind = True
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def pre_run(self):
- LOG.debug("Pre running tasks ...")
- super(SetLdapPasswordApplication, self).pre_run()
-
- # -------------------------------------------------------------------------
- def _run(self):
-
inst = self.ldap_instances[0]
connect_info = self.cfg.ldap_connection[inst]
+
+ LOG.debug("Pre running tasks ...")
msg = _("Using LDAP instance {inst!r} - {url}.").format(inst=inst, url=connect_info.url)
LOG.info(msg)
- self.search_user_dn()
-
if self.do_user_bind and not self.current_password:
first_prompt = _("Current password of user {!r}:").format(self.user_uid) + ' '
second_prompt = _('Repeat password:') + ' '
self.current_password = self.get_password(
first_prompt, second_prompt, may_empty=False, repeat=False)
- if self.do_user_bind:
- self.test_user_bind()
+ if self.do_user_bind and self.schema != self.default_schema:
+ for method in self.available_schemes:
+ schema_id = self.schema_ids[method]
+ if self.verbose > 2:
+ LOG.debug("Testing for {m!r} ({s}) ...".format(m=method, s=schema_id))
+ if schema_id == self.default_schema:
+ self.passlib_context.update(default=method)
+ self.schema = method
+ self.schema_id = schema_id
+ msg = _(
+ "Non admin users must use the default schema {!r} for hashing "
+ "their password.").format(self.schema_id)
+ LOG.warn(msg)
if not self.new_password:
first_prompt = _("New password of user {!r}:").format(self.user_uid) + ' '
self.new_password = self.get_password(
first_prompt, second_prompt, may_empty=False, repeat=True)
- self.get_current_password_hash()
- self.do_set_password()
+ if HAS_CRACKLIB:
+ if self.no_cracklib:
+ msg = _("Checking the quality of the new password was disabled.")
+ LOG.warn(msg)
+ else:
+ LOG.info(_("Testing quality of new password ..."))
+ try:
+ cracklib.VeryFascistCheck(self.new_password)
+ except ValueError as e:
+ msg = _("Quality of the new password is not sufficient:") + ' ' + str(e)
+ LOG.error(msg)
+ self.exit(1)
+ LOG.debug("The quality of the new password seems to be sufficient.")
+ else:
+ msg = _(
+ "Cannot testing the quality of the new password, because the "
+ "Python module {!r} is not installed.").format('cracklib')
+ LOG.warn(msg)
+
+ super(SetLdapPasswordApplication, self).pre_run()
+
+ # -------------------------------------------------------------------------
+ def _run(self):
+
+ inst = self.ldap_instances[0]
+ connect_info = self.cfg.ldap_connection[inst]
+
+ self.search_user_dn()
+
+ if self.do_user_bind:
+ self.test_user_bind()
+ else:
+ self.user_connection = self.ldap_connection[inst]
+
+ try:
+ self.get_current_password_hash()
+ self.do_set_password()
+ finally:
+ if self.do_user_bind:
+ if self.user_connection:
+ if self.verbose > 1:
+ LOG.debug(_("Unbinding user connection from LDAP server {} ...").format(
+ connect_info.url))
+ self.user_connection.unbind()
+ self.user_connection = None
# -------------------------------------------------------------------------
def test_user_bind(self):
msg = _("Successful connected as {dn!r} to {url}.").format(
url=connect_info.url, dn=self.user_dn)
LOG.debug(msg)
+ self.user_connection = ldap_connection
except LDAPBindError as e:
msg = _("Could not connect to {url} as {dn!r}: {e}").format(
url=connect_info.url, dn=self.user_dn, e=e)
self.exit(6, msg)
- finally:
- if ldap_connection:
- if self.verbose > 1:
- LOG.debug(_("Unbinding from LDAP server {!r} ...").format(connect_info.url))
- ldap_connection.unbind()
- ldap_connection = None
- del ldap_connection
-
- if ldap_server:
- if self.verbose > 1:
- LOG.debug(_("Disconnecting from LDAP server {!r} ...").format(
- connect_info.url))
- del ldap_server
-
# -------------------------------------------------------------------------
def get_current_password_hash(self):
# -------------------------------------------------------------------------
def search_user_dn(self):
- """Searching the LDAP DN of the user, whos password should be changed."""
+ """Searching the LDAP DN of the user, whose password should be changed."""
inst = self.ldap_instances[0]
connect_info = self.cfg.ldap_connection[inst]
self.exit(0)
return
+ self.set_user_password(hashed_passwd)
+
+ # -------------------------------------------------------------------------
+ def set_user_password(self, hashed_passwd):
+
+ changes = {}
+ changes['userPassword'] = [(MODIFY_REPLACE, hashed_passwd)]
+
+ inst = self.ldap_instances[0]
+ connect_info = self.cfg.ldap_connection[inst]
+
msg = _("Setting password ...")
LOG.info(msg)
+ try:
+ self.modify_entry(inst, self.user_dn, changes, ldap=self.user_connection)
+ except FatalLDAPError as e:
+ msg = _("{c} on deactivating user {dn!r}: {e}").format(
+ c=e.__class__.__name__, dn=self.user_dn, e=e)
+ msg += '\n' + _('Changes:') + '\n' + pp(changes)
+ LOG.error(msg)
+
# =============================================================================
if __name__ == "__main__":