Browse Source

[FIX] new request management in V16.0

16.0
default 1 year ago
parent
commit
b4c50708de
  1. 23
      galicea_openid_connect/api.py
  2. 7
      galicea_openid_connect/controllers/ext_web_login.py
  3. 90
      galicea_openid_connect/controllers/main.py

23
galicea_openid_connect/api.py

@ -5,6 +5,7 @@ import logging
from functools import wraps
from odoo import http
from odoo.http import request
import werkzeug
_logger = logging.getLogger(__name__)
@ -22,13 +23,13 @@ def resource(path, method, auth='user'):
def endpoint_decorator(func):
@http.route(path, auth='public', type='http', methods=[method, 'OPTIONS'], csrf=False)
@wraps(func)
def func_wrapper(self, req, **query):
def func_wrapper(self, **query):
cors_headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-Debug-Mode, Authorization',
'Access-Control-Max-Age': 60 * 60 * 24,
}
if req.httprequest.method == 'OPTIONS':
if request.httprequest.method == 'OPTIONS':
return http.Response(
status=200,
headers=cors_headers
@ -36,8 +37,8 @@ def resource(path, method, auth='user'):
try:
access_token = None
if 'Authorization' in req.httprequest.headers:
authorization_header = req.httprequest.headers['Authorization']
if 'Authorization' in request.httprequest.headers:
authorization_header = request.httprequest.headers['Authorization']
if authorization_header[:7] == 'Bearer ':
access_token = authorization_header.split(' ', 1)[1]
if access_token is None:
@ -48,7 +49,7 @@ def resource(path, method, auth='user'):
'invalid_request',
)
if auth == 'user':
token = req.env['galicea_openid_connect.access_token'].sudo().search(
token = request.env['galicea_openid_connect.access_token'].sudo().search(
[('token', '=', access_token)]
)
if not token:
@ -56,9 +57,9 @@ def resource(path, method, auth='user'):
'access_token is invalid',
'invalid_request',
)
req.uid = token.user_id.id
request.update_env(user=token.user_id.id)
elif auth == 'client':
token = req.env['galicea_openid_connect.client_access_token'].sudo().search(
token = request.env['galicea_openid_connect.client_access_token'].sudo().search(
[('token', '=', access_token)]
)
if not token:
@ -66,13 +67,11 @@ def resource(path, method, auth='user'):
'access_token is invalid',
'invalid_request',
)
req.uid = token.client_id.system_user_id.id
request.update_env(user=token.client_id.system_user_id.id)
ctx = req.context.copy()
ctx.update({'client_id': token.client_id.id})
req.context = ctx
request.update_env(context={'client_id': token.client_id.id})
response = func(self, req, **query)
response = func(self, **query)
return werkzeug.Response(
response=json.dumps(response),
headers=cors_headers,

7
galicea_openid_connect/controllers/ext_web_login.py

@ -3,15 +3,16 @@
import time
from odoo import http
from odoo.http import request
from odoo.addons import web
class Home(web.controllers.main.Home):
class Home(web.controllers.home.Home):
@http.route('/web/login', type='http', auth="none")
def web_login(self, redirect=None, **kw):
result = super(Home, self).web_login(redirect, **kw)
if result.is_qweb and 'force_auth_and_redirect' in kw:
result.qcontext['redirect'] = kw['force_auth_and_redirect']
if http.request.params.get('login_success'):
http.request.session['auth_time'] = int(time.time())
if request.params.get('login_success'):
request.session['auth_time'] = int(time.time())
return result

90
galicea_openid_connect/controllers/main.py

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import json
import logging
import time
import os
import base64
@ -8,6 +9,7 @@ import base64
from odoo import http
from odoo.http import request
import werkzeug
from werkzeug.urls import url_encode
from .. api import resource
@ -18,6 +20,8 @@ try:
except ImportError:
pass
_logger = logging.getLogger(__name__)
def jwk_from_json(json_key):
key = jwk.JWK()
key.import_key(**json.loads(json_key))
@ -54,24 +58,24 @@ class OAuthException(Exception):
self.type = type
class Main(http.Controller):
def __get_authorization_code_jwk(self, req):
return jwk_from_json(req.env['ir.config_parameter'].sudo().get_param(
def __get_authorization_code_jwk(self):
return jwk_from_json(request.env['ir.config_parameter'].sudo().get_param(
'galicea_openid_connect.authorization_code_jwk'
))
def __get_id_token_jwk(self, req):
return jwk_from_json(req.env['ir.config_parameter'].sudo().get_param(
def __get_id_token_jwk(self):
return jwk_from_json(request.env['ir.config_parameter'].sudo().get_param(
'galicea_openid_connect.id_token_jwk'
))
def __validate_client(self, req, **query):
def __validate_client(self, **query):
if 'client_id' not in query:
raise OAuthException(
'client_id param is missing',
OAuthException.INVALID_CLIENT,
)
client_id = query['client_id']
client = req.env['galicea_openid_connect.client'].sudo().search(
client = request.env['galicea_openid_connect.client'].sudo().search(
[('client_id', '=', client_id)]
)
if not client:
@ -81,7 +85,7 @@ class Main(http.Controller):
)
return client
def __validate_redirect_uri(self, client, req, **query):
def __validate_redirect_uri(self, client, **query):
if 'redirect_uri' not in query:
raise OAuthException(
'redirect_uri param is missing',
@ -97,7 +101,7 @@ class Main(http.Controller):
return redirect_uri
def __validate_client_secret(self, client, req, **query):
def __validate_client_secret(self, client, **query):
if 'client_secret' not in query or query['client_secret'] != client.secret:
raise OAuthException(
'client_secret param is not valid',
@ -125,7 +129,7 @@ class Main(http.Controller):
@http.route('/oauth/jwks', auth='public', type='http')
def jwks(self, **query):
keyset = jwk.JWKSet()
keyset.add(self.__get_id_token_jwk(request))
keyset.add(self.__get_id_token_jwk())
return keyset.export(private_keys=False)
@resource('/oauth/userinfo', method='GET')
@ -152,8 +156,8 @@ class Main(http.Controller):
def authorize(self, **query):
# First, validate client_id and redirect_uri params.
try:
client = self.__validate_client(request, **query)
redirect_uri = self.__validate_redirect_uri(client, request, **query)
client = self.__validate_client(**query)
redirect_uri = self.__validate_redirect_uri(client, **query)
except OAuthException as e:
# If those are not valid, we must not redirect back to the client
# - instead, we display a message to the user
@ -203,14 +207,14 @@ class Main(http.Controller):
needs_login = True
if needs_login:
params = {
'force_auth_and_redirect': '/oauth/authorize?{}'.format(werkzeug.url_encode(query))
'force_auth_and_redirect': '/oauth/authorize?{}'.format(url_encode(query))
}
return self.__redirect('/web/login', params, 'query')
response_types = response_type.split()
extra_claims = {
'sid': http.request.httprequest.session.sid,
'sid': http.request.session.sid,
}
if 'nonce' in query:
extra_claims['nonce'] = query['nonce']
@ -228,7 +232,7 @@ class Main(http.Controller):
'exp': int(time.time()) + 60
}
payload.update(extra_claims)
key = self.__get_authorization_code_jwk(request)
key = self.__get_authorization_code_jwk()
response_params['code'] = jwt_encode(payload, key)
if 'token' in response_types:
access_token = request.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
@ -244,23 +248,24 @@ class Main(http.Controller):
#extra_claims['at_hash'] = base64.urlsafe_b64encode(at_hash[:16]).strip('=')
extra_claims['at_hash'] = base64.urlsafe_b64encode(at_hash[:16])
if 'id_token' in response_types:
response_params['id_token'] = self.__create_id_token(request, user.id, client, extra_claims)
response_params['id_token'] = self.__create_id_token(user.id, client, extra_claims)
return self.__redirect(redirect_uri, response_params, response_mode)
@http.route('/oauth/token', auth='public', type='http', methods=['POST', 'OPTIONS'], csrf=False)
def token(self, req, **query):
def token(self, **query):
cors_headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, X-Debug-Mode, Authorization',
'Access-Control-Max-Age': 60 * 60 * 24,
}
if req.httprequest.method == 'OPTIONS':
_logger.info("Test1 %s" % request.httprequest.method)
if request.httprequest.method == 'OPTIONS':
return http.Response(
status=200,
headers=cors_headers
)
_logger.info("Test2 %s" % query)
try:
if 'grant_type' not in query:
raise OAuthException(
@ -268,12 +273,15 @@ class Main(http.Controller):
OAuthException.INVALID_REQUEST,
)
if query['grant_type'] == 'authorization_code':
return json.dumps(self.__handle_grant_type_authorization_code(req, **query))
_logger.info("Test3")
return json.dumps(self.__handle_grant_type_authorization_code(**query))
elif query['grant_type'] == 'client_credentials':
return json.dumps(self.__handle_grant_type_client_credentials(req, **query))
_logger.info("Test4")
return json.dumps(self.__handle_grant_type_client_credentials(**query))
elif query['grant_type'] == 'password':
_logger.info("Test5")
return werkzeug.Response(
response=json.dumps(self.__handle_grant_type_password(req, **query)),
response=json.dumps(self.__handle_grant_type_password(**query)),
headers=cors_headers
)
else:
@ -285,10 +293,10 @@ class Main(http.Controller):
body = json.dumps({'error': e.type, 'error_description': e})
return werkzeug.Response(response=body, status=400, headers=cors_headers)
def __handle_grant_type_authorization_code(self, req, **query):
client = self.__validate_client(req, **query)
redirect_uri = self.__validate_redirect_uri(client, req, **query)
self.__validate_client_secret(client, req, **query)
def __handle_grant_type_authorization_code(self, **query):
client = self.__validate_client(**query)
redirect_uri = self.__validate_redirect_uri(client, **query)
self.__validate_client_secret(client, **query)
if 'code' not in query:
raise OAuthException(
@ -296,7 +304,7 @@ class Main(http.Controller):
OAuthException.INVALID_GRANT,
)
try:
payload = jwt_decode(query['code'], self.__get_authorization_code_jwk(req))
payload = jwt_decode(query['code'], self.__get_authorization_code_jwk())
except jwt.JWTExpired:
raise OAuthException(
'Code expired',
@ -319,7 +327,7 @@ class Main(http.Controller):
)
# Retrieve/generate access token. We currently only store one per user/client
token = req.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
token = request.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
payload['user_id'],
client.id
)
@ -329,11 +337,11 @@ class Main(http.Controller):
}
if 'openid' in payload['scopes']:
extra_claims = { name: payload[name] for name in payload if name in ['sid', 'nonce'] }
response['id_token'] = self.__create_id_token(req, payload['user_id'], client, extra_claims)
response['id_token'] = self.__create_id_token(payload['user_id'], client, extra_claims)
return response
def __handle_grant_type_password(self, req, **query):
client = self.__validate_client(req, **query)
def __handle_grant_type_password(self, **query):
client = self.__validate_client(**query)
if not client.allow_password_grant:
raise OAuthException(
'This client is not allowed to perform password flow',
@ -346,8 +354,8 @@ class Main(http.Controller):
'{} is required'.format(param),
OAuthException.INVALID_REQUEST
)
user_id = req.env['res.users'].authenticate(
req.env.cr.dbname,
user_id = request.env['res.users'].authenticate(
request.env.cr.dbname,
query['username'],
query['password'],
None
@ -360,7 +368,7 @@ class Main(http.Controller):
scopes = query['scope'].split(' ') if query.get('scope') else []
# Retrieve/generate access token. We currently only store one per user/client
token = req.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
token = request.env['galicea_openid_connect.access_token'].sudo().retrieve_or_create(
user_id,
client.id
)
@ -369,19 +377,19 @@ class Main(http.Controller):
'token_type': 'bearer'
}
if 'openid' in scopes:
response['id_token'] = self.__create_id_token(req, user_id, client, {})
response['id_token'] = self.__create_id_token(user_id, client, {})
return response
def __handle_grant_type_client_credentials(self, req, **query):
client = self.__validate_client(req, **query)
self.__validate_client_secret(client, req, **query)
token = req.env['galicea_openid_connect.client_access_token'].sudo().retrieve_or_create(client.id)
def __handle_grant_type_client_credentials(self, **query):
client = self.__validate_client(**query)
self.__validate_client_secret(client, **query)
token = request.env['galicea_openid_connect.client_access_token'].sudo().retrieve_or_create(client.id)
return {
'access_token': token.token,
'token_type': 'bearer'
}
def __create_id_token(self, req, user_id, client, extra_claims):
def __create_id_token(self, user_id, client, extra_claims):
claims = {
'iss': http.request.httprequest.host_url,
'sub': str(user_id),
@ -397,14 +405,14 @@ class Main(http.Controller):
if 'at_hash' in extra_claims:
claims['at_hash'] = extra_claims['at_hash']
key = self.__get_id_token_jwk(req)
key = self.__get_id_token_jwk()
return jwt_encode(claims, key)
def __redirect(self, url, params, response_mode):
location = '{}{}{}'.format(
url,
'?' if response_mode == 'query' else '#',
werkzeug.url_encode(params)
url_encode(params)
)
return werkzeug.Response(
headers={'Location': location},

Loading…
Cancel
Save