# Own modules
# from fb_tools.common import to_bool, is_sequence, pp
-from fb_tools.common import is_sequence
+from fb_tools.common import is_sequence, pp
from ..xlate import XLATOR
from .ldap import BaseLdapApplication
from .ldap import PasswordFileOptionAction
-__version__ = '0.4.1'
+__version__ = '0.5.1'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
possible_schemes = (
'ldap_des_crypt',
- 'ldap_bcrypt',
'ldap_md5',
'ldap_md5_crypt',
'ldap_salted_md5',
'ldap_sha1',
'ldap_sha1_crypt',
'ldap_salted_sha1',
- 'ldap_pbkdf2_sha1',
'ldap_sha256_crypt',
'ldap_salted_sha256',
- 'ldap_pbkdf2_sha256',
'ldap_sha512_crypt',
'ldap_salted_sha512',
'ldap_pbkdf2_sha512',
schema_ids = {
'ldap_des_crypt': 'CRYPT',
- 'ldap_bcrypt': 'BCRYPT',
'ldap_md5': 'MD5',
- 'ldap_md5_crypt': 'MD5-CRYPT',
+ 'ldap_md5_crypt': 'CRYPT-MD5',
'ldap_salted_md5': 'SMD5',
'ldap_sha1': 'SHA',
'ldap_sha1_crypt': 'SHA-CRYPT',
'ldap_salted_sha1': 'SSHA',
- 'ldap_pbkdf2_sha1': 'PBKDF2-SHA',
- 'ldap_sha256_crypt': 'SHA256-CRYPT',
+ 'ldap_sha256_crypt': 'CRYPT-SHA256',
'ldap_salted_sha256': 'SSHA256',
- 'ldap_pbkdf2_sha256': 'PBKDF2-SHA256',
- 'ldap_sha512_crypt': 'SHA512-CRYPT',
+ 'ldap_sha512_crypt': 'CRYPT-SHA512',
'ldap_salted_sha512': 'SSHA512',
- 'ldap_pbkdf2_sha512': 'PBKDF2-SHA512',
+ 'ldap_pbkdf2_sha512': 'PBKDF2_SHA512',
+ }
+
+ schema_description = {
+ 'ldap_des_crypt': _('The ancient and notorious 3 DES crypt method.'),
+ 'ldap_md5': _('Pure {} hashing method.').format('MD5'),
+ 'ldap_md5_crypt': _("A {} based hashing algorithm.").format('MD5'),
+ 'ldap_salted_md5': _("Salted {} hashing method.").format('MD5'),
+ 'ldap_sha1': _('Pure {} hashing method.').format('SHA-1'),
+ 'ldap_sha1_crypt': _("A {} based hashing algorithm.").format('SHA-1'),
+ 'ldap_salted_sha1': _("Salted {} hashing method.").format('SHA-1'),
+ 'ldap_sha256_crypt': _("A {} based hashing algorithm.").format('SHA-256'),
+ 'ldap_salted_sha256': _("Salted {} hashing method.").format('SHA-256'),
+ 'ldap_sha512_crypt': _("A {} based hashing algorithm.").format('SHA-512'),
+ 'ldap_salted_sha512': _("Salted {} hashing method.").format('SHA-512'),
+ 'ldap_pbkdf2_sha512': _(
+ "A hashing method derived from {} with additional computing rounds.").format(
+ 'SHA-512'),
}
passlib_context = None
default_schema = 'ldap_salted_sha256'
+ default_schema_id = 'SSHA256'
+ default_pbkdf2_rounds = 30000
# -------------------------------------------------------------------------
@classmethod
if schema in all_handlers:
cls.available_schemes.append(schema)
- cls.passlib_context = passlib.context.CryptContext(schemes=cls.available_schemes)
+ cls.passlib_context = passlib.context.CryptContext(
+ schemes=cls.available_schemes, ldap_pbkdf2_sha512__rounds=cls.default_pbkdf2_rounds)
cls.passlib_context.update(default=cls.default_schema)
# -------------------------------------------------------------------------
self.current_password = None
self.need_current_password = False
+ self.current_password_hash = None
self.do_user_bind = False
self.ask_for_password = False
self.new_password = None
self.user_uid = None
self.user_dn = None
- self.schema = None
+ self.schema = self.default_schema
+ self.schema_id = self.default_schema_id
my_appname = self.get_generic_appname(appname)
"asked for it.").format(_("PASSWORD")),
)
+ schema_list = []
+ def_schema = ''
+ for method in self.available_schemes:
+ schema_id = self.schema_ids[method]
+ schema_list.append(schema_id)
+ if method == self.default_schema:
+ def_schema = schema_id
+ schema_list.append('list')
+
+ app_group.add_argument(
+ '-S', '--schema', metavar=_("SCHEMA"), dest="schema", choices=schema_list,
+ help=_(
+ "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)
+ )
+
user_help = _(
"The user, which password in the given LDAP instance should be changed. "
"It may be given by its Uid (the alphanumeric POSIX name), its mail address "
super(SetLdapPasswordApplication, self).post_init()
+ if self.verbose > 5:
+ msg = "Given args:\n" + pp(self.args.__dict__)
+ LOG.debug(msg)
+
+ given_schema = getattr(self.args, 'schema', None)
+ if given_schema:
+ if given_schema == 'list':
+ self._show_hashing_schemes()
+ self.exit(0)
+ return
+ for method in self.available_schemes:
+ schema_id = self.schema_ids[method]
+ LOG.debug("Testing for {m!r} ({s}) ...".format(m=method, s=schema_id))
+ if schema_id == given_schema:
+ self.passlib_context.update(default=method)
+ self.schema = method
+ self.schema_id = schema_id
+ break
+
given_user = getattr(self.args, 'user', None)
if given_user:
self.user_uid = given_user
if not ldap.is_admin or ldap.readonly:
self.do_user_bind = True
+ # -------------------------------------------------------------------------
+ def _show_hashing_schemes(self):
+
+ max_len_schema = 1
+ for method in self.available_schemes:
+ schema_id = self.schema_ids[method]
+ if len(schema_id) > max_len_schema:
+ max_len_schema = len(schema_id)
+
+ title = _("Usable Hashing schemes:")
+ print(title)
+ print('-' * len(title))
+ print()
+
+ for method in self.available_schemes:
+ schema_id = self.schema_ids[method]
+ desc = self.schema_description[method]
+ if 'pbkdf2' in method:
+ desc += ' ' + _(
+ "This schema cannot be used for authentication on a "
+ "current freeradius server.")
+ if method == self.schema:
+ desc += ' ' + _("This is the default schema.")
+
+ line = ' * {id:<{max_len}} - '.format(id=schema_id, max_len=max_len_schema)
+ line += desc
+ print(line)
+
+ print()
+
# -------------------------------------------------------------------------
def pre_run(self):
msg = _("Using LDAP instance {inst!r} - {url}.").format(inst=inst, url=connect_info.url)
LOG.info(msg)
- self.user_dn = self.search_user_dn()
+ 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) + ' '
self.new_password = self.get_password(
first_prompt, second_prompt, may_empty=False, repeat=True)
+ self.get_current_password_hash()
+ self.do_set_password()
+
# -------------------------------------------------------------------------
def test_user_bind(self):
connect_info.url))
del ldap_server
+ # -------------------------------------------------------------------------
+ def get_current_password_hash(self):
+
+ inst = self.ldap_instances[0]
+ connect_info = self.cfg.ldap_connection[inst]
+
+ if self.verbose > 1:
+ msg = _(
+ "Trying to get current password hash of user {!r} ...").format(self.user_dn)
+ LOG.debug(msg)
+
+ attributes = ['carLicense', 'ppFirstPassword', 'userPassword']
+
+ entry = self.get_entry(self.user_dn, inst, attributes=attributes)
+
+ if not entry:
+ msg = _("User with DN {dn!r} not found in {uri}.").format(
+ dn=self.user_dn, uri=connect_info.url)
+ LOG.error(msg)
+ self.exit(6)
+ return None
+
+ cur_pwd_hash = None
+ attribs = self.normalized_attributes(entry)
+ if attribs['userPassword']:
+ cur_pwd_hash = attribs['userPassword'][0]
+
+ self.current_password_hash = cur_pwd_hash
+
# -------------------------------------------------------------------------
def search_user_dn(self):
"""Searching the LDAP DN of the user, whos password should be changed."""
LOG.info(_("Changing the password of user {dn!r} in LDAP instance {inst}.").format(
dn=self.user_dn, inst=connect_info.url))
+ # -------------------------------------------------------------------------
+ def do_set_password(self):
+
+ print()
+ msg = _("Setting password of {dn!r} with hashing schema {schema!r}.").format(
+ dn=self.user_dn, schema=self.schema_id)
+ msg = _("Setting password of '{dn}' with hashing schema '{schema}' ...").format(
+ dn=self.colored(self.user_dn, 'CYAN'), schema=self.colored(self.schema_id, 'CYAN'))
+ print(msg)
+
+ if self.current_password_hash:
+ msg = _("Current password hash: '{}'.").format(
+ self.colored(self.current_password_hash, 'CYAN'))
+ else:
+ msg = _("The user '{}' has currently no password.").format(
+ self.colored(self.user_dn, 'CYAN'))
+ print(msg)
+
+ LOG.debug(_("Used schema: {!r}.").format(self.schema))
+ hashed_passwd = self.passlib_context.hash(self.new_password, self.schema)
+ msg = _("New password hash: '{}'.").format(self.colored(hashed_passwd, 'CYAN'))
+ print(msg)
+
+ print()
+ msg = _("Apply new password? [{yes}/{no}]?").format(
+ yes=self.colored(_('yes'), 'RED'), no=self.colored(_('No'), 'GREEN')) + ' '
+ do_set_passwd = False
+ if self.yes:
+ do_set_passwd = True
+ else:
+ do_set_passwd = self.ask_for_yes_or_no(msg, default_on_empty=False)
+ print()
+
+ if not do_set_passwd:
+ msg = _("Do not setting password for {!r}.").format(self.user_dn)
+ LOG.info(msg)
+ self.exit(0)
+ return
+
+ msg = _("Setting password ...")
+ LOG.info(msg)
+
# =============================================================================
if __name__ == "__main__":