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.
216 lines
9.5 KiB
216 lines
9.5 KiB
# -*- coding: utf-8 -*-
|
|
###############################################################################
|
|
#
|
|
# Cybrosys Technologies Pvt. Ltd.
|
|
#
|
|
# Copyright (C) 2024-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 logging
|
|
import requests
|
|
import werkzeug
|
|
from odoo import _, api, fields, models
|
|
from odoo.addons.auth_signup.models.res_partner import SignupError
|
|
from odoo.exceptions import AccessDenied
|
|
from odoo.tools.misc import ustr
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ResUsers(models.Model):
|
|
"""Inherit res.users model to add fields"""
|
|
_inherit = 'res.users'
|
|
|
|
oauth_token = fields.Char(readonly=True)
|
|
git_username = fields.Char(default="No username",help="Username")
|
|
git_email = fields.Char(string="Github Email",help="Email")
|
|
|
|
def _auth_oauth_rpc(self, endpoint, access_token):
|
|
"""
|
|
Perform an OAuth RPC call to the specified endpoint with the
|
|
provided access token.
|
|
|
|
This method performs an OAuth RPC call to the given endpoint using
|
|
the provided access token.
|
|
If the context indicates GitHub authentication, it specifically
|
|
handles GitHub OAuth calls to retrieve user
|
|
data.
|
|
For other OAuth providers, it uses the standard OAuth authorization
|
|
header or access token for the request.
|
|
It handles the response and returns the user data or error
|
|
information.
|
|
"""
|
|
if self.env.context.get('github'):
|
|
provider = (self.env['auth.oauth.provider'].
|
|
browse(self.env.context.get('provider')))
|
|
params = {
|
|
'client_id': provider.client_id,
|
|
'client_secret': provider.client_secret,
|
|
'code': access_token,
|
|
'scope': 'user:email' # Add this line to request email access
|
|
}
|
|
response = requests.get(endpoint, params=params, timeout=10)
|
|
if response.ok:
|
|
response_data = response.content.decode("UTF-8").split('&')
|
|
if 'error=' in response_data or 'error=' in response_data[0]:
|
|
r_url = "/web/login?oauth_error=5"
|
|
_logger.info(
|
|
'OAuth2: access denied, redirect to main page in case a '
|
|
'valid session exists, without setting cookies.'
|
|
' REASON :- %s' % str(
|
|
response_data[0]))
|
|
redirect = werkzeug.utils.redirect(r_url, 303)
|
|
redirect.autocorrect_location_header = False
|
|
return redirect
|
|
auth_token = response_data[0].split('=')[1]
|
|
user_data_response = requests.get('https://api.github.com/'
|
|
'user/emails', auth=('', auth_token)).json()
|
|
email = [email_data['email'] for email_data in
|
|
user_data_response if email_data['primary']][0]
|
|
user_data = requests.get('https://api.github.com/user',
|
|
auth=('', auth_token)).json()
|
|
params = {
|
|
'key': auth_token,
|
|
'user_id': user_data.get('id'),
|
|
'username': user_data.get('login'),
|
|
'name': user_data.get('name'),
|
|
'email': email
|
|
}
|
|
return params
|
|
else:
|
|
if (self.env['ir.config_parameter'].sudo().
|
|
get_param('auth_oauth.authorization_header')):
|
|
response = requests.get(endpoint,
|
|
headers={'Authorization': 'Bearer %s'
|
|
% access_token}, timeout=10)
|
|
else:
|
|
response = requests.get(endpoint,
|
|
params={'access_token': access_token},
|
|
timeout=10)
|
|
if response.ok: # nb: could be a successful failure
|
|
return response.json()
|
|
auth_challenge = werkzeug.http.parse_www_authenticate_header(
|
|
response.headers.get('WWW-Authenticate'))
|
|
if auth_challenge.type == 'bearer' and 'error' in auth_challenge:
|
|
return dict(auth_challenge)
|
|
return {'error': 'invalid_request'}
|
|
|
|
@api.model
|
|
def _auth_oauth_validate(self, provider, access_token):
|
|
"""
|
|
Validate the OAuth access token with the specified provider and
|
|
retrieve user data.
|
|
|
|
This method validates the provided OAuth access token with the
|
|
given provider's validation endpoint.
|
|
If validation is successful, it retrieves additional user data from
|
|
the provider's data endpoint.
|
|
It then processes the validation response to extract the user's
|
|
subject identity and returns the validation
|
|
data.
|
|
"""
|
|
oauth_provider = self.env['auth.oauth.provider'].browse(provider)
|
|
validation = self._auth_oauth_rpc(oauth_provider.validation_endpoint,
|
|
access_token)
|
|
if validation.get("error"):
|
|
raise Exception(validation['error'])
|
|
if oauth_provider.data_endpoint:
|
|
data = self._auth_oauth_rpc(oauth_provider.data_endpoint,
|
|
access_token)
|
|
validation.update(data)
|
|
if self.env.context.get('github'):
|
|
return validation
|
|
subject = next(filter(None, [
|
|
validation.pop(key, None)
|
|
for key in [
|
|
'sub',
|
|
'id',
|
|
'user_id',
|
|
]
|
|
]), None)
|
|
if not subject:
|
|
raise AccessDenied('Missing subject identity')
|
|
validation['user_id'] = subject
|
|
|
|
return validation
|
|
|
|
def github_api_hit(self):
|
|
"""
|
|
Trigger GitHub OAuth authorization by redirecting to GitHub's
|
|
authorization URL.
|
|
|
|
This method retrieves the GitHub OAuth provider configuration and
|
|
constructs the authorization URL.
|
|
It checks if the client ID is available and redirects the user to
|
|
GitHub's authorization page.
|
|
The authorization URL includes the required client ID and scopes
|
|
(repo and user) for the OAuth request.
|
|
"""
|
|
provider = self.env.ref('github_oauth_app.auth_oauth_provider_github')
|
|
provider = self.env[provider._name].sudo().browse(provider.id)
|
|
if provider:
|
|
if not provider.client_id:
|
|
r_url = "/web/login?oauth_error=6"
|
|
_logger.info(
|
|
'OAuth2: Either of Client ID or Client Secret not present, '
|
|
'access denied, redirect to main page in case a valid '
|
|
'session exists, without setting cookies')
|
|
redirect = werkzeug.utils.redirect(r_url, 303)
|
|
redirect.autocorrect_location_header = False
|
|
return redirect
|
|
url = ("https://github.com/login/oauth/authorize?client_id=%s&"
|
|
"scope=repo,user") % provider.client_id
|
|
response = requests.get(url)
|
|
if response.status_code in [200, 201]:
|
|
return response.url
|
|
|
|
@api.model
|
|
def _signup_create_user(self, values):
|
|
"""
|
|
Create a new user during signup using the default method.
|
|
|
|
This method calls the default user creation method during signup.
|
|
It simply delegates the user creation process to the parent class
|
|
method.
|
|
"""
|
|
return super(ResUsers, self)._signup_create_user(values)
|
|
|
|
def _create_user_from_default_template(self, values):
|
|
"""
|
|
Create a new user based on the default user template.
|
|
|
|
This method creates a new user by copying the default user template.
|
|
It validates the provided values and ensures that essential fields
|
|
like login, name, and partner are
|
|
provided.
|
|
If the template user does not exist or the required values are
|
|
missing, it raises appropriate exceptions.
|
|
"""
|
|
template_user = self.env.ref('base.default_user')
|
|
if not template_user.exists():
|
|
raise ValueError(_('Signup: invalid template user'))
|
|
if not values.get('login'):
|
|
raise ValueError(_('Signup: no login given for new user'))
|
|
if not values.get('partner_id') and not values.get('name'):
|
|
raise ValueError(_('Signup: no name or partner given for new user'))
|
|
values['active'] = True
|
|
try:
|
|
with ((self.env.cr.savepoint())):
|
|
return template_user.with_context(no_reset_password=True
|
|
).copy(values)
|
|
except Exception as e:
|
|
# copy may fail if asked login is not available.
|
|
raise SignupError(ustr(e))
|
|
|