Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] fastapi_auth_partner: Add mail verification #5

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f1784b5
[REF] refactor, introduce partner auth and directory auth
sebastienbeau Dec 11, 2020
34c408b
[WIP] still continue to refactor new auth
sebastienbeau Dec 12, 2020
1d59ec3
[IMP] continue to implement auth
sebastienbeau Dec 13, 2020
8182a59
[FIX] fix missing security rule
sebastienbeau Dec 14, 2020
71d91f6
[IMP] add sign out
sebastienbeau Dec 17, 2020
c2c677e
[MIG] Migration to 14.0
sebastienbeau Dec 19, 2020
267f3c3
[IMP] add reset password
sebastienbeau Jan 8, 2021
79f3d94
[IMP] Big cleanup and use OCA Copier template
kevinkhao Jan 14, 2021
2b48069
[FIX] fix dependency
sebastienbeau Jan 21, 2021
3703c10
[UPD] new rest api (datamodel)
Jan 18, 2021
79b3c3d
[FIX] login is required
sebastienbeau Jan 28, 2021
e469fa0
[FIX] fix issue with sql constraint
sebastienbeau Jan 29, 2021
6ec1950
[IMP] Add send invitation email functionality, add tests, misc. minor…
kevinkhao Jan 31, 2021
ff3ca08
[FIX] Add salt_size option on hash tokens
kevinkhao Jan 31, 2021
382bc09
[FIX] reverting encryption and token changes
kevinkhao Jan 31, 2021
fe05b66
[IMP] finish generating token
sebastienbeau Feb 1, 2021
d2d4e96
[IMP] update copier
sebastienbeau Oct 6, 2021
846f78e
[FIX] duplicate labels
Kev-Roche Jan 16, 2022
49fda62
partner_auth: refactor implementation
sebastienbeau May 13, 2023
53115bd
partner_auth: update api naming finish register
sebastienbeau May 15, 2023
76c2433
partner_auth: add inherit _create_partner_auth, fix cookie duration
sebastienbeau Jun 12, 2023
980674a
partner_auth: refactor code in order to use fastapi
sebastienbeau Aug 16, 2023
95e7dab
fastapi_partner_auth: rename module
sebastienbeau Aug 16, 2023
6186bd3
fastapi_auth_partner: Improve test, add dependencies
sebastienbeau Aug 31, 2023
894ddce
fastapi_auth_partner: Finish first version of the module
sebastienbeau Sep 6, 2023
e7431ec
fastapi_auth_partner: rename endpoint for request_reset_password
sebastienbeau Sep 6, 2023
901aab5
fastapi_auth_partner: clean test and add missing index
sebastienbeau Sep 6, 2023
25e007a
fastapi_auth_partner: better field naming
sebastienbeau Sep 11, 2023
cda1fee
fastapi_auth_partner: migrate to pydantic 2
sebastienbeau Oct 29, 2023
c85de63
fastapi_auth_partner: update AuthPartnerResponse
sebastienbeau Nov 2, 2023
89dd23a
fastapi_auth_partner: fix view
sebastienbeau Nov 5, 2023
fabf253
fastapi_auth_partner: fix access right
sebastienbeau Dec 15, 2023
7c60fc4
fastapi_auth_partner: Migration to V16
sebastienbeau Dec 15, 2023
09fbd32
fastapi_auth_partner: run pre-commit
sebastienbeau Dec 15, 2023
7d38424
fastapi_auth_partner: force send mail as we use it in a job
sebastienbeau Dec 17, 2023
7e3bb42
fix model on security rule
kevinkhao Jan 22, 2024
06b323b
use selection_add instead of redefining selection
kevinkhao Jan 22, 2024
58ecdeb
FIX fastapi_auth_partner: remove partner_ids
bealdav Jan 22, 2024
9ce4f93
fastapi_auth_partner: manager should be allowed to read directory auth
sebastienbeau Feb 25, 2024
4b42388
[IMP] fastapi_auth_partner: Add impersonations
paradoxxxzero Jun 18, 2024
7392e82
Merge pull request #6 from akretion/16.0-add-fastapi-auth-impersonate
sebastienbeau Jun 27, 2024
bb3bce0
[IMP] fastapi_auth_partner: Add mail verification
paradoxxxzero Apr 25, 2024
acb8f3a
[FIX] fastapi_auth_partner: Add default templates
paradoxxxzero May 13, 2024
cb9fc28
fastapi_auth_partner: raise the right http code when cookies is invalid
sebastienbeau Jun 27, 2024
3384623
fastapi_auth_partner: add the option "sliding_session" so session exp…
sebastienbeau Jun 27, 2024
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
1 change: 1 addition & 0 deletions fastapi_auth_partner/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
todo
4 changes: 4 additions & 0 deletions fastapi_auth_partner/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import models
from . import routers
from . import schemas
from . import wizards
38 changes: 38 additions & 0 deletions fastapi_auth_partner/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2020 Akretion
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Partner Auth",
"summary": """
Implements the base features for a authenticable partner""",
"version": "16.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/rest-framework",
"depends": [
"extendable_fastapi",
"mail",
"queue_job",
],
"data": [
"security/res_group.xml",
"security/ir.model.access.csv",
"security/ir_rule.xml",
"data/email_data.xml",
"views/fastapi_endpoint_view.xml",
"views/fastapi_auth_partner_view.xml",
"views/fastapi_auth_directory_view.xml",
"views/res_partner_view.xml",
"wizards/wizard_partner_auth_reset_password_view.xml",
"wizards/wizard_partner_auth_impersonate_view.xml",
],
"demo": [
"demo/fastapi_auth_directory_demo.xml",
"demo/res_partner_demo.xml",
"demo/fastapi_auth_partner_demo.xml",
"demo/fastapi_endpoint_demo.xml",
],
"external_dependencies": {
"python": ["itsdangerous"],
},
}
67 changes: 67 additions & 0 deletions fastapi_auth_partner/data/email_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo noupdate="1">
<record id="email_request_reset_password" model="mail.template">
<field name="name">FastApi Auth Directory: Reset Password</field>
<field name="email_from">[email protected]</field>
<field name="subject">Reset Password</field>
<field name="partner_to">{{object.partner_id.id}}</field>
<field name="model_id" ref="model_fastapi_auth_partner" />
<field name="auto_delete" eval="True" />
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html" type="html">
<div>
Hi <t t-out="object.partner_id.name or ''" />
Click on the following link to reset your password
<a
t-attf-href="https://example.org/password/reset?token={{ object.env.context['token']}}"
target="_blank"
style="color:#FFFFFF; text-decoration:none;"
>Reset Password</a>
</div>
</field>
</record>

