You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
11 KiB
288 lines
11 KiB
# -*- coding: utf-8 -*-
|
|
###############################################################################
|
|
#
|
|
# Cybrosys Technologies Pvt. Ltd.
|
|
#
|
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
|
# Author: Cybrosys Techno Solutions (odoo@cybrosys.com)
|
|
#
|
|
# You can modify it under the terms of the GNU AFFERO
|
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
|
#
|
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
# (AGPL v3) along with this program.
|
|
# If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
###############################################################################
|
|
import json
|
|
import logging
|
|
import werkzeug
|
|
from odoo import _, api, http, SUPERUSER_ID
|
|
from odoo.exceptions import AccessDenied
|
|
from odoo.http import request
|
|
from odoo.addons.auth_oauth.controllers.main import (fragment_to_query_string,
|
|
OAuthController, OAuthLogin)
|
|
from odoo.addons.auth_signup.controllers.main import AuthSignupHome as Home
|
|
from odoo.addons.web.controllers.utils import _get_login_redirect_url, ensure_db
|
|
from werkzeug.exceptions import BadRequest
|
|
from odoo import registry as registry_get
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AuthLoginHome(Home):
|
|
"""HTTP controller to login via github"""
|
|
|
|
@http.route()
|
|
def web_login(self, *args, **kw):
|
|
"""
|
|
Override the web login method to handle OAuth errors and display
|
|
available OAuth providers.
|
|
|
|
"""
|
|
ensure_db()
|
|
|
|
# Handle redirection if the request method is GET, user is
|
|
# authenticated, and redirect URL is provided
|
|
if request.httprequest.method == 'GET' and request.session.uid:
|
|
redirect_url = request.params.get('redirect')
|
|
if redirect_url:
|
|
return request.redirect(redirect_url)
|
|
|
|
# List OAuth providers
|
|
providers = self.list_providers()
|
|
|
|
# Call parent class login method
|
|
response = super(OAuthLogin, self).web_login(*args, **kw)
|
|
|
|
# Process response if it is a QWeb template
|
|
if response.is_qweb:
|
|
# Define error messages in a dictionary
|
|
error_messages = {
|
|
'1': _("You are not allowed to signup on this database."),
|
|
'2': _("Access Denied"),
|
|
'3': _(
|
|
"Email Already Exists.\nPlease contact your "
|
|
"Administrator."),
|
|
'4': _(
|
|
"Validation Endpoint either Not present or invalid.\nPlease"
|
|
"contact your Administrator."),
|
|
'5': _(
|
|
"Github OAuth API Failed, For more information please "
|
|
"contact Administrator."),
|
|
'6': _(
|
|
"Github OAuth API Failed,\nClient ID or Client Secret Not"
|
|
" present or has been compromised.\n"
|
|
"For more information please contact Administrator.")
|
|
}
|
|
|
|
# Get the error message if an OAuth error code is present
|
|
error_code = request.params.get('oauth_error')
|
|
error_message = error_messages.get(error_code)
|
|
|
|
# Add providers and error message to the response context
|
|
response.qcontext.update({
|
|
'providers': providers,
|
|
'error': error_message
|
|
})
|
|
return response
|
|
|
|
|
|
class GitHubOAuthController(OAuthController):
|
|
"""Controller to sign in to home page"""
|
|
|
|
@http.route('/auth_oauth/signin', type='http', auth='none')
|
|
@fragment_to_query_string
|
|
def signin(self, **kw):
|
|
"""
|
|
Handle OAuth sign-in redirection and user authentication.
|
|
|
|
This route is responsible for processing the OAuth sign-in callback
|
|
and initiating the user authentication
|
|
process.
|
|
It retrieves the database name, OAuth provider, and other context
|
|
information from the state parameter.
|
|
The user authentication is performed using the OAuth credentials
|
|
provided in the request.
|
|
After successful authentication, the user is redirected to the
|
|
appropriate page based on the state
|
|
parameters.
|
|
|
|
"""
|
|
# Extract and validate state parameters
|
|
state_json = kw.get('state', '{}')
|
|
try:
|
|
state = json.loads(state_json)
|
|
except json.JSONDecodeError:
|
|
_logger.error("Invalid state parameter: %s", state_json)
|
|
return BadRequest()
|
|
|
|
dbname = state.get('d')
|
|
if not dbname or not http.db_filter([dbname]):
|
|
return BadRequest()
|
|
|
|
provider = state.get('p')
|
|
context = state.get('c', {})
|
|
action = state.get('a')
|
|
menu = state.get('m')
|
|
redirect_url = state.get('r')
|
|
|
|
# Get registry and authenticate
|
|
registry = registry_get(dbname)
|
|
try:
|
|
with registry.cursor() as cr:
|
|
context.update({'provider': provider, 'github': True})
|
|
env = api.Environment(cr, SUPERUSER_ID, context)
|
|
db, login, key = env['res.users'].sudo().auth_oauth(provider,
|
|
kw)
|
|
cr.commit()
|
|
# Determine the redirection URL
|
|
if redirect_url:
|
|
url = werkzeug.urls.url_unquote_plus(redirect_url)
|
|
else:
|
|
url = '/web'
|
|
if action:
|
|
url = f'/web#action={action}'
|
|
elif menu:
|
|
url = f'/web#menu_id={menu}'
|
|
|
|
# Authenticate session and handle redirection
|
|
pre_uid = request.session.authenticate(db, login, key)
|
|
resp_url = _get_login_redirect_url(pre_uid, url)
|
|
resp = request.redirect(resp_url, 303)
|
|
# Adjust location header if necessary
|
|
if werkzeug.urls.url_parse(resp.location).path == '/web':
|
|
resp.location = '/'
|
|
return resp
|
|
except AttributeError:
|
|
_logger.error(
|
|
"auth_signup not installed on database %s: oauth sign up "
|
|
"cancelled.",
|
|
dbname)
|
|
return request.redirect("/web/login?oauth_error=1", 303)
|
|
|
|
except AccessDenied:
|
|
_logger.info(
|
|
'OAuth2: access denied, redirecting to main page if a valid '
|
|
'session exists.')
|
|
return request.redirect("/web/login?oauth_error=3", 303)
|
|
|
|
except Exception as e:
|
|
_logger.exception("OAuth2 error: %s", str(e))
|
|
return request.redirect("/web/login?oauth_error=2", 303)
|
|
|
|
|
|
class OAuthLogin(OAuthLogin):
|
|
"""Controller to login"""
|
|
def list_providers(self):
|
|
"""
|
|
Retrieve a list of enabled OAuth providers.
|
|
|
|
This method queries the database for OAuth providers that are
|
|
enabled.
|
|
For each provider, it generates an authentication link based on
|
|
the provider's details.
|
|
The authentication link is used to initiate the OAuth authentication
|
|
process.
|
|
|
|
"""
|
|
try:
|
|
providers = (request.env['auth.oauth.provider'].sudo().
|
|
search_read([('enabled', '=', True)]))
|
|
except Exception:
|
|
providers = []
|
|
for provider in providers:
|
|
state = self.get_state(provider)
|
|
if provider.get('name') in ['GitHub', 'github']:
|
|
params = dict(
|
|
client_id=provider['client_id'],
|
|
scope=provider['scope'],
|
|
state=json.dumps(state),
|
|
)
|
|
provider['auth_link'] = ("%s?%s"
|
|
% (provider['auth_endpoint'],
|
|
werkzeug.urls.url_encode(params)))
|
|
else:
|
|
return_url = request.httprequest.url_root + 'auth_oauth/signin'
|
|
params = dict(
|
|
response_type='token',
|
|
client_id=provider['client_id'],
|
|
redirect_uri=return_url,
|
|
scope=provider['scope'],
|
|
state=json.dumps(state),
|
|
)
|
|
provider['auth_link'] = ("%s?%s"
|
|
% (provider['auth_endpoint'],
|
|
werkzeug.urls.url_encode(params)))
|
|
return providers
|
|
|
|
|
|
class CallbackHandler(http.Controller):
|
|
"""Controller for call back URL"""
|
|
|
|
@http.route(['/oauth/callback'], auth='public', csrf=False,
|
|
methods=['GET', 'POST'], type='http')
|
|
def get_oauth_token(self, **post):
|
|
"""
|
|
Handle OAuth callback to retrieve access token.
|
|
|
|
This route handles the OAuth callback after a user has authenticated
|
|
with an OAuth provider.
|
|
It extracts the state and provider information from the request
|
|
parameters to identify the OAuth provider.
|
|
Based on the provider, it retrieves the client ID and client secret
|
|
to exchange the authorization code for
|
|
an access token.
|
|
The access token is then appended to the redirect URL and the user
|
|
is redirected to the sign-in page with
|
|
the token.
|
|
"""
|
|
# Determine OAuth provider
|
|
state = post.get('state')
|
|
if state:
|
|
try:
|
|
provider_id = json.loads(state).get('p')
|
|
provider = request.env['auth.oauth.provider'].sudo().browse(
|
|
provider_id)
|
|
except (json.JSONDecodeError, ValueError):
|
|
_logger.error('Invalid state parameter: %s', state)
|
|
return werkzeug.utils.redirect("/web/login?oauth_error=4", 303)
|
|
else:
|
|
provider = request.env.ref(
|
|
'github_oauth_app.auth_oauth_provider_github').sudo()
|
|
|
|
# Prepare redirect URL
|
|
base_redirect_url = request.httprequest.url_root + "auth_oauth/signin"
|
|
|
|
# Handle OAuth code and redirection
|
|
if post.get("code"):
|
|
client_id = provider.client_id
|
|
client_secret = provider.client_secret
|
|
|
|
if not client_id or not client_secret:
|
|
_logger.info(
|
|
'OAuth2: Missing Client ID or Client Secret. Redirecting to '
|
|
'login page.')
|
|
return werkzeug.utils.redirect("/web/login?oauth_error=6", 303)
|
|
|
|
# Build redirect URL with query parameters
|
|
query_params = {
|
|
'access_token': post.get("code"),
|
|
'state': state,
|
|
'provider': provider.id
|
|
}
|
|
redirect_url = (f"{base_redirect_url}?"
|
|
f"{werkzeug.urls.url_encode(query_params)}")
|
|
|
|
return werkzeug.utils.redirect(redirect_url)
|
|
|
|
# Handle missing code in post data
|
|
_logger.warning(
|
|
"OAuth2: Code not present in post data. Redirecting to login page.")
|
|
return werkzeug.utils.redirect("/web/login?oauth_error=4", 303)
|
|
|