準備工作
需要用到的模組:
Odoo/auth_oauth
OCA/server-auth/auth_oidc
因為釘釘的Oauth授權流程比較特殊,需要繼承改造res.users上的幾個方法
models\res_users.py
class ResUsers(models.Model):
_inherit = "res.users"
dingtalk_userid = fields.Char('Dingtalk User ID', copy=False)
# 授權回撥後換取acccess_token
def _auth_oauth_get_tokens_auth_code_flow(self, oauth_provider, params):
if 'dingtalk' in oauth_provider.auth_endpoint:
# https://openid.net/specs/openid-connect-core-1_0.html#AuthResponse
code = params.get("code")
# https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
auth = None
if oauth_provider.client_secret:
auth = (oauth_provider.client_id, oauth_provider.client_secret)
response = requests.post(
oauth_provider.token_endpoint,
json=dict(
clientId=oauth_provider.client_id,
clientSecret=oauth_provider.client_secret,
grantType="authorization_code",
code=code,
),
auth=auth,
timeout=10,
)
response.raise_for_status()
response_json = response.json()
# https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
return response_json.get("accessToken"), response_json.get("refreshToken")
else:
return super()._auth_oauth_get_tokens_auth_code_flow(oauth_provider, params)
@api.model
def auth_oauth(self, provider, params):
oauth_provider = self.env["auth.oauth.provider"].browse(provider)
if oauth_provider.flow == "id_token_code" and 'dingtalk' in oauth_provider.auth_endpoint:
access_token, refresh_token = self._auth_oauth_get_tokens_auth_code_flow(
oauth_provider, params
)
if not access_token:
_logger.error("No access_token in response.")
raise AccessDenied()
# 獲取unionid
resp = requests.get(oauth_provider.validation_endpoint, headers={'x-acs-dingtalk-access-token': access_token})
res = resp.json()
if resp.status_code != 200:
_logger.error("Failed to get user info.\n" % res)
raise AccessDenied()
# 透過unionid換取釘釘使用者id
dingtalk_userid = self.env['dingtalk.client'].get_userid_by_unionid(res['unionId'])
if not dingtalk_userid:
raise AccessDenied()
# 查詢內部使用者
user_id = self.env['res.users'].sudo().search([('dingtalk_userid', '=', dingtalk_userid)], limit=1)
if not user_id.login:
raise AccessDenied()
user_id.write({'oauth_uid': res['unionId'], 'oauth_access_token': access_token})
self.delete_other_session()
# return user credentials
return (self.env.cr.dbname, user_id.login, access_token)
else:
return super().auth_oauth(provider, params)
需要預先用服務端介面把釘釘的userid同步進odoo上
controllers\oauth_login.py
# -*- coding: utf-8 -*-
from odoo.addons.auth_oauth.controllers.main import OAuthLogin
class DingtalkAuthLogin(OAuthLogin):
# 針對釘釘授權登入中的prompt引數,配置在auth_endpoint裡:https://login.dingtalk.com/oauth2/auth?prompt=consent
def list_providers(self):
providers = super().list_providers()
for provider in providers:
if provider['auth_link'].count('?') > 1:
auth_params = provider['auth_link'].split('?')
provider['auth_link'] = '%s?%s' % (auth_params[0], '&'.join(auth_params[1:]))
return providers