@sap/xssec
Advanced tools
Comparing version 2.2.4 to 3.0.0
# Change Log | ||
All notable changes to this project will be documented in this file. | ||
## 3.0.0 - 2020-??-?? | ||
- Replace grant type user_token in method requestToken (TYPE_USER_TOKEN) in favor of urn:ietf:params:oauth:grant-type:jwt-bearer | ||
- Remove obsolete method getToken (use getHdbToken or getAppToken)) | ||
- Remove obsolete method requestTokenForClient (use requestToken) | ||
- Remove obsolete method getIdentityZone (getSubaccountId) | ||
## 2.2.5 - 2020-02-28 | ||
- Update to node-jwt version 1.6.6 | ||
## 2.2.4 - 2019-08-14 | ||
- Support for API methods getUserName and getUniquePrincipalName | ||
- Support for API methods getUserName and getUniquePrincipalName | ||
@@ -8,0 +19,0 @@ ## 2.2.3 - 2019-08-07 |
@@ -6,5 +6,4 @@ 'use strict'; | ||
exports.createSecurityContextCC = xssec.createSecurityContextCC; | ||
exports.createSecurityContext = xssec.createSecurityContext; | ||
exports.constants = require('./constants'); | ||
exports.JWTStrategy = passportStrategy.JWTStrategy; |
1125
lib/xssec.js
'use strict'; | ||
const constants = require('./constants'); | ||
const request = require('request'); | ||
const url = require('url'); | ||
var constants = require('./constants'); | ||
var request = require('request'); | ||
var url = require('url'); | ||
const requests = require('./requests'); | ||
@@ -12,518 +13,202 @@ // use environment variable DEBUG with value 'xssec:*' for trace/error messages | ||
var keycache = require('./keycache'); | ||
const JwtTokenValidator = require('./validator').JwtTokenValidator; | ||
const VerificationKey = require('./verificationkey'); | ||
// Note: the keycache is initialized currently with the default size defined in constants | ||
// Consider making this configurable for the application, e.g. via xssecurity.json | ||
// or (probably worse) via environment variables. | ||
// Similarly, the keycache uses the default expiration time for cache entries as | ||
// defined in constants. Also here, consider making this configurable. | ||
var keyCache = new keycache.KeyCache(constants.KEYCACHE_DEFAULT_CACHE_SIZE, constants.KEYCACHE_DEFAULT_CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES); | ||
debugError.log = console.error.bind(console); | ||
debugTrace.log = console.log.bind(console); | ||
exports.createSecurityContext = createSecurityContext; | ||
function throw500(errorString) { | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
} | ||
function createSecurityContext(token, config, cb) { | ||
var securityContext; | ||
//For Backward compatibilty | ||
exports.createSecurityContext = function (token, config, cb) { | ||
try { | ||
securityContext = new SecurityContext(token, config); | ||
securityContext.init(done); | ||
var securityContext = new SecurityContext(config); | ||
securityContext.verifyToken(token, cb); | ||
} catch (e) { | ||
cb(e); | ||
} | ||
function done(err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
cb(null, securityContext); | ||
} | ||
} | ||
function SecurityContext(token, config) { | ||
this.token = token; | ||
this.config = config; | ||
this.xsappname = ''; | ||
this.isForeignMode = false; | ||
this.tokenContainsAttributes = false; | ||
this.tokenContainsAdditionalAuthAttributes = false; | ||
this.userInfo = { | ||
logonName : '', | ||
givenName : '', | ||
familyName : '', | ||
email : '' | ||
function SecurityContext(config) { | ||
var userInfo = { | ||
logonName: '', | ||
givenName: '', | ||
familyName: '', | ||
email: '' | ||
}; | ||
this.scopes = []; | ||
this.samlToken = ''; | ||
this.clientId = ''; | ||
this.identityZone = ''; | ||
this.subdomain = null; | ||
this.origin = null; | ||
this.userAttributes = ''; | ||
this.additionalAuthAttributes = ''; | ||
this.serviceinstanceid = null; | ||
this.grantType = ''; | ||
// sapssoext | ||
var ssojwt; | ||
if (process.sapnodejwtlib) { | ||
this.ssojwt = process.sapnodejwtlib; | ||
} else { | ||
try { | ||
var jwt = require('@sap/node-jwt'); | ||
this.ssojwt = process.sapnodejwtlib = new jwt(""); | ||
} catch (e) { | ||
var error = new Error('No jwt.node available. Error: ' + e.message ); | ||
error.statuscode = 500; //No jwt.node | ||
throw error; | ||
} | ||
} | ||
var token; | ||
var xsappname; | ||
var scopes; | ||
var samlToken; | ||
var clientId; | ||
var identityZone; | ||
var subdomain = null; | ||
var origin = null; | ||
var userAttributes; | ||
var additionalAuthAttributes; | ||
var serviceinstanceid = null; | ||
var grantType; | ||
var expirationDate; | ||
// validate config input | ||
debugTrace('\nConfiguration (note: clientsecret might be contained but is not traced): ' + JSON.stringify(config, credentialsReplacer, 4)); | ||
if (!this.ssojwt || this.ssojwt.getLibraryVersion() === -1) { | ||
debugTrace('\nSSO library path: ' + process.env['SSOEXT_LIB']); | ||
var error = new Error('JWT validation library could not be loaded. Used ' | ||
+ process.env['SSOEXT_LIB']); | ||
error.statuscode = 500; //lib not found | ||
throw error; | ||
} | ||
var isForeignMode = false; | ||
// validate config input | ||
if (!token || token.length === 0) { | ||
var error = new Error('Invalid token (empty).'); | ||
error.statuscode = 401; //(Token Empty/No Token) | ||
throw error; | ||
} | ||
if (!config) { | ||
var error = new Error('Invalid config (missing).'); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
} | ||
if (!this.config.clientid) { | ||
var error = new Error('Invalid config: Missing clientid.'); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
} | ||
if (!this.config.clientsecret) { | ||
var error = new Error('Invalid config: Missing clientsecret.'); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
} | ||
if (!this.config.url) { | ||
var error = new Error('Invalid config: Missing url.'); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
} | ||
if (!this.config.xsappname) { | ||
if (!process.env.XSAPPNAME) { | ||
var errorString = 'Invalid config: Missing xsappname.\n' | ||
function validateXSAPPNAME() { | ||
if (!config.xsappname) { | ||
if (!process.env.XSAPPNAME) { | ||
var errorString = 'Invalid config: Missing xsappname.\n' | ||
+ 'The application name needs to be defined in xs-security.json.'; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
} else { | ||
this.xsappname = process.env.XSAPPNAME; | ||
debugTrace('\nXSAPPNAME defined in manifest.yml (legacy).\n' | ||
return throw500(errorString); | ||
} else { | ||
xsappname = process.env.XSAPPNAME; | ||
debugTrace('\nXSAPPNAME defined in manifest.yml (legacy).\n' | ||
+ 'You should switch to defining xsappname in xs-security.json.'); | ||
} | ||
} else { | ||
if (!process.env.XSAPPNAME) { | ||
this.xsappname = this.config.xsappname; | ||
} | ||
} else { | ||
if (process.env.XSAPPNAME == this.config.xsappname) { | ||
this.xsappname = process.env.XSAPPNAME; | ||
debugTrace('\nThe application name is defined both in the manifest.yml (legacy) \n' | ||
if (!process.env.XSAPPNAME) { | ||
xsappname = config.xsappname; | ||
} else { | ||
if (process.env.XSAPPNAME == config.xsappname) { | ||
xsappname = process.env.XSAPPNAME; | ||
debugTrace('\nThe application name is defined both in the manifest.yml (legacy) \n' | ||
+ 'as well as in xs-security.json. Remove it in manifest.yml.'); | ||
} else { | ||
var errorString = 'Invalid config: Ambiguous xsappname.\n' | ||
} else { | ||
var errorString = 'Invalid config: Ambiguous xsappname.\n' | ||
+ 'The application name is defined with different values in the manifest.yml (legacy)\n' | ||
+ 'as well as in xs-security.json. Remove it in manifest.yml.'; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 500; //500 (Invalid config) | ||
throw error; | ||
return throw500(errorString); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
SecurityContext.prototype.init = function init(cb) { | ||
var self = this; | ||
function ctor() { | ||
// validate config input | ||
debugTrace('\nConfiguration (note: clientsecret might be contained but is not traced): ' + | ||
JSON.stringify(config, function(key, value) { | ||
return key === 'clientsecret' ? undefined : value; | ||
}, 4)); | ||
offlineValidation( | ||
self.token, | ||
self.config, | ||
self.ssojwt, | ||
function(error, result) { | ||
if (error) { | ||
debugError(error.message); | ||
debugError(error.stack); | ||
return cb(error, null); | ||
} | ||
// validate config input | ||
if (!config) { | ||
return throw500('Invalid config (missing).'); | ||
} | ||
if (!result.cid) { | ||
var errorString = 'Client Id not contained in access token. Giving up!'; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 400; //400 (no clientID) | ||
return cb(error, null); | ||
} | ||
if (!result.zid) { | ||
var errorString = 'Identity Zone not contained in access token. Giving up!'; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 400; //400 (wrong idz) | ||
return cb(error, null); | ||
} | ||
validateXSAPPNAME(); | ||
var subscriptionsAllowed = false; | ||
if (result.cid.indexOf('!t') !== -1 || result.cid.indexOf('!b') !== -1) { | ||
subscriptionsAllowed = true; | ||
} | ||
if ((result.cid === self.config.clientid) | ||
&& (result.zid === self.config.identityzoneid || subscriptionsAllowed === true)) { | ||
// clientid and identity zone of the token must match with the values of the application | ||
// (if the token was issued for the xsuaa application or broker plan it is sufficient that the clientid matches) | ||
if (subscriptionsAllowed === false) { | ||
debugTrace('\nClient Id and Identity Zone of the access token match\n' | ||
+ 'with the current application\'s Client Id and Zone.'); | ||
} else { | ||
debugTrace('\nClient Id of the access token (XSUAA application or broker plan) matches\n' | ||
+ 'with the current application\'s Client Id.'); | ||
} | ||
self.isForeignMode = false; | ||
} else if (self.config.trustedclientidsuffix && self.config.trustedclientidsuffix.length > 0 && | ||
result.cid.substring(result.cid.length - self.config.trustedclientidsuffix.length, result.cid.length) === self.config.trustedclientidsuffix) { | ||
debugTrace('\nClient Id "' + result.cid + '" of the access token allows consumption by the Client Id "' + | ||
self.config.clientid + '" of the current application\n'); | ||
self.isForeignMode = false; | ||
} else if (process.env.SAP_JWT_TRUST_ACL) { | ||
debugTrace('\nClient Id "' | ||
+ result.cid | ||
+ '" and/or Identity Zone "' | ||
+ result.zid | ||
+ '" of the access token\n' | ||
+ 'does/do not match with the Client Id "' | ||
+ self.config.clientid | ||
+ '" and Identity Zone "' | ||
+ self.config.identityzoneid | ||
+ '"\nof the current application.\n' | ||
+ 'Validating token against JWT trust ACL (SAP_JWT_TRUST_ACL).'); | ||
var parsedACL; | ||
try { | ||
parsedACL = JSON.parse(process.env.SAP_JWT_TRUST_ACL); | ||
} catch (er) { | ||
var errorString = 'JWT trust ACL (ACL SAP_JWT_TRUST_ACL):\n' | ||
+ process.env.SAP_JWT_TRUST_ACL | ||
+ '\ncould not be parsed successfully.\n' | ||
+ 'Error: ' + er.message; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 500; //500 (Internal Server Error) | ||
return cb(error, null); | ||
} | ||
var foundMatch = false; | ||
for ( var aclEntry in parsedACL) { | ||
if (((result.cid === parsedACL[aclEntry].clientid) || ('*' === parsedACL[aclEntry].clientid)) | ||
&& ((result.zid === parsedACL[aclEntry].identityzone) || ('*' === parsedACL[aclEntry].identityzone))) { | ||
foundMatch = true; | ||
break; | ||
} | ||
} | ||
if (foundMatch) { | ||
debugTrace('\nForeign token received, but matching entry\n' | ||
+ 'in JWT trust ACL (SAP_JWT_TRUST_ACL) found.'); | ||
self.isForeignMode = true; | ||
} else { | ||
var errorString = 'Client Id "' | ||
+ result.cid | ||
+ '" and/or Identity Zone "' | ||
+ result.zid | ||
+ '" of the access token\n' | ||
+ 'does/do not match with the Client Id "' | ||
+ self.config.clientid | ||
+ '" and Identity Zone "' | ||
+ self.config.identityzoneid | ||
+ '" of the current application.\n' | ||
+ 'No match found in JWT trust ACL (SAP_JWT_TRUST_ACL):\n' | ||
+ JSON.stringify(parsedACL, null, 4); | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 403; //403 Forbidden (as no JWT trust entry) | ||
return cb(error, null); | ||
} | ||
} else { | ||
if (result.cid !== self.config.clientid) { | ||
var errorString = 'Client Id of the access token "' | ||
+ result.cid | ||
+ '" does not match with\nthe OAuth Client Id "' | ||
+ self.config.clientid | ||
+ '" of the application.\n' | ||
+ 'No JWT trust ACL (SAP_JWT_TRUST_ACL) specified in environment.'; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 403; //403 Forbidden (as client IDs do not match and no trust entry exists) | ||
return cb(error, null); | ||
} else { | ||
var errorString = 'Identity Zone of the access token "' | ||
+ result.zid | ||
+ '" does not match\nwith the Identity Zone "' | ||
+ self.config.identityzoneid | ||
+ '" of the application.\n' | ||
+ 'No JWT trust ACL (SAP_JWT_TRUST_ACL) specified in environment.'; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 403; //403 Forbidden (as identity zones do not match and no trust entry exists) | ||
return cb(error, null); | ||
} | ||
} | ||
debugTrace('\nApplication received a token of grant type "' + result.grant_type + '".'); | ||
self.identityZone = result.zid; | ||
self.clientId = result.cid; | ||
if (result.origin !== null && result.origin !== undefined) { | ||
self.origin = result.origin; | ||
} | ||
self.expirationDate = new Date(result.exp * 1000); | ||
self.grantType = result.grant_type; | ||
if (self.grantType !== constants.GRANTTYPE_CLIENTCREDENTIAL) { | ||
self.userInfo.logonName = result.user_name; // jshint ignore:line | ||
if (result.hasOwnProperty('ext_attr') && result.ext_attr.given_name !== undefined) { | ||
self.userInfo.givenName = result.ext_attr.given_name; | ||
} else { | ||
self.userInfo.givenName = result.given_name; // jshint ignore:line | ||
} | ||
if (result.hasOwnProperty('ext_attr') && result.ext_attr.family_name !== undefined) { | ||
self.userInfo.familyName = result.ext_attr.family_name; | ||
} else { | ||
self.userInfo.familyName = result.family_name; // jshint ignore:line | ||
} | ||
self.userInfo.email = result.email; | ||
debugTrace('\nObtained logon name: ' + self.userInfo.logonName); | ||
debugTrace('Obtained given name: ' + self.userInfo.givenName); | ||
debugTrace('Obtained family name: ' + self.userInfo.familyName); | ||
debugTrace('Obtained email: ' + self.userInfo.email); | ||
if (result.hasOwnProperty('ext_cxt')) { | ||
if (result.ext_cxt['hdb.nameduser.saml'] !== undefined) { | ||
self.samlToken = result.ext_cxt['hdb.nameduser.saml']; | ||
} | ||
if (result.ext_cxt['xs.user.attributes'] !== undefined) { | ||
self.userAttributes = result.ext_cxt['xs.user.attributes']; | ||
self.tokenContainsAttributes = true; | ||
debugTrace('\nObtained attributes: ' | ||
+ JSON.stringify(self.userAttributes, null, 4)); | ||
} else { | ||
self.tokenContainsAttributes = false; | ||
debugTrace('\nObtained attributes: no XS user attributes in JWT token available.'); | ||
} | ||
if (!config.clientid) { | ||
return throw500('Invalid config: Missing clientid.'); | ||
} | ||
if (!config.clientsecret) { | ||
return throw500('Invalid config: Missing clientsecret.'); | ||
} | ||
if (!config.url) { | ||
return throw500('Invalid config: Missing url.'); | ||
} | ||
} | ||
} else { | ||
self.samlToken = result['hdb.nameduser.saml']; | ||
if (result.hasOwnProperty('xs.user.attributes')) { | ||
self.userAttributes = result['xs.user.attributes']; | ||
self.tokenContainsAttributes = true; | ||
debugTrace('\nObtained attributes: ' | ||
+ JSON.stringify(self.userAttributes, null, 4)); | ||
} else { | ||
self.tokenContainsAttributes = false; | ||
debugTrace('\nObtained attributes: no XS user attributes in JWT token available.'); | ||
} | ||
} | ||
} | ||
if (result.hasOwnProperty('az_attr')) { | ||
self.additionalAuthAttributes = result['az_attr']; | ||
self.tokenContainsAdditionalAuthAttributes = true; | ||
debugTrace('\nObtained additional authentication attributes: ' | ||
+ JSON.stringify(self.additionalAuthAttributes, null, 4)); | ||
} else { | ||
self.tokenContainsAdditionalAuthAttributes = false; | ||
debugTrace('\nObtained attributes: no additional authentication attributes in JWT token available.'); | ||
} | ||
if (result.hasOwnProperty('ext_attr') && result.ext_attr.serviceinstanceid !== undefined) { | ||
self.serviceinstanceid = result.ext_attr.serviceinstanceid; | ||
} | ||
if (result.hasOwnProperty('ext_attr') && result.ext_attr.zdn !== undefined) { | ||
self.subdomain = result.ext_attr.zdn; | ||
} | ||
debugTrace('\nObtained subdomain: ' + self.subdomain); | ||
debugTrace('Obtained serviceinstanceid: ' + self.serviceinstanceid); | ||
debugTrace('Obtained origin: ' + self.origin); | ||
self.scopes = result.scope; | ||
debugTrace('Obtained scopes: ' | ||
+ JSON.stringify(self.scopes, null, 4)); | ||
cb(); | ||
}); | ||
}; | ||
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; | ||
} | ||
SecurityContext.prototype.getIdentityZone = function() { | ||
return this.identityZone; | ||
}; | ||
this.getSubaccountId = function () { | ||
return identityZone; | ||
}; | ||
SecurityContext.prototype.getSubaccountId = function() { | ||
return this.identityZone; | ||
}; | ||
this.getSubdomain = function () { | ||
return subdomain; | ||
}; | ||
SecurityContext.prototype.getSubdomain = function() { | ||
return this.subdomain; | ||
}; | ||
this.getClientId = function () { | ||
return clientId; | ||
}; | ||
SecurityContext.prototype.getClientId = function() { | ||
return this.clientId; | ||
}; | ||
this.getExpirationDate = function () { | ||
return expirationDate; | ||
}; | ||
SecurityContext.prototype.getExpirationDate = function() { | ||
return this.expirationDate; | ||
}; | ||
this.getOrigin = function () { | ||
return origin; | ||
}; | ||
SecurityContext.prototype.getOrigin = function() { | ||
return this.origin; | ||
}; | ||
this.getLogonName = function () { | ||
return ifNotClientCredentialsToken('SecurityContext.getLogonName', userInfo.logonName); | ||
}; | ||
SecurityContext.prototype.getLogonName = function() { | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.getLogonName', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
return this.userInfo.logonName; | ||
}; | ||
this.getGivenName = function () { | ||
return ifNotClientCredentialsToken('SecurityContext.getGivenName', userInfo.givenName); | ||
}; | ||
SecurityContext.prototype.getGivenName = function() { | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.getGivenName', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
return this.userInfo.givenName; | ||
}; | ||
this.getFamilyName = function () { | ||
return ifNotClientCredentialsToken('SecurityContext.getFamilyName', userInfo.familyName); | ||
}; | ||
SecurityContext.prototype.getFamilyName = function() { | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.getFamilyName', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
return this.userInfo.familyName; | ||
}; | ||
this.getEmail = function () { | ||
return ifNotClientCredentialsToken('SecurityContext.getEmail', userInfo.email); | ||
}; | ||
SecurityContext.prototype.getEmail = function() { | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.getEmail', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
return this.userInfo.email; | ||
}; | ||
this.getUserName = function () { | ||
if (grantType === constants.GRANTTYPE_CLIENTCREDENTIAL) { | ||
return `client/${clientId}`; | ||
} else { | ||
return this.getUniquePrincipalName(origin, userInfo.logonName); | ||
} | ||
}; | ||
SecurityContext.prototype.getUserName = function() { | ||
if (this.grantType === constants.GRANTTYPE_CLIENTCREDENTIAL) { | ||
return `client/${this.clientId}`; | ||
} else { | ||
return this.getUniquePrincipalName(this.origin, this.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}`; | ||
}; | ||
SecurityContext.prototype.getUniquePrincipalName = function(origin, logonName) { | ||
var errorString; | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.getUniquePrincipalName', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
if (!origin) { | ||
errorString = 'Origin claim not set in JWT. Cannot create unique user name. Returning null.'; | ||
debugTrace(errorString); | ||
return null; | ||
} | ||
if (!logonName) { | ||
errorString = 'User login name claim not set in JWT. Cannot create unique user name. Returning null.'; | ||
debugTrace(errorString); | ||
return null; | ||
} | ||
if (origin.includes('/')) { | ||
errorString = 'Illegal \'/\' character detected in origin claim of JWT. Cannot create unique user name. Retuning null.'; | ||
debugTrace(errorString); | ||
return null; | ||
} | ||
return `user/${origin}/${logonName}`; | ||
}; | ||
SecurityContext.prototype.getToken = function(namespace, name) { | ||
if (this.tokenContainsAttributes && this.isForeignMode) { | ||
debugTrace('\nThe SecurityContext has been initialized with an access token of a\n' | ||
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 getToken function does not return a valid token.\n'); | ||
return null; | ||
} | ||
if (namespace === undefined || namespace === null) { | ||
debugTrace('\nInvalid token namespace (may not be null or undefined).'); | ||
return null; | ||
} else if (namespace !== constants.SYSTEM) { | ||
debugTrace('\nNamespace "' + namespace + '" not supported.'); | ||
return null; | ||
} | ||
if (name === undefined || name === null) { | ||
debugTrace('\nInvalid token name (may not be null or undefined).'); | ||
return null; | ||
} | ||
switch (name) { | ||
case constants.JOBSCHEDULER: | ||
return this.token; | ||
case constants.HDB: | ||
if (this.samlToken === undefined || this.samlToken === null) { | ||
return this.token; | ||
} else { | ||
return this.samlToken; | ||
+ 'attributes, the getHdbToken function does not return a valid token.\n'); | ||
return null; | ||
} | ||
default: | ||
debugTrace('\nToken name "' + name + '" not supported.'); | ||
return null; | ||
} | ||
}; | ||
SecurityContext.prototype.getHdbToken = function() { | ||
return this.getToken(constants.SYSTEM, constants.HDB); | ||
}; | ||
return samlToken ? samlToken : token; | ||
}; | ||
SecurityContext.prototype.getAppToken = function() { | ||
return this.token; | ||
}; | ||
this.getAppToken = function () { | ||
return token; | ||
}; | ||
SecurityContext.prototype.requestTokenForClient = function(serviceCredentials, scopes, cb) { | ||
return requestUserToken(this, serviceCredentials, null, scopes, false, cb); | ||
}; | ||
SecurityContext.prototype.requestToken = function(serviceCredentials, type, additionalAttributes, cb) { | ||
if (type === constants.TYPE_USER_TOKEN) { | ||
return requestUserToken(this, serviceCredentials, additionalAttributes, null, true, cb); | ||
} else if (type === constants.TYPE_CLIENT_CREDENTIALS_TOKEN) { | ||
return requestClientCredentialsToken(this, serviceCredentials, additionalAttributes, cb); | ||
} else { | ||
var error = new Error('Invalid grant type.'); | ||
return cb(error, null); | ||
} | ||
}; | ||
SecurityContext.prototype.getAttribute = function(name) { | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.getAttribute', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
if (!this.tokenContainsAttributes) { | ||
debugTrace('\nThe access token contains no user attributes.\n'); | ||
return null; | ||
} | ||
if (this.isForeignMode) { | ||
debugTrace('\nThe SecurityContext has been initialized with an access token of a\n' | ||
this.getAttribute = function (name) { | ||
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' | ||
@@ -533,391 +218,159 @@ + 'access token contains attributes. Due to the fact that we want to\n' | ||
+ 'attributes, the getAttribute function does not return any attributes.\n'); | ||
return null; | ||
} | ||
if (name === undefined || name === null || name === '') { | ||
debugTrace('\nInvalid attribute name (may not be null, empty, or undefined).'); | ||
return null; | ||
} | ||
if (!this.userAttributes.hasOwnProperty(name)) { | ||
debugTrace('\nNo attribute "' + name + '" found for user "' | ||
+ this.userInfo.logonName + '".'); | ||
return null; | ||
} | ||
return this.userAttributes[name]; | ||
}; | ||
return null; | ||
} | ||
if (!name) { | ||
debugTrace('\nInvalid attribute name (may not be null, empty, or undefined).'); | ||
return null; | ||
} | ||
if (!userAttributes[name]) { | ||
debugTrace('\nNo attribute "' + name + '" found for user "' + this.getLogonName() + '".'); | ||
return null; | ||
} | ||
return userAttributes[name]; | ||
}; | ||
SecurityContext.prototype.getAdditionalAuthAttribute = function(name) { | ||
if (!this.tokenContainsAdditionalAuthAttributes) { | ||
debugTrace('\nThe access token contains no additional authentication attributes.\n'); | ||
return null; | ||
} | ||
if (name === undefined || name === null || name == '') { | ||
debugTrace('\nInvalid attribute name (may not be null, empty, or undefined).'); | ||
return null; | ||
} | ||
if (!this.additionalAuthAttributes.hasOwnProperty(name)) { | ||
debugTrace('\nNo attribute "' + name + '" found as additional authentication attribute.'); | ||
return null; | ||
} | ||
return this.additionalAuthAttributes[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]; | ||
}; | ||
SecurityContext.prototype.getCloneServiceInstanceId = function() { | ||
return this.serviceinstanceid; | ||
}; | ||
this.getCloneServiceInstanceId = function () { | ||
return serviceinstanceid; | ||
}; | ||
SecurityContext.prototype.isInForeignMode = function() { | ||
return this.isForeignMode; | ||
}; | ||
this.isInForeignMode = function () { | ||
return isForeignMode; | ||
}; | ||
SecurityContext.prototype.hasAttributes = function() { | ||
try { | ||
forbidClientCredentialsToken('SecurityContext.hasAttributes', this.grantType); | ||
} catch (e) { | ||
return null; | ||
} | ||
return this.tokenContainsAttributes; | ||
}; | ||
this.hasAttributes = function () { | ||
return ifNotClientCredentialsToken('SecurityContext.hasAttributes', userAttributes ? true : false); | ||
}; | ||
SecurityContext.prototype.checkLocalScope = function(scope) { | ||
var scopeName = this.xsappname + '.' + scope; | ||
if ((scope === null) || (scope === undefined)) { | ||
return false; | ||
} | ||
return this.scopes.indexOf(scopeName) !== -1; | ||
}; | ||
this.checkLocalScope = function (scope) { | ||
if (!scope) { | ||
return false; | ||
} | ||
var scopeName = xsappname + '.' + scope; | ||
return scopes.indexOf(scopeName) !== -1; | ||
}; | ||
SecurityContext.prototype.getGrantType = function() { | ||
return this.grantType; | ||
}; | ||
this.getGrantType = function () { | ||
return grantType; | ||
}; | ||
SecurityContext.prototype.checkScope = function(scope) { | ||
var scopeName = scope; | ||
this.checkScope = function (scope) { | ||
if (!scope) { | ||
return false; | ||
} | ||
if ((scope === null) || (scope === undefined)) { | ||
return false; | ||
} | ||
if (scopeName.substring(0, constants.XSAPPNAMEPREFIX.length) === constants.XSAPPNAMEPREFIX) { | ||
scopeName = scopeName.replace(constants.XSAPPNAMEPREFIX, this.xsappname + '.'); | ||
} | ||
return this.scopes.indexOf(scopeName) !== -1; | ||
}; | ||
function loadVerificationKey(accessToken, config, cb) { | ||
if (config.verificationkey === undefined) { | ||
var error = new Error('Error in offline validation of access token, because of missing verificationkey', null); | ||
error.statuscode = 500; //500 (missing verificationkey) | ||
return cb(error); | ||
} | ||
var invalidatedTokenHeaderJSON = null; | ||
try { | ||
var invalidatedTokenParts = accessToken.split('.'); | ||
if (invalidatedTokenParts.length !== 3) { | ||
var error = new Error('Unexpected JWT structure.', null); | ||
error.statuscode = 400; | ||
return cb(error); | ||
if (scope.substring(0, constants.XSAPPNAMEPREFIX.length) === constants.XSAPPNAMEPREFIX) { | ||
scope = scope.replace(constants.XSAPPNAMEPREFIX, xsappname + '.'); | ||
} | ||
var invalidatedTokenHeaderBuffer = Buffer.from(invalidatedTokenParts[0], 'base64'); | ||
var invalidatedTokenHeaderString = invalidatedTokenHeaderBuffer.toString('utf8'); | ||
invalidatedTokenHeaderJSON = JSON.parse(invalidatedTokenHeaderString); | ||
if (!invalidatedTokenHeaderJSON.kid || invalidatedTokenHeaderJSON.kid == 'legacy-token-key' || !invalidatedTokenHeaderJSON.jku) { | ||
return cb(null, config.verificationkey); | ||
} | ||
validateJku(invalidatedTokenHeaderJSON.jku, config.uaadomain, function(err) { | ||
if (err) { | ||
return cb(null, config.verificationkey); | ||
} | ||
keyCache.getKey(invalidatedTokenHeaderJSON.jku, invalidatedTokenHeaderJSON.kid, function(err, key) { | ||
if (err) { | ||
return cb(null, config.verificationkey); | ||
} else { | ||
return cb(null, key); | ||
} | ||
}); | ||
}); | ||
} catch (e) { | ||
e.statuscode = 403; | ||
return cb(e); | ||
} | ||
} | ||
return scopes.indexOf(scope) !== -1; | ||
}; | ||
function validateJku(jkuUrl, uaaDomain, cb) { | ||
if (uaaDomain === null || uaaDomain === undefined) { | ||
var errorString = 'Service is not properly configured in \'VCAP_SERVICES\', attribute \'uaadomain\' is missing. Use legacy-token-key.'; | ||
debugTrace('\n' + errorString); | ||
return cb(new Error(errorString)); | ||
} | ||
var tokenKeyUrl = url.parse(jkuUrl); | ||
if (tokenKeyUrl.hostname.substring(tokenKeyUrl.hostname.indexOf(uaaDomain), tokenKeyUrl.hostname.length) !== uaaDomain) { | ||
var errorString = 'JKU of the JWT token (' + jkuUrl + ') does not match with the uaa domain (' + uaaDomain + '). Use legacy-token-key.'; | ||
debugTrace('\n' + errorString); | ||
return cb(new Error(errorString)); | ||
} | ||
cb(null); | ||
} | ||
function checkTokenLocal(accessToken, verificationkey, ssojwt, cb) { | ||
var ssorc = ssojwt.loadPEM(verificationkey); | ||
if ((ssorc !== 0) && (ssorc === 9)) { | ||
debugTrace('\nSSO library path: ' + process.env['SSOEXT_LIB']); | ||
debugTrace('\nCCL library path: ' + process.env['SSF_LIB']); | ||
debugTrace('\nSSO library version: ' + ssojwt.getLibraryVersion()); | ||
debugTrace('\nSSO library code: ' + ssojwt.getErrorRC()); | ||
var error = new Error('Error in sapssoext, version : ' | ||
+ ssojwt.getLibraryVersion() + ' . Cannot load CCL from path: ' | ||
+ process.env['SSF_LIB'], null); | ||
error.statuscode = 500; //500 (lib not found) | ||
return cb(error); | ||
} | ||
ssojwt.checkToken(accessToken); | ||
if (ssojwt.getErrorDescription() !== "") { | ||
ssorc = ssojwt.getErrorRC(); | ||
debugTrace('\nSSO library path: ' + process.env['SSOEXT_LIB']); | ||
debugTrace('\nCCL library path: ' + process.env['SSF_LIB']); | ||
debugTrace('\nSSO library version: ' + ssojwt.getLibraryVersion()); | ||
debugTrace('\nSSO library code: ' + ssojwt.getErrorRC()); | ||
if ((ssorc !== 0) && (ssorc === 5)) { | ||
// verification key and JWT are not valid, no library error | ||
debugTrace('\nInvalid JWT: ' + accessToken); | ||
var error = new Error( | ||
'Invalid access token. Validation error: ' | ||
+ ssojwt.getErrorDescription(), null); | ||
error.statuscode = 403; //403 (validation error) | ||
return cb(error); | ||
this.requestToken = function (serviceCredentials, type, additionalAttributes, cb) { | ||
if (type === constants.TYPE_USER_TOKEN) { | ||
return requests.requestUserToken(this, serviceCredentials, additionalAttributes, null, true, cb); | ||
} else if (type === constants.TYPE_CLIENT_CREDENTIALS_TOKEN) { | ||
return requests.requestClientCredentialsToken(this, serviceCredentials, additionalAttributes, cb); | ||
} else { | ||
var error = new Error( | ||
'Error in offline validation of access token: ' | ||
+ ssojwt.getErrorDescription(), null); | ||
error.statuscode = 403; //403 (validation error) | ||
return cb(error); | ||
return cb(new Error('Invalid grant type.')); | ||
} | ||
} | ||
var parsedPayload = null; | ||
try { | ||
parsedPayload = JSON.parse(ssojwt.getJWPayload()); | ||
} catch (er) { | ||
var errorString = 'Access token payload could not be parsed successfully.\n' | ||
+ 'Error: ' + er.message; | ||
debugError('\n' + errorString); | ||
var error = new Error(errorString); | ||
error.statuscode = 400; //400 (parsing error) | ||
return cb(error, null); | ||
} | ||
cb(null, parsedPayload); | ||
} | ||
}; | ||
function offlineValidation(accessToken, config, ssojwt, cb) { | ||
loadVerificationKey(accessToken, config, function(err, verificationkey) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
checkTokenLocal(accessToken, verificationkey, ssojwt, cb); | ||
}); | ||
} | ||
function fillContext(encodedToken, decodedToken) { | ||
debugTrace('\nApplication received a token of grant type "' + decodedToken.grant_type + '".'); | ||
function credentialsReplacer(key, value) { | ||
if (key === 'clientsecret') { | ||
return undefined; | ||
} else { | ||
return value; | ||
} | ||
} | ||
token = encodedToken; | ||
scopes = decodedToken.scope; | ||
identityZone = decodedToken.zid; | ||
clientId = decodedToken.cid; | ||
expirationDate = new Date(decodedToken.exp * 1000); | ||
grantType = decodedToken.grant_type; | ||
function requestUserToken(securityContext, serviceCredentials, additionalAttributes, scopes, adaptSubdomain, cb) { | ||
// input validation | ||
if (!serviceCredentials) { | ||
var error = new Error('Parameter serviceCredentials is missing but mandatory.'); | ||
return cb(error, null); | ||
} | ||
if (!serviceCredentials.clientid || !serviceCredentials.clientsecret) { | ||
var error = new Error('Invalid service credentials: Missing clientid/clientsecret.'); | ||
return cb(error, null); | ||
} | ||
if (!serviceCredentials.url) { | ||
var error = new Error('Invalid service credentials: Missing url.'); | ||
return cb(error, null); | ||
} | ||
if (securityContext.checkScope('uaa.user') === false) { | ||
var error = new Error('JWT token does not include scope "uaa.user".'); | ||
return cb(error, null); | ||
} | ||
// adapt subdomain in service url, if necessary | ||
var urlWithCorrectSubdomain = serviceCredentials.url; | ||
if (adaptSubdomain === true) { | ||
var tokenSubdomain = securityContext.getSubdomain(); | ||
var tokenRequestSubdomain = null; | ||
var uaaUrl = url.parse(serviceCredentials.url); | ||
if (uaaUrl.hostname.indexOf('.') === -1) { | ||
tokenRequestSubdomain = null; | ||
} else { | ||
tokenRequestSubdomain = uaaUrl.hostname.substring(0, uaaUrl.hostname.indexOf('.')); | ||
} | ||
if (tokenSubdomain !== null && tokenRequestSubdomain != null && tokenSubdomain !== tokenRequestSubdomain) { | ||
urlWithCorrectSubdomain = uaaUrl.protocol + "//" + tokenSubdomain + uaaUrl.host.substring(uaaUrl.host.indexOf('.'), uaaUrl.host.size); | ||
} | ||
} | ||
// user token flow | ||
var options = { | ||
url : urlWithCorrectSubdomain + '/oauth/token?grant_type=user_token&response_type=token&client_id=' + serviceCredentials.clientid, | ||
headers: { Accept: 'application/json' }, | ||
auth: { | ||
bearer: securityContext.token | ||
}, | ||
timeout: 2000 | ||
}; | ||
if (scopes !== null) { | ||
options.url = options.url + "&scope=" + scopes; | ||
} | ||
if (additionalAttributes !== null) { | ||
var authorities = { "az_attr" : additionalAttributes }; | ||
options.url = options.url + "&authorities=" + encodeURIComponent(JSON.stringify(authorities)); | ||
} | ||
request.post( | ||
options, | ||
function(error, response, body) { | ||
if (error) { | ||
if (error.code === 'ETIMEDOUT' && error.connect === true) { | ||
debugError('requestToken: HTTP connection timeout.'); | ||
} | ||
debugError(error.message); | ||
debugError(error.stack); | ||
return cb(error, null); | ||
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; | ||
} | ||
if (response.statusCode === 401) { | ||
debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: user_token). Bearer token invalid, requesting client does not have grant_type=user_token or no scopes were granted.'); | ||
return cb(new Error('Call to /oauth/token was not successful (grant_type: user_token). Bearer token invalid, requesting client does not have grant_type=user_token or no scopes were granted.'), 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; | ||
} | ||
if (response.statusCode !== 200) { | ||
debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: user_token). HTTP status code: ' + response.statusCode); | ||
return cb(new Error('Call to /oauth/token was not successful (grant_type: user_token). HTTP status code: ' + response.statusCode), null); | ||
if(userAttributes) { | ||
debugTrace('\nObtained attributes: ' + JSON.stringify(userAttributes, null, 4)); | ||
} else { | ||
debugTrace('\nObtained attributes: no XS user attributes in JWT token available.'); | ||
} | ||
var json = null; | ||
try { | ||
json = JSON.parse(body); | ||
} catch (e) { | ||
return cb(e, null); | ||
} | ||
// refresh token flow | ||
options = { | ||
url : urlWithCorrectSubdomain + '/oauth/token?grant_type=refresh_token&refresh_token=' + json.refresh_token, | ||
headers: { Accept: 'application/json' }, | ||
auth: { | ||
user: serviceCredentials.clientid, | ||
pass: serviceCredentials.clientsecret | ||
}, | ||
timeout: 2000 | ||
}; | ||
request.post( | ||
options, | ||
function(error, response, body) { | ||
if (error) { | ||
if (error.code === 'ETIMEDOUT' && error.connect === true) { | ||
debugError('requestToken: HTTP connection timeout.'); | ||
} | ||
debugError(error.message); | ||
debugError(error.stack); | ||
return cb(error, null); | ||
} | ||
if (response.statusCode === 401) { | ||
debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: refresh_token). Client credentials invalid.'); | ||
return cb(new Error('Call to /oauth/token was not successful (grant_type: refresh_token). Client credentials invalid.'), null); | ||
} | ||
if (response.statusCode !== 200) { | ||
debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: refresh_token). HTTP status code: ' + response.statusCode); | ||
return cb(new Error('Call to /oauth/token was not successful (grant_type: refresh_token). HTTP status code ' + response.statusCode), null); | ||
} | ||
var json = null; | ||
try { | ||
json = JSON.parse(body); | ||
} catch (e) { | ||
return cb(e, null); | ||
} | ||
return cb(null, json.access_token) | ||
} | ||
); | ||
} | ||
); | ||
} | ||
function requestClientCredentialsToken(securityContext, serviceCredentials, additionalAttributes, cb) { | ||
// input validation | ||
if (!serviceCredentials) { | ||
var error = new Error('Parameter serviceCredentials is missing but mandatory.'); | ||
return cb(error, null); | ||
} | ||
if (!serviceCredentials.clientid || !serviceCredentials.clientsecret) { | ||
var error = new Error('Invalid service credentials: Missing clientid/clientsecret.'); | ||
return cb(error, null); | ||
} | ||
if (!serviceCredentials.url) { | ||
var error = new Error('Invalid service credentials: Missing url.'); | ||
return cb(error, null); | ||
} | ||
// adapt subdomain in service url, if necessary | ||
var urlWithCorrectSubdomain = serviceCredentials.url; | ||
var tokenSubdomain = securityContext.getSubdomain(); | ||
var tokenRequestSubdomain = null; | ||
var uaaUrl = url.parse(serviceCredentials.url); | ||
if (uaaUrl.hostname.indexOf('.') === -1) { | ||
tokenRequestSubdomain = null; | ||
} else { | ||
tokenRequestSubdomain = uaaUrl.hostname.substring(0, uaaUrl.hostname.indexOf('.')); | ||
} | ||
if (tokenSubdomain !== null && tokenRequestSubdomain != null && tokenSubdomain !== tokenRequestSubdomain) { | ||
urlWithCorrectSubdomain = uaaUrl.protocol + "//" + tokenSubdomain + uaaUrl.host.substring(uaaUrl.host.indexOf('.'), uaaUrl.host.size); | ||
} | ||
// client credentials flow | ||
var options = { | ||
url : urlWithCorrectSubdomain + '/oauth/token?grant_type=client_credentials&response_type=token', | ||
headers: { 'Accept' : 'application/json', 'Content-Type' : 'application/x-www-form-urlencoded' }, | ||
auth: { | ||
user: serviceCredentials.clientid, | ||
pass: serviceCredentials.clientsecret | ||
}, | ||
timeout: 2000 | ||
}; | ||
if (additionalAttributes !== null) { | ||
var authorities = { "az_attr" : additionalAttributes }; | ||
options.url = options.url + "&authorities=" + encodeURIComponent(JSON.stringify(authorities)); | ||
} | ||
request.post( | ||
options, | ||
function(error, response, body) { | ||
if (error) { | ||
if (error.code === 'ETIMEDOUT' && error.connect === true) { | ||
debugError('requestToken: HTTP connection timeout.'); | ||
} | ||
debugError(error.message); | ||
debugError(error.stack); | ||
return cb(error, null); | ||
} | ||
if (response.statusCode === 401) { | ||
debugTrace(body); | ||
debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: client_credentials). Client credentials invalid.'); | ||
return cb(new Error('Call to /oauth/token was not successful (grant_type: client_credentials). Client credentials invalid.'), null); | ||
} | ||
if (response.statusCode !== 200) { | ||
debugTrace('requestToken: Call to /oauth/token was not successful (grant_type: client_credentials). HTTP status code: ' + response.statusCode); | ||
return cb(new Error('Call to /oauth/token was not successful (grant_type: client_credentials). HTTP status code ' + response.statusCode), null); | ||
} | ||
var json = null; | ||
try { | ||
json = JSON.parse(body); | ||
} catch (e) { | ||
return cb(e, null); | ||
} | ||
return cb(null, json.access_token); | ||
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.'); | ||
} | ||
); | ||
}; | ||
function forbidClientCredentialsToken(functionName, grantType) { | ||
if (grantType === constants.GRANTTYPE_CLIENTCREDENTIAL) { | ||
var errorString = '\nCall to ' + functionName + ' not allowed with a token of grant type ' + constants.GRANTTYPE_CLIENTCREDENTIAL + '.'; | ||
debugTrace(errorString); | ||
throw new Error(errorString); | ||
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, cb) { | ||
var verificationKey = new VerificationKey(config); | ||
var jwtValidator = new JwtTokenValidator(verificationKey, config); | ||
//Now validate the tokens | ||
jwtValidator.validateToken(encodedToken, function(err, decodedToken) { | ||
if(err) { | ||
return cb(err); | ||
} | ||
isForeignMode = jwtValidator.isForeignMode(); | ||
//Token is now validated. So just fill local variables | ||
fillContext.call(this, encodedToken, decodedToken); | ||
cb(null, this); | ||
}.bind(this)); | ||
}; | ||
//call constructor | ||
ctor(); | ||
}; |
@@ -1,1 +0,1 @@ | ||
{"bundleDependencies":false,"dependencies":{"@sap/node-jwt":"^1.6.5","debug":"4.1.1","lru-cache":"5.1.1","request":"2.88.0","requestretry":"4.0.0","valid-url":"1.0.9"},"deprecated":false,"description":"XS Advanced Container Security API for node.js","devDependencies":{"@sap/xsenv":"^2.2.0","istanbul":"^0.4.5","jwt-decode":"^2.2.0","mocha":"^5.1.0","should":"^13.2.1"},"keywords":["xs"],"main":"./lib","name":"@sap/xssec","repository":{"type":"git","url":"ssh://git@github.wdf.sap.corp/xs2/node-xs2sec.git"},"scripts":{"prepareRelease":"npm prune --production","test":"make test"},"version":"2.2.4","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bundleDependencies":false,"dependencies":{"debug":"4.1.1","jsonwebtoken":"^8.5.1","lru-cache":"5.1.1","request":"2.88.0","requestretry":"4.0.0","valid-url":"1.0.9"},"deprecated":false,"description":"XS Advanced Container Security API for node.js","devDependencies":{"@sap/xsenv":"^2.2.0","istanbul":"^0.4.5","jwt-decode":"^2.2.0","mocha":"^5.1.0","should":"^13.2.1"},"keywords":["xs"],"main":"./lib","name":"@sap/xssec","repository":{"type":"git","url":"ssh://git@github.wdf.sap.corp/xs2/node-xs2sec.git"},"scripts":{"prepareRelease":"npm prune --production","test":"make test"},"version":"3.0.0","license":"SEE LICENSE IN developer-license-3.1.txt"} |
@@ -247,10 +247,2 @@ @sap/xssec: XS Advanced Container Security API for node.js | ||
### getToken (obsolete, use getHdbToken or getAppToken) | ||
Parameters: | ||
* `namespace` ... Tokens can eventually be used in different contexts, e.g. to access the HANA database, to access another XS2-based service such as the Job Scheduler, or even to access other applications/containers. To differentiate between these use cases, the `namespace` is used. In `lib/constants.js` we define supported namespaces (e.g. `SYSTEM`). | ||
* `name` ... The name is used to differentiate between tokens in a given namespace, e.g. `HDB` for HANA database or `JOBSCHEDULER` for the job scheduler. These names are also defined in the file `lib/constants.js`. | ||
* returns a token that can be used e.g. for contacting the HANA database. If the token, that the security context has been instantiated with, is a foreign token (meaning that the OAuth client contained in the token and the OAuth client of the current application do not match), `null` is returned instead of a token. | ||
### getAppToken | ||
@@ -273,12 +265,2 @@ | ||
### requestTokenForClient (obsolete, use requestToken instead) | ||
Requests a token with `grant_type=user_token` from another client. Prerequisite is that the requesting client has `grant_type=user_token` and that the current user token includes the scope `uaa.user`. | ||
Parameters: | ||
* `serviceCredentials` ... the credentials of the service as JSON object. The attributes `clientid`, `clientsecret` and `url` (UAA) are mandatory. | ||
* `scopes` ... comma-separated list of requested scopes for the token, e.g. `app.scope1,app.scope2`. If null, all scopes are granted. Note that $XSAPPNAME is not supported as part of the scope names. | ||
* `cb(error, token)` ... callback function | ||
### hasAttributes | ||
@@ -318,6 +300,2 @@ | ||
### getIdentityZone (obsolete, use getSubaccountId instead) | ||
* returns the identity zone that the access token has been issued for. | ||
### getSubaccountId | ||
@@ -338,1 +316,8 @@ | ||
* returns the grant type of the JWT token, e.g. `authorization_code`, `password`, `client_credentials` or `urn:ietf:params:oauth:grant-type:saml2-bearer`. | ||
## Latest published Version | ||
Use this command to check for the latest version that is published to the NPM repository: | ||
``` | ||
npm view --registry https://npm.sap.com @sap/xssec versions | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
13
5
284286
1038
320
1
4
+ Addedjsonwebtoken@^8.5.1
+ Addedbuffer-equal-constant-time@1.0.1(transitive)
+ Addedecdsa-sig-formatter@1.0.11(transitive)
+ Addedjsonwebtoken@8.5.1(transitive)
+ Addedjwa@1.4.1(transitive)
+ Addedjws@3.2.2(transitive)
+ Addedlodash.includes@4.3.0(transitive)
+ Addedlodash.isboolean@3.0.3(transitive)
+ Addedlodash.isinteger@4.0.4(transitive)
+ Addedlodash.isnumber@3.0.3(transitive)
+ Addedlodash.isplainobject@4.0.6(transitive)
+ Addedlodash.isstring@4.0.1(transitive)
+ Addedlodash.once@4.1.1(transitive)
+ Addedsemver@5.7.2(transitive)
- Removed@sap/node-jwt@^1.6.5
- Removed@sap/node-jwt@1.6.25(transitive)