1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-05-11 01:17:02 +00:00

add oauth support

This commit is contained in:
lian 2017-11-14 11:53:41 +08:00
parent 8e4f65fef3
commit 50ccccfcb4
13 changed files with 268 additions and 0 deletions

View File

@ -14,3 +14,4 @@ pytz==2015.7
django-formtools
qrcode
requests
requests_oauthlib==0.8.0

View File

@ -245,6 +245,7 @@ def login(request, template_name='registration/login.html',
enable_shib_login = getattr(settings, 'ENABLE_SHIB_LOGIN', False)
enable_krb5_login = getattr(settings, 'ENABLE_KRB5_LOGIN', False)
enable_adfs_login = getattr(settings, 'ENABLE_ADFS_LOGIN', False)
enable_oauth = getattr(settings, 'ENABLE_OAUTH', False)
login_bg_image_path = LOGIN_BG_IMAGE_PATH
# get path that background image of login page
@ -262,6 +263,7 @@ def login(request, template_name='registration/login.html',
'enable_shib_login': enable_shib_login,
'enable_krb5_login': enable_krb5_login,
'enable_adfs_login': enable_adfs_login,
'enable_oauth': enable_oauth,
'login_bg_image_path': login_bg_image_path,
}, context_instance=RequestContext(request))

0
seahub/oauth/__init__.py Normal file
View File

3
seahub/oauth/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

55
seahub/oauth/backends.py Normal file
View File

@ -0,0 +1,55 @@
from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend
from seahub.base.accounts import User
from registration.models import notify_admins_on_activate_request
class OauthRemoteUserBackend(RemoteUserBackend):
"""
This backend is to be used in conjunction with the ``RemoteUserMiddleware``
found in the middleware module of this package, and is used when the server
is handling authentication outside of Django.
By default, the ``authenticate`` method creates ``User`` objects for
usernames that don't already exist in the database. Subclasses can disable
this behavior by setting the ``create_unknown_user`` attribute to
``False``.
"""
# Create a User object if not already in the database?
create_unknown_user = getattr(settings, 'OAUTH_CREATE_UNKNOWN_USER', True)
# Create active user by default.
activate_after_creation = getattr(settings, 'OAUTH_ACTIVATE_USER_AFTER_CREATION', True)
def get_user(self, username):
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
user = None
return user
def authenticate(self, remote_user):
"""
The username passed as ``remote_user`` is considered trusted. This
method simply returns the ``User`` object with the given username,
creating a new ``User`` object if ``create_unknown_user`` is ``True``.
Returns None if ``create_unknown_user`` is ``False`` and a ``User``
object with the given username is not found in the database.
"""
if not remote_user:
return
username = self.clean_username(remote_user)
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
if self.create_unknown_user:
user = User.objects.create_user(
email=username, is_active=self.activate_after_creation)
if user and self.activate_after_creation is False:
notify_admins_on_activate_request(user.email)
else:
user = None
return user

View File

3
seahub/oauth/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
seahub/oauth/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
seahub/oauth/urls.py Normal file
View File

@ -0,0 +1,9 @@
# Copyright (c) 2012-2016 Seafile Ltd.
from django.conf.urls import patterns, url
from seahub.oauth.views import oauth_login, oauth_callback
urlpatterns = patterns('',
url(r'login/$', oauth_login, name='oauth_login'),
url(r'callback/$', oauth_callback, name='oauth_callback'),
)

181
seahub/oauth/views.py Normal file
View File