<record id="email_invite_set_password" model="mail.template">
<field name="name">FastApi Auth Directory: Set Password</field>
<field name="email_from">[email protected]</field>
<field name="subject">Welcome</field>
<field name="partner_to">{{object.partner_id.id}}</field>
<field name="model_id" ref="model_fastapi_auth_partner" />
<field name="auto_delete" eval="True" />
<field name="lang">{{object.partner_id.lang}}</field>
<field name="body_html" type="html">
<div>
Hi <t t-out="object.partner_id.name or ''" />
Welcome, your account have been created
Click on the following link to set your password
<a
t-attf-href="https://example.org/password/reset?token={{ object.env.context['token']}}"
target="_blank"
style="color:#FFFFFF; text-decoration:none;"
>Set Password</a>
</div>
</field>
</record>

<record id="email_invite_validate_email" model="mail.template">
<field name="name">FastApi Auth Directory: Validate Email</field>
<field name="email_from">[email protected]</field>
<field name="subject">Welcome</field>
<field name="partner_to">{{object.partner_id.id}}</field>
<field name="model_id" ref="model_fastapi_auth_partner" />
<field name="auto_delete" eval="True" />
<field name="lang">{{object.partner_id.lang}}</field>
<field name="body_html" type="html">
<div>
Hi <t t-out="object.partner_id.name or ''" />
Welcome to the site, please click on the following link to verify your email
<a
t-attf-href="https://example.org/email/validate?token={{ object.env.context['token']}}"
target="_blank"
style="color:#FFFFFF; text-decoration:none;"
>Validate Email</a>
</div>
</field>
</record>

</odoo>
16 changes: 16 additions & 0 deletions fastapi_auth_partner/demo/fastapi_auth_directory_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="demo_directory" model="fastapi.auth.directory">
<field name="name">Demo Auth Directory</field>
<field
name="request_reset_password_template_id"
ref="email_request_reset_password"
/>
<field name="invite_set_password_template_id" ref="email_invite_set_password" />
<field
name="invite_validate_email_template_id"
ref="email_invite_validate_email"
/>
<field name="cookie_secret_key">SuperSecret</field>
</record>
</odoo>
8 changes: 8 additions & 0 deletions fastapi_auth_partner/demo/fastapi_auth_partner_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="partner_auth_demo" model="fastapi.auth.partner">
<field name="partner_id" ref="res_partner_auth_demo" />
<field name="directory_id" ref="demo_directory" />
<field name="password">Super-secret$1</field>
</record>
</odoo>
20 changes: 20 additions & 0 deletions fastapi_auth_partner/demo/fastapi_endpoint_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="fastapi.endpoint" id="fastapi_endpoint_demo">
<field name="name">Fastapi Auth Partner Demo Endpoint</field>
<field
name="description"
><![CDATA[
# A Dummy FastApi Partner Auth Demo

This demo endpoint has been created by inhering from "fastapi.endpoint", registering
a new app into the app selection field and implementing the `_get_fastapi_routers`
methods. See documentation to learn more about how to create a new app.
]]></field>
<field name="app">demo</field>
<field name="root_path">/fastapi_auth_partner_demo</field>
<field name="demo_auth_method">auth_partner</field>
<field name="user_id" ref="fastapi.my_demo_app_user" />
<field name="directory_id" ref="demo_directory" />
</record>
</odoo>
9 changes: 9 additions & 0 deletions fastapi_auth_partner/demo/res_partner_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>

