blob: 53b10c605d851ad3cb3b1ec11645e1bab724cdc5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A service for authenticating a user against the REST API.
*
* This service broadcasts two events on $rootScope depending on the result of
* authentication operations: 'guacLogin' if authentication was successful and
* a new token was created, and 'guacLogout' if an existing token is being
* destroyed or replaced. Both events will be passed the related token as their
* sole parameter.
*
* If a login attempt results in an existing token being replaced, 'guacLogout'
* will be broadcast first for the token being replaced, followed by
* 'guacLogin' for the new token.
*
* Failed logins may also result in guacInsufficientCredentials or
* guacInvalidCredentials events, if the provided credentials were rejected for
* being insufficient or invalid respectively. Both events will be provided
* the set of parameters originally given to authenticate() and the error that
* rejected the credentials. The Error object provided will contain set of
* expected credentials returned by the REST endpoint. This set of credentials
* will be in the form of a Field array.
*/
angular.module('auth').factory('authenticationService', ['$injector',
function authenticationService($injector) {
// Required types
var AuthenticationResult = $injector.get('AuthenticationResult');
var Error = $injector.get('Error');
// Required services
var $rootScope = $injector.get('$rootScope');
var localStorageService = $injector.get('localStorageService');
var requestService = $injector.get('requestService');
var service = {};
/**
* The most recent authentication result, or null if no authentication
* result is cached.
*
* @type AuthenticationResult
*/
var cachedResult = null;
/**
* The unique identifier of the local storage key which stores the result
* of the last authentication attempt.
*
* @type String
*/
var AUTH_STORAGE_KEY = 'GUAC_AUTH';
/**
* Retrieves the last successful authentication result. If the user has not
* yet authenticated, the user has logged out, or the last authentication
* attempt failed, null is returned.
*
* @returns {AuthenticationResult}
* The last successful authentication result, or null if the user is not
* currently authenticated.
*/
var getAuthenticationResult = function getAuthenticationResult() {
// Use cached result, if any
if (cachedResult)
return cachedResult;
// Return explicit null if no auth data is currently stored
var data = localStorageService.getItem(AUTH_STORAGE_KEY);
if (!data)
return null;
// Update cache and return retrieved auth result
return (cachedResult = new AuthenticationResult(data));
};
/**
* Stores the given authentication result for future retrieval. The given
* result MUST be the result of the most recent authentication attempt.
*
* @param {AuthenticationResult} data
* The last successful authentication result, or null if the last
* authentication attempt failed.
*/
var setAuthenticationResult = function setAuthenticationResult(data) {
// Clear the currently-stored result if the last attempt failed
if (!data) {
cachedResult = null;
localStorageService.removeItem(AUTH_STORAGE_KEY);
}
// Otherwise store the authentication attempt directly
else {
// Always store in cache
cachedResult = data;
// Persist result past tab/window closure ONLY if not anonymous
if (data.username !== AuthenticationResult.ANONYMOUS_USERNAME)
localStorageService.setItem(AUTH_STORAGE_KEY, data);
}
};
/**
* Clears the stored authentication result, if any. If no authentication
* result is currently stored, this function has no effect.
*/
var clearAuthenticationResult = function clearAuthenticationResult() {
setAuthenticationResult(null);
};
/**
* Makes a request to authenticate a user using the token REST API endpoint
* and given arbitrary parameters, returning a promise that succeeds only
* if the authentication operation was successful. The resulting
* authentication data can be retrieved later via getCurrentToken() or
* getCurrentUsername().
*
* The provided parameters can be virtually any object, as each property
* will be sent as an HTTP parameter in the authentication request.
* Standard parameters include "username" for the user's username,
* "password" for the user's associated password, and "token" for the
* auth token to check/update.
*
* If a token is provided, it will be reused if possible.
*
* @param {Object} parameters
* Arbitrary parameters to authenticate with.
*
* @returns {Promise}
* A promise which succeeds only if the login operation was successful.
*/
service.authenticate = function authenticate(parameters) {
// Attempt authentication
return requestService({
method: 'POST',
url: 'api/tokens',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: $.param(parameters)
})
// If authentication succeeds, handle received auth data
.then(function authenticationSuccessful(data) {
var currentToken = service.getCurrentToken();
// If a new token was received, ensure the old token is invalidated,
// if any, and notify listeners of the new token
if (data.authToken !== currentToken) {
// If an old token existed, request that the token be revoked
if (currentToken) {
service.logout().catch(angular.noop)
}
// Notify of login and new token
setAuthenticationResult(new AuthenticationResult(data));
$rootScope.$broadcast('guacLogin', data.authToken);
}
// Update cached authentication result, even if the token remains
// the same
else
setAuthenticationResult(new AuthenticationResult(data));
// Authentication was successful
return data;
})
// If authentication fails, propogate failure to returned promise
['catch'](requestService.createErrorCallback(function authenticationFailed(error) {
// Request credentials if provided credentials were invalid
if (error.type === Error.Type.INVALID_CREDENTIALS)
$rootScope.$broadcast('guacInvalidCredentials', parameters, error);
// Request more credentials if provided credentials were not enough
else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS)
$rootScope.$broadcast('guacInsufficientCredentials', parameters, error);
// Authentication failed
throw error;
}));
};
/**
* Makes a request to update the current auth token, if any, using the
* token REST API endpoint. If the optional parameters object is provided,
* its properties will be included as parameters in the update request.
* This function returns a promise that succeeds only if the authentication
* operation was successful. The resulting authentication data can be
* retrieved later via getCurrentToken() or getCurrentUsername().
*
* If there is no current auth token, this function behaves identically to
* authenticate(), and makes a general authentication request.
*
* @param {Object} [parameters]
* Arbitrary parameters to authenticate with, if any.
*
* @returns {Promise}
* A promise which succeeds only if the login operation was successful.
*/
service.updateCurrentToken = function updateCurrentToken(parameters) {
// HTTP parameters for the authentication request
var httpParameters = {};
// Add token parameter if current token is known
var token = service.getCurrentToken();
if (token)
httpParameters.token = service.getCurrentToken();
// Add any additional parameters
if (parameters)
angular.extend(httpParameters, parameters);
// Make the request
return service.authenticate(httpParameters);
};
/**
* Makes a request to authenticate a user using the token REST API endpoint
* with a username and password, ignoring any currently-stored token,
* returning a promise that succeeds only if the login operation was
* successful. The resulting authentication data can be retrieved later
* via getCurrentToken() or getCurrentUsername().
*
* @param {String} username
* The username to log in with.
*
* @param {String} password
* The password to log in with.
*
* @returns {Promise}
* A promise which succeeds only if the login operation was successful.
*/
service.login = function login(username, password) {
return service.authenticate({
username: username,
password: password
});
};
/**
* Makes a request to logout a user using the login REST API endpoint,
* returning a promise succeeds only if the logout operation was
* successful.
*
* @returns {Promise}
* A promise which succeeds only if the logout operation was
* successful.
*/
service.logout = function logout() {
// Clear authentication data
var token = service.getCurrentToken();
clearAuthenticationResult();
// Notify listeners that a token is being destroyed
$rootScope.$broadcast('guacLogout', token);
// Delete old token
return requestService({
method: 'DELETE',
url: 'api/tokens/' + token
});
};
/**
* Returns whether the current user has authenticated anonymously. An
* anonymous user is denoted by the identifier reserved by the Guacamole
* extension API for anonymous users (the empty string).
*
* @returns {Boolean}
* true if the current user has authenticated anonymously, false
* otherwise.
*/
service.isAnonymous = function isAnonymous() {
return service.getCurrentUsername() === '';
};
/**
* Returns the username of the current user. If the current user is not
* logged in, this value may not be valid.
*
* @returns {String}
* The username of the current user, or null if no authentication data
* is present.
*/
service.getCurrentUsername = function getCurrentUsername() {
// Return username, if available
var authData = getAuthenticationResult();
if (authData)
return authData.username;
// No auth data present
return null;
};
/**
* Returns the auth token associated with the current user. If the current
* user is not logged in, this token may not be valid.
*
* @returns {String}
* The auth token associated with the current user, or null if no
* authentication data is present.
*/
service.getCurrentToken = function getCurrentToken() {
// Return auth token, if available
var authData = getAuthenticationResult();
if (authData)
return authData.authToken;
// No auth data present
return null;
};
/**
* Returns the identifier of the data source that authenticated the current
* user. If the current user is not logged in, this value may not be valid.
*
* @returns {String}
* The identifier of the data source that authenticated the current
* user, or null if no authentication data is present.
*/
service.getDataSource = function getDataSource() {
// Return data source, if available
var authData = getAuthenticationResult();
if (authData)
return authData.dataSource;
// No auth data present
return null;
};
/**
* Returns the identifiers of all data sources available to the current
* user. If the current user is not logged in, this value may not be valid.
*
* @returns {String[]}
* The identifiers of all data sources availble to the current user,
* or an empty array if no authentication data is present.
*/
service.getAvailableDataSources = function getAvailableDataSources() {
// Return data sources, if available
var authData = getAuthenticationResult();
if (authData)
return authData.availableDataSources;
// No auth data present
return [];
};
return service;
}]);