diff --git a/frontend/src/components/dialog/confirm-disconnect-weixin.js b/frontend/src/components/dialog/confirm-disconnect-weixin.js
new file mode 100644
index 0000000000..c0c3c53adb
--- /dev/null
+++ b/frontend/src/components/dialog/confirm-disconnect-weixin.js
@@ -0,0 +1,45 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
+import { gettext } from '../../utils/constants';
+
+const propTypes = {
+ formActionURL: PropTypes.string.isRequired,
+ csrfToken: PropTypes.string.isRequired,
+ toggle: PropTypes.func.isRequired
+};
+
+class ConfirmDisconnectWeixin extends Component {
+
+ constructor(props) {
+ super(props);
+ this.form = React.createRef();
+ }
+
+ disconnect = () => {
+ this.form.current.submit();
+ };
+
+ render() {
+ const { formActionURL, csrfToken, toggle } = this.props;
+ return (
+
+ {gettext('Disconnect')}
+
+ {gettext('Are you sure you want to disconnect?')}
+
+
+
+
+
+
+
+ );
+ }
+}
+
+ConfirmDisconnectWeixin.propTypes = propTypes;
+
+export default ConfirmDisconnectWeixin;
diff --git a/frontend/src/components/user-settings/social-login-weixin.js b/frontend/src/components/user-settings/social-login-weixin.js
new file mode 100644
index 0000000000..bb09f0935a
--- /dev/null
+++ b/frontend/src/components/user-settings/social-login-weixin.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import { gettext, siteRoot } from '../../utils/constants';
+import ModalPortal from '../modal-portal';
+import ConfirmDisconnectWeixin from '../dialog/confirm-disconnect-weixin';
+
+const {
+ csrfToken,
+ langCode,
+ socialConnectedWeixin,
+ socialNextPage
+} = window.app.pageOptions;
+
+class SocialLoginWeixin extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isConfirmDialogOpen: false
+ };
+ }
+
+ confirmDisconnect = () => {
+ this.setState({
+ isConfirmDialogOpen: true
+ });
+ };
+
+ toggleDialog = () => {
+ this.setState({
+ isConfirmDialogOpen: !this.state.isConfirmDialogOpen
+ });
+ };
+
+ render() {
+ return (
+
+
+
{gettext('Social Login')}
+
{langCode == 'zh-cn' ? '微信' : 'Weixin'}
+ {socialConnectedWeixin ?
+
:
+
{gettext('Connect')}
+ }
+
+ {this.state.isConfirmDialogOpen && (
+
+
+
+ )}
+
+ );
+ }
+}
+
+export default SocialLoginWeixin;
diff --git a/frontend/src/settings.js b/frontend/src/settings.js
index 025894e84f..4dd69b4eba 100644
--- a/frontend/src/settings.js
+++ b/frontend/src/settings.js
@@ -19,6 +19,7 @@ import EmailNotice from './components/user-settings/email-notice';
import TwoFactorAuthentication from './components/user-settings/two-factor-auth';
import SocialLogin from './components/user-settings/social-login';
import SocialLoginDingtalk from './components/user-settings/social-login-dingtalk';
+import SocialLoginWeixin from './components/user-settings/social-login-weixin';
import SocialLoginSAML from './components/user-settings/social-login-saml';
import LinkedDevices from './components/user-settings/linked-devices';
import DeleteAccount from './components/user-settings/delete-account';
@@ -39,6 +40,7 @@ const {
twoFactorAuthEnabled,
enableWechatWork,
enableDingtalk,
+ enableWeixin,
isOrgContext,
enableADFS,
enableMultiADFS,
@@ -59,7 +61,7 @@ class Settings extends React.Component {
{ show: true, href: '#lang-setting', text: gettext('Language') },
{ show: isPro, href: '#email-notice', text: gettext('Email Notification') },
{ show: twoFactorAuthEnabled, href: '#two-factor-auth', text: gettext('Two-Factor Authentication') },
- { show: (enableWechatWork || enableDingtalk || enableADFS || (enableMultiADFS || isOrgContext)), href: '#social-auth', text: gettext('Social Login') },
+ { show: (enableWechatWork || enableDingtalk || enableWeixin || enableADFS || (enableMultiADFS || isOrgContext)), href: '#social-auth', text: gettext('Social Login') },
{ show: true, href: '#linked-devices', text: gettext('Linked Devices') },
{ show: enableDeleteAccount, href: '#del-account', text: gettext('Delete Account') },
];
@@ -180,6 +182,7 @@ class Settings extends React.Component {
{twoFactorAuthEnabled && }
{enableWechatWork && }
{enableDingtalk && }
+ {enableWeixin && }
{(enableADFS || (enableMultiADFS && isOrgContext)) && }
{enableDeleteAccount && }
diff --git a/seahub/profile/templates/profile/set_profile_react.html b/seahub/profile/templates/profile/set_profile_react.html
index 18999b3697..4fd2dac780 100644
--- a/seahub/profile/templates/profile/set_profile_react.html
+++ b/seahub/profile/templates/profile/set_profile_react.html
@@ -78,6 +78,12 @@ window.app.pageOptions = {
socialNextPage: "{{ social_next_page|escapejs }}",
{% endif %}
+ enableWeixin: {% if enable_weixin %} true {% else %} false {% endif %},
+ {% if enable_weixin %}
+ socialConnectedWeixin: {% if social_connected_weixin %} true {% else %} false {% endif %},
+ socialNextPage: "{{ social_next_page|escapejs }}",
+ {% endif %}
+
enableADFS: {% if enable_adfs %} true {% else %} false {% endif %},
{% if enable_adfs %}
samlConnected: {% if saml_connected %} true {% else %} false {% endif %},
diff --git a/seahub/profile/views.py b/seahub/profile/views.py
index aa349931a2..91b01d4a09 100644
--- a/seahub/profile/views.py
+++ b/seahub/profile/views.py
@@ -26,10 +26,11 @@ from seahub.views import get_owned_repo_list
from seahub.work_weixin.utils import work_weixin_oauth_check
from seahub.settings import ENABLE_DELETE_ACCOUNT, ENABLE_UPDATE_USER_INFO, ENABLE_ADFS_LOGIN, ENABLE_MULTI_ADFS
from seahub.dingtalk.settings import ENABLE_DINGTALK
+from seahub.weixin.settings import ENABLE_WEIXIN
from constance import config
try:
from seahub.settings import SAML_PROVIDER_IDENTIFIER
-except ImportError as e:
+except ImportError:
SAML_PROVIDER_IDENTIFIER = 'saml'
@@ -107,6 +108,14 @@ def edit_profile(request):
enable_dingtalk = False
social_connected_dingtalk = False
+ if ENABLE_WEIXIN:
+ enable_weixin = True
+ social_connected_weixin = SocialAuthUser.objects.filter(
+ username=request.user.username, provider='weixin').count() > 0
+ else:
+ enable_weixin = False
+ social_connected_weixin = False
+
if ENABLE_ADFS_LOGIN:
enable_adfs = True
saml_connected = SocialAuthUser.objects.filter(
@@ -164,6 +173,8 @@ def edit_profile(request):
'social_connected': social_connected,
'enable_dingtalk': enable_dingtalk,
'social_connected_dingtalk': social_connected_dingtalk,
+ 'enable_weixin': enable_weixin,
+ 'social_connected_weixin': social_connected_weixin,
'ENABLE_USER_SET_CONTACT_EMAIL': settings.ENABLE_USER_SET_CONTACT_EMAIL,
'ENABLE_USER_SET_NAME': settings.ENABLE_USER_SET_NAME,
'user_unusable_password': request.user.enc_password == UNUSABLE_PASSWORD,
diff --git a/seahub/settings.py b/seahub/settings.py
index 64dad3d546..3228edf7d0 100644
--- a/seahub/settings.py
+++ b/seahub/settings.py
@@ -271,6 +271,7 @@ INSTALLED_APPS = [
'seahub.file_tags',
'seahub.related_files',
'seahub.work_weixin',
+ 'seahub.weixin',
'seahub.dingtalk',
'seahub.file_participants',
'seahub.repo_api_tokens',
diff --git a/seahub/weixin/templates/weixin/weixin_user_not_found_error.html b/seahub/weixin/templates/weixin/weixin_user_not_found_error.html
new file mode 100644
index 0000000000..06e5534fda
--- /dev/null
+++ b/seahub/weixin/templates/weixin/weixin_user_not_found_error.html
@@ -0,0 +1,8 @@
+{% extends 'base.html' %}
+
+{% block main_content %}
+
+
没找到对应的账号,请先用邮箱注册一个团队账号,然后在个人设置页绑定微信后再扫码登录。
+
[注册团队账号]
+
+{% endblock %}
diff --git a/seahub/weixin/urls.py b/seahub/weixin/urls.py
index bc1bd71855..c92c75b36a 100644
--- a/seahub/weixin/urls.py
+++ b/seahub/weixin/urls.py
@@ -2,9 +2,13 @@
# encoding: utf-8
from django.urls import path
-from seahub.weixin.views import weixin_oauth_login, weixin_oauth_callback
+from seahub.weixin.views import weixin_oauth_login, weixin_oauth_callback, \
+ weixin_oauth_disconnect, weixin_oauth_connect, weixin_oauth_connect_callback
urlpatterns = [
path('oauth-login/', weixin_oauth_login, name='weixin_oauth_login'),
path('oauth-callback/', weixin_oauth_callback, name='weixin_oauth_callback'),
+ path('oauth-connect/', weixin_oauth_connect, name='weixin_oauth_connect'),
+ path('oauth-connect-callback/', weixin_oauth_connect_callback, name='weixin_oauth_connect_callback'),
+ path('oauth-disconnect/', weixin_oauth_disconnect, name='weixin_oauth_disconnect'),
]
diff --git a/seahub/weixin/views.py b/seahub/weixin/views.py
index 18f74a3e9c..6835d0c2db 100644
--- a/seahub/weixin/views.py
+++ b/seahub/weixin/views.py
@@ -9,6 +9,7 @@ from django.http import HttpResponseRedirect
from django.urls import reverse
from django.core.files.base import ContentFile
from django.utils.translation import gettext as _
+from django.shortcuts import render
from seahub.api2.utils import get_api_token
@@ -18,13 +19,15 @@ from seahub.avatar.models import Avatar
from seahub.profile.models import Profile
from seahub.utils import render_error, get_site_scheme_and_netloc
from seahub.auth.models import SocialAuthUser
+from seahub.utils.auth import VIRTUAL_ID_EMAIL_DOMAIN
+from seahub.auth.decorators import login_required
from seahub.settings import SITE_ROOT
from seahub.weixin.settings import ENABLE_WEIXIN, \
WEIXIN_OAUTH_APP_ID, WEIXIN_OAUTH_APP_SECRET, \
WEIXIN_OAUTH_SCOPE, WEIXIN_OAUTH_RESPONSE_TYPE, WEIXIN_OAUTH_QR_CONNECT_URL, \
WEIXIN_OAUTH_GRANT_TYPE, WEIXIN_OAUTH_ACCESS_TOKEN_URL, \
- WEIXIN_OAUTH_USER_INFO_URL
+ WEIXIN_OAUTH_USER_INFO_URL, WEIXIN_OAUTH_CREATE_UNKNOWN_USER
logger = logging.getLogger(__name__)
@@ -34,15 +37,17 @@ logger = logging.getLogger(__name__)
def weixin_oauth_login(request):
if not ENABLE_WEIXIN:
- return render_error(request, _('Error, please contact administrator.'))
+ return render_error(request, _('Feature is not enabled.'))
state = str(uuid.uuid4())
request.session['weixin_oauth_login_state'] = state
- request.session['weixin_oauth_login_redirect'] = request.GET.get(auth.REDIRECT_FIELD_NAME, '/')
+ redirect_to = request.GET.get(auth.REDIRECT_FIELD_NAME, SITE_ROOT)
+ request.session['weixin_oauth_login_redirect'] = redirect_to
+ redirect_uri = get_site_scheme_and_netloc() + reverse('weixin_oauth_callback')
data = {
'appid': WEIXIN_OAUTH_APP_ID,
- 'redirect_uri': get_site_scheme_and_netloc() + reverse('weixin_oauth_callback'),
+ 'redirect_uri': redirect_uri,
'response_type': WEIXIN_OAUTH_RESPONSE_TYPE,
'scope': WEIXIN_OAUTH_SCOPE,
'state': state,
@@ -54,7 +59,7 @@ def weixin_oauth_login(request):
def weixin_oauth_callback(request):
if not ENABLE_WEIXIN:
- return render_error(request, _('Error, please contact administrator.'))
+ return render_error(request, _('Feature is not enabled.'))
state = request.GET.get('state', '')
if not state or state != request.session.get('weixin_oauth_login_state', ''):
@@ -89,6 +94,9 @@ def weixin_oauth_callback(request):
email = None
is_new_user = True
+ if is_new_user and not WEIXIN_OAUTH_CREATE_UNKNOWN_USER:
+ return render(request, 'weixin/weixin_user_not_found_error.html')
+
try:
user = auth.authenticate(remote_user=email)
email = user.username
@@ -147,3 +155,88 @@ def weixin_oauth_callback(request):
SITE_ROOT))
response.set_cookie('seahub_auth', email + '@' + api_token.key)
return response
+
+
+@login_required
+def weixin_oauth_connect(request):
+
+ if not ENABLE_WEIXIN:
+ return render_error(request, _('Feature is not enabled.'))
+
+ state = str(uuid.uuid4())
+ request.session['weixin_oauth_connect_state'] = state
+ redirect_to = request.GET.get(auth.REDIRECT_FIELD_NAME, SITE_ROOT)
+ request.session['weixin_oauth_connect_redirect'] = redirect_to
+
+ redirect_uri = get_site_scheme_and_netloc() + reverse('weixin_oauth_connect_callback')
+ data = {
+ 'appid': WEIXIN_OAUTH_APP_ID,
+ 'redirect_uri': redirect_uri,
+ 'response_type': WEIXIN_OAUTH_RESPONSE_TYPE,
+ 'scope': WEIXIN_OAUTH_SCOPE,
+ 'state': state,
+ }
+ url = WEIXIN_OAUTH_QR_CONNECT_URL + '?' + urllib.parse.urlencode(data)
+ return HttpResponseRedirect(url)
+
+
+@login_required
+def weixin_oauth_connect_callback(request):
+
+ if not ENABLE_WEIXIN:
+ return render_error(request, _('Feature is not enabled.'))
+
+ state = request.GET.get('state', '')
+ if not state or state != request.session.get('weixin_oauth_connect_state', ''):
+ logger.error('invalid state')
+ return render_error(request, _('Error, please contact administrator.'))
+
+ # get access_token and user openid
+ parameters = {
+ 'appid': WEIXIN_OAUTH_APP_ID,
+ 'secret': WEIXIN_OAUTH_APP_SECRET,
+ 'code': request.GET.get('code'),
+ 'grant_type': WEIXIN_OAUTH_GRANT_TYPE,
+ }
+
+ access_token_url = WEIXIN_OAUTH_ACCESS_TOKEN_URL + '?' + urllib.parse.urlencode(parameters)
+ access_token_json = requests.get(access_token_url).json()
+
+ openid = access_token_json.get('openid', '')
+ access_token = access_token_json.get('access_token', '')
+ if not access_token or not openid:
+ logger.error('invalid access_token or openid')
+ logger.error(access_token_url)
+ logger.error(access_token_json)
+ return render_error(request, _('Error, please contact administrator.'))
+
+ auth_user = SocialAuthUser.objects.get_by_provider_and_uid('weixin', openid)
+ if auth_user:
+ logger.warning('weixin account already exists %s' % openid)
+ return render_error(request, '出错了,此微信账号已被绑定')
+
+ username = request.user.username
+ SocialAuthUser.objects.add(username, 'weixin', openid)
+
+ weixin_oauth_connect_redirect = request.session['weixin_oauth_connect_redirect']
+ response = HttpResponseRedirect(weixin_oauth_connect_redirect)
+ return response
+
+
+@login_required
+def weixin_oauth_disconnect(request):
+
+ if not ENABLE_WEIXIN:
+ return render_error(request, _('Feature is not enabled.'))
+
+ username = request.user.username
+ if username[-(len(VIRTUAL_ID_EMAIL_DOMAIN)):] == VIRTUAL_ID_EMAIL_DOMAIN:
+ profile = Profile.objects.get_profile_by_user(username)
+ if not profile:
+ return render_error(request, '出错了,当前账号不能解绑微信')
+
+ SocialAuthUser.objects.delete_by_username_and_provider(username, 'weixin')
+
+ # redirect user to page
+ response = HttpResponseRedirect(request.GET.get(auth.REDIRECT_FIELD_NAME, SITE_ROOT))
+ return response
{gettext('Social Login')}
+{langCode == 'zh-cn' ? '微信' : 'Weixin'}
+ {socialConnectedWeixin ? + : + {gettext('Connect')} + } +