<record id="res_partner_auth_demo" model="res.partner">
<field name="name">Demo auth partner</field>
<field name="email">[email protected]</field>
</record>

</odoo>
73 changes: 73 additions & 0 deletions fastapi_auth_partner/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import logging
import sys
from typing import Any, Dict, Union

from itsdangerous import URLSafeTimedSerializer
from starlette.status import HTTP_401_UNAUTHORIZED

from odoo.api import Environment

from odoo.addons.base.models.res_partner import Partner
from odoo.addons.fastapi.dependencies import fastapi_endpoint, odoo_env
from odoo.addons.fastapi.models import FastapiEndpoint

from fastapi import Cookie, Depends, HTTPException, Request, Response

if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated

_logger = logging.getLogger(__name__)


Payload = Dict[str, Any]


class AuthPartner:
def __init__(self, allow_unauthenticated: bool = False):
self.allow_unauthenticated = allow_unauthenticated

def __call__(
self,
request: Request,
response: Response,
env: Annotated[
Environment,
Depends(odoo_env),
],
endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)],
fastapi_auth_partner: Annotated[Union[str, None], Cookie()] = None,
) -> Partner:
if not fastapi_auth_partner and self.allow_unauthenticated:
return env["res.partner"].with_user(env.ref("base.public_user")).browse()

elif fastapi_auth_partner:
directory = endpoint.sudo().directory_id
try:
vals = URLSafeTimedSerializer(directory.cookie_secret_key).loads(
fastapi_auth_partner, max_age=directory.cookie_duration * 60
)
except Exception as e:
_logger.error("Invalid cookies error %s", e)
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED) from e
if vals["did"] == directory.id and vals["pid"]:
partner = env["res.partner"].browse(vals["pid"]).exists()
if partner:
auth = partner.sudo().auth_partner_ids.filtered(
lambda s: s.directory_id == directory
)
if auth:
if directory.sliding_session:
auth._set_auth_cookie(response)
return partner
_logger.info("Could not determine partner from 'fastapi_auth_partner' cookie.")
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED)


auth_partner_authenticated_partner = AuthPartner()
auth_partner_optionally_authenticated_partner = AuthPartner(allow_unauthenticated=True)
4 changes: 4 additions & 0 deletions fastapi_auth_partner/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import fastapi_auth_directory
from . import fastapi_auth_partner
from . import res_partner
from . import fastapi_endpoint
93 changes: 93 additions & 0 deletions fastapi_auth_partner/models/fastapi_auth_directory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2020 Akretion
# Copyright 2020 Odoo SA (some code have been inspired from res_users code)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).


from odoo import fields, models


class FastApiAuthDirectory(models.Model):
_name = "fastapi.auth.directory"
_description = "FastApi Auth Directory"

name = fields.Char(required=True)
set_password_token_duration = fields.Integer(
default=1440, help="In minute, default 1440 minutes => 24h", required=True
)
impersonating_token_duration = fields.Integer(
default=1, help="In minute, default 1 minute", required=True
)
request_reset_password_template_id = fields.Many2one(
"mail.template",
"Mail Template Forget Password",
required=True,
default=lambda self: self.env.ref(
"fastapi_auth_partner.email_request_reset_password",
raise_if_not_found=False,
),
)
invite_set_password_template_id = fields.Many2one(
"mail.template",
"Mail Template New Password",
required=True,
default=lambda self: self.env.ref(
"fastapi_auth_partner.email_invite_set_password",
raise_if_not_found=False,
),
)
invite_validate_email_template_id = fields.Many2one(
"mail.template",
"Mail Template Validate Email",
required=True,
default=lambda self: self.env.ref(
"fastapi_auth_partner.email_invite_validate_email",
raise_if_not_found=False,
),
)
cookie_secret_key = fields.Char(
required=True,
groups="base.group_system",
)
cookie_duration = fields.Integer(
default=525600,
help="In minute, default 525600 minutes => 1 year",
required=True,
)
count_partner = fields.Integer(compute="_compute_count_partner")

fastapi_endpoint_ids = fields.One2many(
"fastapi.endpoint",
"directory_id",
string="FastAPI Endpoints",
)
impersonating_user_ids = fields.Many2many(
"res.users",
"fastapi_auth_directory_impersonating_user_rel",
"directory_id",
"user_id",
string="Impersonating Users",
help="These odoo users can impersonate any partner of this directory",
default=lambda self: (
self.env.ref("base.user_root") | self.env.ref("base.user_admin")
).ids,
groups="fastapi_auth_partner.group_partner_auth_manager",
)
sliding_session = fields.Boolean()

def _compute_count_partner(self):
data = self.env["fastapi.auth.partner"].read_group(
[
("directory_id", "in", self.ids),
],
["directory_id"],
groupby=["directory_id"],
lazy=False,
)
res = {item["directory_id"][0]: item["__count"] for item in data}

for record in self:
record.count_partner = res.get(record.id, 0)

@property
def _server_env_fields(self):
return {"cookie_secret_key": {}}
Loading
Loading