131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
import datetime
|
|
import functools
|
|
import json
|
|
import uuid
|
|
|
|
import jwt
|
|
|
|
from odoo import fields, http
|
|
from odoo.http import request
|
|
from odoo.tools import config
|
|
|
|
|
|
JWT_ISSUER = 'odoo'
|
|
JWT_AUDIENCE = 'odoo-api'
|
|
|
|
|
|
def json_response(payload, status=200):
|
|
return request.make_response(
|
|
json.dumps(payload),
|
|
headers=[('Content-Type', 'application/json')],
|
|
status=status,
|
|
)
|
|
|
|
|
|
def get_jwt_secret():
|
|
secret_key = config.get('jwt_secret')
|
|
if not secret_key or len(secret_key) < 32:
|
|
return None
|
|
return secret_key
|
|
|
|
|
|
def check_jwt_auth(func):
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
auth_header = request.httprequest.headers.get('Authorization', '')
|
|
|
|
if not auth_header.startswith('Bearer '):
|
|
return json_response({'error': 'Bearer token is required.'}, status=401)
|
|
|
|
secret_key = get_jwt_secret()
|
|
if not secret_key:
|
|
return json_response({'error': 'JWT secret is not configured.'}, status=500)
|
|
|
|
token = auth_header.split(' ', 1)[1].strip()
|
|
|
|
try:
|
|
payload = jwt.decode(
|
|
token,
|
|
secret_key,
|
|
algorithms=['HS256'],
|
|
issuer=JWT_ISSUER,
|
|
audience=JWT_AUDIENCE,
|
|
options={'require': ['exp', 'iat', 'sub', 'client_id']},
|
|
)
|
|
user_id = int(payload['sub'])
|
|
client = request.env['api.client'].sudo().search([
|
|
('client_id', '=', payload['client_id']),
|
|
('active', '=', True),
|
|
('user_id', '=', user_id),
|
|
], limit=1)
|
|
user = request.env['res.users'].sudo().browse(user_id).exists()
|
|
if not client or not user or not user.active:
|
|
return json_response({'error': 'Token subject is not allowed.'}, status=401)
|
|
|
|
request.jwt_payload = payload
|
|
request.jwt_client = client
|
|
request.jwt_user_id = user_id
|
|
request.update_env(user=user_id)
|
|
except jwt.ExpiredSignatureError:
|
|
return json_response({'error': 'Token has expired.'}, status=401)
|
|
except (jwt.InvalidTokenError, ValueError):
|
|
return json_response({'error': 'Token is invalid.'}, status=401)
|
|
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
class JwtAuthController(http.Controller):
|
|
|
|
@http.route('/api/auth/token', type='http', auth='public', methods=['POST'], csrf=False)
|
|
def generate_token(self, **kwargs):
|
|
data = request.httprequest.get_json(silent=True) or kwargs
|
|
client_id = data.get('client_id')
|
|
client_secret = data.get('client_secret') or data.get('secret_key')
|
|
|
|
if not client_id or not client_secret:
|
|
return json_response({'error': 'client_id and client_secret are required.'}, status=400)
|
|
|
|
secret_key = get_jwt_secret()
|
|
if not secret_key:
|
|
return json_response({'error': 'JWT secret is not configured.'}, status=500)
|
|
|
|
client = request.env['api.client'].sudo().search([
|
|
('client_id', '=', client_id),
|
|
('active', '=', True),
|
|
], limit=1)
|
|
|
|
if not client or not client._check_secret(client_secret) or not client.user_id.active:
|
|
return json_response({'error': 'Invalid credentials.'}, status=401)
|
|
|
|
now = datetime.datetime.now(datetime.UTC)
|
|
expires_at = now + datetime.timedelta(seconds=client.token_ttl)
|
|
payload = {
|
|
'iss': JWT_ISSUER,
|
|
'aud': JWT_AUDIENCE,
|
|
'exp': expires_at,
|
|
'iat': now,
|
|
'jti': str(uuid.uuid4()),
|
|
'sub': str(client.user_id.id),
|
|
'client_id': client.client_id,
|
|
}
|
|
token = jwt.encode(payload, secret_key, algorithm='HS256')
|
|
client.last_used = fields.Datetime.now()
|
|
|
|
return json_response({
|
|
'access_token': token,
|
|
'expires_in': client.token_ttl,
|
|
'token_type': 'Bearer',
|
|
})
|
|
|
|
@http.route('/api/ifc/projects', type='http', auth='public', methods=['GET', 'POST'], csrf=False)
|
|
@check_jwt_auth
|
|
def get_ifc_projects(self, **kwargs):
|
|
return json_response({
|
|
'message': 'JWT token is valid.',
|
|
'user_id': request.env.user.id,
|
|
'user_name': request.env.user.name,
|
|
'client_id': request.jwt_payload.get('client_id'),
|
|
'data': ['Proyek_IFC_Gedung_A', 'Proyek_IFC_Infrastruktur_B'],
|
|
})
|