Skip to content

Commit 4acdcdf

Browse files
lasleyhbrunn
authored andcommitted
[9.0][ADD] Password Security Settings (OCA#531)
* [ADD] res_users_password_security: New module * Create new module to lock down user passwords * [REF] res_users_password_security: PR Review fixes * Also add beta pass history rule * [ADD] res_users_password_security: Pass history and min time * Add pass history memory and threshold * Add minimum time for pass resets through web reset * Begin controller tests * Fix copyright, wrong year for new file * Add tests for password_security_home * Left to do web_auth_reset_password * Fix minimum reset threshold and finish tests * Bug fixes per review * [REF] password_security: PR review improvements * Change tech name to password_security * Use new except format * Limit 1 & new api * Cascade deletion for pass history * [REF] password_security: Fix travis + style * Fix travis errors * self to cls * Better variable names in tests * [FIX] password_security: Fix travis errors
1 parent 7da1be9 commit 4acdcdf

18 files changed

+1016
-0
lines changed

password_security/README.rst

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
2+
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
3+
:alt: License: LGPL-3
4+
5+
=================
6+
Password Security
7+
=================
8+
9+
This module allows admin to set company-level password security requirements
10+
and enforces them on the user.
11+
12+
It contains features such as
13+
14+
* Password expiration days
15+
* Password length requirement
16+
* Password minimum number of lowercase letters
17+
* Password minimum number of uppercase letters
18+
* Password minimum number of numbers
19+
* Password minimum number of special characters
20+
21+
Configuration
22+
=============
23+
24+
# Navigate to company you would like to set requirements on
25+
# Click the ``Password Policy`` page
26+
# Set the policies to your liking.
27+
28+
Password complexity requirements will be enforced upon next password change for
29+
any user in that company.
30+
31+
32+
Settings & Defaults
33+
-------------------
34+
35+
These are defined at the company level:
36+
37+
===================== ======= ===================================================
38+
Name Default Description
39+
===================== ======= ===================================================
40+
password_expiration 60 Days until passwords expire
41+
password_length 12 Minimum number of characters in password
42+
password_lower True Require lowercase letter in password
43+
password_upper True Require uppercase letters in password
44+
password_numeric True Require number in password
45+
password_special True Require special character in password
46+
password_history 30 Disallow reuse of this many previous passwords
47+
password_minimum 24 Amount of hours that must pass until another reset
48+
===================== ======= ===================================================
49+
50+
Known Issues / Roadmap
51+
======================
52+
53+
54+
Bug Tracker
55+
===========
56+
57+
Bugs are tracked on `GitHub Issues
58+
<https://github.com/LasLabs/odoo-base/issues>`_. In case of trouble, please
59+
check there if your issue has already been reported. If you spotted it first,
60+
help us to smash it by providing detailed and welcomed feedback.
61+
62+
63+
Credits
64+
=======
65+
66+
Images
67+
------
68+
69+
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
70+
71+
Contributors
72+
------------
73+
74+
* James Foster <jfoster@laslabs.com>
75+
* Dave Lasley <dave@laslabs.com>
76+
77+
Maintainer
78+
----------
79+
80+
.. image:: https://odoo-community.org/logo.png
81+
:alt: Odoo Community Association
82+
:target: https://odoo-community.org
83+
84+
This module is maintained by the OCA.
85+
86+
OCA, or the Odoo Community Association, is a nonprofit organization whose
87+
mission is to support the collaborative development of Odoo features and
88+
promote its widespread use.
89+
90+
To contribute to this module, please visit https://odoo-community.org.

password_security/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
5+
from . import controllers
6+
from . import models

password_security/__openerp__.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
{
5+
6+
'name': 'Password Security',
7+
"summary": "Allow admin to set password security requirements.",
8+
'version': '9.0.1.0.2',
9+
'author': "LasLabs, Odoo Community Association (OCA)",
10+
'category': 'Base',
11+
'depends': [
12+
'auth_crypt',
13+
'auth_signup',
14+
],
15+
"website": "https://laslabs.com",
16+
"license": "LGPL-3",
17+
"data": [
18+
'views/res_company_view.xml',
19+
'security/ir.model.access.csv',
20+
'security/res_users_pass_history.xml',
21+
],
22+
'installable': True,
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
5+
from . import main

password_security/controllers/main.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
5+
import operator
6+
7+
from openerp import http
8+
from openerp.http import request
9+
from openerp.addons.auth_signup.controllers.main import AuthSignupHome
10+
from openerp.addons.web.controllers.main import ensure_db, Session
11+
12+
from ..exceptions import PassError
13+
14+
15+
class PasswordSecuritySession(Session):
16+
17+
@http.route()
18+
def change_password(self, fields):
19+
new_password = operator.itemgetter('new_password')(
20+
dict(map(operator.itemgetter('name', 'value'), fields))
21+
)
22+
user_id = request.env.user
23+
user_id.check_password(new_password)
24+
return super(PasswordSecuritySession, self).change_password(fields)
25+
26+
27+
class PasswordSecurityHome(AuthSignupHome):
28+
29+
def do_signup(self, qcontext):
30+
password = qcontext.get('password')
31+
user_id = request.env.user
32+
user_id.check_password(password)
33+
return super(PasswordSecurityHome, self).do_signup(qcontext)
34+
35+
@http.route()
36+
def web_login(self, *args, **kw):
37+
ensure_db()
38+
response = super(PasswordSecurityHome, self).web_login(*args, **kw)
39+
if not request.httprequest.method == 'POST':
40+
return response
41+
uid = request.session.authenticate(
42+
request.session.db,
43+
request.params['login'],
44+
request.params['password']
45+
)
46+
if not uid:
47+
return response
48+
users_obj = request.env['res.users'].sudo()
49+
user_id = users_obj.browse(request.uid)
50+
if not user_id._password_has_expired():
51+
return response
52+
user_id.action_expire_password()
53+
redirect = user_id.partner_id.signup_url
54+
return http.redirect_with_hash(redirect)
55+
56+
@http.route()
57+
def web_auth_signup(self, *args, **kw):
58+
try:
59+
return super(PasswordSecurityHome, self).web_auth_signup(
60+
*args, **kw
61+
)
62+
except PassError as e:
63+
qcontext = self.get_auth_signup_qcontext()
64+
qcontext['error'] = e.message
65+
return request.render('auth_signup.signup', qcontext)
66+
67+
@http.route()
68+
def web_auth_reset_password(self, *args, **kw):
69+
""" It provides hook to disallow front-facing resets inside of min
70+
Unfortuantely had to reimplement some core logic here because of
71+
nested logic in parent
72+
"""
73+
qcontext = self.get_auth_signup_qcontext()
74+
if (
75+
request.httprequest.method == 'POST' and
76+
qcontext.get('login') and
77+
'error' not in qcontext and
78+
'token' not in qcontext
79+
):
80+
login = qcontext.get('login')
81+
user_ids = request.env.sudo().search(
82+
[('login', '=', login)],
83+
limit=1,
84+
)
85+
if not user_ids:
86+
user_ids = request.env.sudo().search(
87+
[('email', '=', login)],
88+
limit=1,
89+
)
90+
user_ids._validate_pass_reset()
91+
return super(PasswordSecurityHome, self).web_auth_reset_password(
92+
*args, **kw
93+
)

password_security/exceptions.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
5+
from openerp.exceptions import Warning as UserError
6+
7+
8+
class PassError(UserError):
9+
""" Example: When you try to create an insecure password."""
10+
def __init__(self, msg):
11+
self.message = msg
12+
super(PassError, self).__init__(msg)

password_security/models/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
5+
from . import res_users
6+
from . import res_company
7+
from . import res_users_pass_history
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2015 LasLabs Inc.
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
4+
5+
from openerp import models, fields
6+
7+
8+
class ResCompany(models.Model):
9+
_inherit = 'res.company'
10+
11+
password_expiration = fields.Integer(
12+
'Days',
13+
default=60,
14+
help='How many days until passwords expire',
15+
)
16+
password_length = fields.Integer(
17+
'Characters',
18+
default=12,
19+
help='Minimum number of characters',
20+
)
21+
password_lower = fields.Boolean(
22+
'Lowercase',
23+
default=True,
24+
help='Require lowercase letters',
25+
)
26+
password_upper = fields.Boolean(
27+
'Uppercase',
28+
default=True,
29+
help='Require uppercase letters',
30+
)
31+
password_numeric = fields.Boolean(
32+
'Numeric',
33+
default=True,
34+
help='Require numeric digits',
35+
)
36+
password_special = fields.Boolean(
37+
'Special',
38+
default=True,
39+
help='Require special characters',
40+
)
41+
password_history = fields.Integer(
42+
'History',
43+
default=30,
44+
help='Disallow reuse of this many previous passwords - use negative '
45+
'number for infinite, or 0 to disable',
46+
)
47+
password_minimum = fields.Integer(
48+
'Minimum Hours',
49+
default=24,
50+
help='Amount of hours until a user may change password again',
51+
)

0 commit comments

Comments
 (0)