'use strict'; const constants = require('../constants'); const requests = require('../requests'); const {JwtTokenValidatorUAA} = require('../validator') // use environment variable DEBUG with value 'xssec:*' for trace/error messages var debug = require('debug'); var debugTrace = debug('xssec:securitycontext'); var debugError = debug('xssec:securitycontext'); debugError.log = console.error.bind(console); debugTrace.log = console.log.bind(console); module.exports.SecurityContext = function(config, configArr) { var userInfo = { logonName: '', givenName: '', familyName: '', email: '' }; const xsappname = config.xsappname; var token; var scopes; var samlToken; var clientId; var subaccountid; var zid; var subdomain = null; var origin = null; var userAttributes; var additionalAuthAttributes; var serviceinstanceid = null; var grantType; var expirationDate; var tokenInfo = null; var isForeignMode = false; this.getConfigType = function () { return "UAA"; } function ifNotClientCredentialsToken(functionName, value) { if (grantType === constants.GRANTTYPE_CLIENTCREDENTIAL) { var errorString = '\nCall to ' + functionName + ' not allowed with a token of grant type ' + constants.GRANTTYPE_CLIENTCREDENTIAL + '.'; debugTrace(errorString); return null; } return value; } this.getSubaccountId = function () { return subaccountid; }; this.getZoneId = function () { return zid; }; this.getAppTID = function () { return zid; }; this.getSubdomain = function () { return subdomain; }; this.getClientId = function () { return clientId; }; this.getExpirationDate = function () { return expirationDate; }; this.getOrigin = function () { return origin; }; this.getLogonName = function () { return ifNotClientCredentialsToken('SecurityContext.getLogonName', userInfo.logonName); }; this.getGivenName = function () { return ifNotClientCredentialsToken('SecurityContext.getGivenName', userInfo.givenName); }; this.getFamilyName = function () { return ifNotClientCredentialsToken('SecurityContext.getFamilyName', userInfo.familyName); }; this.getEmail = function () { return ifNotClientCredentialsToken('SecurityContext.getEmail', userInfo.email); }; this.getUserName = function () { if (grantType === constants.GRANTTYPE_CLIENTCREDENTIAL) { return `client/${clientId}`; } else { return this.getUniquePrincipalName(origin, userInfo.logonName); } }; this.getUniquePrincipalName = function (origin, logonName) { if (!ifNotClientCredentialsToken('SecurityContext.getUniquePrincipalName', true)) { return null; } if (!origin) { debugTrace('Origin claim not set in JWT. Cannot create unique user name. Returning null.'); return null; } if (!logonName) { debugTrace('User login name claim not set in JWT. Cannot create unique user name. Returning null.'); return null; } if (origin.includes('/')) { debugTrace('Illegal \'/\' character detected in origin claim of JWT. Cannot create unique user name. Retuning null.'); return null; } return `user/${origin}/${logonName}`; }; this.getHdbToken = function () { if (userAttributes && isForeignMode) { debugTrace('\nThe SecurityContext has been initialized with an access token of a\n' + 'foreign OAuth Client Id and/or Identity Zone. Furthermore, the \n' + 'access token contains attributes. Due to the fact that we want to\n' + 'restrict attribute access to the application that provided the \n' + 'attributes, the getHdbToken function does not return a valid token.\n'); return null; } return samlToken ? samlToken : this.getAppToken(); }; this.getAppToken = function () { return token; }; this.getTokenInfo = function () { return tokenInfo; } this.getAttributes = function () { if (!ifNotClientCredentialsToken('SecurityContext.getAttribute', true)) { return null; } if (!userAttributes) { debugTrace('\nThe access token contains no user attributes.\n'); return null; } if (isForeignMode) { debugTrace('\nThe SecurityContext has been initialized with an access token of a\n' + 'foreign OAuth Client Id and/or Identity Zone. Furthermore, the \n' + 'access token contains attributes. Due to the fact that we want to\n' + 'restrict attribute access to the application that provided the \n' + 'attributes, the getAttribute function does not return any attributes.\n'); return null; } return userAttributes; } this.getAttribute = function (name) { const attributes = this.getAttributes(); if (!attributes) return null; if (!name) { debugTrace('\nInvalid attribute name (may not be null, empty, or undefined).'); return null; } if (!attributes[name]) { debugTrace('\nNo attribute "' + name + '" found for user "' + this.getLogonName() + '".'); return null; } return attributes[name]; }; this.getAdditionalAuthAttribute = function (name) { if (!additionalAuthAttributes) { debugTrace('\nThe access token contains no additional authentication attributes.\n'); return null; } if (!name) { debugTrace('\nInvalid attribute name (may not be null, empty, or undefined).'); return null; } if (!additionalAuthAttributes[name]) { debugTrace('\nNo attribute "' + name + '" found as additional authentication attribute.'); return null; } return additionalAuthAttributes[name]; }; this.getCloneServiceInstanceId = function () { return serviceinstanceid; }; this.isInForeignMode = function () { return isForeignMode; }; this.hasAttributes = function () { return ifNotClientCredentialsToken('SecurityContext.hasAttributes', userAttributes ? true : false); }; this.checkLocalScope = function (scope) { if (!scope || !scopes) { return false; } var scopeName = xsappname + '.' + scope; return scopes.indexOf(scopeName) !== -1; }; this.getGrantType = function () { return grantType; }; this.checkScope = function (scope) { if (!scope || !scopes) { return false; } if (scope.substring(0, constants.XSAPPNAMEPREFIX.length) === constants.XSAPPNAMEPREFIX) { scope = scope.replace(constants.XSAPPNAMEPREFIX, xsappname + '.'); } return scopes.indexOf(scope) !== -1; }; this.checkFollowingInstanceScope = function (scope) { if (!scope || !scopes) { return false; } const payload = this.getTokenInfo() ? this.getTokenInfo().getPayload() : null; const cID = payload ? payload["client_id"] : ""; if(cID.indexOf('sb-') != 0) { return false; } const appId = cID.substring(3); if(!appId.indexOf("|") === -1) { return false; } const scopeToSearch = appId + "." + scope; return scopes.indexOf(scopeToSearch) !== -1; } function cleanUpUserAttributes(attr) { for (var n in attr) { return attr; } return null; } this.requestToken = function (serviceCredentials, type, additionalAttributes, cb) { if (type === constants.TYPE_USER_TOKEN) { return requests.requestUserToken(this.getAppToken(), serviceCredentials, additionalAttributes, null, this.getSubdomain(), cb); } else if (type === constants.TYPE_CLIENT_CREDENTIALS_TOKEN) { return requests.requestClientCredentialsToken(this.getSubdomain(), serviceCredentials, additionalAttributes, cb); } else { return cb(new Error('Invalid grant type.')); } }; function fillContext(encodedToken, info) { tokenInfo = info; var decodedToken = tokenInfo.getPayload(); debugTrace('\nApplication received a token of grant type "' + decodedToken.grant_type + '".'); token = encodedToken; scopes = decodedToken.scope || []; zid = tokenInfo.getAppTID(); subaccountid = decodedToken["ext_attr"] ? decodedToken["ext_attr"].subaccountid : zid; if (!subaccountid) { subaccountid = zid; } clientId = tokenInfo.getClientId(); expirationDate = new Date(decodedToken.exp * 1000); grantType = decodedToken.grant_type; origin = decodedToken.origin || null; if (grantType !== constants.GRANTTYPE_CLIENTCREDENTIAL) { var givenName, familyName; if (decodedToken.ext_attr) { givenName = decodedToken.ext_attr.given_name || null; familyName = decodedToken.ext_attr.family_name || null; } userInfo.givenName = givenName || decodedToken.given_name || ''; userInfo.familyName = familyName || decodedToken.family_name || ''; userInfo.email = decodedToken.email || ''; userInfo.logonName = decodedToken.user_name || ''; debugTrace('\nObtained logon name: ' + this.getLogonName()); debugTrace('Obtained given name: ' + this.getGivenName()); debugTrace('Obtained family name: ' + this.getFamilyName()); debugTrace('Obtained email: ' + this.getEmail()); if (decodedToken.ext_cxt) { userAttributes = decodedToken.ext_cxt['xs.user.attributes'] || null; samlToken = decodedToken.ext_cxt['hdb.nameduser.saml'] || null; } else { userAttributes = decodedToken['xs.user.attributes']; samlToken = decodedToken['hdb.nameduser.saml'] || null; } userAttributes = cleanUpUserAttributes(userAttributes); if (userAttributes) { debugTrace('\nObtained attributes: ' + JSON.stringify(userAttributes, null, 4)); } else { debugTrace('\nObtained attributes: no XS user attributes in JWT token available.'); } } additionalAuthAttributes = decodedToken.az_attr || null; if (additionalAuthAttributes) { debugTrace('\nObtained additional authentication attributes: ' + JSON.stringify(additionalAuthAttributes, null, 4)); } else { debugTrace('\nObtained attributes: no additional authentication attributes in JWT token available.'); } if (decodedToken.ext_attr) { serviceinstanceid = decodedToken.ext_attr.serviceinstanceid || null; subdomain = decodedToken.ext_attr.zdn || null; } debugTrace('\nObtained subdomain: ' + this.getSubdomain()); debugTrace('Obtained serviceinstanceid: ' + this.getCloneServiceInstanceId()); debugTrace('Obtained origin: ' + this.getOrigin()); debugTrace('Obtained scopes: ' + JSON.stringify(scopes, null, 4)); } this.verifyToken = function (encodedToken, attributes, cb) { const validator = new JwtTokenValidatorUAA(configArr, config, attributes); validator.validateToken(encodedToken, function (err, tokenInfo) { if (err) { try { cb(err, null, tokenInfo); } catch(e) { debugError("xssec: Unhandled Exception in Callback"); debugError(e); } return; } isForeignMode = validator.isForeignMode(); //Token is now validated. So just fill local variables fillContext.call(this, encodedToken, tokenInfo); try { cb(null, this, tokenInfo); } catch(e) { debugError("xssec: Unhandled Exception in Callback"); debugError(e); } }.bind(this)); }; };