Skip to content

Commit

Permalink
[FIX} auth_sms: refactor and make it work on Odoo 16.0
Browse files Browse the repository at this point in the history
Also take care of compatibility with auth_oauth.
  • Loading branch information
NL66278 committed Nov 8, 2024
1 parent c3eb0f9 commit a425cc6
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 106 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ repos:
- --color
- --fix
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
# exclude autogenerated files
Expand Down
7 changes: 4 additions & 3 deletions auth_sms/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Two factor authentication via SMS",
"version": "10.0.1.0.0",
"version": "16.0.1.0.0",
"author": "Therp BV,Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Extra Tools",
"category": "Tools",
"website": "https://github.com/OCA/server-auth",
"summary": "Allow users to turn on two factor authentication via SMS",
"depends": [
"mail",
Expand All @@ -18,7 +19,7 @@
"views/sms_provider.xml",
"views/res_users.xml",
"security/ir_rule.xml",
"views/templates.xml",
"templates/template_code.xml",
"security/ir.model.access.csv",
],
}
158 changes: 77 additions & 81 deletions auth_sms/controllers/auth_sms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright 2019 Therp BV <https://therp.nl>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import base64
import itertools
import random

from odoo import _, http
Expand All @@ -25,97 +24,94 @@ def web_login(self, redirect=None, **kw):
try:
request.env["res.users"].sudo()._auth_sms_send(exception.user.id)
except AccessDeniedSmsRateLimit:
return request.render(
"web.login",
dict(
request.params.copy(),
error=_("Rate limit for SMS exceeded"),
),
)
secret = self._auth_sms_generate_secret()
request.session["auth_sms.password"] = self._auth_sms_xor(
request.params["password"],
secret,
)
return request.render(
"auth_sms.template_code",
dict(
request.params.copy(),
secret=base64.b64encode(secret),
redirect=redirect,
message=_("Please fill in the code sent to you by SMS"),
**kw
),
)
return self._show_rate_limit(redirect=None, **kw)
return self._show_sms_entry(redirect=None, **kw)

def _show_rate_limit(self, redirect=None, **kw):
"""User has requested to much sms codes in a short period."""
# providers here as elsewhere are included in case auth_oauth is installed.
return request.render(
"web.login",
dict(
request.params.copy(),
providers=[],
error=_("Rate limit for SMS exceeded"),
),
)

def _show_sms_entry(self, redirect=None, **kw):
"""Show copy of login screen for sms entry."""
# password will be stored, encrypted, in the session, while
# the secret will be send (and later retrieved) from the browser.
password_bytes = request.params["password"].encode("utf8")
secret = self._auth_sms_generate_secret()
encrypted_password = self._auth_sms_xor(password_bytes, secret)
request.session["auth_sms.password"] = encrypted_password
encoded_secret_string = base64.b64encode(secret).decode("utf8")
return request.render(
"auth_sms.template_code",
dict(
request.params.copy(),
secret=encoded_secret_string,
redirect=redirect,
providers=[],
message=_("Please fill in the code sent to you by SMS"),
**kw
),
)

@http.route("/auth_sms/code", auth="none")
def code(self, password=None, secret=None, redirect=None, **kw):
# IN this case the password argument really contains the sms code.
request.session["auth_sms.code"] = password
encrypted_password = request.session["auth_sms.password"]
decoded_secret_bytes = base64.b64decode((secret or "").encode("utf8"))
decrypted_password = self._auth_sms_xor(
encrypted_password, decoded_secret_bytes
)
request.params["password"] = decrypted_password.decode("utf8")
request.params["login"] = request.params["user_login"]
try:
request.params["password"] = bytearray(
b
for b in self._auth_sms_xor(
request.session["auth_sms.password"] or bytearray(),
bytearray(base64.b64decode(secret or "")),
decode_input=False,
pad=False,
)
# as we pad the password with null bytes, remove them here
if b
).decode("utf8")
request.params["login"] = request.params["user_login"]
return self.web_login(
redirect=redirect, **dict(kw, password=request.params["password"])
)
except AccessDeniedWrongSmsCode:
del request.session["auth_sms.code"]
return request.render(
"auth_sms.template_code",
dict(
request.params.copy(),
secret=secret,
redirect=redirect,
databases=[],
error=_("Could not verify code"),
**kw
),
)
return self._show_wrong_sms_code(secret, redirect=None, **kw)

