From a8d34b46fb73f1de550e97399dedd26631916abb Mon Sep 17 00:00:00 2001 From: Graham Mainwaring Date: Tue, 10 Dec 2019 13:09:46 -0500 Subject: [PATCH 01/11] Add setting for configurable login redirect URL --- awx/api/conf.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/awx/api/conf.py b/awx/api/conf.py index 688aad162f..b2cb2a641c 100644 --- a/awx/api/conf.py +++ b/awx/api/conf.py @@ -62,3 +62,14 @@ register( category=_('Authentication'), category_slug='authentication', ) +register( + 'LOGIN_REDIRECT_OVERRIDE', + field_class=fields.CharField, + allow_blank=True, + required=False, + label=_('Login redirect override URL'), + help_text=_('URL to which unauthorized users will be redirected to log in. ' + 'If blank, users will be sent to the Tower login page.'), + category=_('Authentication'), + category_slug='authentication', +) From 7700050d1029e84532a41fe8ad40af31cceed65d Mon Sep 17 00:00:00 2001 From: Graham Mainwaring Date: Tue, 10 Dec 2019 14:57:03 -0500 Subject: [PATCH 02/11] Add default for LOGIN_REDIRECT_OVERRIDE --- awx/settings/defaults.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index d105828225..08aa4f73f6 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -373,6 +373,10 @@ TACACSPLUS_AUTH_PROTOCOL = 'ascii' # Note: This setting may be overridden by database settings. AUTH_BASIC_ENABLED = True +# If set, specifies a URL that unauthenticated users will be redirected to +# when trying to access a UI page that requries authentication. +LOGIN_REDIRECT_OVERRIDE = None + # If set, serve only minified JS for UI. USE_MINIFIED_JS = False From f467e26842cae49bf799087d79aebb4c45afac0c Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 10 Dec 2019 15:07:12 -0500 Subject: [PATCH 03/11] Adds login redirect override field to the System (Misc System) Settings interface --- .../forms/system-form/sub-forms/system-misc.form.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js index 18d3b1c8be..636d4de7f3 100644 --- a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js +++ b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js @@ -40,6 +40,10 @@ export default ['i18n', function(i18n) { ALLOW_OAUTH2_FOR_EXTERNAL_USERS: { type: 'toggleSwitch', }, + LOGIN_REDIRECT_OVERRIDE: { + type: 'text', + reset: 'LOGIN_REDIRECT_OVERRIDE' + }, ACCESS_TOKEN_EXPIRE_SECONDS: { type: 'text', reset: 'ACCESS_TOKEN_EXPIRE_SECONDS' From 2b111c81df5cc6ede7fc3cea80e1a267e55b4098 Mon Sep 17 00:00:00 2001 From: Graham Mainwaring Date: Tue, 10 Dec 2019 15:29:21 -0500 Subject: [PATCH 04/11] Add /login convenience URL --- awx/main/views.py | 5 ++++- awx/urls.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/awx/main/views.py b/awx/main/views.py index 1947bd001c..bc791976db 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -4,7 +4,7 @@ import json # Django -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render from django.utils.html import format_html from django.utils.translation import ugettext_lazy as _ @@ -97,3 +97,6 @@ def handle_csp_violation(request): logger = logging.getLogger('awx') logger.error(json.loads(request.body)) return HttpResponse(content=None) + +def handle_login_redirect(request): + return HttpResponseRedirect("/#/login") diff --git a/awx/urls.py b/awx/urls.py index 970047151d..ba0f0ee421 100644 --- a/awx/urls.py +++ b/awx/urls.py @@ -9,6 +9,7 @@ from awx.main.views import ( handle_404, handle_500, handle_csp_violation, + handle_login_redirect, ) @@ -22,6 +23,7 @@ urlpatterns = [ url(r'^(?:api/)?404.html$', handle_404), url(r'^(?:api/)?500.html$', handle_500), url(r'^csp-violation/', handle_csp_violation), + url(r'^login/', handle_login_redirect), ] if settings.SETTINGS_MODULE == 'awx.settings.development': From 9c9496a683a2e8d490f220822dfff2bc633401e3 Mon Sep 17 00:00:00 2001 From: Graham Mainwaring Date: Tue, 10 Dec 2019 15:46:56 -0500 Subject: [PATCH 05/11] Expose login redirect URL in unauthenticated /api view --- awx/api/views/root.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 91f6b62149..e4e0657652 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -60,6 +60,7 @@ class ApiRootView(APIView): data['oauth2'] = drf_reverse('api:oauth_authorization_root_view') data['custom_logo'] = settings.CUSTOM_LOGO data['custom_login_info'] = settings.CUSTOM_LOGIN_INFO + data['login_redirect_override'] = settings.LOGIN_REDIRECT_OVERRIDE return Response(data) From 181421a2ee6191eea51192faeabb7dd2b5c66274 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 11 Dec 2019 09:19:38 -0500 Subject: [PATCH 06/11] Adds logic to redirect unauthenticated user if LOGIN_REDIRECT_OVERRIDE is set as long as the user is not navigating to /login or /#/login. Also redirects on logout if LOGIN_REDIRECT_OVERRIDE is set. --- awx/ui/client/src/app.js | 14 ++++++++++++-- awx/ui/client/src/login/logout.route.js | 6 +++++- .../src/shared/load-config/load-config.factory.js | 10 ++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 54e05c4a1e..e6902339e2 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -375,7 +375,13 @@ angular if (!/^\/(login|logout)/.test($location.path())) { $rootScope.preAuthUrl = $location.path(); } - $location.path('/login'); + if ($location.path() !== '/login') { + if (global.$AnsibleConfig.login_redirect_override) { + window.location.replace(global.$AnsibleConfig.login_redirect_override); + } else { + $location.path('/login'); + } + } } else { var lastUser = $cookies.getObject('current_user'), timestammp = Store('sessionTime'); @@ -383,7 +389,11 @@ angular var stime = timestammp[lastUser.id].time, now = new Date().getTime(); if ((stime - now) <= 0) { - $location.path('/login'); + if (global.$AnsibleConfig.login_redirect_override) { + window.location.replace(global.$AnsibleConfig.login_redirect_override); + } else { + $location.path('/login'); + } } } // If browser refresh, set the user_is_superuser value diff --git a/awx/ui/client/src/login/logout.route.js b/awx/ui/client/src/login/logout.route.js index 47da767ec5..cf5c228a30 100644 --- a/awx/ui/client/src/login/logout.route.js +++ b/awx/ui/client/src/login/logout.route.js @@ -11,7 +11,11 @@ export default { route: '/logout', controller: ['Authorization', '$state', function(Authorization, $state) { Authorization.logout().then( () =>{ - $state.go('signIn'); + if (global.$AnsibleConfig.login_redirect_override) { + window.location.replace(global.$AnsibleConfig.login_redirect_override); + } else { + $state.go('signIn'); + } }); }], diff --git a/awx/ui/client/src/shared/load-config/load-config.factory.js b/awx/ui/client/src/shared/load-config/load-config.factory.js index ca12f1739b..38a2d8b3b7 100644 --- a/awx/ui/client/src/shared/load-config/load-config.factory.js +++ b/awx/ui/client/src/shared/load-config/load-config.factory.js @@ -2,7 +2,6 @@ export default function LoadConfig($log, $rootScope, $http, Store) { return function() { - var configSettings = {}; var configInit = function() { @@ -10,12 +9,11 @@ export default if ($rootScope.loginConfig) { $rootScope.loginConfig.resolve('config loaded'); } + global.$AnsibleConfig = configSettings; + Store('AnsibleConfig', global.$AnsibleConfig); $rootScope.$emit('ConfigReady'); // Load new hardcoded settings from above - - global.$AnsibleConfig = configSettings; - Store('AnsibleConfig', global.$AnsibleConfig); $rootScope.$emit('LoadConfig'); }; @@ -39,6 +37,10 @@ export default configSettings.custom_login_info = false; } + if (data.login_redirect_override) { + configSettings.login_redirect_override = data.login_redirect_override; + } + configInit(); }).catch(({error}) => { From 794ce96b17323e202f15c3d3e664004948723978 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 11 Dec 2019 10:56:46 -0500 Subject: [PATCH 07/11] Reverts changes to logout logic. We don't want to redirect to an override url if the user explicitly logs out. --- awx/ui/client/src/login/logout.route.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/awx/ui/client/src/login/logout.route.js b/awx/ui/client/src/login/logout.route.js index cf5c228a30..47da767ec5 100644 --- a/awx/ui/client/src/login/logout.route.js +++ b/awx/ui/client/src/login/logout.route.js @@ -11,11 +11,7 @@ export default { route: '/logout', controller: ['Authorization', '$state', function(Authorization, $state) { Authorization.logout().then( () =>{ - if (global.$AnsibleConfig.login_redirect_override) { - window.location.replace(global.$AnsibleConfig.login_redirect_override); - } else { - $state.go('signIn'); - } + $state.go('signIn'); }); }], From 1d9ce6cc15573f78b200c942008e32d44ebc9aa2 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 11 Dec 2019 11:19:55 -0500 Subject: [PATCH 08/11] Moves config request out to block of code that gets executed before the app is bootstrapped. This should allow us to redirect to the override url before the app begins to render, improving the UX. --- awx/ui/client/src/app.js | 8 +-- awx/ui/client/src/app.start.js | 23 ++++++- .../shared/load-config/load-config.factory.js | 69 +++++++------------ 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e6902339e2..071db41067 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -375,13 +375,7 @@ angular if (!/^\/(login|logout)/.test($location.path())) { $rootScope.preAuthUrl = $location.path(); } - if ($location.path() !== '/login') { - if (global.$AnsibleConfig.login_redirect_override) { - window.location.replace(global.$AnsibleConfig.login_redirect_override); - } else { - $location.path('/login'); - } - } + $location.path('/login'); } else { var lastUser = $cookies.getObject('current_user'), timestammp = Store('sessionTime'); diff --git a/awx/ui/client/src/app.start.js b/awx/ui/client/src/app.start.js index 44f8113567..61c8ac5bb4 100644 --- a/awx/ui/client/src/app.start.js +++ b/awx/ui/client/src/app.start.js @@ -15,7 +15,9 @@ function bootstrap (callback) { angular.module('I18N').constant('LOCALE', locale); } - angular.element(document).ready(() => callback()); + fetchConfig((config) => { + angular.element(document).ready(() => callback()); + }); }); } @@ -49,6 +51,25 @@ function fetchLocaleStrings (callback) { request.fail(() => callback({ code: DEFAULT_LOCALE })); } +function fetchConfig (callback) { + const request = $.ajax(`/api`); + + request.done(res => { + angular.module('awApp').constant('ConfigSettings', res); + if (res.login_redirect_override) { + if (!document.cookie.split(';').filter((item) => item.includes('userLoggedIn=true')).length && !window.location.href.includes('/#/login')) { + window.location.replace(res.login_redirect_override); + } else { + callback(); + } + } else { + callback(); + } + }); + + request.fail(() => callback()); +} + /** * Grabs the language off of navigator for browser compatibility. * If the language isn't set, then it falls back to the DEFAULT_LOCALE. The diff --git a/awx/ui/client/src/shared/load-config/load-config.factory.js b/awx/ui/client/src/shared/load-config/load-config.factory.js index 38a2d8b3b7..c1adfeee8f 100644 --- a/awx/ui/client/src/shared/load-config/load-config.factory.js +++ b/awx/ui/client/src/shared/load-config/load-config.factory.js @@ -1,57 +1,40 @@ export default - function LoadConfig($log, $rootScope, $http, Store) { + function LoadConfig($rootScope, Store, ConfigSettings) { return function() { var configSettings = {}; - var configInit = function() { - // Auto-resolving what used to be found when attempting to load local_setting.json - if ($rootScope.loginConfig) { - $rootScope.loginConfig.resolve('config loaded'); - } - global.$AnsibleConfig = configSettings; - Store('AnsibleConfig', global.$AnsibleConfig); - $rootScope.$emit('ConfigReady'); + if(ConfigSettings.custom_logo) { + configSettings.custom_logo = true; + $rootScope.custom_logo = ConfigSettings.custom_logo; + } else { + configSettings.custom_logo = false; + } - // Load new hardcoded settings from above - $rootScope.$emit('LoadConfig'); - }; + if(ConfigSettings.custom_login_info) { + configSettings.custom_login_info = ConfigSettings.custom_login_info; + $rootScope.custom_login_info = ConfigSettings.custom_login_info; + } else { + configSettings.custom_login_info = false; + } - // Retrieve the custom logo information - update configSettings from above - $http({ - method: 'GET', - url: '/api/', - }) - .then(function({data}) { - if(data.custom_logo) { - configSettings.custom_logo = true; - $rootScope.custom_logo = data.custom_logo; - } else { - configSettings.custom_logo = false; - } + if (ConfigSettings.login_redirect_override) { + configSettings.login_redirect_override = ConfigSettings.login_redirect_override; + } - if(data.custom_login_info) { - configSettings.custom_login_info = data.custom_login_info; - $rootScope.custom_login_info = data.custom_login_info; - } else { - configSettings.custom_login_info = false; - } + // Auto-resolving what used to be found when attempting to load local_setting.json + if ($rootScope.loginConfig) { + $rootScope.loginConfig.resolve('config loaded'); + } + global.$AnsibleConfig = configSettings; + Store('AnsibleConfig', global.$AnsibleConfig); + $rootScope.$emit('ConfigReady'); - if (data.login_redirect_override) { - configSettings.login_redirect_override = data.login_redirect_override; - } - - configInit(); - - }).catch(({error}) => { - $log.debug(error); - configInit(); - }); + // Load new hardcoded settings from above + $rootScope.$emit('LoadConfig'); }; } LoadConfig.$inject = - [ '$log', '$rootScope', '$http', - 'Store' - ]; + [ '$rootScope', 'Store', 'ConfigSettings' ]; From efbff24528aebe8533ee588a767cbc4a38a58c06 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 11 Dec 2019 13:33:54 -0500 Subject: [PATCH 09/11] Adds trailing slash to /api request --- awx/ui/client/src/app.start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/app.start.js b/awx/ui/client/src/app.start.js index 61c8ac5bb4..310fae9de3 100644 --- a/awx/ui/client/src/app.start.js +++ b/awx/ui/client/src/app.start.js @@ -52,7 +52,7 @@ function fetchLocaleStrings (callback) { } function fetchConfig (callback) { - const request = $.ajax(`/api`); + const request = $.ajax('/api/'); request.done(res => { angular.module('awApp').constant('ConfigSettings', res); From 4b3d3537b418314d702f9080af0ea4b8fc155a11 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 11 Dec 2019 13:37:39 -0500 Subject: [PATCH 10/11] Fix linting error (unused var) --- awx/ui/client/src/app.start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/app.start.js b/awx/ui/client/src/app.start.js index 310fae9de3..026ee0162c 100644 --- a/awx/ui/client/src/app.start.js +++ b/awx/ui/client/src/app.start.js @@ -15,7 +15,7 @@ function bootstrap (callback) { angular.module('I18N').constant('LOCALE', locale); } - fetchConfig((config) => { + fetchConfig(() => { angular.element(document).ready(() => callback()); }); }); From 7ceaa9ec4af85c501867e527c7720e94a7890f40 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 11 Dec 2019 16:31:39 -0500 Subject: [PATCH 11/11] Changes redirect logic slightly to lean on a global var to store the config response rather than a constant on the awApp module. This should allow us to avoid test changes. --- awx/ui/client/src/app.js | 2 ++ awx/ui/client/src/app.start.js | 2 +- .../shared/load-config/load-config.factory.js | 18 +++++++++--------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 071db41067..5ba84826d4 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -3,6 +3,8 @@ global.$AnsibleConfig = null; // Provided via Webpack DefinePlugin in webpack.config.js global.$ENV = {}; +global.$ConfigResponse = {}; + var urlPrefix; if ($basePath) { diff --git a/awx/ui/client/src/app.start.js b/awx/ui/client/src/app.start.js index 026ee0162c..ef0c4a8fd5 100644 --- a/awx/ui/client/src/app.start.js +++ b/awx/ui/client/src/app.start.js @@ -55,7 +55,7 @@ function fetchConfig (callback) { const request = $.ajax('/api/'); request.done(res => { - angular.module('awApp').constant('ConfigSettings', res); + global.$ConfigResponse = res; if (res.login_redirect_override) { if (!document.cookie.split(';').filter((item) => item.includes('userLoggedIn=true')).length && !window.location.href.includes('/#/login')) { window.location.replace(res.login_redirect_override); diff --git a/awx/ui/client/src/shared/load-config/load-config.factory.js b/awx/ui/client/src/shared/load-config/load-config.factory.js index c1adfeee8f..56916cd7a4 100644 --- a/awx/ui/client/src/shared/load-config/load-config.factory.js +++ b/awx/ui/client/src/shared/load-config/load-config.factory.js @@ -1,25 +1,25 @@ export default - function LoadConfig($rootScope, Store, ConfigSettings) { + function LoadConfig($rootScope, Store) { return function() { var configSettings = {}; - if(ConfigSettings.custom_logo) { + if(global.$ConfigResponse.custom_logo) { configSettings.custom_logo = true; - $rootScope.custom_logo = ConfigSettings.custom_logo; + $rootScope.custom_logo = global.$ConfigResponse.custom_logo; } else { configSettings.custom_logo = false; } - if(ConfigSettings.custom_login_info) { - configSettings.custom_login_info = ConfigSettings.custom_login_info; - $rootScope.custom_login_info = ConfigSettings.custom_login_info; + if(global.$ConfigResponse.custom_login_info) { + configSettings.custom_login_info = global.$ConfigResponse.custom_login_info; + $rootScope.custom_login_info = global.$ConfigResponse.custom_login_info; } else { configSettings.custom_login_info = false; } - if (ConfigSettings.login_redirect_override) { - configSettings.login_redirect_override = ConfigSettings.login_redirect_override; + if (global.$ConfigResponse.login_redirect_override) { + configSettings.login_redirect_override = global.$ConfigResponse.login_redirect_override; } // Auto-resolving what used to be found when attempting to load local_setting.json @@ -37,4 +37,4 @@ export default } LoadConfig.$inject = - [ '$rootScope', 'Store', 'ConfigSettings' ]; + [ '$rootScope', 'Store' ];