<commit_before># -*- coding: utf-8 -*-

""" License

    Copyright (C) 2013 YunoHost

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Affero 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program; if not, see http://www.gnu.org/licenses

"""

import sys
import os
import json
import cracklib
import string

SMALL_PWD_LIST = ["<PASSWORD>", "<PASSWORD>", "ol<PASSWORD>", "raspberry", "admin",
                  "root", "test", "rpi"]

MOST_USED_PASSWORDS = '/usr/local/share/dict/cracklib/100000-most-used'

# Length, digits, lowers, uppers, others
STRENGTH_LEVELS = [
    (6, 0, 0, 0, 0),
    (8, 1, 1, 1, 0),
    (8, 1, 1, 1, 1),
    (12, 1, 1, 1, 1),
]

def assert_password_is_strong_enough(profile, password):
    PasswordValidator(profile).validate(password)

class PasswordValidator(object):

    def __init__(self, profile):
        """
        Initialize a password validator.

        The profile shall be either "user" or "admin"
        and will correspond to a validation strength
        defined via the setting "security.password.<profile>.strength"
        """

        self.profile = profile
        try:
            # We do this "manually" instead of using settings_get()
            # from settings.py because this file is also meant to be
            # use as a script by ssowat.
            # (or at least that's my understanding -- Alex)
            settings = json.load(open('/etc/yunohost/settings.json', "r"))
            setting_key = "security.password." + profile + ".strength"
            self.validation_strength = int(settings[setting_key])
        except Exception as e:
            # Fallback to default value if we can't fetch settings for some reason
            self.validation_strength = 2 if profile == 'admin' else 1

    def validate(self, password):
        """
        Check the validation_summary and trigger the corresponding
        exception or info message

        This method is meant to be used from inside YunoHost's code
        (compared to validation_summary which is meant to be called
        by ssowat)
        """
        if self.validation_strength == -1:
            return

        # Note that those imports are made here and can't be put
        # on top (at least not the moulinette ones)
        # because the moulinette needs to be correctly initialized
        # as well as modules available in python's path.
        import errno
        import logging
        from moulinette import m18n
        from moulinette.core import MoulinetteError
        from moulinette.utils.log import getActionLogger

        logger = logging.getLogger('yunohost.utils.password')

        status, msg = validation_summary(password)
        if status == "error":
            raise MoulinetteError(1, m18n.n(msg))
        elif status == "warning":
            logger.info(m18n.n(msg))

    def validation_summary(self, password):
        """
        Check if a password is listed in the list of most used password
        and if the overall strength is good enough compared to the
        validation_strength defined in the constructor.

        Produces a summary-tuple comprised of a level (success, error, warning)
        and a message key describing the issues found.
        """
        if self.validation_strength <= 0:
            return ("success", "")

        listed = password in SMALL_PWD_LIST or self.is_in_cracklib_list(password)
        strength_level = self.strength_level(password)
        if listed:
            return ("error", "password_listed")
        if strength_level < self.validation_strength:
            return ("error", "password_too_simple_%s" % self.validation_strength)

        if strength_level < 3:
            return ("warning", 'password_advice')
        return ("success", "")

    def strength(self, password):
        """
        Returns the strength of a password, defined as a tuple
        containing the length of the password, the number of digits,
        lowercase letters, uppercase letters, and other characters.

        For instance, "P<PASSWORD>" is (11, 2, 7, 2, 0)
        """
<commit_msg>Consistency with comment in settings.py<commit_after># -*- coding: utf-8 -*-

""" License

    Copyright (C) 2013 YunoHost

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Affero 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program; if not, see http://www.gnu.org/licenses

"""

import sys
import os
import json
import cracklib
import string

SMALL_PWD_LIST = ["<PASSWORD>", "<PASSWORD>", "<PASSWORD>", "raspberry", "admin",
                  "root", "test", "rpi"]

MOST_USED_PASSWORDS = '/usr/local/share/dict/cracklib/100000-most-used'

# Length, digits, lowers, uppers, others
STRENGTH_LEVELS = [
    (6, 0, 0, 0, 0),
    (8, 1, 1, 1, 0),
    (8, 1, 1, 1, 1),
    (12, 1, 1, 1, 1),
]

def assert_password_is_strong_enough(profile, password):
    PasswordValidator(profile).validate(password)

class PasswordValidator(object):

    def __init__(self, profile):
        """
        Initialize a password validator.

        The profile shall be either "user" or "admin"
        and will correspond to a validation strength
        defined via the setting "security.password.<profile>.strength"
        """

        self.profile = profile
        try:
            # We do this "manually" instead of using settings_get()
            # from settings.py because this file is also meant to be
            # use as a script by ssowat.
            # (or at least that's my understanding -- Alex)
            settings = json.load(open('/etc/yunohost/settings.json', "r"))
            setting_key = "security.password." + profile + ".strength"
            self.validation_strength = int(settings[setting_key])
        except Exception as e:
            # Fallback to default value if we can't fetch settings for some reason
            self.validation_strength = 2 if profile == 'admin' else 1

    def validate(self, password):
        """
        Check the validation_summary and trigger the corresponding
        exception or info message

        This method is meant to be used from inside YunoHost's code
        (compared to validation_summary which is meant to be called
        by ssowat)
        """
        if self.validation_strength == -1:
            return

        # Note that those imports are made here and can't be put
        # on top (at least not the moulinette ones)
        # because the moulinette needs to be correctly initialized
        # as well as modules available in python's path.
        import errno
        import logging
        from moulinette import m18n
        from moulinette.core import MoulinetteError
        from moulinette.utils.log import getActionLogger

        logger = logging.getLogger('yunohost.utils.password')

        status, msg = validation_summary(password)
        if status == "error":
            raise MoulinetteError(1, m18n.n(msg))
        elif status == "warning":
            logger.info(m18n.n(msg))

    def validation_summary(self, password):
        """
        Check if a password is listed in the list of most used password
        and if the overall strength is good enough compared to the
        validation_strength defined in the constructor.

        Produces a summary-tuple comprised of a level (success, error, warning)
        and a message key describing the issues found.
        """
        if self.validation_strength < 0:
            return ("success", "")

        listed = password in SMALL_PWD_LIST or self.is_in_cracklib_list(password)
        strength_level = self.strength_level(password)
        if listed:
            return ("error", "password_listed")
        if strength_level < self.validation_strength:
            return ("error", "password_too_simple_%s" % self.validation_strength)

        if strength_level < 3:
            return ("warning", 'password_advice')
        return ("success", "")

    def strength(self, password):
        """
        Returns the strength of a password, defined as a tuple
        containing the length of the password, the number of digits,
        lowercase letters, uppercase letters, and other characters.

        For instance, "PikachuDu67" is (11, 2, 7, 2, 0)
        """

{'fraction_non_alphanumeric': 0.08081464630050915, 'fraction_numerical': 0.011266385007041491, 'mean_word_length': 2.507598784194529, 'pattern_counts': {'":': 4, '<': 18, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6621857', 'n_tokens_mistral': 2544, 'n_tokens_neox': 2390, 'n_words': 972}