diff --git a/login_recaptcha/README.rst b/login_recaptcha/README.rst new file mode 100644 index 00000000000..3c432edf8de --- /dev/null +++ b/login_recaptcha/README.rst @@ -0,0 +1,28 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=============== +Login ReCaptcha +=============== + +This module was written to allow a captcha confirmation when users log in into the system. +If the user fails 3 times to log in a Google reCAPTCHA is shown. +If the user fails 8 times the system send reset password email. + +Credits +======= + +Contributors +------------ + +* Lesmed Gutiérrez + +Maintainer +---------- + +.. image:: https://avatars0.githubusercontent.com/u/7594691?v=3&s=200 + :alt: ClearCorp + :target: http://clearcorp.cr + +This module is maintained by ClearCorp. diff --git a/login_recaptcha/__init__.py b/login_recaptcha/__init__.py new file mode 100644 index 00000000000..f7629aed51c --- /dev/null +++ b/login_recaptcha/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models, controllers diff --git a/login_recaptcha/__openerp__.py b/login_recaptcha/__openerp__.py new file mode 100644 index 00000000000..3609bb1f469 --- /dev/null +++ b/login_recaptcha/__openerp__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Login reCAPTCHA', + 'summary': 'Login captcha using Google reCAPTCHA', + 'version': '8.0.1.0', + 'category': 'Hidden', + 'website': 'http://clearcorp.cr', + 'author': 'ClearCorp', + 'license': 'AGPL-3', + 'sequence': 10, + 'application': False, + 'installable': True, + 'auto_install': False, + 'external_dependencies': { + 'python': [], + 'bin': [], + }, + 'depends': [ + 'website' + ], + 'data': [ + 'static/src/xml/login.xml', + 'views/website_config.xml' + ], + 'qweb': [ + ], +} diff --git a/login_recaptcha/controllers/__init__.py b/login_recaptcha/controllers/__init__.py new file mode 100644 index 00000000000..9049ba4a379 --- /dev/null +++ b/login_recaptcha/controllers/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import web diff --git a/login_recaptcha/controllers/web.py b/login_recaptcha/controllers/web.py new file mode 100644 index 00000000000..51e8f47124c --- /dev/null +++ b/login_recaptcha/controllers/web.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import http, _ +from openerp.addons.web.controllers import main +from openerp.http import request +from werkzeug.contrib.securecookie import SecureCookie +import json + +SECRET_KEY = '\x9a\x832I\x80\\\x83\x88\x1c\xc0\xd4u)\x8f\xed\xbb\xdbK\x8e\xb6' + + +class JSONSecureCookie(SecureCookie): + serialization_method = json + + +class Home(main.Home): + + def _load_cookie(self, name): + _data = request.httprequest.cookies.get('session_data') + return JSONSecureCookie.load_cookie(request.httprequest, key=name, + secret_key=SECRET_KEY) + + def _action_reset_password(self, login): + user = request.website.env['res.users'].sudo().search( + [('login', '=', login)]) + if user: + user.action_reset_password() + return True + return False + + @http.route('/web/login', type='http', auth="none") + def web_login(self, redirect=None, **kw): + cookie = self._load_cookie('session_data') + login_attemps = 0 + if 'login_attemps' in cookie: + login_attemps = int(cookie['login_attemps']) + if 'g-recaptcha-response' in kw and\ + not request.website.is_captcha_valid( + kw['g-recaptcha-response']): + response = super(Home, self).web_login(redirect, **kw) + if login_attemps >= 8: + response.qcontext.update({ + 'error': _( + """The amount of login attemps have exceeded the + restriction. + A password reset link has been sent to the user's + email. + """) + } + ) + else: + response.qcontext.update({ + 'error': _("Wrong Captcha") + } + ) + return request.render('web.login', response.qcontext) + else: + response = super(Home, self).web_login(redirect, **kw) + secure_cookie = self._load_cookie('session_data') + if 'error' in response.qcontext: + if 'login_attemps' in secure_cookie: + login_attemps = int(secure_cookie['login_attemps']) + secure_cookie['login_attemps'] = str(login_attemps + 1) + else: + secure_cookie['login_attemps'] = str(1) + elif 'login_attemps' in secure_cookie: + pass + else: + secure_cookie['login_attemps'] = str(0) + response.qcontext.update( + {'login_attemps': int(secure_cookie['login_attemps'])}) + if hasattr(response, 'set_cookie'): + secure_cookie.save_cookie(response, 'session_data', + httponly=True, max_age=60*3) + return response diff --git a/login_recaptcha/i18n/es.po b/login_recaptcha/i18n/es.po new file mode 100644 index 00000000000..702c1380f94 --- /dev/null +++ b/login_recaptcha/i18n/es.po @@ -0,0 +1,70 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * login_recaptcha +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-04 21:24+0000\n" +"PO-Revision-Date: 2016-04-04 21:24+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: login_recaptcha +#: field:website,login_recaptcha_private_key:0 +msgid "Login Google reCAPTCHA Private Key" +msgstr "Google reCAPTCHA clave privada" + +#. module: login_recaptcha +#: field:website,login_recaptcha_site_key:0 +msgid "Login Google reCAPTCHA site Key" +msgstr "Google reCAPTCHA clave pública" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Login reCAPTCHA" +msgstr "Login reCAPTCHA" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Social Media" +msgstr "Medios sociales" + +#. module: login_recaptcha +#: code:addons/login_recaptcha/controllers/web.py:48 +#, python-format +msgid "The amount of login attemps have exceeded the\n" +" restriction.\n" +" A password reset link has been sent to the user's\n" +" email.\n" +" " +msgstr "Los intentos de inicio de sesión han superado el límite restringido." +" Un enlace para reestablecer la contraseña ha sido enviado al email del usuario" + + +#. module: login_recaptcha +#: model:ir.model,name:login_recaptcha.model_website +msgid "Website" +msgstr "Sitio web" + +#. module: login_recaptcha +#: code:addons/login_recaptcha/controllers/web.py:58 +#, python-format +msgid "Wrong Captcha" +msgstr "Captcha inválido" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Your reCAPTCHA private key" +msgstr "Su Google reCAPTCHA clave privada" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Your reCAPTCHA site key" +msgstr "Su Google reCAPTCHA clave pública" + diff --git a/login_recaptcha/i18n/login_recaptcha.pot b/login_recaptcha/i18n/login_recaptcha.pot new file mode 100644 index 00000000000..216b2efb9c9 --- /dev/null +++ b/login_recaptcha/i18n/login_recaptcha.pot @@ -0,0 +1,69 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * login_recaptcha +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-04 21:24+0000\n" +"PO-Revision-Date: 2016-04-04 21:24+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: login_recaptcha +#: field:website,login_recaptcha_private_key:0 +msgid "Login Google reCAPTCHA Private Key" +msgstr "" + +#. module: login_recaptcha +#: field:website,login_recaptcha_site_key:0 +msgid "Login Google reCAPTCHA site Key" +msgstr "" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Login reCAPTCHA" +msgstr "" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Social Media" +msgstr "" + +#. module: login_recaptcha +#: code:addons/login_recaptcha/controllers/web.py:48 +#, python-format +msgid "The amount of login attemps have exceeded the\n" +" restriction.\n" +" A password reset link has been sent to the user's\n" +" email.\n" +" " +msgstr "" + + +#. module: login_recaptcha +#: model:ir.model,name:login_recaptcha.model_website +msgid "Website" +msgstr "" + +#. module: login_recaptcha +#: code:addons/login_recaptcha/controllers/web.py:58 +#, python-format +msgid "Wrong Captcha" +msgstr "" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Your reCAPTCHA private key" +msgstr "" + +#. module: login_recaptcha +#: view:website.config.settings:login_recaptcha.view_website_config_settings +msgid "Your reCAPTCHA site key" +msgstr "" + diff --git a/login_recaptcha/models/__init__.py b/login_recaptcha/models/__init__.py new file mode 100644 index 00000000000..fea15af4ba5 --- /dev/null +++ b/login_recaptcha/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import res_config, website diff --git a/login_recaptcha/models/res_config.py b/login_recaptcha/models/res_config.py new file mode 100644 index 00000000000..8fcb124d357 --- /dev/null +++ b/login_recaptcha/models/res_config.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models + + +class website_config_settings(models.TransientModel): + + _inherit = 'website.config.settings' + + login_recaptcha_site_key = fields.Char( + related='website_id.login_recaptcha_site_key', + string='Login Google reCAPTCHA site Key') + login_recaptcha_private_key = fields.Char( + related='website_id.login_recaptcha_private_key', + string='Login Google reCAPTCHA Private Key') diff --git a/login_recaptcha/models/website.py b/login_recaptcha/models/website.py new file mode 100644 index 00000000000..2d197e1037c --- /dev/null +++ b/login_recaptcha/models/website.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models +import json +import requests + + +class Website(models.Model): + + _inherit = 'website' + + login_recaptcha_site_key = fields.Char( + string='Login Google reCAPTCHA site Key', + default='6Lf8ghoTAAAAANdd_v5uNvdKa0qWYlOJTdr0TOIy') + login_recaptcha_private_key = fields.Char( + string='Login Google reCAPTCHA Private Key', + default='6Lf8ghoTAAAAAEyfOnnXXg0VAIpeCbvESlS3mH3b') + + def is_captcha_valid(self, response): + for website in self.browse(self._ids): + get_res = {'secret': website.login_recaptcha_private_key, + 'response': response} + try: + response = requests.get( + 'https://www.google.com/recaptcha/api/siteverify', + params=get_res) + except Exception, e: + raise models.except_orm(('Invalid Data!'), ("%s.") % (e)) + res_con = json.loads(response.content) + if 'success' in res_con and res_con['success']: + return True + return False diff --git a/login_recaptcha/static/description/icon.png b/login_recaptcha/static/description/icon.png new file mode 100644 index 00000000000..253242ee261 Binary files /dev/null and b/login_recaptcha/static/description/icon.png differ diff --git a/login_recaptcha/static/src/xml/login.xml b/login_recaptcha/static/src/xml/login.xml new file mode 100644 index 00000000000..79454e88b9a --- /dev/null +++ b/login_recaptcha/static/src/xml/login.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/login_recaptcha/views/website_config.xml b/login_recaptcha/views/website_config.xml new file mode 100644 index 00000000000..62510459c70 --- /dev/null +++ b/login_recaptcha/views/website_config.xml @@ -0,0 +1,28 @@ + + + + + Website settings + website.config.settings + + + + + + + + + + \ No newline at end of file diff --git a/password_security/README.rst b/password_security/README.rst new file mode 100644 index 00000000000..b6bc128513e --- /dev/null +++ b/password_security/README.rst @@ -0,0 +1,27 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================= +Password Security +================= + +This module was written improve the users's password security criteria +====================================================================== + +Credits +======= + +Contributors +------------ + +* Lesmed Gutiérrez + +Maintainer +---------- + +.. image:: https://avatars0.githubusercontent.com/u/7594691?v=3&s=200 + :alt: ClearCorp + :target: http://clearcorp.cr + +This module is maintained by ClearCorp. diff --git a/password_security/__init__.py b/password_security/__init__.py new file mode 100644 index 00000000000..1c8683a2160 --- /dev/null +++ b/password_security/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/password_security/__openerp__.py b/password_security/__openerp__.py new file mode 100644 index 00000000000..5172a05009a --- /dev/null +++ b/password_security/__openerp__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Password Security', + 'summary': 'Password security configurations', + 'version': '8.0.1.0', + 'category': 'Hidden', + 'website': 'http://clearcorp.cr', + 'author': 'ClearCorp', + 'license': 'AGPL-3', + 'sequence': 10, + 'application': False, + 'installable': True, + 'auto_install': False, + 'external_dependencies': { + 'python': [], + 'bin': [], + }, + 'depends': [ + 'base_setup', 'auth_signup', 'web' + ], + 'data': [ + 'views/base_config_settings.xml' + ], + 'qweb': [ + ], +} diff --git a/password_security/i18n/es.po b/password_security/i18n/es.po new file mode 100644 index 00000000000..aa6f0a4002d --- /dev/null +++ b/password_security/i18n/es.po @@ -0,0 +1,127 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * password_security +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-04 22:11+0000\n" +"PO-Revision-Date: 2016-04-04 22:11+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: password_security +#: view:base.config.settings:password_security.password_security_configuration +msgid "---------" +msgstr "---------" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:35 +#, python-format +msgid "Have at least %s characters long" +msgstr "Tener al menos %s caracteres de longitud" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:54 +#, python-format +msgid "Have at least %s lowercase letters" +msgstr "Tener al menos %s letras minúsculas" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:63 +#, python-format +msgid "Have at least %s numbers" +msgstr "Tener al menos %s caracteres numéricos" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:73 +#, python-format +msgid "Have at least %s special characters" +msgstr "Tener al menos %s caracteres especiales" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:45 +#, python-format +msgid "Have at least %s uppercase letters" +msgstr "Tener al menos %s letras mayúsculas" + +#. module: password_security +#: field:base.config.settings,password_security_include_letters:0 +msgid "Include Letters" +msgstr "Incluir Letras" + +#. module: password_security +#: field:base.config.settings,password_security_include_lowercase:0 +msgid "Include lowercase letters" +msgstr "Incluir letras minúsculas" + +#. module: password_security +#: field:base.config.settings,password_security_include_numbers:0 +msgid "Include numbers" +msgstr "Incluir números" + +#. module: password_security +#: field:base.config.settings,password_security_include_special:0 +msgid "Include special characters (@#$%+=-*)" +msgstr "Incluir caracteres especiales (@#$%+=-*)" + +#. module: password_security +#: field:base.config.settings,password_security_include_uppercase:0 +msgid "Include uppercase letters" +msgstr "Incluir letras mayúsculas" + +#. module: password_security +#: field:base.config.settings,password_security_length:0 +msgid "Length" +msgstr "Longitud" + +#. module: password_security +#: field:base.config.settings,password_security_lowercase_length:0 +msgid "Length lowercase letters" +msgstr "Longitud letras minúsculas" + +#. module: password_security +#: field:base.config.settings,password_security_numbers_length:0 +msgid "Length numbers" +msgstr "Longitud números" + +#. module: password_security +#: field:base.config.settings,password_security_special_length:0 +msgid "Length special characters" +msgstr "Longitud caracteres especiales" + +#. module: password_security +#: field:base.config.settings,password_security_uppercase_length:0 +msgid "Length uppercase letters" +msgstr "Longitud letras mayúsculas" + +#. module: password_security +#: view:base.config.settings:password_security.password_security_configuration +msgid "Password Security" +msgstr "Seguridad de la contraseña" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:77 +#, python-format +msgid "The password must satisfy the following criteria:\n" +"\n" +"" +msgstr "La contraseña debe satisfacer el siguiente criterio:\n" +"\n" +"" + +#. module: password_security +#: model:ir.model,name:password_security.model_res_users +msgid "Users" +msgstr "Usuarios" + +#. module: password_security +#: field:base.config.settings,password_security_validate:0 +msgid "Validate user password" +msgstr "Validar contraseña del usuario" + diff --git a/password_security/i18n/password_security.pot b/password_security/i18n/password_security.pot new file mode 100644 index 00000000000..d7abb338ac4 --- /dev/null +++ b/password_security/i18n/password_security.pot @@ -0,0 +1,125 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * password_security +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-04 22:11+0000\n" +"PO-Revision-Date: 2016-04-04 22:11+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: password_security +#: view:base.config.settings:password_security.password_security_configuration +msgid "---------" +msgstr "" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:35 +#, python-format +msgid "Have at least %s characters long" +msgstr "" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:54 +#, python-format +msgid "Have at least %s lowercase letters" +msgstr "" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:63 +#, python-format +msgid "Have at least %s numbers" +msgstr "" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:73 +#, python-format +msgid "Have at least %s special characters" +msgstr "" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:45 +#, python-format +msgid "Have at least %s uppercase letters" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_include_letters:0 +msgid "Include Letters" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_include_lowercase:0 +msgid "Include lowercase letters" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_include_numbers:0 +msgid "Include numbers" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_include_special:0 +msgid "Include special characters (@#$%+=-*)" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_include_uppercase:0 +msgid "Include uppercase letters" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_length:0 +msgid "Length" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_lowercase_length:0 +msgid "Length lowercase letters" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_numbers_length:0 +msgid "Length numbers" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_special_length:0 +msgid "Length special characters" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_uppercase_length:0 +msgid "Length uppercase letters" +msgstr "" + +#. module: password_security +#: view:base.config.settings:password_security.password_security_configuration +msgid "Password Security" +msgstr "" + +#. module: password_security +#: code:addons/password_security/models/res_users.py:77 +#, python-format +msgid "The password must satisfy the following criteria:\n" +"\n" +"" +msgstr "" + +#. module: password_security +#: model:ir.model,name:password_security.model_res_users +msgid "Users" +msgstr "" + +#. module: password_security +#: field:base.config.settings,password_security_validate:0 +msgid "Validate user password" +msgstr "" + diff --git a/password_security/models/__init__.py b/password_security/models/__init__.py new file mode 100644 index 00000000000..9194c6d07fc --- /dev/null +++ b/password_security/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import res_config, res_users diff --git a/password_security/models/res_config.py b/password_security/models/res_config.py new file mode 100644 index 00000000000..ba0b3243bbb --- /dev/null +++ b/password_security/models/res_config.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import models, fields, api + + +class BaseConfigSettings(models.TransientModel): + _inherit = 'base.config.settings' + + password_security_validate = fields.Boolean( + string='Validate user password') + password_security_length = fields.Integer(string='Length', default=1) + password_security_include_letters = fields.Boolean( + string='Include Letters') + password_security_include_uppercase = fields.Boolean( + string='Include uppercase letters') + password_security_uppercase_length = fields.Integer( + string='Length uppercase letters', default=1) + password_security_include_lowercase = fields.Boolean( + string='Include lowercase letters') + password_security_lowercase_length = fields.Integer( + string='Length lowercase letters', default=1) + password_security_include_numbers = fields.Boolean( + string='Include numbers') + password_security_numbers_length = fields.Integer( + string='Length numbers', default=1) + password_security_include_special = fields.Boolean( + string='Include special characters (@#$%+=-*)') + password_security_special_length = fields.Integer( + string='Length special characters', default=1) + + @api.multi + def set_password_security_validate(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_validate", + record.password_security_validate or '') + + @api.multi + def get_default_password_security_validate(self): + password_security_validate =\ + self.env["ir.config_parameter"].get_param( + "password_security_validate", default=None) + return { + 'password_security_validate': password_security_validate or False + } + + @api.multi + def set_password_security_length(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_length", + record.password_security_length or '') + + @api.multi + def get_default_password_security_length(self): + password_security_length =\ + self.env["ir.config_parameter"].get_param( + "password_security_length", default=None) + return { + 'password_security_length': int(password_security_length or False) + } + + @api.multi + def set_password_security_include_letters(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_include_letters", + record.password_security_include_letters or '') + + @api.multi + def get_default_password_security_include_letters(self): + password_security_include_letters =\ + self.env["ir.config_parameter"].get_param( + "password_security_include_letters", default=None) + return { + 'password_security_include_letters': + password_security_include_letters or False + } + + @api.multi + def set_password_security_include_uppercase(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_include_uppercase", + record.password_security_include_uppercase or '') + + @api.multi + def get_default_password_security_include_uppercase(self): + password_security_include_uppercase =\ + self.env["ir.config_parameter"].get_param( + "password_security_include_uppercase", default=None) + return { + 'password_security_include_uppercase': + password_security_include_uppercase or False + } + + @api.multi + def set_password_security_uppercase_length(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_uppercase_length", + record.password_security_uppercase_length or '') + + @api.multi + def get_default_password_security_uppercase_length(self): + password_security_uppercase_length =\ + self.env["ir.config_parameter"].get_param( + "password_security_uppercase_length", default=None) + return { + 'password_security_uppercase_length': + int(password_security_uppercase_length or False) + } + + @api.multi + def set_password_security_include_lowercase(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_include_lowercase", + record.password_security_include_lowercase or '') + + @api.multi + def get_default_password_security_include_lowercase(self): + password_security_include_lowercase =\ + self.env["ir.config_parameter"].get_param( + "password_security_include_lowercase", default=None) + return { + 'password_security_include_lowercase': + password_security_include_lowercase or False + } + + @api.multi + def set_password_security_lowercase_length(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_lowercase_length", + record.password_security_lowercase_length or '') + + @api.multi + def get_default_password_security_lowercase_length(self): + password_security_lowercase_length =\ + self.env["ir.config_parameter"].get_param( + "password_security_lowercase_length", default=None) + return { + 'password_security_lowercase_length': + int(password_security_lowercase_length or False) + } + + @api.multi + def set_password_security_include_numbers(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_include_numbers", + record.password_security_include_numbers or '') + + @api.multi + def get_default_password_security_include_numbers(self): + password_security_include_numbers =\ + self.env["ir.config_parameter"].get_param( + "password_security_include_numbers", default=None) + return { + 'password_security_include_numbers': + password_security_include_numbers or False + } + + @api.multi + def set_password_security_numbers_length(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_numbers_length", + record.password_security_numbers_length or '') + + @api.multi + def get_default_password_security_numbers_length(self): + password_security_numbers_length =\ + self.env["ir.config_parameter"].get_param( + "password_security_numbers_length", default=None) + return { + 'password_security_numbers_length': + int(password_security_numbers_length or False) + } + + @api.multi + def set_password_security_include_special(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_include_special", + record.password_security_include_special or '') + + @api.multi + def get_default_password_security_include_special(self): + password_security_include_special =\ + self.env["ir.config_parameter"].get_param( + "password_security_include_special", default=None) + return { + 'password_security_include_special': + password_security_include_special or False + } + + @api.multi + def set_password_security_special_length(self): + config_parameters = self.env["ir.config_parameter"] + for record in self.browse(self._ids): + config_parameters.set_param( + "password_security_special_length", + record.password_security_special_length or '') + + @api.multi + def get_default_password_security_special_length(self): + password_security_special_length =\ + self.env["ir.config_parameter"].get_param( + "password_security_special_length", default=None) + return { + 'password_security_special_length': + int(password_security_special_length or False) + } diff --git a/password_security/models/res_users.py b/password_security/models/res_users.py new file mode 100644 index 00000000000..356a811c383 --- /dev/null +++ b/password_security/models/res_users.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# © 2016 ClearCorp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import models, api, _ +from openerp.exceptions import Warning +import re + + +class ResUsers(models.Model): + _inherit = 'res.users' + + @api.model + def create(self, vals): + if 'password' in vals: + password = vals['password'] + self._validate_password(password) + return super(ResUsers, self).create(vals) + + @api.multi + def write(self, vals): + if 'password' in vals: + password = vals['password'] + self._validate_password(password) + return super(ResUsers, self).write(vals) + + def _validate_password(self, password): + params = self._load_params() + messages = [] + regex = '' + if bool(params['password_security_validate']): + length_password = int(params['password_security_length']) + if length_password != 0: + regex = (".{%s,}" % length_password) + if not(re.search(regex, password)): + messages.append(_( + "Have at least %s characters long" + % length_password)) + if bool(params['password_security_include_letters']): + if bool(params['password_security_include_uppercase']): + length_uppercase = \ + int(params['password_security_uppercase_length']) + if length_uppercase != 0: + regex = ('.[A-Z]{%s,}' % length_uppercase) + if not (re.search(regex, password)): + messages.append(_( + """Have at least %s uppercase letters""" + % length_uppercase)) + if bool(params['password_security_include_lowercase']): + length_lowercase = \ + int(params['password_security_lowercase_length']) + if length_lowercase != 0: + regex = ('.[a-z]{%s,}' % length_lowercase) + if not (re.search(regex, password)): + messages.append(_( + """Have at least %s lowercase letters""" + % length_lowercase)) + if bool(params['password_security_include_numbers']): + length_numbers = \ + int(params['password_security_numbers_length']) + if length_numbers != 0: + regex = ('.[0-9]{%s,}' % length_numbers) + if not (re.search(regex, password)): + messages.append(_( + """Have at least %s numbers""" + % length_numbers)) + if bool(params['password_security_include_special']): + length_special = \ + int(params['password_security_special_length']) + if length_special != 0: + regex = r'[!#$%&\'\*\+\-/=\?\^`\{\|\}~]{' +\ + str(length_special) + ',}' + if not (re.search(regex, password)): + messages.append(_( + """Have at least %s special characters""" + % length_special)) + if len(messages) > 0: + warning = _( + "The password must satisfy the following criteria:\n\n") + for message in messages: + warning += "* " + message + "\n" + raise Warning(warning) + + def _load_params(self): + params = {'password_security_validate': '', + 'password_security_length': '', + 'password_security_include_letters': '', + 'password_security_include_uppercase': '', + 'password_security_uppercase_length': '', + 'password_security_include_lowercase': '', + 'password_security_lowercase_length': '', + 'password_security_include_numbers': '', + 'password_security_numbers_length': '', + 'password_security_include_special': '', + 'password_security_special_length': '' + } + for param in params: + params[param] = self._get_param_value(param) + return params + + def _get_param_value(self, param): + ir_config_parameter = self.env["ir.config_parameter"] + domain = ir_config_parameter.get_param(param) + return domain + + @api.model + def signup(self, values, token=None): + partner = self.env['res.partner']._signup_retrieve_partner( + token, check_validity=False, raise_exception=False) + try: + return super(ResUsers, self).signup(values, token) + except Warning as e: + partner.write({'signup_token': token}) + raise Warning(e.message) diff --git a/password_security/static/description/icon.png b/password_security/static/description/icon.png new file mode 100644 index 00000000000..253242ee261 Binary files /dev/null and b/password_security/static/description/icon.png differ diff --git a/password_security/views/base_config_settings.xml b/password_security/views/base_config_settings.xml new file mode 100644 index 00000000000..9f4950990e6 --- /dev/null +++ b/password_security/views/base_config_settings.xml @@ -0,0 +1,70 @@ + + + + + + + Password Security Settings + base.config.settings + + + + + + + + + + \ No newline at end of file