diff --git a/apps/authentication/__init__.py b/apps/authentication/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/admin.py b/apps/authentication/admin.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/admin.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py new file mode 100644 index 000000000..0869b64fd --- /dev/null +++ b/apps/authentication/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class AuthenticationConfig(AppConfig): + name = 'authentication' + + def ready(self): + from . import signals_handlers + super().ready() + diff --git a/apps/authentication/models.py b/apps/authentication/models.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/models.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/openid/__init__.py b/apps/authentication/openid/__init__.py new file mode 100644 index 000000000..bc4c753ca --- /dev/null +++ b/apps/authentication/openid/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# + +from django.conf import settings +from .models import Client + + +def new_client(): + """ + :return: authentication.models.Client + """ + return Client( + server_url=settings.AUTH_OPENID_SERVER_URL, + realm_name=settings.AUTH_OPENID_REALM_NAME, + client_id=settings.AUTH_OPENID_CLIENT_ID, + client_secret=settings.AUTH_OPENID_CLIENT_SECRET + ) + + +client = new_client() diff --git a/apps/authentication/openid/backends.py b/apps/authentication/openid/backends.py new file mode 100644 index 000000000..15a758acc --- /dev/null +++ b/apps/authentication/openid/backends.py @@ -0,0 +1,90 @@ +# coding:utf-8 +# + +from django.contrib.auth import get_user_model +from django.conf import settings + +from . import client +from common.utils import get_logger +from authentication.openid.models import OIDT_ACCESS_TOKEN + +UserModel = get_user_model() + +logger = get_logger(__file__) + +BACKEND_OPENID_AUTH_CODE = \ + 'authentication.openid.backends.OpenIDAuthorizationCodeBackend' + + +class BaseOpenIDAuthorizationBackend(object): + + @staticmethod + def user_can_authenticate(user): + """ + Reject users with is_active=False. Custom user models that don't have + that attribute are allowed. + """ + is_active = getattr(user, 'is_active', None) + return is_active or is_active is None + + def get_user(self, user_id): + try: + user = UserModel._default_manager.get(pk=user_id) + except UserModel.DoesNotExist: + return None + + return user if self.user_can_authenticate(user) else None + + +class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend): + + def authenticate(self, request, **kwargs): + logger.info('1.openid code backend') + + code = kwargs.get('code') + redirect_uri = kwargs.get('redirect_uri') + + if not code or not redirect_uri: + return None + + try: + oidt_profile = client.update_or_create_from_code( + code=code, + redirect_uri=redirect_uri + ) + + except Exception as e: + logger.error(e) + + else: + # Check openid user single logout or not with access_token + request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token + + user = oidt_profile.user + + return user if self.user_can_authenticate(user) else None + + +class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend): + + def authenticate(self, request, username=None, password=None, **kwargs): + logger.info('2.openid password backend') + + if not settings.AUTH_OPENID: + return None + + elif not username: + return None + + try: + oidt_profile = client.update_or_create_from_password( + username=username, password=password + ) + + except Exception as e: + logger.error(e) + + else: + user = oidt_profile.user + return user if self.user_can_authenticate(user) else None + diff --git a/apps/authentication/openid/middleware.py b/apps/authentication/openid/middleware.py new file mode 100644 index 000000000..128b20984 --- /dev/null +++ b/apps/authentication/openid/middleware.py @@ -0,0 +1,42 @@ +# coding:utf-8 +# + +from django.conf import settings +from django.contrib.auth import logout +from django.utils.deprecation import MiddlewareMixin +from django.contrib.auth import BACKEND_SESSION_KEY + +from . import client +from common.utils import get_logger +from .backends import BACKEND_OPENID_AUTH_CODE +from authentication.openid.models import OIDT_ACCESS_TOKEN + +logger = get_logger(__file__) + + +class OpenIDAuthenticationMiddleware(MiddlewareMixin): + """ + Check openid user single logout (with access_token) + """ + + def process_request(self, request): + + # Don't need openid auth if AUTH_OPENID is False + if not settings.AUTH_OPENID: + return + + # Don't need check single logout if user not authenticated + if not request.user.is_authenticated: + return + + elif request.session[BACKEND_SESSION_KEY] != BACKEND_OPENID_AUTH_CODE: + return + + # Check openid user single logout or not with access_token + try: + client.openid_connect_client.userinfo( + token=request.session.get(OIDT_ACCESS_TOKEN)) + + except Exception as e: + logout(request) + logger.error(e) diff --git a/apps/authentication/openid/models.py b/apps/authentication/openid/models.py new file mode 100644 index 000000000..e3c0a4842 --- /dev/null +++ b/apps/authentication/openid/models.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# + +from django.db import transaction +from django.contrib.auth import get_user_model +from keycloak.realm import KeycloakRealm +from keycloak.keycloak_openid import KeycloakOpenID +from ..signals import post_create_openid_user + +OIDT_ACCESS_TOKEN = 'oidt_access_token' + + +class OpenIDTokenProfile(object): + + def __init__(self, user, access_token, refresh_token): + """ + :param user: User object + :param access_token: + :param refresh_token: + """ + self.user = user + self.access_token = access_token + self.refresh_token = refresh_token + + def __str__(self): + return "{}'s OpenID token profile".format(self.user.username) + + +class Client(object): + + def __init__(self, server_url, realm_name, client_id, client_secret): + self.server_url = server_url + self.realm_name = realm_name + self.client_id = client_id + self.client_secret = client_secret + self.realm = self.new_realm() + self.openid_client = self.new_openid_client() + self.openid_connect_client = self.new_openid_connect_client() + + def new_realm(self): + """ + :param authentication.openid.models.Realm realm: + :return keycloak.realm.Realm: + """ + return KeycloakRealm( + server_url=self.server_url, + realm_name=self.realm_name, + headers={} + ) + + def new_openid_connect_client(self): + """ + :rtype: keycloak.openid_connect.KeycloakOpenidConnect + """ + openid_connect = self.realm.open_id_connect( + client_id=self.client_id, + client_secret=self.client_secret + ) + return openid_connect + + def new_openid_client(self): + """ + :rtype: keycloak.keycloak_openid.KeycloakOpenID + """ + + return KeycloakOpenID( + server_url='%sauth/' % self.server_url, + realm_name=self.realm_name, + client_id=self.client_id, + client_secret_key=self.client_secret, + ) + + def update_or_create_from_password(self, username, password): + """ + Update or create an user based on an authentication username and password. + + :param str username: authentication username + :param str password: authentication password + :return: authentication.models.OpenIDTokenProfile + """ + token_response = self.openid_client.token( + username=username, password=password + ) + + return self._update_or_create(token_response=token_response) + + def update_or_create_from_code(self, code, redirect_uri): + """ + Update or create an user based on an authentication code. + Response as specified in: + + https://tools.ietf.org/html/rfc6749#section-4.1.4 + + :param str code: authentication code + :param str redirect_uri: + :rtype: authentication.models.OpenIDTokenProfile + """ + + token_response = self.openid_connect_client.authorization_code( + code=code, redirect_uri=redirect_uri) + + return self._update_or_create(token_response=token_response) + + def _update_or_create(self, token_response): + """ + Update or create an user based on a token response. + + `token_response` contains the items returned by the OpenIDConnect Token API + end-point: + - id_token + - access_token + - expires_in + - refresh_token + - refresh_expires_in + + :param dict token_response: + :rtype: authentication.openid.models.OpenIDTokenProfile + """ + + userinfo = self.openid_connect_client.userinfo( + token=token_response['access_token']) + + with transaction.atomic(): + user, _ = get_user_model().objects.update_or_create( + username=userinfo.get('preferred_username', ''), + defaults={ + 'email': userinfo.get('email', ''), + 'first_name': userinfo.get('given_name', ''), + 'last_name': userinfo.get('family_name', '') + } + ) + + oidt_profile = OpenIDTokenProfile( + user=user, + access_token=token_response['access_token'], + refresh_token=token_response['refresh_token'], + ) + + if user: + post_create_openid_user.send(sender=user.__class__, user=user) + + return oidt_profile + + def __str__(self): + return self.client_id + + +class Nonce(object): + """ + The openid-login is stored in cache as a temporary object, recording the + user's redirect_uri and next_pat + """ + + def __init__(self, redirect_uri, next_path): + import uuid + self.state = uuid.uuid4() + self.redirect_uri = redirect_uri + self.next_path = next_path + diff --git a/apps/authentication/openid/tests.py b/apps/authentication/openid/tests.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/openid/views.py b/apps/authentication/openid/views.py new file mode 100644 index 000000000..9aeb0bf7b --- /dev/null +++ b/apps/authentication/openid/views.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# + +import logging + +from django.urls import reverse +from django.conf import settings +from django.core.cache import cache +from django.views.generic.base import RedirectView +from django.contrib.auth import authenticate, login +from django.http.response import ( + HttpResponseBadRequest, + HttpResponseServerError, + HttpResponseRedirect +) + +from . import client +from .models import Nonce +from users.models import LoginLog +from users.tasks import write_login_log_async +from common.utils import get_request_ip + +logger = logging.getLogger(__name__) + + +def get_base_site_url(): + return settings.BASE_SITE_URL + + +class LoginView(RedirectView): + + def get_redirect_url(self, *args, **kwargs): + nonce = Nonce( + redirect_uri=get_base_site_url() + reverse( + "authentication:openid-login-complete"), + + next_path=self.request.GET.get('next') + ) + + cache.set(str(nonce.state), nonce, 24*3600) + + self.request.session['openid_state'] = str(nonce.state) + + authorization_url = client.openid_connect_client.\ + authorization_url( + redirect_uri=nonce.redirect_uri, scope='code', + state=str(nonce.state) + ) + + return authorization_url + + +class LoginCompleteView(RedirectView): + + def get(self, request, *args, **kwargs): + if 'error' in request.GET: + return HttpResponseServerError(self.request.GET['error']) + + if 'code' not in self.request.GET and 'state' not in self.request.GET: + return HttpResponseBadRequest() + + if self.request.GET['state'] != self.request.session['openid_state']: + return HttpResponseBadRequest() + + nonce = cache.get(self.request.GET['state']) + + if not nonce: + return HttpResponseBadRequest() + + user = authenticate( + request=self.request, + code=self.request.GET['code'], + redirect_uri=nonce.redirect_uri + ) + + cache.delete(str(nonce.state)) + + if not user: + return HttpResponseBadRequest() + + login(self.request, user) + + data = { + 'username': user.username, + 'mfa': int(user.otp_enabled), + 'reason': LoginLog.REASON_NOTHING, + 'status': True + } + self.write_login_log(data) + + return HttpResponseRedirect(nonce.next_path or '/') + + def write_login_log(self, data): + login_ip = get_request_ip(self.request) + user_agent = self.request.META.get('HTTP_USER_AGENT', '') + tmp_data = { + 'ip': login_ip, + 'type': 'W', + 'user_agent': user_agent + } + data.update(tmp_data) + write_login_log_async.delay(**data) diff --git a/apps/authentication/signals.py b/apps/authentication/signals.py new file mode 100644 index 000000000..f33a3b821 --- /dev/null +++ b/apps/authentication/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + + +post_create_openid_user = Signal(providing_args=('user',)) diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py new file mode 100644 index 000000000..7cf240386 --- /dev/null +++ b/apps/authentication/signals_handlers.py @@ -0,0 +1,33 @@ +from django.http.request import QueryDict +from django.contrib.auth.signals import user_logged_out +from django.dispatch import receiver +from django.conf import settings +from .openid import client +from .signals import post_create_openid_user + + +@receiver(user_logged_out) +def on_user_logged_out(sender, request, user, **kwargs): + if not settings.AUTH_OPENID: + return + + query = QueryDict('', mutable=True) + query.update({ + 'redirect_uri': settings.BASE_SITE_URL + }) + + openid_logout_url = "%s?%s" % ( + client.openid_connect_client.get_url( + name='end_session_endpoint'), + query.urlencode() + ) + + request.COOKIES['next'] = openid_logout_url + + +@receiver(post_create_openid_user) +def on_post_create_openid_user(sender, user=None, **kwargs): + if user and user.username != 'admin': + user.source = user.SOURCE_OPENID + user.save() + diff --git a/apps/authentication/tests.py b/apps/authentication/tests.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/tests.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/urls/api_urls.py @@ -0,0 +1 @@ + diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py new file mode 100644 index 000000000..4d4e6753a --- /dev/null +++ b/apps/authentication/urls/view_urls.py @@ -0,0 +1,16 @@ +# coding:utf-8 +# + +from django.urls import path +from authentication.openid import views + +app_name = 'authentication' + +urlpatterns = [ + # openid + path('openid/login/', views.LoginView.as_view(), name='openid-login'), + path('openid/login/complete/', views.LoginCompleteView.as_view(), + name='openid-login-complete'), + + # other +] diff --git a/apps/authentication/views.py b/apps/authentication/views.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/authentication/views.py @@ -0,0 +1 @@ + diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 6635b35e6..212878386 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -64,6 +64,7 @@ INSTALLED_APPS = [ 'common.apps.CommonConfig', 'terminal.apps.TerminalConfig', 'audits.apps.AuditsConfig', + 'authentication.apps.AuthenticationConfig', # authentication 'rest_framework', 'rest_framework_swagger', 'drf_yasg', @@ -94,6 +95,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'authentication.openid.middleware.OpenIDAuthenticationMiddleware', # openid 'jumpserver.middleware.TimezoneMiddleware', 'jumpserver.middleware.DemoMiddleware', 'jumpserver.middleware.RequestMiddleware', @@ -389,6 +391,24 @@ AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend' if AUTH_LDAP: AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND) +# openid +# Auth OpenID settings +BASE_SITE_URL = CONFIG.BASE_SITE_URL +AUTH_OPENID = CONFIG.AUTH_OPENID +AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL +AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME +AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID +AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET +AUTH_OPENID_BACKENDS = [ + 'authentication.openid.backends.OpenIDAuthorizationPasswordBackend', + 'authentication.openid.backends.OpenIDAuthorizationCodeBackend', +] + +if AUTH_OPENID: + LOGIN_URL = reverse_lazy("authentication:openid-login") + AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[0]) + AUTHENTICATION_BACKENDS.insert(0, AUTH_OPENID_BACKENDS[1]) + # Celery using redis as broker CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % { 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 22351e509..2319d81e5 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -75,6 +75,7 @@ app_view_patterns = [ path('ops/', include('ops.urls.view_urls', namespace='ops')), path('audits/', include('audits.urls.view_urls', namespace='audits')), path('orgs/', include('orgs.urls.views_urls', namespace='orgs')), + path('auth/', include('authentication.urls.view_urls'), name='auth'), ] if settings.XPACK_ENABLED: diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 9f41c1d67..bb3a01026 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 0f8a6448f..361e05589 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-01 13:58+0800\n" +"POT-Creation-Date: 2018-11-08 19:18+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -115,7 +115,7 @@ msgid "Port" msgstr "端口" #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:243 assets/templates/assets/admin_user_list.html:28 +#: assets/models/asset.py:253 assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:16 @@ -1758,7 +1758,7 @@ msgstr "改密日志" #: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28 #: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76 -#: users/views/group.py:92 users/views/login.py:331 users/views/user.py:68 +#: users/views/group.py:92 users/views/login.py:334 users/views/user.py:68 #: users/views/user.py:83 users/views/user.py:111 users/views/user.py:193 #: users/views/user.py:354 users/views/user.py:404 users/views/user.py:439 msgid "Users" @@ -2438,7 +2438,7 @@ msgstr "任务列表" msgid "Task run history" msgstr "执行历史" -#: orgs/mixins.py:78 orgs/models.py:24 +#: orgs/mixins.py:77 orgs/models.py:24 msgid "Organization" msgstr "组织管理" @@ -3092,11 +3092,11 @@ msgstr "登录频繁, 稍后重试" msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: users/api/auth.py:195 +#: users/api/auth.py:196 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: users/api/auth.py:207 +#: users/api/auth.py:208 msgid "MFA certification failed" msgstr "MFA认证失败" @@ -3448,7 +3448,7 @@ msgstr "获取更多信息" #: users/templates/users/forgot_password.html:11 #: users/templates/users/forgot_password.html:27 -#: users/templates/users/login.html:79 +#: users/templates/users/login.html:81 msgid "Forgot password" msgstr "忘记密码" @@ -3497,6 +3497,14 @@ msgstr "改变世界,从一点点开始。" msgid "Captcha invalid" msgstr "验证码错误" +#: users/templates/users/login.html:87 +msgid "More login options" +msgstr "更多登录方式" + +#: users/templates/users/login.html:91 +msgid "Keycloak" +msgstr "" + #: users/templates/users/login_otp.html:46 #: users/templates/users/user_detail.html:91 #: users/templates/users/user_profile.html:85 @@ -3995,19 +4003,19 @@ msgstr "" "
\n" " " -#: users/utils.py:148 +#: users/utils.py:162 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:150 +#: users/utils.py:164 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:163 +#: users/utils.py:177 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" -#: users/utils.py:286 users/utils.py:296 +#: users/utils.py:300 users/utils.py:310 msgid "Bit" msgstr " 位" @@ -4027,52 +4035,52 @@ msgstr "用户组授权资产" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: users/views/login.py:179 users/views/user.py:526 users/views/user.py:551 +#: users/views/login.py:180 users/views/user.py:526 users/views/user.py:551 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" -#: users/views/login.py:208 +#: users/views/login.py:211 msgid "Logout success" msgstr "退出登录成功" -#: users/views/login.py:209 +#: users/views/login.py:212 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: users/views/login.py:225 +#: users/views/login.py:228 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:238 +#: users/views/login.py:241 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:239 +#: users/views/login.py:242 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:252 +#: users/views/login.py:255 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:253 +#: users/views/login.py:256 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:274 users/views/login.py:287 +#: users/views/login.py:277 users/views/login.py:290 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:283 +#: users/views/login.py:286 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:293 users/views/user.py:127 users/views/user.py:422 +#: users/views/login.py:296 users/views/user.py:127 users/views/user.py:422 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:331 +#: users/views/login.py:334 msgid "First login" msgstr "首次登陆" diff --git a/apps/users/models/user.py b/apps/users/models/user.py index bf2cae7f1..8192ae095 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -40,9 +40,11 @@ class User(AbstractUser): ) SOURCE_LOCAL = 'local' SOURCE_LDAP = 'ldap' + SOURCE_OPENID = 'openid' SOURCE_CHOICES = ( (SOURCE_LOCAL, 'Local'), (SOURCE_LDAP, 'LDAP/AD'), + (SOURCE_OPENID, 'OpenID'), ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) username = models.CharField( diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html index 93ed4e023..6582dd447 100644 --- a/apps/users/templates/users/login.html +++ b/apps/users/templates/users/login.html @@ -75,9 +75,23 @@

