Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions password_security/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=================
Password Security
=================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github
:target: https://github.com/OCA/server-auth/tree/19.0/password_security
:alt: OCA/server-auth
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-auth-19-0/server-auth-19-0-password_security
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows admin to set company-level password security
requirements and enforces them on the user.

It contains features such as

- Password expiration days
- Password length requirement
- Password minimum number of lowercase letters
- Password minimum number of uppercase letters
- Password minimum number of numbers
- Password minimum number of special characters

**Table of contents**

.. contents::
:local:

Configuration
=============

Navigate to General Settings under Configuration Scroll down to the
``Password Policy`` section Set the policies to your liking.

Password complexity requirements will be enforced upon next password
change for any user in that company.

**Settings & Defaults**

These are defined at the company level:

+------------------------------+------------------+
| Parameter | Default value |
+==============================+==================+
| Password expiration (days) | 60 |
+------------------------------+------------------+
| Minimum hours before reset | 24 |
+------------------------------+------------------+
| Password history | 30 |
+------------------------------+------------------+
| Lowercase characters | 1 |
+------------------------------+------------------+
| Uppercase characters | 1 |
+------------------------------+------------------+
| Numeric characters | 1 |
+------------------------------+------------------+
| Special characters | 1 |
+------------------------------+------------------+

Usage
=====

Configure using above instructions for each company that should have
password security mandates.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20password_security%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* LasLabs

Contributors
~~~~~~~~~~~~

- James Foster <jfoster@laslabs.com>
- Dave Lasley <dave@laslabs.com>
- Kaushal Prajapati <kbprajapati@live.com>
- Petar Najman <petar.najman@modoolar.com>
- Shepilov Vladislav <shepilov.v@protonmail.com>
- Florian Kantelberg <florian.kantelberg@initos.com>
- Carlos Jimeno <carlos.jimeno@bt-group.com>
- Dhara Solanki <dhara.solanki@initos.com>
- `Open Source Integrators <https://opensourceintegrators.com>`_:

- Chandresh Thakkar <cthakkar@opensourceintegrators.com>
- Daniel Reis <dreis@opensourceintegrators.com>

- `Onestein <https://www.onestein.nl>`_:

