diff --git a/cms/djangoapps/appsembler/urls.py b/cms/djangoapps/appsembler/urls.py new file mode 100644 index 000000000000..200913eeb344 --- /dev/null +++ b/cms/djangoapps/appsembler/urls.py @@ -0,0 +1,17 @@ +"""Perform Studio local login/logout + +The purpose of this is to address the issue that Ironwood introduced login +redirect to the LMS, which breaks in multisite custom domain environments + +We have this code in the Appsembler CMS app to help isolate custom code +""" +from django.conf import settings +from django.urls import path +from django.contrib.auth.views import LogoutView +from .views import LoginView + +urlpatterns = [ + path('login/', LoginView.as_view(), name='login'), + path('logout/', LogoutView.as_view( + next_page=settings.LOGOUT_REDIRECT_URL), name='logout'), +] diff --git a/cms/djangoapps/appsembler/views.py b/cms/djangoapps/appsembler/views.py new file mode 100644 index 000000000000..394a037d0d34 --- /dev/null +++ b/cms/djangoapps/appsembler/views.py @@ -0,0 +1,69 @@ +"""Appsembler custom views for Studio + +Views here provide Studio local login/logout +""" + +from django.conf import settings +from django.contrib.auth import authenticate, login +from django.shortcuts import redirect +from django.views import View +from django.views.decorators.clickjacking import xframe_options_deny +from django.views.decorators.csrf import ensure_csrf_cookie +from django.urls import reverse +from django.utils.decorators import method_decorator + +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from openedx.core.djangoapps.user_authn.views.login import _get_user_by_email + +from edxmako.shortcuts import render_to_response + + +def forgot_password_link(): + return "//{base}/login#forgot-password-modal".format(base=settings.LMS_BASE) + + +def platform_name(): + return configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME) + + +def render_login_page(show_login_error_message=False): + """Convenience function to put the login page + + Arguments: + show_login_error_message (bool): flag to show if a login attempt failed + + Returns: + django.http.response.HttpResponse object with the login page content + """ + return render_to_response( + 'login_page.html', + { + 'show_login_error_message': show_login_error_message, + 'forgot_password_link': forgot_password_link(), + 'platform_name': platform_name(), + } + ) + + +class LoginView(View): + """Basic login view to allow for Studio local logins + """ + + @method_decorator(ensure_csrf_cookie) + @method_decorator(xframe_options_deny) + def get(self, request): + return render_login_page() + + @method_decorator(ensure_csrf_cookie) + def post(self, request, *args, **kwargs): + user = _get_user_by_email(request) + password = request.POST['password'] + + if user: + user = authenticate(request, username=user.username, password=password) + + if not user: + return render_login_page(show_login_error_message=True) + + login(request, user) + return redirect(reverse('home')) diff --git a/cms/envs/common.py b/cms/envs/common.py index 0090c0cd9be9..9a70fad0dd38 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -406,6 +406,7 @@ # .. toggle_tickets: https://openedx.atlassian.net/browse/DEPR-58 # .. toggle_status: supported 'DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO': True, + 'STUDIO_LOCAL_LOGIN': False, } ENABLE_JASMINE = False diff --git a/cms/envs/production.py b/cms/envs/production.py index 389cafc88cd8..03200d55858f 100644 --- a/cms/envs/production.py +++ b/cms/envs/production.py @@ -531,6 +531,17 @@ def get_env_setting(setting): ############### Settings for edx-rbac ############### SYSTEM_WIDE_ROLE_CLASSES = ENV_TOKENS.get('SYSTEM_WIDE_ROLE_CLASSES') or SYSTEM_WIDE_ROLE_CLASSES + +### Appsembler customization - Studio local login ### +if FEATURES.get('STUDIO_LOCAL_LOGIN'): + CMS_ROOT_URL = '//localhost:18010' + LOGIN_URL = reverse_lazy('login') + FRONTEND_LOGIN_URL = lambda settings: settings.CMS_ROOT_URL + '/login' + derived('FRONTEND_LOGIN_URL') + FRONTEND_LOGOUT_URL = lambda settings: settings.CMS_ROOT_URL + '/logout/' + derived('FRONTEND_LOGOUT_URL') + LOGOUT_REDIRECT_URL = reverse_lazy('home') + ####################### Plugin Settings ########################## # This is at the bottom because it is going to load more settings after base settings are loaded diff --git a/cms/templates/login_page.html b/cms/templates/login_page.html new file mode 100644 index 000000000000..b22ac096063c --- /dev/null +++ b/cms/templates/login_page.html @@ -0,0 +1,50 @@ +<%namespace name='static' file='/static_content.html'/> +<%page expression_filter="h"/> +<%inherit file="base.html" /> +<%def name="online_help_token()"><% return "login" %> +<%! +from django.utils.translation import ugettext as _ +from django.urls import reverse +%> +<%block name="title">${_("Sign In")} +<%block name="bodyclass">not-signedin view-signin + +<%block name="content"> + +
+
+
+

${_("Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}

+
+
+
+
+ %if show_login_error_message: +
+ ${_("Email or password is incorrect. Please ensure that you are a course staff in order to use Studio.")} +
+ %endif +
+ +
    +
  1. + + +
  2. + +
  3. + + + ${_("Forgot password?")} +
  4. +
+
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/cms/urls.py b/cms/urls.py index f15f21df967d..f61b066319f6 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -300,3 +300,8 @@ urlpatterns += ( url(r'^hijack/', include('hijack.urls')), ) + +if settings.FEATURES.get('STUDIO_LOCAL_LOGIN'): + urlpatterns += [ + url(r'', include('cms.djangoapps.appsembler.urls')) + ]