{% endif %} - - {% trans 'Forgot password' %}? - +
+
+ + {% trans 'Forgot password' %}? + +
+ + {% if AUTH_OPENID %} +
+

{% trans "More login options" %}

+
+ +
+ {% endif %}

diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 4f2308d04..971b1cf2d 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -132,6 +132,7 @@ class UserLoginView(FormView): def get_context_data(self, **kwargs): context = { 'demo_mode': os.environ.get("DEMO_MODE"), + 'AUTH_OPENID': settings.AUTH_OPENID, } kwargs.update(context) return super().get_context_data(**kwargs) @@ -200,6 +201,9 @@ class UserLogoutView(TemplateView): def get(self, request, *args, **kwargs): auth_logout(request) + next_uri = request.COOKIES.get("next") + if next_uri: + return redirect(next_uri) response = super().get(request, *args, **kwargs) return response diff --git a/config_example.py b/config_example.py index a96f0d7c9..d0fc4bff9 100644 --- a/config_example.py +++ b/config_example.py @@ -54,6 +54,14 @@ class Config: REDIS_DB_CELERY = os.environ.get('REDIS_DB') or 3 REDIS_DB_CACHE = os.environ.get('REDIS_DB') or 4 + # Use OpenID authorization + # BASE_SITE_URL = 'http://localhost:8080' + # AUTH_OPENID = False # True or False + # AUTH_OPENID_SERVER_URL = 'https://openid-auth-server.com/' + # AUTH_OPENID_REALM_NAME = 'realm-name' + # AUTH_OPENID_CLIENT_ID = 'client-id' + # AUTH_OPENID_CLIENT_SECRET = 'client-secret' + def __init__(self): pass