@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
import os
import logging
from requests_oauthlib import OAuth2Session
from django.http import HttpResponseRedirect
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from seahub import auth
from seahub.profile.models import Profile
from seahub.utils import is_valid_email
import seahub.settings as settings
logger = logging.getLogger(__name__)
ENABLE_OAUTH = getattr(settings, 'ENABLE_OAUTH', False)
if ENABLE_OAUTH:
if getattr(settings, 'OAUTH_ENABLE_INSECURE_TRANSPORT', False):
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
# Used for oauth workflow.
CLIENT_ID = getattr(settings, 'OAUTH_CLIENT_ID', '')
CLIENT_SECRET = getattr(settings, 'OAUTH_CLIENT_SECRET', '')
AUTHORIZATION_URL = getattr(settings, 'OAUTH_AUTHORIZATION_URL', '')
REDIRECT_URL = getattr(settings, 'OAUTH_REDIRECT_URL', '')
TOKEN_URL = getattr(settings, 'OAUTH_TOKEN_URL', '')
USER_INFO_URL = getattr(settings, 'OAUTH_USER_INFO_URL', '')
SCOPE = getattr(settings, 'OAUTH_SCOPE', '')
# Used for init an user for Seahub.
PROVIDER_DOMAIN = getattr(settings, 'OAUTH_PROVIDER_DOMAIN', '')
ATTRIBUTE_MAP = {
'id': (True, "email"),
}
ATTRIBUTE_MAP.update(getattr(settings, 'OAUTH_ATTRIBUTE_MAP', {}))
session = OAuth2Session(client_id=CLIENT_ID,
scope=SCOPE, redirect_uri=REDIRECT_URL)
def oauth_check(func):
""" Decorator for check if OAuth valid.
"""
def _decorated(request):
error = False
if not ENABLE_OAUTH:
logger.error('OAuth not enabled.')
error = True
else:
if not CLIENT_ID or not CLIENT_SECRET or not AUTHORIZATION_URL \
or not REDIRECT_URL or not TOKEN_URL or not USER_INFO_URL \
or not SCOPE or not PROVIDER_DOMAIN:
logger.error('OAuth relevant settings invalid.')
logger.error('CLIENT_ID: %s' % CLIENT_ID)
logger.error('CLIENT_SECRET: %s' % CLIENT_SECRET)
logger.error('AUTHORIZATION_URL: %s' % AUTHORIZATION_URL)
logger.error('REDIRECT_URL: %s' % REDIRECT_URL)
logger.error('TOKEN_URL: %s' % TOKEN_URL)
logger.error('USER_INFO_URL: %s' % USER_INFO_URL)
logger.error('SCOPE: %s' % SCOPE)
logger.error('PROVIDER_DOMAIN: %s' % PROVIDER_DOMAIN)
error = True
if error:
return render_to_response('error.html', {
'error_msg': _('Error, please contact administrator.'),
}, context_instance=RequestContext(request))
return func(request)
return _decorated
# https://requests-oauthlib.readthedocs.io/en/latest/examples/github.html
# https://requests-oauthlib.readthedocs.io/en/latest/examples/google.html
@oauth_check
def oauth_login(request):
"""Step 1: User Authorization.
Redirect the user/resource owner to the OAuth provider (i.e. Github)
using an URL with a few key OAuth parameters.
"""
try:
authorization_url, state = session.authorization_url(
AUTHORIZATION_URL)
except Exception as e:
logger.error(e)
return render_to_response('error.html', {
'error_msg': _('Error, please contact administrator.'),
}, context_instance=RequestContext(request))
return HttpResponseRedirect(authorization_url)
# Step 2: User authorization, this happens on the provider.
@oauth_check
def oauth_callback(request):
""" Step 3: Retrieving an access token.
The user has been redirected back from the provider to your registered
callback URL. With this redirection comes an authorization code included
in the redirect URL. We will use that to obtain an access token.
"""
try:
session.fetch_token(TOKEN_URL, client_secret=CLIENT_SECRET,
authorization_response=request.get_full_path())
user_info_resp = session.get(USER_INFO_URL)
except Exception as e:
logger.error(e)
return render_to_response('error.html', {
'error_msg': _('Error, please contact administrator.'),
}, context_instance=RequestContext(request))
def format_user_info(user_info_resp):
error = False
user_info = {}
user_info_json = user_info_resp.json()
for item, attr in ATTRIBUTE_MAP.items():
required, user_attr = attr
value = str(user_info_json.get(item, ''))
# ccnet email
if user_attr == 'email':
user_info[user_attr] = value if is_valid_email(value) else \
'%s@%s' % (value, PROVIDER_DOMAIN)
else:
user_info[user_attr] = value
if required and not value:
error = True
return user_info, error
user_info, error = format_user_info(user_info_resp)
if error:
logger.error('Required user info not found.')
logger.error(user_info)
return render_to_response('error.html', {
'error_msg': _('Error, please contact administrator.'),
}, context_instance=RequestContext(request))
# seahub authenticate user
email = user_info['email']
user = auth.authenticate(remote_user=email)
if not user or not user.is_active:
logger.error('User %s not found or inactive.' % email)
# a page for authenticate user failed
return render_to_response('error.html', {
'error_msg': _(u'User %s not found.') % email
}, context_instance=RequestContext(request))
# User is valid. Set request.user and persist user in the session
# by logging the user in.
request.user = user
auth.login(request, user)
user.set_unusable_password()
user.save()
# update user's profile
name = user_info['name'] if user_info.has_key('name') else ''
contact_email = user_info['contact_email'] if \
user_info.has_key('contact_email') else ''
profile = Profile.objects.get_profile_by_user(email)
if not profile:
profile = Profile(user=email)
if name:
profile.nickname = name.strip()
if contact_email:
profile.contact_email = contact_email.strip()
profile.save()
# redirect user to home page
return HttpResponseRedirect(reverse('libraries'))

View File

@ -240,7 +240,12 @@ CONSTANCE_DATABASE_CACHE_BACKEND = 'default'
AUTHENTICATION_BACKENDS = (
'seahub.base.accounts.AuthBackend',
'seahub.oauth.backends.OauthRemoteUserBackend',
)
ENABLE_OAUTH = False
LOGIN_REDIRECT_URL = '/profile/'
LOGIN_URL = SITE_ROOT + 'accounts/login'
LOGOUT_REDIRECT_URL = None

View File

@ -66,6 +66,10 @@ html, body, #wrapper { height:100%; }
<button type="submit" class="submit">{% trans "Log In" %}</button>
</form>
{% if enable_oauth %}
<a href="{% url 'oauth_login' %}" title="{% trans "Single Sign-On" %}">{% trans "Single Sign-On" %}</a>
{% endif %}
{% if enable_adfs_login %}
<a id="adfs-login" href="#" class="normal">ADFS</a>
{% endif %}

View File

@ -110,6 +110,8 @@ urlpatterns = patterns(
(r'^sso/$', sso),
url(r'^shib-login/', shib_login, name="shib_login"),
(r'^oauth/', include('seahub.oauth.urls')),
url(r'^$', libraries, name='libraries'),
#url(r'^home/$', direct_to_template, { 'template': 'home.html' } ),
url(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')),