1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-31 14:42:10 +00:00

dingtalk new (#5393)

This commit is contained in:
lian
2023-03-16 16:01:36 +08:00
committed by GitHub
parent 613d809e37
commit 1da123ee76
6 changed files with 436 additions and 19 deletions

View File

@@ -28,13 +28,24 @@ from seahub.avatar.models import Avatar
from seahub.group.utils import validate_group_name
from seahub.auth.models import ExternalDepartment
from seahub.dingtalk.utils import dingtalk_get_access_token
from seahub.dingtalk.settings import ENABLE_DINGTALK, \
DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, \
DINGTALK_DEPARTMENT_GET_DEPARTMENT_URL, \
DINGTALK_DEPARTMENT_GET_DEPARTMENT_USER_LIST_URL, \
DINGTALK_DEPARTMENT_USER_SIZE, DINGTALK_PROVIDER
# for 10.0 or later
from seahub.dingtalk.settings import DINGTALK_APP_KEY, \
DINGTALK_APP_SECRET
if DINGTALK_APP_KEY and DINGTALK_APP_SECRET:
from seahub.dingtalk.utils import \
dingtalk_get_orgapp_token as dingtalk_get_access_token
else:
from seahub.dingtalk.utils import dingtalk_get_access_token
DEPARTMENT_OWNER = 'system admin'
logger = logging.getLogger(__name__)
@@ -300,7 +311,7 @@ class AdminDingtalkDepartmentsImport(APIView):
sub_department_resp_json = requests.get(DINGTALK_DEPARTMENT_LIST_DEPARTMENT_URL, params=data).json()
sub_department_list = sub_department_resp_json.get('department', [])
department_list = current_department_list + sub_department_list
department_list = sorted(department_list, key=lambda x:x['id'])
department_list = sorted(department_list, key=lambda x: x['id'])
# get department user list
data = {

View File

@@ -1,7 +1,44 @@
import seahub.settings as settings
# constants
DINGTALK_PROVIDER = 'dingtalk'
ENABLE_DINGTALK = getattr(settings, 'ENABLE_DINGTALK', False)
DINGTALK_AGENT_ID = getattr(settings, 'DINGTALK_AGENT_ID', '')
# for 10.0 or later
# base setting
DINGTALK_APP_KEY = getattr(settings, 'DINGTALK_APP_KEY', '')
DINGTALK_APP_SECRET = getattr(settings, 'DINGTALK_APP_SECRET', '')
# oauth login
DINGTALK_OAUTH_RESPONSE_TYPE = getattr(settings, 'DINGTALK_OAUTH_RESPONSE_TYPE', 'code')
DINGTALK_OAUTH_SCOPE = getattr(settings, 'DINGTALK_OAUTH_SCOPE', 'openid')
DINGTALK_OAUTH_PROMPT = getattr(settings, 'DINGTALK_OAUTH_PROMPT', 'consent')
DINGTALK_OAUTH_AUTH_URL = getattr(settings, 'DINGTALK_OAUTH_AUTH_URL', 'https://login.dingtalk.com/oauth2/auth')
DINGTALK_OAUTH_GRANT_TYPE = getattr(settings, 'DINGTALK_OAUTH_GRANT_TYPE', 'authorization_code')
DINGTALK_OAUTH_USER_ACCESS_TOKEN_URL = getattr(settings,
'DINGTALK_OAUTH_USER_ACCESS_TOKEN_URL',
'https://api.dingtalk.com/v1.0/oauth2/userAccessToken')
DINGTALK_OAUTH_CREATE_UNKNOWN_USER = getattr(settings, 'DINGTALK_OAUTH_CREATE_UNKNOWN_USER', True)
DINGTALK_OAUTH_ACTIVATE_USER_AFTER_CREATION = getattr(settings, 'DINGTALK_OAUTH_ACTIVATE_USER_AFTER_CREATION', True)
DINGTALK_GET_USER_INFO_URL = getattr(settings,
'DINGTALK_GET_USER_INFO_URL',
'https://api.dingtalk.com/v1.0/contact/users/')
DINGTALK_GET_ORGAPP_TOKEN_URL = getattr(settings,
'DINGTALK_GET_ORGAPP_TOKEN_URL',
'https://oapi.dingtalk.com/gettoken')
DINGTALK_TOPAPI_GET_USERID_BY_UNIONID_URL = getattr(settings,
'DINGTALK_TOPAPI_GET_USERID_BY_UNIONID_URL',
'https://oapi.dingtalk.com/topapi/user/getbyunionid')
DINGTALK_TOPAPI_GET_DETAILED_USER_INFO_URL = getattr(settings,
'DINGTALK_TOPAPI_GET_DETAILED_USER_INFO_URL',
'https://oapi.dingtalk.com/topapi/v2/user/get')
# for 9.0 or before
DINGTALK_GET_USERID_BY_UNIONID = getattr(settings, 'DINGTALK_GET_USERID_BY_UNIONID', 'https://oapi.dingtalk.com/user/getUseridByUnionid')
# for dingtalk qr connect
@@ -28,6 +65,3 @@ DINGTALK_DEPARTMENT_USER_SIZE = 100
DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL = getattr(settings, 'DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL', 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2')
DINGTALK_GET_DETAILED_USER_INFO_URL = getattr(settings, 'DINGTALK_GET_DETAILED_USER_INFO_URL', 'https://oapi.dingtalk.com/user/get')
# constants
DINGTALK_PROVIDER = 'dingtalk'

View File

@@ -1,9 +1,17 @@
import urllib
import logging
import requests
from django.core.cache import cache
from seahub.utils import normalize_cache_key
# for 10.0 or later
from seahub.dingtalk.settings import DINGTALK_APP_KEY, DINGTALK_APP_SECRET, \
DINGTALK_GET_ORGAPP_TOKEN_URL, \
DINGTALK_TOPAPI_GET_USERID_BY_UNIONID_URL, \
DINGTALK_TOPAPI_GET_DETAILED_USER_INFO_URL
# for 9.0 or before
from seahub.dingtalk.settings import DINGTALK_DEPARTMENT_APP_KEY, \
DINGTALK_DEPARTMENT_APP_SECRET, \
DINGTALK_DEPARTMENT_GET_ACCESS_TOKEN_URL, \
@@ -12,6 +20,87 @@ from seahub.dingtalk.settings import DINGTALK_DEPARTMENT_APP_KEY, \
logger = logging.getLogger(__name__)
# for 10.0 or later
def dingtalk_get_orgapp_token():
"""
https://open.dingtalk.com/document/orgapp/obtain-orgapp-token
"""
cache_key = normalize_cache_key('DINGTALK_ORGAPP_TOKEN')
access_token = cache.get(cache_key, None)
if access_token:
return access_token
data = {
'appkey': DINGTALK_APP_KEY,
'appsecret': DINGTALK_APP_SECRET,
}
resp_json = requests.get(DINGTALK_GET_ORGAPP_TOKEN_URL,
params=data).json()
access_token = resp_json.get('access_token', '')
if not access_token:
logger.warning('failed to get dingtalk access_token')
logger.warning(DINGTALK_GET_ORGAPP_TOKEN_URL)
logger.warning(data)
logger.warning(resp_json)
return ''
expires_in = resp_json.get('expires_in', 7200)
cache.set(cache_key, access_token, expires_in)
return access_token
def dingtalk_get_userid_by_unionid_new(union_id):
"""
https://open.dingtalk.com/document/orgapp/query-a-user-by-the-union-id
"""
cache_key = normalize_cache_key('DINGTALK_UNION_ID_%s_TO_USER_ID' % union_id)
user_id = cache.get(cache_key, None)
if user_id:
return user_id
access_token = dingtalk_get_orgapp_token()
parameters = {'access_token': access_token}
data = {'unionid': union_id}
url = DINGTALK_TOPAPI_GET_USERID_BY_UNIONID_URL + '?' + urllib.parse.urlencode(parameters)
resp_json = requests.post(url, data=data).json()
result = resp_json.get('result', {})
user_id = result.get('userid')
if not user_id:
logger.warning('failed to get userid by unionid: %s' % union_id)
logger.warning(DINGTALK_TOPAPI_GET_USERID_BY_UNIONID_URL)
logger.warning(data)
logger.warning(resp_json)
return ''
cache.set(cache_key, user_id)
return user_id
def dingtalk_get_detailed_user_info_new(user_id):
"""
https://open.dingtalk.com/document/orgapp/query-user-details
"""
access_token = dingtalk_get_orgapp_token()
parameters = {
'access_token': access_token,
}
data = {'userid': user_id}
url = DINGTALK_TOPAPI_GET_DETAILED_USER_INFO_URL + '?' + urllib.parse.urlencode(parameters)
return requests.post(url, data=data).json()
# for 9.0 or before
def dingtalk_get_access_token():
cache_key = normalize_cache_key('DINGTALK_ACCESS_TOKEN')

View File

@@ -18,15 +18,26 @@ from seahub.api2.utils import get_api_token
from seahub import auth
from seahub.profile.models import Profile
from seahub.utils import render_error, get_site_scheme_and_netloc
from seahub.utils.auth import gen_user_virtual_id
from seahub.utils.auth import gen_user_virtual_id, VIRTUAL_ID_EMAIL_DOMAIN
from seahub.base.accounts import User
from seahub.auth.models import SocialAuthUser
from seahub.auth.decorators import login_required
from seahub.dingtalk.utils import dingtalk_get_detailed_user_info
from seahub.dingtalk.utils import dingtalk_get_detailed_user_info, \
dingtalk_get_orgapp_token, dingtalk_get_userid_by_unionid_new, \
dingtalk_get_detailed_user_info_new
from seahub.dingtalk.settings import ENABLE_DINGTALK, \
DINGTALK_QR_CONNECT_APP_ID, DINGTALK_QR_CONNECT_APP_SECRET, \
DINGTALK_QR_CONNECT_AUTHORIZATION_URL, \
from seahub.dingtalk.settings import ENABLE_DINGTALK
# for 10.0 or later
from seahub.dingtalk.settings import DINGTALK_APP_KEY, DINGTALK_APP_SECRET, \
DINGTALK_OAUTH_RESPONSE_TYPE, DINGTALK_OAUTH_SCOPE, \
DINGTALK_OAUTH_PROMPT, DINGTALK_OAUTH_AUTH_URL, \
DINGTALK_OAUTH_GRANT_TYPE, DINGTALK_OAUTH_USER_ACCESS_TOKEN_URL, \
DINGTALK_GET_USER_INFO_URL
# for 9.0 or before
from seahub.dingtalk.settings import DINGTALK_QR_CONNECT_APP_ID, \
DINGTALK_QR_CONNECT_APP_SECRET, DINGTALK_QR_CONNECT_AUTHORIZATION_URL, \
DINGTALK_QR_CONNECT_USER_INFO_URL, DINGTALK_QR_CONNECT_RESPONSE_TYPE, \
DINGTALK_QR_CONNECT_SCOPE, DINGTALK_QR_CONNECT_LOGIN_REMEMBER_ME
@@ -38,6 +49,10 @@ def dingtalk_login(request):
if not ENABLE_DINGTALK:
return render_error(request, _('Error, please contact administrator.'))
# for 10.0 or later
if DINGTALK_APP_KEY and DINGTALK_APP_SECRET:
return dingtalk_login_new(request)
state = str(uuid.uuid4())
request.session['dingtalk_login_state'] = state
request.session['dingtalk_login_redirect'] = request.GET.get(auth.REDIRECT_FIELD_NAME, '/')
@@ -58,6 +73,10 @@ def dingtalk_callback(request):
if not ENABLE_DINGTALK:
return render_error(request, _('Error, please contact administrator.'))
# for 10.0 or later
if DINGTALK_APP_KEY and DINGTALK_APP_SECRET:
return dingtalk_callback_new(request)
state = request.GET.get('state', '')
if not state or state != request.session.get('dingtalk_login_state', ''):
logger.error('invalid state')
@@ -141,6 +160,10 @@ def dingtalk_connect(request):
if not ENABLE_DINGTALK:
return render_error(request, _('Error, please contact administrator.'))
# for 10.0 or later
if DINGTALK_APP_KEY and DINGTALK_APP_SECRET:
return dingtalk_connect_new(request)
state = str(uuid.uuid4())
request.session['dingtalk_connect_state'] = state
request.session['dingtalk_connect_redirect'] = request.GET.get(auth.REDIRECT_FIELD_NAME, '/')
@@ -162,6 +185,10 @@ def dingtalk_connect_callback(request):
if not ENABLE_DINGTALK:
return render_error(request, _('Error, please contact administrator.'))
# for 10.0 or later
if DINGTALK_APP_KEY and DINGTALK_APP_SECRET:
return dingtalk_connect_callback_new(request)
state = request.GET.get('state', '')
if not state or state != request.session.get('dingtalk_connect_state', ''):
logger.error('invalid state')
@@ -216,8 +243,7 @@ def dingtalk_connect_callback(request):
profile.contact_email = contact_email
profile.save()
response = HttpResponseRedirect(request.session['dingtalk_connect_redirect'])
return response
return HttpResponseRedirect(request.session['dingtalk_connect_redirect'])
@login_required
@@ -227,6 +253,252 @@ def dingtalk_disconnect(request):
return render_error(request, _('Error, please contact administrator.'))
username = request.user.username
if username.endswith(VIRTUAL_ID_EMAIL_DOMAIN):
Profile.objects.filter(user=username).delete()
SocialAuthUser.objects.delete_by_username_and_provider(username, 'dingtalk')
response = HttpResponseRedirect(request.GET.get(auth.REDIRECT_FIELD_NAME, '/'))
return HttpResponseRedirect(request.GET.get(auth.REDIRECT_FIELD_NAME, '/'))
# for 10.0 or later
def dingtalk_login_new(request):
"""
https://open.dingtalk.com/document/orgapp/sso-overview
"""
state = str(uuid.uuid4())
request.session['dingtalk_login_state'] = state
request.session['dingtalk_login_redirect'] = request.GET.get(auth.REDIRECT_FIELD_NAME, '/')
data = {
'redirect_uri': get_site_scheme_and_netloc() + reverse('dingtalk_callback'),
'response_type': DINGTALK_OAUTH_RESPONSE_TYPE,
'client_id': DINGTALK_APP_KEY,
'scope': DINGTALK_OAUTH_SCOPE,
'state': state,
'prompt': DINGTALK_OAUTH_PROMPT,
}
url = DINGTALK_OAUTH_AUTH_URL + '?' + urllib.parse.urlencode(data)
return HttpResponseRedirect(url)
def dingtalk_callback_new(request):
state = request.GET.get('state', '')
if not state or state != request.session.get('dingtalk_login_state', ''):
logger.error('invalid state')
return render_error(request, _('Error, please contact administrator.'))
def get_oauth_access_token(code):
"""
https://open.dingtalk.com/document/orgapp/sso-overview
"""
data = {
"clientId": DINGTALK_APP_KEY,
"code": code,
"clientSecret": DINGTALK_APP_SECRET,
"grantType": DINGTALK_OAUTH_GRANT_TYPE,
}
headers = {'Content-Type': 'application/json'}
access_token_resp = requests.post(DINGTALK_OAUTH_USER_ACCESS_TOKEN_URL,
headers=headers,
data=json.dumps(data))
return access_token_resp.json().get('accessToken')
code = request.GET.get('authCode')
oauth_access_token = get_oauth_access_token(code)
def get_user_info(oauth_access_token, union_id='me'):
"""
https://open.dingtalk.com/document/orgapp/dingtalk-retrieve-user-information#
"""
user_info_url = urllib.parse.urljoin(DINGTALK_GET_USER_INFO_URL, union_id)
user_info_resp = requests.get(user_info_url,
headers={'x-acs-dingtalk-access-token': oauth_access_token})
# {
# 'avatarUrl': 'https://static-legacy.dingtalk.com/media/lADPDgQ9rt4yNOLNAjDNAjA_560_560.jpg',
# 'mobile': '15313912424',
# 'nick': 'lian',
# 'openId': 'h1Nar64KnUuY40iiajR3cXAiEiE',
# 'stateCode': '86',
# 'unionId': '3os80f94Zf4oeiPOpiSiSgiigQiEiE'
# }
return user_info_resp.json()
user_info = get_user_info(oauth_access_token)
# seahub authenticate user
if 'unionId' not in user_info:
logger.error('Required user info not found.')
logger.error(user_info)
return render_error(request, _('Error, please contact administrator.'))
union_id = user_info['unionId']
auth_user = SocialAuthUser.objects.get_by_provider_and_uid('dingtalk', union_id)
if auth_user:
email = auth_user.username
else:
email = gen_user_virtual_id()
SocialAuthUser.objects.add(email, 'dingtalk', union_id)
try:
user = auth.authenticate(remote_user=email)
except User.DoesNotExist:
user = None
except Exception as e:
logger.error(e)
return render_error(request, _('Error, please contact administrator.'))
if not user or not user.is_active:
return render_error(request, _('User %s not found or inactive.') % email)
# User is valid. Set request.user and persist user in the session
# by logging the user in.
request.user = user
request.session['remember_me'] = True
auth.login(request, user)
# update user's profile
profile = Profile.objects.get_profile_by_user(email)
if not profile:
profile = Profile(user=email)
name = user_info['nick'] if 'nick' in user_info else ''
if name:
profile.nickname = name.strip()
profile.save()
try:
user_id = dingtalk_get_userid_by_unionid_new(union_id)
detailed_user_info = dingtalk_get_detailed_user_info_new(user_id)
contact_email = detailed_user_info.get('result', {}).get('email', '')
if contact_email:
profile.contact_email = contact_email
profile.save()
except Exception as e:
logger.error(e)
# generate auth token for Seafile client
api_token = get_api_token(request)
# redirect user to home page
response = HttpResponseRedirect(request.session.get('dingtalk_login_redirect', '/'))
response.set_cookie('seahub_auth', email + '@' + api_token.key)
return response
def dingtalk_connect_new(request):
"""
https://open.dingtalk.com/document/orgapp/sso-overview
"""
state = str(uuid.uuid4())
request.session['dingtalk_connect_state'] = state
request.session['dingtalk_connect_redirect'] = request.GET.get(auth.REDIRECT_FIELD_NAME, '/')
data = {
'redirect_uri': get_site_scheme_and_netloc() + reverse('dingtalk_connect_callback'),
'response_type': DINGTALK_OAUTH_RESPONSE_TYPE,
'client_id': DINGTALK_APP_KEY,
'scope': DINGTALK_OAUTH_SCOPE,
'state': state,
'prompt': DINGTALK_OAUTH_PROMPT,
}
url = DINGTALK_OAUTH_AUTH_URL + '?' + urllib.parse.urlencode(data)
return HttpResponseRedirect(url)
@login_required
def dingtalk_connect_callback_new(request):
state = request.GET.get('state', '')
if not state or state != request.session.get('dingtalk_connect_state', ''):
logger.error('invalid state')
return render_error(request, _('Error, please contact administrator.'))
def get_oauth_access_token(code):
"""
https://open.dingtalk.com/document/orgapp/sso-overview
"""
data = {
"clientId": DINGTALK_APP_KEY,
"code": code,
"clientSecret": DINGTALK_APP_SECRET,
"grantType": DINGTALK_OAUTH_GRANT_TYPE,
}
headers = {'Content-Type': 'application/json'}
access_token_resp = requests.post(DINGTALK_OAUTH_USER_ACCESS_TOKEN_URL,
headers=headers,
data=json.dumps(data))
return access_token_resp.json().get('accessToken')
code = request.GET.get('authCode')
oauth_access_token = get_oauth_access_token(code)
def get_user_info(oauth_access_token, union_id='me'):
"""
https://open.dingtalk.com/document/orgapp/dingtalk-retrieve-user-information#
"""
user_info_url = urllib.parse.urljoin(DINGTALK_GET_USER_INFO_URL, union_id)
user_info_resp = requests.get(user_info_url,
headers={'x-acs-dingtalk-access-token': oauth_access_token})
# {
# 'avatarUrl': 'https://static-legacy.dingtalk.com/media/lADPDgQ9rt4yNOLNAjDNAjA_560_560.jpg',
# 'mobile': '15313912424',
# 'nick': 'lian',
# 'openId': 'h1Nar64KnUuY40iiajR3cXAiEiE',
# 'stateCode': '86',
# 'unionId': '3os80f94Zf4oeiPOpiSiSgiigQiEiE'
# }
return user_info_resp.json()
user_info = get_user_info(oauth_access_token)
# seahub authenticate user
if 'unionId' not in user_info:
logger.error('Required user info not found.')
logger.error(user_info)
return render_error(request, _('Error, please contact administrator.'))
union_id = user_info['unionId']
username = request.user.username
auth_user = SocialAuthUser.objects.get_by_provider_and_uid('dingtalk', union_id)
if auth_user:
logger.error('dingtalk account already exists %s' % union_id)
return render_error(request, '出错了,此钉钉账号已被绑定')
SocialAuthUser.objects.add(username, 'dingtalk', union_id)
# update user's profile
profile = Profile.objects.get_profile_by_user(username)
if not profile:
profile = Profile(user=username)
name = user_info['nick'] if 'nick' in user_info else ''
if name:
profile.nickname = name.strip()
profile.save()
try:
user_id = dingtalk_get_userid_by_unionid_new(union_id)
detailed_user_info = dingtalk_get_detailed_user_info_new(user_id)
contact_email = detailed_user_info.get('result', {}).get('email', '')
if contact_email:
profile.contact_email = contact_email
profile.save()
except Exception as e:
logger.error(e)
response = HttpResponseRedirect(request.session.get('dingtalk_connect_redirect', '/'))
return response

View File

@@ -16,10 +16,20 @@ from seahub.notifications.models import UserNotification
from seahub.utils import get_site_scheme_and_netloc, get_site_name
from seahub.auth.models import SocialAuthUser
from seahub.dingtalk.utils import dingtalk_get_access_token, dingtalk_get_userid_by_unionid
from seahub.dingtalk.settings import DINGTALK_MESSAGE_SEND_TO_CONVERSATION_URL, \
DINGTALK_AGENT_ID
# for 10.0 or later
from seahub.dingtalk.settings import DINGTALK_APP_KEY, \
DINGTALK_APP_SECRET
if DINGTALK_APP_KEY and DINGTALK_APP_SECRET:
from seahub.dingtalk.utils import dingtalk_get_orgapp_token as dingtalk_get_access_token
from seahub.dingtalk.utils import dingtalk_get_userid_by_unionid_new as dingtalk_get_userid_by_unionid
else:
from seahub.dingtalk.utils import dingtalk_get_access_token, dingtalk_get_userid_by_unionid
# Get an instance of a logger
logger = logging.getLogger(__name__)
@@ -87,7 +97,7 @@ class Command(BaseCommand, CommandLogMixin):
}
}
resp_json = requests.post(self.dingtalk_message_send_to_conversation_url,
data=json.dumps(data)).json()
data=json.dumps(data)).json()
if resp_json.get('errcode') != 0:
self.log_info(resp_json)

