@attivio/suit
Version:
Attivio SUIT, the Search UI Toolkit, is a library for creating search clients for searching the Attivio platform.
541 lines (470 loc) • 19.1 kB
JavaScript
'use strict';
exports.__esModule = true;
exports.default = undefined;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _class, _temp;
var _md = require('crypto-js/md5');
var _md2 = _interopRequireDefault(_md);
var _FetchUtils = require('./FetchUtils');
var _FetchUtils2 = _interopRequireDefault(_FetchUtils);
var _ObjectUtils = require('./ObjectUtils');
var _ObjectUtils2 = _interopRequireDefault(_ObjectUtils);
var _StringUtils = require('./StringUtils');
var _StringUtils2 = _interopRequireDefault(_StringUtils);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Utility class for handling auhentication in SUIT-based applications. Also handles
* configuration of application including the configuration used for components
* wrapped with the Configurable higher-order component.
*/
var AuthUtils = (_temp = _class = function () {
function AuthUtils() {
_classCallCheck(this, AuthUtils);
}
/**
* Called by the application to pass in configuration to the
* library's utility and API classes.
*
* @param users the contents of the users.xml file, converted to JavaScript objects
* @param config the contents of the configuration.properties.js file with any
* maps converted already
* @param simpleValidation if set to true, then a lot of the validation
* specific to search applications won't be done
* @returns a configuraiton error message, if one occurred
*/
AuthUtils.configure = function configure(users, config) {
var simpleValidation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var mappifiedConfig = AuthUtils.mappify(config);
var configError = AuthUtils.validateConfiguration(mappifiedConfig, simpleValidation);
if (configError) {
return configError;
}
if (mappifiedConfig.ALL.authType === 'XML') {
// Only validate users if the auth type is XML
var usersError = AuthUtils.validateUsers(users);
if (usersError) {
return usersError;
}
}
AuthUtils.users = users;
AuthUtils.config = mappifiedConfig;
return null;
};
/** Go through the passed in object and conver any nested objects to JavaScript
* Map objects if and only if their name starts with a lowercase letter. Thus
* orig.ALL and orig.Masthead won't be converted but orig.ALL.entityColors
* will.
*/
/**
* The special role representing a top-level admin, granting full permission.
*/
AuthUtils.mappify = function mappify(orig) {
var copy = {};
Object.entries(orig).forEach(function (entry) {
var key = entry[0],
value = entry[1];
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null && (!Array.isArray(value) || value instanceof Map)) {
// If the value is an object that's not null or an Array or a Map, convert it
if (key.charAt(0) === key.charAt(0).toLowerCase()) {
copy[key] = _ObjectUtils2.default.toMap(value);
} else {
// recurse on non-final objects
copy[key] = AuthUtils.mappify(value);
}
} else {
// Just set it as is
copy[key] = value;
}
});
return copy;
};
/**
* Logs the currently logged-in user out.
*/
AuthUtils.logout = function logout() {
// Remove the user we added to session storage
sessionStorage.removeItem(AuthUtils.USER_KEY);
// If it's just XML authentication, removing saved user (above) is good enough.
// Otherwise, we need to also tell the server
if (AuthUtils.config.authType !== 'XML') {
// The servlet mimics the node's login endpoint with the logout action to log out of the SAML IdP
// so regardless of how we're authenticated, we just need to access that to log out.
// But first, remove the session cookies
var baseUri = AuthUtils.getConfig().ALL.baseUri;
document.cookie = 'SessionId=; Path=' + baseUri + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
document.cookie = 'JSESSIONID=; Path=' + baseUri + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
var headers = new Headers({
Accept: 'application/json',
'Content-Type': 'application/json'
});
var params = {
method: 'GET',
headers: headers,
credentials: 'include',
mode: 'cors'
};
var fetchRequest = new Request('login?action=logout', params);
fetch(fetchRequest).then(function () /* response: Response */{
var loggedOutUri = baseUri + '?action=loggedout';
// let loggedOutUri = baseUri;
// if (AuthUtils.getConfig() && AuthUtils.getConfig().ALL && AuthUtils.getConfig().ALL.authType === 'XML') {
// // If we're doing XML authentication, go to the login page with the "We've logged you out" flag set
// }
setTimeout(function () {
document.location.assign(loggedOutUri);
}, 100);
}).catch(function (error) {
console.warn('Failed to log out:', error);
});
}
};
/**
* Implementation of the obfuscation algorithm from Jetty.
*/
AuthUtils.obfuscate = function obfuscate(orig) {
var buf = [];
var bytes = orig.split('').map(function (c) {
return c.charCodeAt(0);
});
bytes.forEach(function (b1, i) {
var b2 = bytes[orig.length - (i + 1)];
var i1 = 127 + b1 + b2;
var i2 = 127 + b1 - b2;
var i0 = i1 * 256 + i2;
var newChar = i0.toString(36);
switch (newChar.length) {
case 1:
buf.push('000' + newChar);
break;
case 2:
buf.push('00' + newChar);
break;
case 3:
buf.push('0' + newChar);
break;
default:
buf.push(newChar);
break;
}
});
return buf.join('');
};
/**
* Validate a password against a hashed one.
*/
AuthUtils.passwordMatches = function passwordMatches(comp, compTo) {
if (compTo.startsWith('OBF:')) {
var remainder = compTo.substring(4);
var obfuscatedComp = AuthUtils.obfuscate(comp);
return obfuscatedComp === remainder;
} else if (compTo.startsWith('MD5:')) {
var _remainder = compTo.substring(4);
var md5Comp = (0, _md2.default)(comp).toString();
return md5Comp === _remainder;
}
return comp === compTo;
};
/**
* Find the user info for a given user name.
* This is only applicable in the case of XML authentication.
*/
AuthUtils.findUser = function findUser(username) {
if (this.config.ALL.authType === 'XML') {
if (Array.isArray(AuthUtils.users.principals.user)) {
return AuthUtils.users.principals.user.find(function (testUser) {
return testUser.$.id === username;
});
}
// This will happen if there's only use user...
if (AuthUtils.users.principals.user.$.id === username) {
return AuthUtils.users.principals.user;
}
}
return null;
};
/**
* Attempt to log the specified user in. If an error
* occurs logging in, then it is returned. Otherwise
* this returns null. The log in is valid for the timeout
* set in the authentication configuration.
*/
AuthUtils.login = function login(username, password) {
if (!AuthUtils.config || !AuthUtils.config.ALL) {
return null;
}
if (AuthUtils.config.ALL.authType === 'NONE') {
return null;
}
if (AuthUtils.config.ALL.authType === 'XML') {
var userObject = AuthUtils.findUser(username);
if (userObject) {
if (AuthUtils.passwordMatches(password, userObject.$.password)) {
sessionStorage.setItem(AuthUtils.USER_KEY, JSON.stringify(userObject));
return null;
}
}
return new Error('Invalid log-in credentials.');
}
return null;
};
/**
* Save the currently logged-in user's info into session storage for easy access.
*/
AuthUtils.saveLoggedInUser = function saveLoggedInUser(userInfo) {
if (userInfo) {
var userInfoCopy = JSON.parse(JSON.stringify(userInfo));
sessionStorage.setItem(AuthUtils.USER_KEY, JSON.stringify(userInfoCopy));
} else {
sessionStorage.removeItem(AuthUtils.USER_KEY);
}
};
/**
* Check whether the user has a particular permission.
*/
AuthUtils.hasRole = function hasRole(user, role) {
if (AuthUtils.config && AuthUtils.config.ALL && AuthUtils.config.ALL.authType === 'NONE') {
// check if the user has inherited or been directly assigned the provided role
if (user.roles && (user.roles.includes(role) ||
// The admin role is equivalent to all roles
user.roles.includes(AuthUtils.ADMIN_ROLE))) {
return true;
}
}
return false;
};
/**
* Check whether there is a user currently logged in.
* Optionally with the provided role
*/
AuthUtils.isLoggedIn = function isLoggedIn(role) {
if (!AuthUtils.config || !AuthUtils.config.ALL) {
return false;
}
var user = AuthUtils.getSavedUser();
if (user) {
if (role) {
return AuthUtils.hasRole(user, role);
}
return true;
}
return false;
};
/**
* Get the full info object for a logged-in user we already know about.
* If no user is logged in or if the logged-in user's login has expired,
* then this method returns null.
*/
AuthUtils.getSavedUser = function getSavedUser() {
var userObjectJson = sessionStorage.getItem(AuthUtils.USER_KEY);
if (userObjectJson) {
var userObject = JSON.parse(userObjectJson);
return userObject;
}
return null;
};
/**
* Get the full info object for the logged-in user and call the passed-in
* callback function with this info. If no user is logged in, the callback
* is passed null.
*
* @param callback a function which takes the user info as a parameter
*/
AuthUtils.getLoggedInUserInfo = function getLoggedInUserInfo(callback) {
var userObject = AuthUtils.getSavedUser();
if (userObject) {
callback(userObject);
} else if (AuthUtils.config.ALL.authType === 'SAML' || AuthUtils.config.ALL.authType === 'NONE') {
// If the authentication is done on the front-end, we shouldn't
// ever get here because if there's no saved user, then
// no one is logged in yet...
var fetchResponseCallback = function fetchResponseCallback(userInfo, error) {
if (userInfo) {
AuthUtils.saveLoggedInUser(userInfo);
}
if (error) {
console.error('Got an error retrieving the current user\'s details.', error);
}
callback(userInfo);
};
_FetchUtils2.default.fetch(AuthUtils.config.ALL.baseUri + '/rest/serverDetailsApi/user', null, fetchResponseCallback, 'GET', 'Got an error retrieving the current user\u2019s details.');
} else {
// If we're doing our own authentication, and nobody is logged in, pass null to the callback
callback(null);
}
};
AuthUtils.getLoggedInUserId = function getLoggedInUserId() {
var userInfo = AuthUtils.getSavedUser();
if (userInfo) {
if (userInfo.$ && userInfo.$.id) {
// Special case for XML-based user authentication
return userInfo.$.id;
}
if (userInfo.userId) {
return userInfo.userId;
}
}
return '';
};
/**
* Get the user name to display given the user info passed in.
*/
AuthUtils.getUserName = function getUserName(userInfo) {
if (userInfo) {
if (userInfo.$) {
// Special case for XML-based user authentication
if (userInfo.$.name && userInfo.$.name.length > 0) {
return userInfo.$.name;
}
return userInfo.$.id;
}
if (userInfo.fullName) {
return userInfo.fullName;
}
if (userInfo.firstName && userInfo.lastName) {
return userInfo.firstName + ' ' + userInfo.lastName;
}
if (userInfo.firstName) {
return userInfo.firstName;
}
if (userInfo.lastName) {
return userInfo.lastName;
}
if (userInfo.userId) {
return userInfo.userId;
}
}
return '';
};
/**
* Validate the users object to make sure it won't cause us any
* grief. Return null if it's good, or an error message otherwise.
* This should only be called if auth type is 'XML'
*/
AuthUtils.validateUsers = function validateUsers(users) {
if (!users) {
return 'The users.xml file was not properly loaded.';
}
if (!users.principals) {
return 'The users.xml file is invalid; it must contain an outer <principals> element.';
}
if (!users.principals.user) {
return 'The users.xml file is invalid; it must contain at least one <user> definition.';
}
if (Array.isArray(users.principals.user)) {
for (var i = 0; i < users.principals.user.length; i += 1) {
if (!users.principals.user[i].$ || !_StringUtils2.default.notEmpty(users.principals.user[i].$.id)) {
return 'The users.xml file is invalid; the user at position ' + i + ' is missing an "id" attribute.';
}
}
} else if (!users.principals.user.$ || !_StringUtils2.default.notEmpty(users.principals.user.$.id)) {
return 'The users.xml file is invalid; the user is missing an "id" attribute.';
}
return null;
};
/**
* Validate the configuration object to make sure it won't cause us any
* grief. Return null if it's good, or an error message otherwise.
*/
AuthUtils.validateConfiguration = function validateConfiguration(config) {
var simpleValidation = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!config) {
return 'The configuration object must be specified.';
}
if (!config.ALL) {
return 'The configuration object is missing the \'ALL\' value.';
}
if (config.ALL.baseUri === null || typeof config.ALL.baseUri === 'undefined') {
// Note that baseUri can be the empty string, as in the case of running inside a node
return 'The configuration object is missing the \'ALL.baseUri\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.basename)) {
return 'The configuration object is missing the \'ALL.basename\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.authType)) {
return 'The configuration object is missing the \'ALL.authType\' value.';
}
if (config.ALL.authType !== 'XML' && config.ALL.authType !== 'SAML' && config.ALL.authType !== 'NONE') {
return 'The configuration object has an invalid value for \'ALL.authType\': it must be \'XML,\' \'SAML,\' or \'NONE\' but it is \'' + config.ALL.authType + '.\''; // eslint-disable-line max-len
}
if (config.ALL.authType === 'XML' && !config.ALL.loginPage) {
return 'The configuration is missing the \'All.loginPage\' value whiich is required when authentication is set to XML.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.defaultRealm)) {
return 'The configuration object is missing the \'ALL.defaultRealm\' value.';
}
if (!simpleValidation) {
if (!config.ALL.entityFields) {
return 'The configuration object is missing the \'ALL.entityFields\' value.';
}
if (!(config.ALL.entityFields instanceof Map)) {
return 'The configuration object\'s \'ALL.entityFields\' value should be a Map.';
}
if (!config.ALL.entityColors) {
return 'The configuration object is missing the \'ALL.entityColors\' value.';
}
if (!(config.ALL.entityColors instanceof Map)) {
return 'The configuration object\'s \'ALL.entityColors\' value should be a Map.';
}
if (!config.ALL.fields) {
return 'The configuration object is missing the \'ALL.fields\' value.';
}
if (!Array.isArray(config.ALL.fields)) {
return 'The configuration object\'s \'ALL.fields\' value should be an array with at least one value.';
}
if (config.ALL.fields.length < 1) {
return 'The configuration object\'s \'ALL.fields\' value should be an array with at least one value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.title)) {
return 'The configuration object is missing the \'ALL.title\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.uri)) {
return 'The configuration object is missing the \'ALL.uri\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.table)) {
return 'The configuration object is missing the \'ALL.table\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.latitude)) {
return 'The configuration object is missing the \'ALL.latitude\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.longitude)) {
return 'The configuration object is missing the \'ALL.longitude\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.mimetype)) {
return 'The configuration object is missing the \'ALL.mimetype\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.sourcePath)) {
return 'The configuration object is missing the \'ALL.sourcePath\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.previewImageUri)) {
return 'The configuration object is missing the \'ALL.previewImageUri\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.thumbnailImageUri)) {
return 'The configuration object is missing the \'ALL.thumbnailImageUri\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.moreLikeThisQuery)) {
return 'The configuration object is missing the \'ALL.moreLikeThisQuery\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.teaser)) {
return 'The configuration object is missing the \'ALL.teaser\' value.';
}
if (!_StringUtils2.default.notEmpty(config.ALL.text)) {
return 'The configuration object is missing the \'ALL.text\' value.';
}
}
return null;
};
AuthUtils.getEntityColors = function getEntityColors() {
if (this.config && this.config.ALL && this.config.ALL.entityColors) {
return this.config.ALL.entityColors;
}
// If it's not configured, return an empty map.
return new Map();
};
AuthUtils.getConfig = function getConfig() {
return this.config;
};
return AuthUtils;
}(), _class.USER_KEY = 'suit-user', _class.ADMIN_ROLE = 'AIE_Administrator', _temp);
exports.default = AuthUtils;
module.exports = exports['default'];