def _auth_sms_generate_secret(self):
"""generate an OTP for storing the password in the session"""
return bytearray(
[
random.randrange(256)
for dummy in range(
int(
request.env["ir.config_parameter"]
.sudo()
.get_param(
"auth_sms.otp_size",
128,
)
)
)
]
def _show_wrong_sms_code(self, secret, redirect=None, **kw):
"""Wrong sms code entered, user can try again."""
del request.session["auth_sms.code"]
return request.render(
"auth_sms.template_code",
dict(
request.params.copy(),
secret=secret,
providers=[],
redirect=redirect,
databases=[],
error=_("Could not verify code"),
**kw
),
)

def _auth_sms_xor(self, input_string, secret, decode_input=True, pad=True):
"""xor input string with a pregenerated OTP, pad with 0"""
input_bytes = (
decode_input and bytearray(input_string, encoding="utf8") or input_string
)
return bytearray(
c ^ otp
for c, otp in zip(
itertools.chain(
input_bytes,
pad
and itertools.repeat(
0,
len(secret) - len(input_bytes) % (len(secret) or 1),
)
or [],
),
itertools.cycle(secret),
def _auth_sms_generate_secret(self):
"""Generate an OTP for storing the password in the session."""
otp_size = int(
request.env["ir.config_parameter"]
.sudo()
.get_param(
"auth_sms.otp_size",
128,
)
)
return bytes(bytearray([random.randrange(256) for dummy in range(otp_size)]))

def _auth_sms_xor(self, password, secret):
"""Xor password with secret, to encrypt or decrypt password.
password and secret should both be byte strings.
"""
assert len(secret) >= len(password)
assert isinstance(password, bytes)
assert isinstance(secret, bytes)
return bytes(bytearray(c ^ otp for c, otp in zip(password, secret)))
4 changes: 2 additions & 2 deletions auth_sms/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright 2019 Therp BV <https://therp.nl>
# Copyright 20192024 Therp BV <https://therp.nl>.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).


class AccessDeniedNoSmsCode(Exception):
def __init__(self, user, message=None):
self.user = user
super(AccessDeniedNoSmsCode, self).__init__(message)
super().__init__(message)


class AccessDeniedWrongSmsCode(Exception):
Expand Down
1 change: 1 addition & 0 deletions auth_sms/models/sms_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def _send_sms_messagebird(self, number, text, **kwargs):
"recipients": number,
"body": text,
},
timeout=60,
).json()
_logger.debug(result)
if result.get("errors"):
Expand Down
11 changes: 4 additions & 7 deletions auth_sms/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@

/*
:Author: David Goodger ([email protected])
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -275,7 +274,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: gray; } /* line numbers */
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -301,7 +300,7 @@
span.pre {
white-space: pre }

span.problematic, pre.problematic {
span.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -461,9 +460,7 @@ <h2><a class="toc-backref" href="#toc-entry-8">Other credits</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="template_code" inherit_id="web.login" primary="True">
<template id="template_code" inherit_id="web.login" primary="True" priority="128">
<xpath expr="//form" position="attributes">
<attribute name="t-attf-action">/auth_sms/code</attribute>
<attribute name="onsubmit" />
</xpath>
<xpath expr="//form" position="inside">
<input type="hidden" name="secret" t-att-value="secret" />
Expand Down
2 changes: 1 addition & 1 deletion auth_sms/tests/test_auth_sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_auth_sms_rate_limit(self):
mock_request_post.return_value.json.return_value = {
"originator": "originator",
}
for i in range(9):
for _i in range(9):
response = endpoint()
self.assertNotIn("error", response.qcontext)
response = endpoint()
Expand Down
3 changes: 2 additions & 1 deletion auth_sms_auth_signup/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"version": "16.0.1.0.0",
"author": "Therp BV,Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Extra Tools",
"category": "Tools",
"website": "https://github.com/OCA/server-auth",
"summary": "Enforces SMS verification for password resets",
"depends": [
"auth_signup",
Expand Down
11 changes: 4 additions & 7 deletions auth_sms_auth_signup/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@

/*
:Author: David Goodger ([email protected])
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -275,7 +274,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: gray; } /* line numbers */
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -301,7 +300,7 @@
span.pre {
white-space: pre }

span.problematic, pre.problematic {
span.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -416,9 +415,7 @@ <h2><a class="toc-backref" href="#toc-entry-5">Other credits</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
Expand Down
1 change: 0 additions & 1 deletion setup/auth_sms/odoo/__init__.py

This file was deleted.

1 change: 0 additions & 1 deletion setup/auth_sms/odoo/addons/__init__.py

This file was deleted.

0 comments on commit a425cc6

Please sign in to comment.