From 06276a621b9005f08f4878ddd8221b31a675eea3 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 27 Aug 2018 03:40:50 +0200 Subject: [PATCH] [enh] Check password in cli/api --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/app.py | 3 +++ src/yunohost/settings.py | 15 ++++++++++++--- src/yunohost/tools.py | 33 ++++++++++++++++++++++++++++++++- src/yunohost/user.py | 7 +------ 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 8509bfb23..9c8ac191c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1489,6 +1489,9 @@ tools: --ignore-dyndns: help: Do not subscribe domain to a DynDNS service action: store_true + --force-password: + help: Use this if you really want to set a weak password + action: store_true ### tools_update() update: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1fed09425..f405a4070 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2251,6 +2251,9 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None): raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_choice_invalid', name=arg_name, choices='yes, no, y, n, 1, 0')) + elif arg_type == 'password': + from yunohost.tools import _check_password + _check_password(arg_value) args_dict[arg_name] = arg_value # END loop over action_args... diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index aba6e32b3..e694a861a 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -35,6 +35,12 @@ DEFAULTS = OrderedDict([ ("example.int", {"type": "int", "default": 42}), ("example.string", {"type": "string", "default": "yolo swag"}), ("example.enum", {"type": "enum", "default": "a", "choices": ["a", "b", "c"]}), + # Control the way password are checked + # -1 No control + # 0 Just display weak password info in debug + # 1 Warn user about weak password + # 2 Raise an error when the user put a weak password + ("security.password.check_mode", {"type": "int", "default": 2}), ]) @@ -90,9 +96,12 @@ def settings_set(key, value): received_type=type(value).__name__, expected_type=key_type)) elif key_type == "int": if not isinstance(value, int) or isinstance(value, bool): - raise MoulinetteError(errno.EINVAL, m18n.n( - 'global_settings_bad_type_for_setting', setting=key, - received_type=type(value).__name__, expected_type=key_type)) + if isinstance(value, str): + value=int(value) + else: + raise MoulinetteError(errno.EINVAL, m18n.n( + 'global_settings_bad_type_for_setting', setting=key, + received_type=type(value).__name__, expected_type=key_type)) elif key_type == "string": if not isinstance(value, basestring): raise MoulinetteError(errno.EINVAL, m18n.n( diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f9ee14994..b46d61259 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -32,6 +32,7 @@ import logging import subprocess import pwd import socket +import cracklib from xmlrpclib import Fault from importlib import import_module from collections import OrderedDict @@ -53,6 +54,7 @@ from yunohost.monitor import monitor_disk, monitor_system from yunohost.utils.packages import ynh_packages_version from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation, OperationLogger +from yunohost.settings import settings_get # FIXME this is a duplicate from apps.py APPS_SETTING_PATH = '/etc/yunohost/apps/' @@ -127,6 +129,8 @@ def tools_adminpw(auth, new_password): """ from yunohost.user import _hash_user_password + + _check_password(new_password) try: auth.update("cn=admin", { "userPassword": _hash_user_password(new_password), @@ -250,7 +254,8 @@ def _is_inside_container(): @is_unit_operation() -def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False): +def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, + force_password=False): """ YunoHost post-install @@ -268,6 +273,10 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False): raise MoulinetteError(errno.EPERM, m18n.n('yunohost_already_installed')) + # Check password + if not force_password: + _check_password(password) + if not ignore_dyndns: # Check if yunohost dyndns can handle the given domain # (i.e. is it a .nohost.me ? a .noho.st ?) @@ -299,6 +308,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False): else: dyndns = False + operation_logger.start() logger.info(m18n.n('yunohost_installing')) @@ -1046,3 +1056,24 @@ class Migration(object): @property def description(self): return m18n.n("migration_description_%s" % self.id) + +def _check_password(password): + security_level = settings_get('security.password.check_mode') + if security_level == -1: + return + try: + if password in ["yunohost", "olinuxino", "olinux"]: + raise MoulinetteError(errno.EINVAL, m18n.n('password_too_weak') + + ' : it is based on a (reversed) dictionary word' ) + + try: + cracklib.VeryFascistCheck(password) + except ValueError as e: + raise MoulinetteError(errno.EINVAL, m18n.n('password_too_weak') + " : " + str(e) ) + except MoulinetteError as e: + if security_level >= 2: + raise + elif security_level == 1: + logger.warn(e.strerror) + else: + logger.debug(e.strerror) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 7c5b847a2..ae7edfa2e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -39,15 +39,10 @@ from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from yunohost.service import service_status from yunohost.log import is_unit_operation +from yunohost.tools import _check_password logger = getActionLogger('yunohost.user') -def _check_password(password): - try: - cracklib.VeryFascistCheck(password) - except ValueError as e: - raise MoulinetteError(errno.EINVAL, m18n.n('password_too_weak') + " : " + str(e) ) - def user_list(auth, fields=None): """ List users