# Task Team Connector — OAuth2 Authorization Server bridge import json import logging import urllib.parse from odoo import http from odoo.http import request _logger = logging.getLogger(__name__) class TaskTeamOAuthController(http.Controller): """ Implements a minimal OAuth2 Authorization Code flow so that Task Team can authenticate users via their Odoo credentials. Flow: 1. Task Team redirects browser to GET /task_team/oauth/authorize 2. Odoo shows a consent page (user must be logged in) 3. User approves → Odoo redirects to redirect_uri?code=XXX&state=YYY 4. Task Team backend calls POST /task_team/oauth/token to exchange code 5. Response includes Odoo user info + tt_user_id 6. Task Team uses tt_user_id / email to issue its own JWT """ # ------------------------------------------------------------------ # Authorization endpoint # ------------------------------------------------------------------ @http.route( '/task_team/oauth/authorize', type='http', auth='user', methods=['GET', 'POST'], website=False, ) def authorize( self, client_id=None, redirect_uri=None, response_type=None, state=None, **kwargs, ): ICP = request.env['ir.config_parameter'].sudo() expected_client_id = ICP.get_param('task_team_connector.oauth_client_id', '') def _error(msg, status=400): return request.make_response( json.dumps({'error': msg}), headers=[('Content-Type', 'application/json')], status=status, ) if not client_id or client_id != expected_client_id: return _error('invalid_client') if response_type != 'code': return _error('unsupported_response_type') if request.httprequest.method == 'POST': # User approved consent → issue code code = request.env['task.team.oauth.code'].sudo().generate_code( user_id=request.env.user.id, client_id=client_id, redirect_uri=redirect_uri or '', state=state or '', ) params = urllib.parse.urlencode( {'code': code, 'state': state or ''}, quote_via=urllib.parse.quote ) return request.redirect(f'{redirect_uri}?{params}') # GET → render consent form return request.render( 'task_team_connector.oauth_consent_template', { 'client_id': client_id, 'redirect_uri': redirect_uri, 'state': state, 'user_name': request.env.user.name, }, ) # ------------------------------------------------------------------ # Token endpoint # ------------------------------------------------------------------ @http.route( '/task_team/oauth/token', type='json', auth='public', methods=['POST'], csrf=False, ) def token(self, **kwargs): data = request.get_json_data() grant_type = data.get('grant_type') client_id = data.get('client_id') client_secret = data.get('client_secret') code = data.get('code') ICP = request.env['ir.config_parameter'].sudo() expected_client_id = ICP.get_param('task_team_connector.oauth_client_id', '') expected_secret = ICP.get_param('task_team_connector.oauth_client_secret', '') if client_id != expected_client_id or client_secret != expected_secret: return {'error': 'invalid_client'} if grant_type != 'authorization_code': return {'error': 'unsupported_grant_type'} if not code: return {'error': 'invalid_request'} code_record = ( request.env['task.team.oauth.code'].sudo().validate_and_consume(code, client_id) ) if not code_record: return {'error': 'invalid_grant'} user = code_record.user_id tt_user_id = user._tt_get_or_create_user() return { 'token_type': 'bearer', 'odoo_user_id': user.id, 'odoo_email': user.email, 'odoo_name': user.name, 'tt_user_id': tt_user_id, } # ------------------------------------------------------------------ # Userinfo endpoint # ------------------------------------------------------------------ @http.route('/task_team/oauth/userinfo', type='json', auth='user', methods=['GET']) def userinfo(self, **kwargs): user = request.env.user return { 'sub': str(user.id), 'email': user.email, 'name': user.name, 'tt_user_id': user.tt_user_id, }