Files
gzth/yuthon_wechat/controllers/main.py
T
2026-02-23 12:05:53 +08:00

335 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from urllib.parse import quote
import json
import uuid
import logging
from odoo import http
from odoo.http import request
from odoo.exceptions import AccessDenied
from werkzeug.wrappers import Response as WerkzeugResponse
from ..utils.common import WeChatOAuthException
_logger = logging.getLogger(__name__)
class Home(http.Controller):
@http.route('/WW_verify_fC7AX158Ffby8Ed1.txt', type='http', auth="public")
def WW_verify_UP42BAWNwtWK60RH(self):
return 'fC7AX158Ffby8Ed1'
@http.route('/wechat/login', type='http', auth="none", methods=['GET'])
def wechat_login(self):
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
redirect_uri = quote(f'{base_url}/wechat/login_by_code')
redirect_uri = redirect_uri.replace(' ', '%20')
appid = 'wwc955c117b738b052'
agent_id = '1000120'
url = f'https://open.weixin.qq.com/connect/oauth2/authorize?appid={appid}&redirect_uri={redirect_uri}&response_type=code&scope=snsapi_base&state=STATE&agentid={agent_id}#wechat_redirect'
return http.request.redirect(url, 301, False)
@http.route('/wechat/login_by_code', type='http', auth="none", methods=['GET'])
def wechat_login_by_code(self, code, state=None, **kw):
"""
企业微信OAuth回调接口,支持state参数重定向到目标URL
参数:
code: 企业微信OAuth授权码
state: 目标URL(编码后的),登录成功后重定向到此URL
"""
provider = request.env.ref('yuthon_wechat.wechat_corp_web_auth_oauth_provider')
(db, login, oauth_code) = request.env['res.users'].sudo().auth_oauth_wechat(provider.sudo(), {'code': code})
# 如果没有state参数,返回原始值,保持与原本方法行为一致
if not state:
return (db, login, oauth_code)
# 如果有state参数,执行登录并重定向到目标URL
try:
# 查找用户并创建session
user = request.env['res.users'].sudo().search([('login', '=', login)], limit=1)
if not user:
_logger.error(f"OAuth登录后未找到用户: {login}")
return http.request.redirect('/web/login?error=user_not_found', 302)
# 创建session
dbname = request.env.cr.dbname
try:
uid = request.session.authenticate(dbname, user.login, user.login)
except Exception:
# 如果authenticate失败,直接设置session
request.session.uid = user.id
request.session.login = user.login
request.session.db = dbname
uid = user.id
_logger.info(f"企业微信OAuth登录成功: 用户 {user.login} (ID: {user.id})")
# 解码并重定向到目标URL
try:
from urllib.parse import unquote
target_url = unquote(state)
# 确保是相对路径或同域名
if target_url.startswith('http') and 'oa.thtzjt.com' not in target_url:
# 如果不是同域名,默认重定向到/web
target_url = '/web'
_logger.info(f"OAuth登录成功,重定向到: {target_url}")
return http.request.redirect(target_url, 302)
except Exception as e:
_logger.error(f"解析state参数失败: {str(e)}")
return http.request.redirect('/web', 302)
except AccessDenied as e:
_logger.error(f"企业微信OAuth登录失败: {str(e)}")
return http.request.redirect('/web/login?error=access_denied', 302)
except Exception as e:
_logger.exception(f"企业微信OAuth回调处理异常: {str(e)}")
return http.request.redirect('/web/login?error=oauth_error', 302)
@http.route('/wechat/login_by_user_id', type='http', auth="none", methods=['GET', 'POST'], csrf=False)
def wechat_login_by_user_id(self, user_id=None, login=None, **kw):
"""
免登录接口:通过用户ID或登录名直接登录,绕过密码校验
/wechat/login_by_user_id?user_id=2&redirect_url=/web#action=3584
/wechat/login_by_user_id?user_id=2&redirect_url=/web#action=3584
https://oa.thtzjt.com/web#action=3584&cids=3-1-2-4&menu_id=9668
参数:
user_id: 用户ID (res.users的id)
login: 用户登录名 (res.users的login字段)
返回:
JSON格式的响应,包含登录结果
"""
try:
# 优先使用user_id,如果没有则使用login查找
if user_id:
try:
user_id = int(user_id)
user = request.env['res.users'].sudo().browse(user_id)
except (ValueError, TypeError):
return json.dumps({
'success': False,
'message': '用户ID格式错误'
}), 200, [('Content-Type', 'application/json')]
elif login:
user = request.env['res.users'].sudo().search([('login', '=', login)], limit=1)
else:
return json.dumps({
'success': False,
'message': '请提供user_id或login参数'
}), 200, [('Content-Type', 'application/json')]
# 验证用户是否存在且激活
if not user or not user.exists():
return json.dumps({
'success': False,
'message': '用户不存在'
}), 200, [('Content-Type', 'application/json')]
if not user.active:
return json.dumps({
'success': False,
'message': '用户已被禁用'
}), 200, [('Content-Type', 'application/json')]
# 直接设置session来绕过密码验证
dbname = request.env.cr.dbname
# 方法1: 尝试使用authenticate(如果用户密码恰好是login)
try:
uid = request.session.authenticate(dbname, user.login, user.login)
except Exception:
# 方法2: 如果authenticate失败,直接设置session
# 这是绕过密码验证的关键步骤
request.session.uid = user.id
request.session.login = user.login
request.session.db = dbname
uid = user.id
_logger.info(f"免登录接口:用户 {user.login} (ID: {user.id}) 登录成功")
# 重定向到主页或返回成功信息
redirect_url = kw.get('redirect_url', '/web')
if kw.get('return_json', 'false').lower() == 'true':
return json.dumps({
'success': True,
'message': '登录成功',
'user_id': user.id,
'login': user.login,
'name': user.name
}), 200, [('Content-Type', 'application/json')]
else:
return http.request.redirect(redirect_url, 302)
except Exception as e:
_logger.exception(f"免登录接口错误: {str(e)}")
return json.dumps({
'success': False,
'message': f'登录失败: {str(e)}'
}), 200, [('Content-Type', 'application/json')]
@http.route('/phone/wechat/login', type='http', auth="none", csrf=False, cors='*', methods=['POST'])
def phone_wechat_login(self, **kw):
"""手机端企业微信OAuth登录接口"""
try:
code = kw.get('code')
if not code:
return request.make_response(
json.dumps({
'code': 400,
'message': '缺少code参数',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
try:
provider = request.env.ref('yuthon_wechat.wechat_corp_web_auth_oauth_provider').sudo()
except Exception as e:
_logger.error(f"获取企业微信OAuth provider失败: {str(e)}")
return request.make_response(
json.dumps({
'code': 500,
'message': '企业微信配置错误',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
try:
(db, login, oauth_code) = request.env['res.users'].sudo().auth_oauth_wechat(provider, {'code': code})
except AccessDenied as e:
_logger.warning(f"企业微信登录失败: {str(e)}")
return request.make_response(
json.dumps({
'code': 403,
'message': str(e) or '未找到对应的用户账号',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
except Exception as e:
_logger.error(f"企业微信OAuth认证异常: {str(e)}", exc_info=True)
return request.make_response(
json.dumps({
'code': 500,
'message': f'企业微信认证失败: {str(e)}',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
user = request.env['res.users'].sudo().search([('login', '=', login)], limit=1)
if not user:
_logger.warning(f"未找到用户: {login}")
return request.make_response(
json.dumps({
'code': 404,
'message': '未找到对应的用户账号',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
employee = user.with_company(user.company_id.id).employee_id
if not employee:
oauth_uid = user.oauth_uid
if oauth_uid:
employee = request.env['hr.employee'].sudo().search([
('wecom_userid', '=', oauth_uid)
], limit=1)
if not employee:
_logger.warning(f"未找到对应的员工信息, user: {login}")
return request.make_response(
json.dumps({
'code': 404,
'message': '未找到对应的员工信息',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
# access_list = []
# applet_security_setting_ids = request.env['applet.security'].search(
# [('employee_id', '=', employee.id)]).applet_security_setting_ids
# access_list = [access.name for access in applet_security_setting_ids]
# _logger.info(f"企业微信登录用户{user.name},权限组{access_list}---")
applet_security_setting_ids = request.env['applet.security'].sudo().search(
[('employee_id', '=', employee.id)]).applet_security_setting_ids
access_list = [access.name for access in applet_security_setting_ids]
_logger.info(f"企业微信登录用户{user.name},权限组{access_list}---")
company_list = []
for company in user.company_ids:
company_dict = {
'state': 1,
'company_id': company.id,
'company_name': company.name,
}
company_list.append(company_dict)
token = str(uuid.uuid4())
data = {
'name': employee.name,
'id': user.id,
'access': access_list,
'phone': employee.mobile_phone,
'company_data': company_list,
'token': token,
}
return request.make_response(
json.dumps({
'code': 200,
'message': '',
'data': data
}),
headers=[('Content-Type', 'application/json')]
)
except Exception as e:
_logger.error(f"企业微信登录异常: {str(e)}", exc_info=True)
return request.make_response(
json.dumps({
'code': 500,
'message': f'登录失败: {str(e)}',
'data': None
}),
headers=[('Content-Type', 'application/json')]
)
@http.route('/phone/wechat/callback', type='http', auth="none", csrf=False, cors='*', methods=['GET'])
def phone_wechat_callback(self, code=None, state=None, **kw):
try:
frontend_base_url = 'https://phone.thtzjt.com'
if code:
redirect_url = f"{frontend_base_url}/phone/WechatCallback?code={code}"
if state:
redirect_url += f"&state={state}"
_logger.info(f"手机端OAuth回调,重定向到: {redirect_url}")
else:
redirect_url = f"{frontend_base_url}/phone/Login"
_logger.warning("手机端OAuth回调缺少code参数,重定向到登录页")
response = WerkzeugResponse(
'',
status=302,
headers=[('Location', redirect_url)]
)
return response
except Exception as e:
_logger.error(f"企业微信回调处理异常: {str(e)}", exc_info=True)
frontend_base_url = 'https://phone.thtzjt.com'
redirect_url = f"{frontend_base_url}/phone/Login"
return WerkzeugResponse(
'',
status=302,
headers=[('Location', redirect_url)]
)