Skip to content

Commit

Permalink
[MIG] password_security: Add flag to toggle password security policy
Browse files Browse the repository at this point in the history
This is a forward-ported commit of #500. Some portions were modified to
handle multi-company environments
  • Loading branch information
hwangh95 committed Mar 22, 2024
1 parent 7e72e73 commit aff70d1
Show file tree
Hide file tree
Showing 16 changed files with 112 additions and 34 deletions.
30 changes: 17 additions & 13 deletions password_security/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Password Security
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:dc29155c73a519d3732e2806f60bd11ebb31cd8c181ebd5918e4bc68080d37aa
!! source digest: sha256:5f0bed48b7eca2655dceb715c14f64fd6a2703a6447e4f8f0401072dc03c3933
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down Expand Up @@ -59,18 +59,19 @@ any user in that company.

These are defined at the company level:

===================== ======= ===================================================
Name Default Description
===================== ======= ===================================================
password_expiration 60 Days until passwords expire
password_length 12 Minimum number of characters in password
password_lower 0 Minimum number of lowercase letter in password
password_upper 0 Minimum number of uppercase letters in password
password_numeric 0 Minimum number of number in password
password_special 0 Minimum number of unique special character in password
password_history 30 Disallow reuse of this many previous passwords
password_minimum 24 Amount of hours that must pass until another reset
===================== ======= ===================================================
========================= ======= ===================================================
Name Default Description
========================= ======= ===================================================
password_policy_enabled False Enables password security requirements
password_expiration 60 Days until passwords expire
password_length 12 Minimum number of characters in password
password_lower 0 Minimum number of lowercase letter in password
password_upper 0 Minimum number of uppercase letters in password
password_numeric 0 Minimum number of number in password
password_special 0 Minimum number of unique special character in password
password_history 30 Disallow reuse of this many previous passwords
password_minimum 24 Amount of hours that must pass until another reset
========================= ======= ===================================================

Usage
=====
Expand Down Expand Up @@ -120,6 +121,9 @@ Contributors
* `Onestein <https://www.onestein.nl>`_:
* Andrea Stirpe <a.stirpe@onestein.nl>

* `twio.tech <https://www.twio.tech>`_:
* Dawn Hwang <hwangh95@gmail.com>

Maintainers
~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion password_security/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "Password Security",
"summary": "Allow admin to set password security requirements.",
"version": "16.0.1.0.0",
"version": "16.0.1.1.0",
"author": "LasLabs, "
"Onestein, "
"Kaushal Prajapati, "
Expand Down
17 changes: 17 additions & 0 deletions password_security/migrations/16.0.1.1.0/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)

import logging

from odoo import SUPERUSER_ID, api

_logger = logging.getLogger(__name__)


def migrate(cr, version):
if not version:
return
env = api.Environment(cr, SUPERUSER_ID, {})
companies = env["res.company"].with_context(active_test=False).search([])
_logger.info("Enable the password policy on %s companies", len(companies))
_logger.info(companies.mapped("name"))
companies.write({"password_policy_enabled": True})
5 changes: 5 additions & 0 deletions password_security/models/res_company.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
class ResCompany(models.Model):
_inherit = "res.company"

