200 lines
9.6 KiB
Python
200 lines
9.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
import werkzeug.urls
|
||
from urllib.parse import urlparse
|
||
import urllib.request
|
||
import requests
|
||
import json
|
||
|
||
from odoo import api, fields, models
|
||
from odoo.tools.misc import ustr
|
||
from odoo.exceptions import AccessDenied
|
||
from odoo.http import request
|
||
from odoo.addons.auth_signup.models.res_users import SignupError
|
||
from odoo.addons.auth_oauth.controllers.main import OAuthLogin
|
||
import logging
|
||
|
||
from ..utils.common import WeChatOAuthException
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class OAuthLoginWechat(OAuthLogin):
|
||
def list_providers(self):
|
||
providers = super(OAuthLoginWechat, self).list_providers()
|
||
for provider in providers:
|
||
if provider['auth_endpoint'].find('weixin') >= 0:
|
||
# 对微信的回调链接做特殊处理
|
||
# 企业微信扫码登录链接 https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE
|
||
# 企业微信内直接登录链接 https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
|
||
|
||
# 如果auth_endpoint链接中含有 “?create_user” 字样,则自动创建用户
|
||
wechat_url = provider['auth_endpoint']
|
||
create_user = False
|
||
auth_endpoint = wechat_url.split('?')
|
||
if len(auth_endpoint) >= 2:
|
||
wechat_url = auth_endpoint[0]
|
||
create_user = auth_endpoint[1].find('create_user') >= 0
|
||
|
||
client_ids = provider['client_id'].split(' ')
|
||
return_url = request.httprequest.url_root + 'auth_oauth/signin'
|
||
state = self.get_state(provider)
|
||
if create_user:
|
||
state['c'] = {'wechat_create_user': True}
|
||
params = dict(
|
||
appid=client_ids[0].strip(),
|
||
agentid=client_ids[1].strip(),
|
||
redirect_uri=return_url,
|
||
state=json.dumps(state),
|
||
)
|
||
if wechat_url.find('authorize') >= 0: # 企业微信内直接登录
|
||
params.update({
|
||
'response_type': 'code',
|
||
'scope': 'snsapi_base',
|
||
})
|
||
provider['auth_link'] = "%s?%s#wechat_redirect" % (wechat_url, werkzeug.urls.url_encode(params))
|
||
else:
|
||
provider['auth_link'] = "%s?%s" % (wechat_url, werkzeug.urls.url_encode(params))
|
||
return providers
|
||
|
||
|
||
class ResUsers(models.Model):
|
||
_inherit = 'res.users'
|
||
|
||
wecom_fields = fields.Char(string="免登录验证字段", compute='_compute_wecom_fields', stroe=True)
|
||
|
||
@api.depends('login')
|
||
def _compute_wecom_fields(self):
|
||
for i in self:
|
||
i.wecom_fields = i.login
|
||
|
||
@api.model
|
||
def auth_oauth(self, provider, params):
|
||
oauth_provider = self.env['auth.oauth.provider'].browse(provider)
|
||
if oauth_provider.auth_endpoint.find('weixin') >= 0: # 微信认证
|
||
return self.auth_oauth_wechat(oauth_provider, params)
|
||
else:
|
||
return super(ResUsers, self).auth_oauth(provider, params)
|
||
|
||
@api.model
|
||
def auth_oauth_wechat(self, provider, params):
|
||
# 微信获取访问用户身份的URL https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
|
||
# 微信获取access_token的URL https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
|
||
#
|
||
#
|
||
def gettoken(url, corpid, corpsecret):
|
||
url_token = "%s?corpid=%s&corpsecret=%s" % (url, corpid, corpsecret)
|
||
headers = {'Content-Type': 'application/json'}
|
||
response = requests.get(url_token, headers=headers)
|
||
dict_token = response.json()
|
||
if dict_token["errcode"] == 0:
|
||
return dict_token["access_token"]
|
||
else:
|
||
raise OAuthLogin(
|
||
"微信获取access_token错误:err_code=%s, err_msg=%s" % (dict_token["errcode"], dict_token["errmsg"]))
|
||
|
||
client_ids = provider.client_id.split(' ')
|
||
if not client_ids or len(client_ids) != 3:
|
||
raise OAuthLogin("请正确设置微信登录验证的corpid agentid corpsecret!")
|
||
corpid = client_ids[0]
|
||
corpsecret = client_ids[2]
|
||
access_token = provider.scope
|
||
code = params.get('code', False)
|
||
if not access_token:
|
||
access_token = gettoken(provider.validation_endpoint, corpid, corpsecret)
|
||
if not code:
|
||
raise OAuthLogin("微信扫码错误:没有 code!")
|
||
provider.scope = access_token
|
||
|
||
oauth_uid = False
|
||
headers = {'Content-Type': 'application/json'}
|
||
user_url = "%s?access_token=%s&code=%s" % (provider.data_endpoint, access_token, code)
|
||
response = requests.get(user_url, headers=headers)
|
||
dict_user = response.json()
|
||
errcode = dict_user.get("errcode", 0)
|
||
if errcode in [42001]:
|
||
response = requests.get(user_url, headers=headers)
|
||
dict_user = response.json()
|
||
|
||
_logger.warning("------------------" + str(dict_user))
|
||
|
||
if dict_user["errcode"] == 0:
|
||
oauth_uid = dict_user["UserId"]
|
||
elif dict_user["errcode"] == 40014 or dict_user[
|
||
"errcode"] == 42001: # 40014: 不合法的access_token, 42001: access_token expired
|
||
access_token = gettoken(provider.validation_endpoint, corpid, corpsecret)
|
||
provider.scope = access_token
|
||
user_url = "%s?access_token=%s&code=%s" % (provider.data_endpoint, access_token, code)
|
||
response = requests.get(user_url, headers=headers)
|
||
dict_user = response.json()
|
||
_logger.warning("------------------" + str(dict_user))
|
||
oauth_uid = dict_user["UserId"]
|
||
else:
|
||
raise WeChatOAuthException(dict_user["errcode"], "微信获取访问用户身份错误:err_code=%s, err_msg=%s" % (dict_user["errcode"], dict_user["errmsg"]))
|
||
# raise OAuthLogin(
|
||
# "微信获取访问用户身份错误:err_code=%s, err_msg=%s" % (dict_user["errcode"], dict_user["errmsg"]))
|
||
|
||
user_id = self.search([('oauth_uid', '=', oauth_uid), ('oauth_uid', '!=', False)], limit=1)
|
||
if not user_id:
|
||
if self.env.context.get('wechat_create_user', False): # 自动创建企业微信User
|
||
user_id = self.create_from_wechat(provider, access_token, oauth_uid, code)
|
||
if not user_id:
|
||
_logger.exception(
|
||
"OAuth Wechat: 微信访问用户【UserId:%s】没有关联Odoo账号,且自动创建失败!" % (oauth_uid,))
|
||
raise AccessDenied("微信访问用户【UserId:%s】没有关联系统账号!" % (oauth_uid,))
|
||
else:
|
||
_logger.exception("OAuth Wechat: 微信访问用户【UserId:%s】没有关联Odoo账号!" % (oauth_uid,))
|
||
raise AccessDenied("微信访问用户【UserId:%s】没有关联系统账号!" % (oauth_uid,))
|
||
else:
|
||
user_id.oauth_access_token = code
|
||
|
||
return (self.env.cr.dbname, user_id.login, code)
|
||
|
||
@api.model
|
||
def create_from_wechat(self, provider, access_token, userid, code):
|
||
# 读取企业通讯录的成员详情: https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID
|
||
|
||
headers = {'Content-Type': 'application/json'}
|
||
user_url = "https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
||
user_url = "%s?access_token=%s&userid=%s" % (user_url, access_token, userid)
|
||
response = requests.get(user_url, headers=headers)
|
||
dict_user = response.json()
|
||
if dict_user["errcode"] == 0:
|
||
mobile = dict_user.get("mobile", '')
|
||
name = dict_user.get("name", '')
|
||
email = dict_user.get("email", '')
|
||
telephone = dict_user.get("telephone", '')
|
||
values = {
|
||
'login': email or mobile or userid,
|
||
'name': name or userid,
|
||
'mobile': mobile,
|
||
'email': email,
|
||
'phone': telephone,
|
||
'oauth_uid': userid,
|
||
'oauth_provider_id': provider.id,
|
||
'oauth_access_token': code,
|
||
'active': True,
|
||
}
|
||
template_user = self.env.ref("base.default_user")
|
||
try:
|
||
with self.env.cr.savepoint():
|
||
user_id = template_user.sudo().with_context(no_reset_password=True).copy(values)
|
||
# 移除财务管理员权限组
|
||
groups_to_remove = []
|
||
group_manager = self.env.ref('account.group_account_manager', raise_if_not_found=False)
|
||
group_invoice = self.env.ref('account.group_account_invoice', raise_if_not_found=False)
|
||
if group_manager:
|
||
groups_to_remove.append((3, group_manager.id))
|
||
if group_invoice:
|
||
groups_to_remove.append((3, group_invoice.id))
|
||
if groups_to_remove:
|
||
user_id.sudo().write({'groups_id': groups_to_remove})
|
||
return user_id
|
||
except Exception as e:
|
||
_logger.exception("OAuth Wechat: 微信访问用户【UserId:%s】自动创建失败【Odoo错误:%s】!" % (userid, ustr(e)))
|
||
raise OAuthLogin(ustr(e))
|
||
else:
|
||
_logger.exception(
|
||
"OAuth Wechat: 微信访问用户【UserId:%s】自动创建失败【微信错误码:%s】!" % (userid, dict_user["errcode"]))
|
||
return False
|