View File

@@ -8,6 +8,7 @@ from seahub.work_weixin.settings import ENABLE_WORK_WEIXIN
from seahub.weixin.settings import ENABLE_WEIXIN
from seahub.dingtalk.settings import ENABLE_DINGTALK
class OauthRemoteUserBackend(RemoteUserBackend):
"""
This backend is to be used in conjunction with the ``RemoteUserMiddleware``
@@ -36,10 +37,10 @@ class OauthRemoteUserBackend(RemoteUserBackend):
activate_after_creation = WEIXIN_OAUTH_ACTIVATE_USER_AFTER_CREATION
if ENABLE_DINGTALK:
from seahub.dingtalk.settings import DINGTALK_QR_CONNECT_CREATE_UNKNOWN_USER, \
DINGTALK_QR_CONNECT_ACTIVATE_USER_AFTER_CREATION
create_unknown_user = DINGTALK_QR_CONNECT_CREATE_UNKNOWN_USER
activate_after_creation = DINGTALK_QR_CONNECT_ACTIVATE_USER_AFTER_CREATION
from seahub.dingtalk.settings import DINGTALK_OAUTH_CREATE_UNKNOWN_USER, \
DINGTALK_OAUTH_ACTIVATE_USER_AFTER_CREATION
create_unknown_user = DINGTALK_OAUTH_CREATE_UNKNOWN_USER
activate_after_creation = DINGTALK_OAUTH_ACTIVATE_USER_AFTER_CREATION
def get_user(self, username):
try: