Skip to content

Commit

Permalink
[IMP] auth_oidc: allow assign groups from token claims
Browse files Browse the repository at this point in the history
  • Loading branch information
hbrunn authored and OdyX committed Sep 6, 2024
1 parent 0beb977 commit 3e0bcb5
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 2 deletions.
6 changes: 5 additions & 1 deletion auth_oidc/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"summary": "Allow users to login through OpenID Connect Provider",
"external_dependencies": {"python": ["python-jose"]},
"depends": ["auth_oauth"],
"data": ["views/auth_oauth_provider.xml", "data/auth_oauth_data.xml"],
"data": [
"security/ir.model.access.csv",
"views/auth_oauth_provider.xml",
"data/auth_oauth_data.xml",
],
"demo": ["demo/local_keycloak.xml"],
}
5 changes: 5 additions & 0 deletions auth_oidc/demo/local_keycloak.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@
name="jwks_uri"
>http://localhost:8080/auth/realms/master/protocol/openid-connect/certs</field>
</record>
<record id="local_keycloak_group_line" model="auth.oauth.provider.group_line">
<field name="provider_id" ref="local_keycloak" />
<field name="group_id" ref="base.group_no_one" />
<field name="expression">token['name'] == 'test'</field>
</record>
</odoo>
39 changes: 38 additions & 1 deletion auth_oidc/models/auth_oauth_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
# Copyright 2021 ACSONE SA/NV <https://acsone.eu>
# License: AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

import collections
import logging
import secrets

import requests

from odoo import fields, models, tools
from odoo import api, exceptions, fields, models, tools

try:
from jose import jwt
Expand Down Expand Up @@ -46,6 +47,11 @@ class AuthOauthProvider(models.Model):
string="Token URL", help="Required for OpenID Connect authorization code flow."
)
jwks_uri = fields.Char(string="JWKS URL", help="Required for OpenID Connect.")
group_line_ids = fields.One2many(
"auth.oauth.provider.group_line",
"provider_id",
string="Group mappings",
)

@tools.ormcache("self.jwks_uri", "kid")
def _get_keys(self, kid):
Expand Down Expand Up @@ -104,3 +110,34 @@ def _decode_id_token(self, access_token, id_token, kid):
if error:
raise error
return {}


class AuthOauthProviderGroupLine(models.Model):
_name = "auth.oauth.provider.group_line"

provider_id = fields.Many2one("auth.oauth.provider", required=True)
group_id = fields.Many2one("res.groups", required=True)
expression = fields.Char(required=True, help="Variables: user, token")

@api.constrains("expression")
def _check_expression(self):
for this in self:
try:
this._eval_expression(self.env.user, {})
except (AttributeError, KeyError, NameError) as e:
raise exceptions.ValidationError("\n".join(e.args))

def _eval_expression(self, user, token):
self.ensure_one()

class Defaultdict2(collections.defaultdict):
def __init__(self, *args, **kwargs):
super().__init__(Defaultdict2, *args, **kwargs)

return tools.safe_eval.safe_eval(
self.expression,
{
"user": user,
"token": Defaultdict2(token),
},
)
25 changes: 25 additions & 0 deletions auth_oidc/models/res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ def auth_oauth(self, provider, params):
_logger.error("No id_token in response.")
raise AccessDenied()
validation = oauth_provider._parse_id_token(id_token, access_token)
if oauth_provider.data_endpoint:
data = requests.get(
oauth_provider.data_endpoint,
headers={"Authorization": "Bearer %s" % access_token},
).json()
validation.update(data)
# required check
if "sub" in validation and "user_id" not in validation:
# set user_id for auth_oauth, user_id is not an OpenID Connect standard
Expand All @@ -80,3 +86,22 @@ def auth_oauth(self, provider, params):
raise AccessDenied()
# return user credentials
return (self.env.cr.dbname, login, access_token)

@api.model
def _auth_oauth_signin(self, provider, validation, params):
login = super()._auth_oauth_signin(provider, validation, params)
user = self.search([("login", "=", login)])
if user:
group_updates = []
for group_line in (
self.env["auth.oauth.provider"].browse(provider).group_line_ids
):
if group_line._eval_expression(user, validation):
if group_line.group_id not in user.groups_id:
group_updates.append((4, group_line.group_id.id))
else:
if group_line.group_id in user.groups_id:
group_updates.append((3, group_line.group_id.id))
if group_updates:
user.write({"groups_id": group_updates})
return login
2 changes: 2 additions & 0 deletions auth_oidc/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_auth_oauth_provider_group_line,auth_oauth_provider,model_auth_oauth_provider_group_line,base.group_system,1,1,1,1
6 changes: 6 additions & 0 deletions auth_oidc/tests/test_auth_oidc_auth_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,9 @@ def test_login_with_jwk_format(self):
)
self.assertEqual(token, "122/3")
self.assertEqual(login, user.login)

def test_group_expression(self):
"""Test that group expressions evaluate correctly"""
group_line = self.env.ref("auth_oidc.local_keycloak").group_line_ids[:1]
group_line.expression = 'token["test"]["test"] == 1'
self.assertFalse(group_line._eval_expression(self.env.user, {}))
10 changes: 10 additions & 0 deletions auth_oidc/views/auth_oauth_provider.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
<field name="token_endpoint" />
<field name="jwks_uri" />
</field>
<xpath expr="//sheet/group[last()]" position="after">
<group name="mappings">
<field name="group_line_ids">
<tree editable="bottom">
<field name="group_id" />
<field name="expression" />
</tree>
</field>
</group>
</xpath>
</field>
</record>
</odoo>

0 comments on commit 3e0bcb5

Please sign in to comment.