password_policy_enabled = fields.Boolean(
"Password Policy",
default=False,
help="Enable password security requirements",
)
password_expiration = fields.Integer(
"Days",
default=60,
Expand Down
3 changes: 3 additions & 0 deletions password_security/models/res_config_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

password_policy_enabled = fields.Boolean(
related="company_id.password_policy_enabled", readonly=False
)
password_expiration = fields.Integer(
related="company_id.password_expiration", readonly=False
)
Expand Down
19 changes: 17 additions & 2 deletions password_security/models/res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def write(self, vals):
def get_password_policy(self):
data = super(ResUsers, self).get_password_policy()
company_id = self.env.user.company_id

if not company_id.password_policy_enabled:
return data

data.update(
{
"password_lower": company_id.password_lower,
Expand All @@ -49,10 +53,10 @@ def get_password_policy(self):
def _check_password_policy(self, passwords):
result = super(ResUsers, self)._check_password_policy(passwords)

for password in passwords:
for user, password in zip(self, passwords):
if not password:
continue
self._check_password(password)
user._check_password(password)

return result

Expand Down Expand Up @@ -92,6 +96,8 @@ def password_match_message(self):
return "\r".join(message)

def _check_password(self, password):
if not self.company_id.password_policy_enabled:
return True
self._check_password_rules(password)
self._check_password_history(password)
return True
Expand All @@ -118,6 +124,9 @@ def _check_password_rules(self, password):

def _password_has_expired(self):
self.ensure_one()
if not self.company_id.password_policy_enabled:
return False

if not self.password_write_date:
return True

Expand All @@ -140,6 +149,9 @@ def _validate_pass_reset(self):
:return: True on allowed reset
"""
for user in self:
if not user.company_id.password_policy_enabled:
continue

pass_min = user.company_id.password_minimum
if pass_min <= 0:
continue
Expand Down Expand Up @@ -180,6 +192,9 @@ def _set_encrypted_password(self, uid, pw):
"""It saves password crypt history for history rules"""
res = super(ResUsers, self)._set_encrypted_password(uid, pw)

if not self.browse(uid).company_id.password_policy_enabled:
return res

self.env["res.users.pass.history"].create(
{
"user_id": uid,
Expand Down
25 changes: 13 additions & 12 deletions password_security/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ any user in that company.

These are defined at the company level:

===================== ======= ===================================================
Name Default Description
===================== ======= ===================================================
password_expiration 60 Days until passwords expire
password_length 12 Minimum number of characters in password
password_lower 0 Minimum number of lowercase letter in password
password_upper 0 Minimum number of uppercase letters in password
password_numeric 0 Minimum number of number in password
password_special 0 Minimum number of unique special character in password
password_history 30 Disallow reuse of this many previous passwords
password_minimum 24 Amount of hours that must pass until another reset
===================== ======= ===================================================
========================= ======= ===================================================
Name Default Description
========================= ======= ===================================================
password_policy_enabled False Enables password security requirements
password_expiration 60 Days until passwords expire
password_length 12 Minimum number of characters in password
password_lower 0 Minimum number of lowercase letter in password
password_upper 0 Minimum number of uppercase letters in password
password_numeric 0 Minimum number of number in password
password_special 0 Minimum number of unique special character in password
password_history 30 Disallow reuse of this many previous passwords
password_minimum 24 Amount of hours that must pass until another reset
========================= ======= ===================================================
3 changes: 3 additions & 0 deletions password_security/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@

* `Onestein <https://www.onestein.nl>`_:
* Andrea Stirpe <a.stirpe@onestein.nl>

* `twio.tech <https://www.twio.tech>`_:
* Dawn Hwang <hwangh95@gmail.com>
20 changes: 16 additions & 4 deletions password_security/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ <h1 class="title">Password Security</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:dc29155c73a519d3732e2806f60bd11ebb31cd8c181ebd5918e4bc68080d37aa
!! source digest: sha256:5f0bed48b7eca2655dceb715c14f64fd6a2703a6447e4f8f0401072dc03c3933
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-auth/tree/16.0/password_security"><img alt="OCA/server-auth" src="https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-password_security"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-auth&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows admin to set company-level password security requirements
Expand Down Expand Up @@ -406,9 +406,9 @@ <h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>These are defined at the company level:</p>
<table border="1" class="docutils">
<colgroup>
<col width="26%" />
<col width="9%" />
<col width="66%" />
<col width="29%" />
<col width="8%" />
<col width="63%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">Name</th>
Expand All @@ -417,6 +417,10 @@ <h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
</tr>
</thead>
<tbody valign="top">
<tr><td>password_policy_enabled</td>
<td>False</td>
<td>Enables password security requirements</td>
</tr>
<tr><td>password_expiration</td>
<td>60</td>
<td>Days until passwords expire</td>
Expand Down Expand Up @@ -511,6 +515,14 @@ <h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt><a class="reference external" href="https://www.twio.tech">twio.tech</a>:</dt>
<dd><ul class="first last simple">
<li>Dawn Hwang &lt;<a class="reference external" href="mailto:hwangh95&#64;gmail.com">hwangh95&#64;gmail.com</a>&gt;</li>
</ul>
</dd>
</dl>
</li>
</ul>
</div>
<div class="section" id="maintainers">
Expand Down
1 change: 1 addition & 0 deletions password_security/tests/test_change_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def login(self, username, password):
self.session = http.root.session_store.new()
self.opener = Opener(self.env.cr)
self.opener.cookies.set("session_id", self.session.sid, domain=HOST, path="/")
self.env.company.password_policy_enabled = True

with mock.patch("odoo.http.db_filter") as db_filter:
db_filter.side_effect = lambda dbs, host=None: [get_db_name()]
Expand Down
1 change: 1 addition & 0 deletions password_security/tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def setUp(self):
super().setUp()
self.username = "jackoneill"
self.passwd = "!asdQWE12345_3"
self.env.company.password_policy_enabled = True

# Create user with strong password: no error raised
new_test_user(self.env, self.username, password=self.passwd)
Expand Down
1 change: 1 addition & 0 deletions password_security/tests/test_password_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def test_check_password_history(self):
user = self.env.ref("base.user_admin")
user.company_id.update(
{
"password_policy_enabled": True,
"password_lower": 0,
"password_history": 1,
"password_numeric": 0,
Expand Down
1 change: 1 addition & 0 deletions password_security/tests/test_res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def setUp(self):
}
self.password = "asdQWE123$%^"
self.main_comp = self.env.ref("base.main_company")
self.main_comp.password_policy_enabled = True
self.vals = {
"name": "User",
"login": self.login,
Expand Down
1 change: 1 addition & 0 deletions password_security/tests/test_reset_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class TestPasswordSecurityReset(HttpCase):
def setUp(self):
super().setUp()
self.env.company.password_policy_enabled = True

# Create user with strong password: no error raised
new_test_user(self.env, "jackoneill", password="!asdQWE12345_3")
Expand Down
1 change: 1 addition & 0 deletions password_security/tests/test_signup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def signup(self, username, password):
self.session = http.root.session_store.new()
self.opener = Opener(self.env.cr)
self.opener.cookies.set("session_id", self.session.sid, domain=HOST, path="/")
self.env.company.password_policy_enabled = True

with mock.patch("odoo.http.db_filter") as db_filter:
db_filter.side_effect = lambda dbs, host=None: [get_db_name()]
Expand Down
16 changes: 14 additions & 2 deletions password_security/views/res_config_settings_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,24 @@
</xpath>

<!-- Ensure our settings will come after any former customization -->
<xpath
expr="//div[@id='password_policy']//div[hasclass('o_setting_left_pane')]"
position="inside"
>
<field name="password_policy_enabled" />
</xpath>
<xpath
expr="//div[@id='password_policy']//div[hasclass('o_setting_right_pane')]/*"
position="after"
>
<label string="Password Policy" for="password_expiration" />
<div class="content-group">
<label string="Password Policy" for="password_policy_enabled" />
<div class="text-muted">
Enable password security requirements
</div>
<div
class="content-group"
attrs="{'invisible': [('password_policy_enabled','=', False)]}"
>
<div class="mt16">
<span>
Password expires in
Expand Down

0 comments on commit aff70d1

Please sign in to comment.