|
|
|
|
@ -9,86 +9,95 @@ import requests
|
|
|
|
|
from djangoblog.utils import cache_decorator
|
|
|
|
|
from oauth.models import OAuthUser, OAuthConfig
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
logger = logging.getLogger(__name__) #flj
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OAuthAccessTokenException(Exception):
|
|
|
|
|
'''
|
|
|
|
|
oauth授权失败异常
|
|
|
|
|
'''
|
|
|
|
|
class OAuthAccessTokenException(Exception): #fkc
|
|
|
|
|
'''oauth授权失败异常'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseOauthManager(metaclass=ABCMeta):
|
|
|
|
|
"""获取用户授权"""
|
|
|
|
|
class BaseOauthManager(metaclass=ABCMeta): #cll
|
|
|
|
|
"""OAuth授权基类,定义统一接口规范"""
|
|
|
|
|
#cll 授权页面URL
|
|
|
|
|
AUTH_URL = None
|
|
|
|
|
"""获取token"""
|
|
|
|
|
#cll 获取访问令牌URL
|
|
|
|
|
TOKEN_URL = None
|
|
|
|
|
"""获取用户信息"""
|
|
|
|
|
#cll 获取用户信息URL
|
|
|
|
|
API_URL = None
|
|
|
|
|
'''icon图标名'''
|
|
|
|
|
#cll 平台图标标识名
|
|
|
|
|
ICON_NAME = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, access_token=None, openid=None):
|
|
|
|
|
#cll 访问令牌
|
|
|
|
|
self.access_token = access_token
|
|
|
|
|
#cll 第三方平台用户唯一标识
|
|
|
|
|
self.openid = openid
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_access_token_set(self):
|
|
|
|
|
"""判断访问令牌是否已设置"""
|
|
|
|
|
return self.access_token is not None
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_authorized(self):
|
|
|
|
|
"""判断是否已完成授权(令牌和openid均存在)"""
|
|
|
|
|
return self.is_access_token_set and self.access_token is not None and self.openid is not None
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def get_authorization_url(self, nexturl='/'):
|
|
|
|
|
"""抽象方法:获取授权跳转URL"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def get_access_token_by_code(self, code):
|
|
|
|
|
"""抽象方法:通过授权码获取访问令牌"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def get_oauth_userinfo(self):
|
|
|
|
|
"""抽象方法:通过访问令牌获取用户信息"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def get_picture(self, metadata):
|
|
|
|
|
"""抽象方法:从元数据中提取用户头像URL"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def do_get(self, url, params, headers=None):
|
|
|
|
|
"""通用GET请求方法"""
|
|
|
|
|
rsp = requests.get(url=url, params=params, headers=headers)
|
|
|
|
|
logger.info(rsp.text)
|
|
|
|
|
return rsp.text
|
|
|
|
|
|
|
|
|
|
def do_post(self, url, params, headers=None):
|
|
|
|
|
"""通用POST请求方法"""
|
|
|
|
|
rsp = requests.post(url, params, headers=headers)
|
|
|
|
|
logger.info(rsp.text)
|
|
|
|
|
return rsp.text
|
|
|
|
|
|
|
|
|
|
def get_config(self):
|
|
|
|
|
"""获取当前平台的OAuth配置(从数据库读取)"""
|
|
|
|
|
value = OAuthConfig.objects.filter(type=self.ICON_NAME)
|
|
|
|
|
return value[0] if value else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WBOauthManager(BaseOauthManager):
|
|
|
|
|
class WBOauthManager(BaseOauthManager): #xy
|
|
|
|
|
"""微博OAuth授权管理器"""
|
|
|
|
|
AUTH_URL = 'https://api.weibo.com/oauth2/authorize'
|
|
|
|
|
TOKEN_URL = 'https://api.weibo.com/oauth2/access_token'
|
|
|
|
|
API_URL = 'https://api.weibo.com/2/users/show.json'
|
|
|
|
|
ICON_NAME = 'weibo'
|
|
|
|
|
|
|
|
|
|
def __init__(self, access_token=None, openid=None):
|
|
|
|
|
# cll获取微博OAuth配置
|
|
|
|
|
config = self.get_config()
|
|
|
|
|
self.client_id = config.appkey if config else ''
|
|
|
|
|
self.client_secret = config.appsecret if config else ''
|
|
|
|
|
self.callback_url = config.callback_url if config else ''
|
|
|
|
|
super(
|
|
|
|
|
WBOauthManager,
|
|
|
|
|
self).__init__(
|
|
|
|
|
access_token=access_token,
|
|
|
|
|
openid=openid)
|
|
|
|
|
super().__init__(access_token=access_token, openid=openid)
|
|
|
|
|
|
|
|
|
|
def get_authorization_url(self, nexturl='/'):
|
|
|
|
|
"""构建微博授权跳转URL,包含回调地址和后续跳转路径"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'response_type': 'code',
|
|
|
|
|
@ -98,7 +107,7 @@ class WBOauthManager(BaseOauthManager):
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
def get_access_token_by_code(self, code):
|
|
|
|
|
|
|
|
|
|
"""通过授权码获取微博访问令牌和用户UID"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'client_secret': self.client_secret,
|
|
|
|
|
@ -107,8 +116,9 @@ class WBOauthManager(BaseOauthManager):
|
|
|
|
|
'redirect_uri': self.callback_url
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_post(self.TOKEN_URL, params)
|
|
|
|
|
|
|
|
|
|
obj = json.loads(rsp)
|
|
|
|
|
|
|
|
|
|
# cll 成功获取令牌后,存储并返回用户信息
|
|
|
|
|
if 'access_token' in obj:
|
|
|
|
|
self.access_token = str(obj['access_token'])
|
|
|
|
|
self.openid = str(obj['uid'])
|
|
|
|
|
@ -117,22 +127,27 @@ class WBOauthManager(BaseOauthManager):
|
|
|
|
|
raise OAuthAccessTokenException(rsp)
|
|
|
|
|
|
|
|
|
|
def get_oauth_userinfo(self):
|
|
|
|
|
"""通过访问令牌获取微博用户信息(昵称、头像、邮箱等)"""
|
|
|
|
|
if not self.is_authorized:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
'uid': self.openid,
|
|
|
|
|
'access_token': self.access_token
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_get(self.API_URL, params)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
datas = json.loads(rsp)
|
|
|
|
|
user = OAuthUser()
|
|
|
|
|
user.metadata = rsp
|
|
|
|
|
user.picture = datas['avatar_large']
|
|
|
|
|
user.nickname = datas['screen_name']
|
|
|
|
|
user.openid = datas['id']
|
|
|
|
|
user.type = 'weibo'
|
|
|
|
|
user.token = self.access_token
|
|
|
|
|
user.metadata = rsp # cll 存储原始返回数据
|
|
|
|
|
user.picture = datas['avatar_large'] # cll 大尺寸头像
|
|
|
|
|
user.nickname = datas['screen_name'] # cll 昵称
|
|
|
|
|
user.openid = datas['id'] # cll 用户唯一标识
|
|
|
|
|
user.type = 'weibo' # cll 平台类型
|
|
|
|
|
user.token = self.access_token # cll 存储访问令牌
|
|
|
|
|
|
|
|
|
|
# cll 若返回邮箱则存储
|
|
|
|
|
if 'email' in datas and datas['email']:
|
|
|
|
|
user.email = datas['email']
|
|
|
|
|
return user
|
|
|
|
|
@ -142,12 +157,15 @@ class WBOauthManager(BaseOauthManager):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_picture(self, metadata):
|
|
|
|
|
"""从元数据中提取微博用户头像URL"""
|
|
|
|
|
datas = json.loads(metadata)
|
|
|
|
|
return datas['avatar_large']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProxyManagerMixin:
|
|
|
|
|
class ProxyManagerMixin:
|
|
|
|
|
"""代理请求混入类,支持通过环境变量配置HTTP代理"""
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
# cll 从环境变量读取代理配置
|
|
|
|
|
if os.environ.get("HTTP_PROXY"):
|
|
|
|
|
self.proxies = {
|
|
|
|
|
"http": os.environ.get("HTTP_PROXY"),
|
|
|
|
|
@ -157,17 +175,20 @@ class ProxyManagerMixin:
|
|
|
|
|
self.proxies = None
|
|
|
|
|
|
|
|
|
|
def do_get(self, url, params, headers=None):
|
|
|
|
|
"""带代理的GET请求"""
|
|
|
|
|
rsp = requests.get(url=url, params=params, headers=headers, proxies=self.proxies)
|
|
|
|
|
logger.info(rsp.text)
|
|
|
|
|
return rsp.text
|
|
|
|
|
|
|
|
|
|
def do_post(self, url, params, headers=None):
|
|
|
|
|
"""带代理的POST请求"""
|
|
|
|
|
rsp = requests.post(url, params, headers=headers, proxies=self.proxies)
|
|
|
|
|
logger.info(rsp.text)
|
|
|
|
|
return rsp.text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
"""Google OAuth授权管理器(支持代理)"""
|
|
|
|
|
AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
|
|
|
|
TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
|
|
|
|
|
API_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
|
|
|
|
|
@ -178,13 +199,10 @@ class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
self.client_id = config.appkey if config else ''
|
|
|
|
|
self.client_secret = config.appsecret if config else ''
|
|
|
|
|
self.callback_url = config.callback_url if config else ''
|
|
|
|
|
super(
|
|
|
|
|
GoogleOauthManager,
|
|
|
|
|
self).__init__(
|
|
|
|
|
access_token=access_token,
|
|
|
|
|
openid=openid)
|
|
|
|
|
super().__init__(access_token=access_token, openid=openid)
|
|
|
|
|
|
|
|
|
|
def get_authorization_url(self, nexturl='/'):
|
|
|
|
|
"""构建Google授权跳转URL,请求openid和email权限"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'response_type': 'code',
|
|
|
|
|
@ -195,43 +213,45 @@ class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
def get_access_token_by_code(self, code):
|
|
|
|
|
"""通过授权码获取Google访问令牌"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'client_secret': self.client_secret,
|
|
|
|
|
'grant_type': 'authorization_code',
|
|
|
|
|
'code': code,
|
|
|
|
|
|
|
|
|
|
'redirect_uri': self.callback_url
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_post(self.TOKEN_URL, params)
|
|
|
|
|
|
|
|
|
|
obj = json.loads(rsp)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if 'access_token' in obj:
|
|
|
|
|
self.access_token = str(obj['access_token'])
|
|
|
|
|
self.openid = str(obj['id_token'])
|
|
|
|
|
self.openid = str(obj['id_token']) # cll Google用id_token作为openid
|
|
|
|
|
logger.info(self.ICON_NAME + ' oauth ' + rsp)
|
|
|
|
|
return self.access_token
|
|
|
|
|
else:
|
|
|
|
|
raise OAuthAccessTokenException(rsp)
|
|
|
|
|
|
|
|
|
|
def get_oauth_userinfo(self):
|
|
|
|
|
"""通过访问令牌获取Google用户信息"""
|
|
|
|
|
if not self.is_authorized:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
'access_token': self.access_token
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_get(self.API_URL, params)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
|
|
datas = json.loads(rsp)
|
|
|
|
|
user = OAuthUser()
|
|
|
|
|
user.metadata = rsp
|
|
|
|
|
user.picture = datas['picture']
|
|
|
|
|
user.nickname = datas['name']
|
|
|
|
|
user.openid = datas['sub']
|
|
|
|
|
user.picture = datas['picture'] # cll 头像URL
|
|
|
|
|
user.nickname = datas['name'] # cll 姓名
|
|
|
|
|
user.openid = datas['sub'] # cll 唯一标识
|
|
|
|
|
user.token = self.access_token
|
|
|
|
|
user.type = 'google'
|
|
|
|
|
|
|
|
|
|
if datas['email']:
|
|
|
|
|
user.email = datas['email']
|
|
|
|
|
return user
|
|
|
|
|
@ -241,11 +261,13 @@ class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_picture(self, metadata):
|
|
|
|
|
"""从元数据提取Google用户头像"""
|
|
|
|
|
datas = json.loads(metadata)
|
|
|
|
|
return datas['picture']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
"""GitHub OAuth授权管理器(支持代理)"""
|
|
|
|
|
AUTH_URL = 'https://github.com/login/oauth/authorize'
|
|
|
|
|
TOKEN_URL = 'https://github.com/login/oauth/access_token'
|
|
|
|
|
API_URL = 'https://api.github.com/user'
|
|
|
|
|
@ -256,13 +278,10 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
self.client_id = config.appkey if config else ''
|
|
|
|
|
self.client_secret = config.appsecret if config else ''
|
|
|
|
|
self.callback_url = config.callback_url if config else ''
|
|
|
|
|
super(
|
|
|
|
|
GitHubOauthManager,
|
|
|
|
|
self).__init__(
|
|
|
|
|
access_token=access_token,
|
|
|
|
|
openid=openid)
|
|
|
|
|
super().__init__(access_token=access_token, openid=openid)
|
|
|
|
|
|
|
|
|
|
def get_authorization_url(self, next_url='/'):
|
|
|
|
|
"""构建GitHub授权跳转URL,请求user权限"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'response_type': 'code',
|
|
|
|
|
@ -273,16 +292,17 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
def get_access_token_by_code(self, code):
|
|
|
|
|
"""通过授权码获取GitHub访问令牌"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'client_secret': self.client_secret,
|
|
|
|
|
'grant_type': 'authorization_code',
|
|
|
|
|
'code': code,
|
|
|
|
|
|
|
|
|
|
'redirect_uri': self.callback_url
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_post(self.TOKEN_URL, params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# cll GitHub返回格式为form-encoded,需解析
|
|
|
|
|
from urllib import parse
|
|
|
|
|
r = parse.parse_qs(rsp)
|
|
|
|
|
if 'access_token' in r:
|
|
|
|
|
@ -292,19 +312,21 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
raise OAuthAccessTokenException(rsp)
|
|
|
|
|
|
|
|
|
|
def get_oauth_userinfo(self):
|
|
|
|
|
|
|
|
|
|
"""通过访问令牌获取GitHub用户信息(需在请求头携带令牌)"""
|
|
|
|
|
rsp = self.do_get(self.API_URL, params={}, headers={
|
|
|
|
|
"Authorization": "token " + self.access_token
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
datas = json.loads(rsp)
|
|
|
|
|
user = OAuthUser()
|
|
|
|
|
user.picture = datas['avatar_url']
|
|
|
|
|
user.nickname = datas['name']
|
|
|
|
|
user.openid = datas['id']
|
|
|
|
|
user.picture = datas['avatar_url'] # cll 头像URL
|
|
|
|
|
user.nickname = datas['name'] # cll 姓名(可能为空)
|
|
|
|
|
user.openid = datas['id'] # cll 唯一标识
|
|
|
|
|
user.type = 'github'
|
|
|
|
|
user.token = self.access_token
|
|
|
|
|
user.metadata = rsp
|
|
|
|
|
|
|
|
|
|
if 'email' in datas and datas['email']:
|
|
|
|
|
user.email = datas['email']
|
|
|
|
|
return user
|
|
|
|
|
@ -314,11 +336,13 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_picture(self, metadata):
|
|
|
|
|
"""从元数据提取GitHub用户头像"""
|
|
|
|
|
datas = json.loads(metadata)
|
|
|
|
|
return datas['avatar_url']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager): #fkc
|
|
|
|
|
"""Facebook OAuth授权管理器(支持代理)"""
|
|
|
|
|
AUTH_URL = 'https://www.facebook.com/v16.0/dialog/oauth'
|
|
|
|
|
TOKEN_URL = 'https://graph.facebook.com/v16.0/oauth/access_token'
|
|
|
|
|
API_URL = 'https://graph.facebook.com/me'
|
|
|
|
|
@ -329,13 +353,10 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
self.client_id = config.appkey if config else ''
|
|
|
|
|
self.client_secret = config.appsecret if config else ''
|
|
|
|
|
self.callback_url = config.callback_url if config else ''
|
|
|
|
|
super(
|
|
|
|
|
FaceBookOauthManager,
|
|
|
|
|
self).__init__(
|
|
|
|
|
access_token=access_token,
|
|
|
|
|
openid=openid)
|
|
|
|
|
super().__init__(access_token=access_token, openid=openid)
|
|
|
|
|
|
|
|
|
|
def get_authorization_url(self, next_url='/'):
|
|
|
|
|
"""构建Facebook授权跳转URL,请求邮箱和公开资料权限"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'response_type': 'code',
|
|
|
|
|
@ -346,17 +367,16 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
def get_access_token_by_code(self, code):
|
|
|
|
|
"""通过授权码获取Facebook访问令牌"""
|
|
|
|
|
params = {
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
'client_secret': self.client_secret,
|
|
|
|
|
# 'grant_type': 'authorization_code',
|
|
|
|
|
'code': code,
|
|
|
|
|
|
|
|
|
|
'redirect_uri': self.callback_url
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_post(self.TOKEN_URL, params)
|
|
|
|
|
|
|
|
|
|
obj = json.loads(rsp)
|
|
|
|
|
|
|
|
|
|
if 'access_token' in obj:
|
|
|
|
|
token = str(obj['access_token'])
|
|
|
|
|
self.access_token = token
|
|
|
|
|
@ -365,21 +385,26 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
raise OAuthAccessTokenException(rsp)
|
|
|
|
|
|
|
|
|
|
def get_oauth_userinfo(self):
|
|
|
|
|
"""通过访问令牌获取Facebook用户信息(指定返回字段)"""
|
|
|
|
|
params = {
|
|
|
|
|
'access_token': self.access_token,
|
|
|
|
|
'fields': 'id,name,picture,email'
|
|
|
|
|
'fields': 'id,name,picture,email' # cll 指定需要返回的字段
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
rsp = self.do_get(self.API_URL, params)
|
|
|
|
|
datas = json.loads(rsp)
|
|
|
|
|
user = OAuthUser()
|
|
|
|
|
user.nickname = datas['name']
|
|
|
|
|
user.openid = datas['id']
|
|
|
|
|
user.nickname = datas['name'] # cll 姓名
|
|
|
|
|
user.openid = datas['id'] # cll 唯一标识
|
|
|
|
|
user.type = 'facebook'
|
|
|
|
|
user.token = self.access_token
|
|
|
|
|
user.metadata = rsp
|
|
|
|
|
|
|
|
|
|
if 'email' in datas and datas['email']:
|
|
|
|
|
user.email = datas['email']
|
|
|
|
|
|
|
|
|
|
# cll 解析头像URL(Facebook返回格式嵌套较深)
|
|
|
|
|
if 'picture' in datas and datas['picture'] and datas['picture']['data'] and datas['picture']['data']['url']:
|
|
|
|
|
user.picture = str(datas['picture']['data']['url'])
|
|
|
|
|
return user
|
|
|
|
|
@ -388,14 +413,17 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_picture(self, metadata):
|
|
|
|
|
"""从元数据提取Facebook用户头像"""
|
|
|
|
|
datas = json.loads(metadata)
|
|
|
|
|
return str(datas['picture']['data']['url'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class QQOauthManager(BaseOauthManager):
|
|
|
|
|
class QQOauthManager(BaseOauthManager): #cll
|
|
|
|
|
"""QQ OAuth授权管理器"""
|
|
|
|
|
AUTH_URL = 'https://graph.qq.com/oauth2.0/authorize'
|
|
|
|
|
TOKEN_URL = 'https://graph.qq.com/oauth2.0/token'
|
|
|
|
|
API_URL = 'https://graph.qq.com/user/get_user_info'
|
|
|
|
|
# cll QQ需单独请求openid的URL
|
|
|
|
|
OPEN_ID_URL = 'https://graph.qq.com/oauth2.0/me'
|
|
|
|
|
ICON_NAME = 'qq'
|
|
|
|
|
|
|
|
|
|
@ -404,13 +432,10 @@ class QQOauthManager(BaseOauthManager):
|
|
|
|
|
self.client_id = config.appkey if config else ''
|
|
|
|
|
self.client_secret = config.appsecret if config else ''
|
|
|
|
|
self.callback_url = config.callback_url if config else ''
|
|
|
|
|
super(
|
|
|
|
|
QQOauthManager,
|
|
|
|
|
self).__init__(
|
|
|
|
|
access_token=access_token,
|
|
|
|
|
openid=openid)
|
|
|
|
|
super().__init__(access_token=access_token, openid=openid)
|
|
|
|
|
|
|
|
|
|
def get_authorization_url(self, next_url='/'):
|
|
|
|
|
"""构建QQ授权跳转URL"""
|
|
|
|
|
params = {
|
|
|
|
|
'response_type': 'code',
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
@ -420,6 +445,7 @@ class QQOauthManager(BaseOauthManager):
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
def get_access_token_by_code(self, code):
|
|
|
|
|
"""通过授权码获取QQ访问令牌"""
|
|
|
|
|
params = {
|
|
|
|
|
'grant_type': 'authorization_code',
|
|
|
|
|
'client_id': self.client_id,
|
|
|
|
|
@ -428,77 +454,90 @@ class QQOauthManager(BaseOauthManager):
|
|
|
|
|
'redirect_uri': self.callback_url
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_get(self.TOKEN_URL, params)
|
|
|
|
|
|
|
|
|
|
if rsp:
|
|
|
|
|
# cll QQ返回格式为form-encoded,解析令牌
|
|
|
|
|
d = urllib.parse.parse_qs(rsp)
|
|
|
|
|
if 'access_token' in d:
|
|
|
|
|
token = d['access_token']
|
|
|
|
|
self.access_token = token[0]
|
|
|
|
|
return token
|
|
|
|
|
else:
|
|
|
|
|
raise OAuthAccessTokenException(rsp)
|
|
|
|
|
raise OAuthAccessTokenException(rsp)
|
|
|
|
|
|
|
|
|
|
def get_open_id(self):
|
|
|
|
|
"""QQ需单独请求openid(通过访问令牌获取)"""
|
|
|
|
|
if self.is_access_token_set:
|
|
|
|
|
params = {
|
|
|
|
|
'access_token': self.access_token
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_get(self.OPEN_ID_URL, params)
|
|
|
|
|
|
|
|
|
|
if rsp:
|
|
|
|
|
rsp = rsp.replace(
|
|
|
|
|
'callback(', '').replace(
|
|
|
|
|
')', '').replace(
|
|
|
|
|
';', '')
|
|
|
|
|
# 去除QQ返回的callback包裹符
|
|
|
|
|
rsp = rsp.replace('callback(', '').replace(')', '').replace(';', '')
|
|
|
|
|
obj = json.loads(rsp)
|
|
|
|
|
openid = str(obj['openid'])
|
|
|
|
|
self.openid = openid
|
|
|
|
|
return openid
|
|
|
|
|
|
|
|
|
|
def get_oauth_userinfo(self):
|
|
|
|
|
"""获取QQ用户信息(需先获取openid)"""
|
|
|
|
|
openid = self.get_open_id()
|
|
|
|
|
if openid:
|
|
|
|
|
params = {
|
|
|
|
|
'access_token': self.access_token,
|
|
|
|
|
'oauth_consumer_key': self.client_id,
|
|
|
|
|
'oauth_consumer_key': self.client_id, # cll QQ要求传入appkey
|
|
|
|
|
'openid': self.openid
|
|
|
|
|
}
|
|
|
|
|
rsp = self.do_get(self.API_URL, params)
|
|
|
|
|
logger.info(rsp)
|
|
|
|
|
obj = json.loads(rsp)
|
|
|
|
|
|
|
|
|
|
user = OAuthUser()
|
|
|
|
|
user.nickname = obj['nickname']
|
|
|
|
|
user.openid = openid
|
|
|
|
|
user.nickname = obj['nickname'] # cll 昵称
|
|
|
|
|
user.openid = openid # cll 唯一标识
|
|
|
|
|
user.type = 'qq'
|
|
|
|
|
user.token = self.access_token
|
|
|
|
|
user.metadata = rsp
|
|
|
|
|
|
|
|
|
|
if 'email' in obj:
|
|
|
|
|
user.email = obj['email']
|
|
|
|
|
if 'figureurl' in obj:
|
|
|
|
|
user.picture = str(obj['figureurl'])
|
|
|
|
|
user.picture = str(obj['figureurl']) # cll 头像URL
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
def get_picture(self, metadata):
|
|
|
|
|
"""从元数据提取QQ用户头像"""
|
|
|
|
|
datas = json.loads(metadata)
|
|
|
|
|
return str(datas['figureurl'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@cache_decorator(expiration=100 * 60)
|
|
|
|
|
def get_oauth_apps():
|
|
|
|
|
def get_oauth_apps(): #xy
|
|
|
|
|
"""获取所有启用的OAuth应用(缓存100分钟)"""
|
|
|
|
|
# cll 读取数据库中启用的OAuth配置
|
|
|
|
|
configs = OAuthConfig.objects.filter(is_enable=True).all()
|
|
|
|
|
if not configs:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
# 提取已启用的平台类型
|
|
|
|
|
configtypes = [x.type for x in configs]
|
|
|
|
|
# cll 获取所有BaseOauthManager的子类(各平台实现)
|
|
|
|
|
applications = BaseOauthManager.__subclasses__()
|
|
|
|
|
# cll 筛选出已启用的平台实例
|
|
|
|
|
apps = [x() for x in applications if x().ICON_NAME.lower() in configtypes]
|
|
|
|
|
return apps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_manager_by_type(type):
|
|
|
|
|
def get_manager_by_type(type):
|
|
|
|
|
"""根据平台类型获取对应的OAuth管理器实例"""
|
|
|
|
|
applications = get_oauth_apps()
|
|
|
|
|
if applications:
|
|
|
|
|
# cll 匹配平台类型(不区分大小写)
|
|
|
|
|
finds = list(
|
|
|
|
|
filter(
|
|
|
|
|
lambda x: x.ICON_NAME.lower() == type.lower(),
|
|
|
|
|
applications))
|
|
|
|
|
if finds:
|
|
|
|
|
return finds[0]
|
|
|
|
|
return None
|
|
|
|
|
return None
|