+
- OAuth authentication in progress...
+ Authentication in progress...
diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js
index d79889ec2..76d6b00f1 100644
--- a/app/portainer/views/auth/authController.js
+++ b/app/portainer/views/auth/authController.js
@@ -1,123 +1,71 @@
+import angular from 'angular';
import uuidv4 from 'uuid/v4';
-angular.module('portainer.app')
-.controller('AuthenticationController', ['$async', '$q', '$scope', '$state', '$stateParams', '$sanitize', 'Authentication', 'UserService', 'EndpointService', 'ExtensionService', 'StateManager', 'Notifications', 'SettingsService', 'URLHelper', 'LocalStorage',
-function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, UserService, EndpointService, ExtensionService, StateManager, Notifications, SettingsService, URLHelper, LocalStorage) {
- $scope.logo = StateManager.getState().application.logo;
+class AuthenticationController {
+ /* @ngInject */
+ constructor($async, $scope, $state, $stateParams, $sanitize, Authentication, UserService, EndpointService, ExtensionService, StateManager, Notifications, SettingsService, URLHelper, LocalStorage) {
+ this.$async = $async;
+ this.$scope = $scope;
+ this.$state = $state;
+ this.$stateParams = $stateParams;
+ this.$sanitize = $sanitize;
+ this.Authentication = Authentication;
+ this.UserService = UserService;
+ this.EndpointService = EndpointService;
+ this.ExtensionService = ExtensionService;
+ this.StateManager = StateManager;
+ this.Notifications = Notifications;
+ this.SettingsService = SettingsService;
+ this.URLHelper = URLHelper;
+ this.LocalStorage = LocalStorage;
- $scope.formValues = {
- Username: '',
- Password: ''
- };
+ this.logo = this.StateManager.getState().application.logo;
+ this.formValues = {
+ Username: '',
+ Password: ''
+ };
+ this.state = {
+ AuthenticationError: '',
+ loginInProgress: true,
+ OAuthProvider: ''
+ };
- $scope.state = {
- AuthenticationError: '',
- isInOAuthProcess: true,
- OAuthProvider: ''
- };
+ this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this);
+ this.retrievePermissionsAsync = this.retrievePermissionsAsync.bind(this);
+ this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this);
+ this.postLoginSteps = this.postLoginSteps.bind(this);
- function retrieveAndSaveEnabledExtensions() {
- return $async(retrieveAndSaveEnabledExtensionsAsync);
+ this.oAuthLoginAsync = this.oAuthLoginAsync.bind(this);
+ this.retryLoginSanitizeAsync = this.retryLoginSanitizeAsync.bind(this);
+ this.internalLoginAsync = this.internalLoginAsync.bind(this);
+
+ this.authenticateUserAsync = this.authenticateUserAsync.bind(this);
+
+ this.manageOauthCodeReturn = this.manageOauthCodeReturn.bind(this);
+ this.authEnabledFlowAsync = this.authEnabledFlowAsync.bind(this);
+ this.onInit = this.onInit.bind(this);
}
- async function retrieveAndSaveEnabledExtensionsAsync() {
- try {
- await ExtensionService.retrieveAndSaveEnabledExtensions();
- } catch (err) {
- Notifications.error('Failure', err, 'Unable to retrieve enabled extensions');
- $scope.state.loginInProgress = false;
+ /**
+ * UTILS FUNCTIONS SECTION
+ */
+
+ logout() {
+ this.Authentication.logout();
+ this.state.loginInProgress = false;
+ this.generateOAuthLoginURI();
+ }
+
+ error(err, message) {
+ this.state.AuthenticationError = message;
+ if (!err) {
+ err = {};
}
+ this.Notifications.error('Failure', err, message);
+ this.state.loginInProgress = false;
}
- function permissionsError() {
- $scope.state.permissionsError = true;
- Authentication.logout();
- $scope.state.AuthenticationError = 'Unable to retrieve permissions.'
- $scope.state.loginInProgress = false;
- return Promise.reject();
- }
-
- $scope.authenticateUser = function() {
- var username = $scope.formValues.Username;
- var password = $scope.formValues.Password;
- $scope.state.loginInProgress = true;
-
- Authentication.login(username, password)
- .then(() => Authentication.retrievePermissions().catch(permissionsError))
- .then(function success() {
- return retrieveAndSaveEnabledExtensions();
- })
- .then(function () {
- checkForEndpoints();
- })
- .catch(function error() {
- if ($scope.state.permissionsError) {
- return;
- }
- SettingsService.publicSettings()
- .then(function success(settings) {
- if (settings.AuthenticationMethod === 1) {
- return Authentication.login($sanitize(username), $sanitize(password));
- }
- return $q.reject();
- })
- .then(function success() {
- return retrieveAndSaveEnabledExtensions();
- })
- .then(function() {
- $state.go('portainer.updatePassword');
- })
- .catch(function error() {
- $scope.state.AuthenticationError = 'Invalid credentials';
- $scope.state.loginInProgress = false;
- });
- });
- };
-
- function unauthenticatedFlow() {
- EndpointService.endpoints(0, 100)
- .then(function success(endpoints) {
- if (endpoints.value.length === 0) {
- $state.go('portainer.init.endpoint');
- } else {
- $state.go('portainer.home');
- }
- })
- .catch(function error(err) {
- Notifications.error('Failure', err, 'Unable to retrieve endpoints');
- });
- }
-
- function authenticatedFlow() {
- UserService.administratorExists()
- .then(function success(exists) {
- if (!exists) {
- $state.go('portainer.init.admin');
- }
- })
- .catch(function error(err) {
- Notifications.error('Failure', err, 'Unable to verify administrator account existence');
- });
- }
-
- function checkForEndpoints() {
- EndpointService.endpoints(0, 100)
- .then(function success(data) {
- var endpoints = data.value;
-
- if (endpoints.length === 0 && Authentication.isAdmin()) {
- $state.go('portainer.init.endpoint');
- } else {
- $state.go('portainer.home');
- }
- })
- .catch(function error(err) {
- Notifications.error('Failure', err, 'Unable to retrieve endpoints');
- $scope.state.loginInProgress = false;
- });
- }
-
- function determineOauthProvider(LoginURI) {
+ determineOauthProvider(LoginURI) {
if (LoginURI.indexOf('login.microsoftonline.com') !== -1) {
return 'Microsoft';
}
@@ -130,70 +78,199 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us
return 'OAuth';
}
- function generateState() {
+ generateState() {
const uuid = uuidv4();
- LocalStorage.storeLoginStateUUID(uuid);
+ this.LocalStorage.storeLoginStateUUID(uuid);
return '&state=' + uuid;
}
- function hasValidState(state) {
- const savedUUID = LocalStorage.getLoginStateUUID();
- return savedUUID === state;
+ generateOAuthLoginURI() {
+ this.OAuthLoginURI = this.state.OAuthLoginURI + this.generateState();
}
- function initView() {
- SettingsService.publicSettings()
- .then(function success(settings) {
- $scope.AuthenticationMethod = settings.AuthenticationMethod;
- $scope.state.OAuthProvider = determineOauthProvider(settings.OAuthLoginURI);
- $scope.OAuthLoginURI = settings.OAuthLoginURI + generateState();
- });
+ hasValidState(state) {
+ const savedUUID = this.LocalStorage.getLoginStateUUID();
+ return savedUUID && state && savedUUID === state;
+ }
- if ($stateParams.logout || $stateParams.error) {
- Authentication.logout();
- $scope.state.AuthenticationError = $stateParams.error;
- $scope.state.isInOAuthProcess = false;
- return;
- }
+ /**
+ * END UTILS FUNCTIONS SECTION
+ */
- if (Authentication.isAuthenticated()) {
- $state.go('portainer.home');
- }
+ /**
+ * POST LOGIN STEPS SECTION
+ */
- var authenticationEnabled = $scope.applicationState.application.authentication;
- if (!authenticationEnabled) {
- unauthenticatedFlow();
- } else {
- authenticatedFlow();
- }
-
- const code = URLHelper.getParameter('code');
- const state = URLHelper.getParameter('state');
- if (code && hasValidState(state)) {
- oAuthLogin(code);
- } else {
- $scope.state.isInOAuthProcess = false;
+ async retrievePermissionsAsync() {
+ try {
+ await this.Authentication.retrievePermissions();
+ } catch (err) {
+ this.state.permissionsError = true;
+ this.logout();
+ this.error(err, 'Unable to retrieve permissions.');
}
}
- function oAuthLogin(code) {
- return Authentication.OAuthLogin(code)
- .then(() => Authentication.retrievePermissions().catch(permissionsError))
- .then(function success() {
- return retrieveAndSaveEnabledExtensions();
- })
- .then(function() {
- URLHelper.cleanParameters();
- })
- .catch(function error() {
- if ($scope.state.permissionsError) {
+ async retrieveAndSaveEnabledExtensionsAsync() {
+ try {
+ await this.ExtensionService.retrieveAndSaveEnabledExtensions();
+ } catch (err) {
+ this.error(err, 'Unable to retrieve enabled extensions');
+ }
+ }
+
+ async checkForEndpointsAsync(noAuth) {
+ try {
+ const endpoints = await this.EndpointService.endpoints(0, 1);
+ const isAdmin = noAuth || this.Authentication.isAdmin();
+
+ if (endpoints.value.length === 0 && isAdmin) {
+ return this.$state.go('portainer.init.endpoint');
+ } else {
+ return this.$state.go('portainer.home');
+ }
+ } catch (err) {
+ this.error(err, 'Unable to retrieve endpoints');
+ }
+ }
+
+ async postLoginSteps() {
+ await this.retrievePermissionsAsync();
+ await this.retrieveAndSaveEnabledExtensionsAsync();
+ await this.checkForEndpointsAsync(false);
+ }
+ /**
+ * END POST LOGIN STEPS SECTION
+ */
+
+ /**
+ * LOGIN METHODS SECTION
+ */
+
+ async oAuthLoginAsync(code) {
+ try {
+ await this.Authentication.OAuthLogin(code);
+ this.URLHelper.cleanParameters();
+ } catch (err) {
+ this.error(err, 'Unable to login via OAuth');
+ }
+ }
+
+ async retryLoginSanitizeAsync(username, password) {
+ try {
+ await this.internalLoginAsync(this.$sanitize(username), this.$sanitize(password));
+ this.$state.go('portainer.updatePassword');
+ } catch (err) {
+ this.error(err, 'Invalid credentials');
+ }
+ }
+
+ async internalLoginAsync(username, password) {
+ await this.Authentication.login(username, password);
+ await this.postLoginSteps();
+ }
+
+ /**
+ * END LOGIN METHODS SECTION
+ */
+
+ /**
+ * AUTHENTICATE USER SECTION
+ */
+
+ async authenticateUserAsync() {
+ try {
+ var username = this.formValues.Username;
+ var password = this.formValues.Password;
+ this.state.loginInProgress = true;
+ await this.internalLoginAsync(username, password);
+ } catch (err) {
+ if (this.state.permissionsError) {
return;
}
- $scope.state.AuthenticationError = 'Unable to login via OAuth';
- $scope.state.isInOAuthProcess = false;
- });
+ // This login retry is necessary to avoid conflicts with databases
+ // containing users created before Portainer 1.19.2
+ // See https://github.com/portainer/portainer/issues/2199 for more info
+ await this.retryLoginSanitizeAsync(username, password);
+ }
}
+ authenticateUser() {
+ return this.$async(this.authenticateUserAsync)
+ }
- initView();
-}]);
+ /**
+ * END AUTHENTICATE USER SECTION
+ */
+
+ /**
+ * ON INIT SECTION
+ */
+ async manageOauthCodeReturn(code, state) {
+ if (this.hasValidState(state)) {
+ await this.oAuthLoginAsync(code);
+ } else {
+ this.error(null, 'Invalid OAuth state, try again.');
+ }
+ }
+
+ async authEnabledFlowAsync() {
+ try {
+ const exists = await this.UserService.administratorExists();
+ if (!exists) {
+ this.$state.go('portainer.init.admin');
+ }
+ } catch (err) {
+ this.error(err, 'Unable to verify administrator account existence')
+ }
+ }
+
+ async onInit() {
+ try {
+ const settings = await this.SettingsService.publicSettings();
+ this.AuthenticationMethod = settings.AuthenticationMethod;
+ this.state.OAuthProvider = this.determineOauthProvider(settings.OAuthLoginURI);
+ this.state.OAuthLoginURI = settings.OAuthLoginURI;
+
+ const code = this.URLHelper.getParameter('code');
+ const state = this.URLHelper.getParameter('state');
+ if (code && state) {
+ await this.manageOauthCodeReturn(code, state);
+ this.generateOAuthLoginURI();
+ return;
+ }
+ this.generateOAuthLoginURI();
+
+ if (this.$stateParams.logout || this.$stateParams.error) {
+ this.logout();
+ this.state.AuthenticationError = this.$stateParams.error;
+ return;
+ }
+
+ if (this.Authentication.isAuthenticated()) {
+ await this.postLoginSteps();
+ }
+ this.state.loginInProgress = false;
+
+ const authenticationEnabled = this.$scope.applicationState.application.authentication;
+ if (!authenticationEnabled) {
+ await this.checkForEndpointsAsync(true);
+ } else {
+ await this.authEnabledFlowAsync();
+ }
+ } catch (err) {
+ this.Notifications.error('Failure', err, 'Unable to retrieve public settings');
+ }
+ }
+
+ $onInit() {
+ return this.$async(this.onInit);
+ }
+
+ /**
+ * END ON INIT SECTION
+ */
+}
+
+export default AuthenticationController;
+angular.module('portainer.app').controller('AuthenticationController', AuthenticationController);