- Andrea Stirpe <a.stirpe@onestein.nl>

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

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/19.0/password_security>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
5 changes: 5 additions & 0 deletions password_security/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2015 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from .post_install import init_config_parameters
from . import controllers, models
34 changes: 34 additions & 0 deletions password_security/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2015 LasLabs Inc.
# Copyright 2018 Modoolar <info@modoolar.com>.
# Copyright 2019 initOS GmbH
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
{
"name": "Password Security",
"summary": "Allow admin to set password security requirements.",
"version": "19.0.1.0.0",
"author": "LasLabs, "
"Onestein, "
"Kaushal Prajapati, "
"Tecnativa, "
"initOS GmbH, "
"Omar Nasr, "
"Odoo Community Association (OCA)",
"category": "Base",
"depends": [
"auth_signup",
"auth_password_policy_signup",
"auth_totp",
],
"website": "https://github.com/OCA/server-auth",
"license": "LGPL-3",
"data": [
"views/res_config_settings_views.xml",
"security/ir.model.access.csv",
"security/res_users_pass_history.xml",
],
"demo": [
"demo/res_users.xml",
],
"post_init_hook": "init_config_parameters",
"installable": True,
}
5 changes: 5 additions & 0 deletions password_security/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2015 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from . import main
from . import home
23 changes: 23 additions & 0 deletions password_security/controllers/home.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2022 brain-tec AG (https://bt-group.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from odoo import http
from odoo.http import request

from odoo.addons.auth_totp.controllers.home import Home


class PasswordSecurity2FAHome(Home):
@http.route()
def web_totp(self, redirect=None, **kwargs):
already_logged_in = request.session.uid
result = super().web_totp(redirect, **kwargs)
if already_logged_in or not (
request.session.uid and request.env.user._password_has_expired()
):
return result
# Mot de passe expiré : déconnexion forcée
request.env.user.action_expire_password()
request.session.logout(keep_db=True)
redirect = request.env.user.partner_id._get_signup_url()
return request.redirect(redirect)
59 changes: 59 additions & 0 deletions password_security/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2015 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

import logging

from werkzeug.exceptions import BadRequest

from odoo import http
from odoo.http import request

from odoo.addons.auth_signup.controllers.main import AuthSignupHome
from odoo.addons.web.controllers.home import ensure_db

_logger = logging.getLogger(__name__)


class PasswordSecurityHome(AuthSignupHome):
def do_signup(self, qcontext):
password = qcontext.get("password")
user = request.env.user
user._check_password(password)
return super().do_signup(qcontext)

@http.route()
def web_login(self, *args, **kw):
ensure_db()
response = super().web_login(*args, **kw)
if not request.params.get("login_success"):
return response
if not request.env.user:
return response
# Utilisateur authentifié — avec 2FA, une seconde étape est nécessaire
if not (request.session.uid and request.env.user._password_has_expired()):
return response
# Mot de passe expiré : déconnexion forcée
request.env.user.action_expire_password()
request.session.logout(keep_db=True)
request.params["login_success"] = False
redirect = request.env.user.partner_id._get_signup_url()
return request.redirect(redirect)

@http.route()
def web_auth_signup(self, *args, **kw):
"""Intercepte toutes les exceptions non gérées par la méthode parente."""

try:
qcontext = self.get_auth_signup_qcontext()
except Exception:
raise BadRequest from None # HTTPError: 400 Client Error: BAD REQUEST

try:
return super().web_auth_signup(*args, **kw)
except Exception as e:
# UserError est déjà géré par la méthode parente web_auth_signup()
qcontext["error"] = str(e)
response = request.render("auth_signup.signup", qcontext)
response.headers["X-Frame-Options"] = "SAMEORIGIN"
response.headers["Content-Security-Policy"] = "frame-ancestors 'self'"
return response
9 changes: 9 additions & 0 deletions password_security/demo/res_users.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!--
Copyright 2016 LasLabs Inc.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="base.user_root" model="res.users">
<field name="password_write_date" eval="datetime.now()" />
</record>
</odoo>
6 changes: 6 additions & 0 deletions password_security/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright 2015 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from . import res_config_settings
from . import res_users
from . import res_users_pass_history
64 changes: 64 additions & 0 deletions password_security/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2018 Modoolar <info@modoolar.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

# Imagine that the ir.config_parameter password_security.numeric has
# a default value of 1. If the user sets the value to 0 on the config page,
# the ir.config_parameter is deleted... but when the ir.config_parameter is not
# present in the database, Odoo displays the default value
# on the config page => Odoo displays 1 !
# So, when the users sets the value of 0 on the config page, he will see 1
# after saving the page !!!
# If the default value is 0 (like auth_password_policy.minlength in the
# module auth_password_policy of the official addons), there is no problem.
# So the solution to avoid this problem and have a non-null default value:
# 1) define the ir.config_parameter fields on res.config.settings with default=0
# 2) initialize the ir.config_parameter with a default value in the init script
# So the default value of the fields below are written in post_install.py
password_expiration = fields.Integer(
string="Days",
default=0,
config_parameter="password_security.expiration_days",
help="How many days until passwords expire",
)
password_minimum = fields.Integer(
string="Minimum Hours",
default=0,
config_parameter="password_security.minimum_hours",
help="Number of hours until a user may change password again",
)
password_history = fields.Integer(
string="History",
default=0,
config_parameter="password_security.history",
help="Disallow reuse of this many previous passwords - use negative "
"number for infinite, or 0 to disable",
)
password_lower = fields.Integer(
string="Lowercase",
default=0,
config_parameter="password_security.lower",
help="Require number of lowercase letters",
)
password_upper = fields.Integer(
string="Uppercase",
default=0,
config_parameter="password_security.upper",
help="Require number of uppercase letters",
)
password_numeric = fields.Integer(
string="Numeric",
default=0,
config_parameter="password_security.numeric",
help="Require number of numeric digits",
)
password_special = fields.Integer(
string="Special",
default=0,
config_parameter="password_security.special",
help="Require number of unique special characters",
)
Loading
Loading