UNPKG

@sap/xssec

Version:

XS Advanced Container Security API for node.js

152 lines (131 loc) 5.88 kB
const createSecurityContext = require("../context/createSecurityContext"); const ConfigurationError = require("../error/configuration/ConfigurationError"); const { getLogger } = require('../util/logging'); const XsuaaService = require("../service/XsuaaService"); const IdentityService = require("../service/IdentityService"); const UaaService = require("../service/IdentityService"); const createServiceFromCredentials = require("./createService"); const ValidationError = require("../error/validation/ValidationError"); const XsaService = require("../service/XsaService"); /** * @typedef {import("../context/SecurityContext")} SecurityContext * @typedef {import("../token/Token")} Token * @typedef {import("../util/Types").SecurityContextConfig} SecurityContextConfig * @typedef {import("../util/Types").ServiceConfig} ServiceConfig */ const LOG = getLogger("createSecurityContextV3.js"); /** * @callback createSecurityContextV3Callback * @param {Error} error * @param {SecurityContext} xssec3SecurityContext * @param {Token} xssec3TokenInfo */ /** * * @param {String} jwt * @param {Object} configParameter * @param {boolean|createSecurityContextV3Callback} forceType * @param {createSecurityContextV3Callback} cb */ async function createSecurityContextV3(jwt, configParameter, forceType, cb) { if (typeof forceType === 'function') { cb = forceType; forceType = null; } if (cb == null || typeof cb !== 'function') { throw new ConfigurationError("The callback parameter must be a function."); } let securityContext; try { if (configParameter == null) { throw new ConfigurationError("The configParameter parameter must not be null or undefined."); } else if (jwt == null) { throw new ConfigurationError("The jwt parameter must not be null or undefined."); } const contextConfig = buildContextConfig(configParameter, jwt); const serviceConfig = buildServiceConfig(configParameter); const services = buildServices(contextConfig.credentials, forceType, serviceConfig); securityContext = await createSecurityContext(services, contextConfig); } catch (error) { if (error instanceof ValidationError) { return cb(error, null, { isValid: () => false, getErrorObject: () => error }); } return cb(error); } return cb(null, securityContext, securityContext.token); } /** * Build new contextConfig structure based on old configParameter structure, which can have options potentially located inside credentials * @param {Object} configParameter * @param {String} jwt * @returns {SecurityContextConfig} */ function buildContextConfig(configParameter, jwt) { // if configParameter has no credentials property, assume it is a credentials object or array itself const contextConfig = configParameter.credentials ? configParameter : { credentials: configParameter }; contextConfig.jwt = jwt; contextConfig.correlationId ??= contextConfig.credentials.correlationId; contextConfig.clientCertificatePem ??= contextConfig.x509Certificate ?? contextConfig.credentials.x509Certificate; contextConfig.skipValidation ??= contextConfig.credentials.skipValidation; return contextConfig; } /** * Build new serviceConfig structure based on old configParameter structure * @param {Object} configParameter * @returns {ServiceConfig} */ function buildServiceConfig(configParameter) { const serviceConfig = {}; serviceConfig.endpoints ??= configParameter.endpoints || {}; serviceConfig.validation ??= configParameter.validation || {}; serviceConfig.validation.x5t ??= {}; serviceConfig.validation.x5t.enabled ??= configParameter.x5tValidation; serviceConfig.validation.jwks ??= configParameter.jwksCache || {}; serviceConfig.validation.jwks.shared = true; // use the same JWKS cache for subsequent calls to createSecurityContextV3, otherwise EACH request will fetch the JWKS again serviceConfig.validation.signatureCache = configParameter.signatureCache; serviceConfig.requests = configParameter.requests || {}; if (configParameter.disableCache) { LOG.warn(`The 'disableCache' option to disable the JWKS cache is not supported by the v3 compatibility package. The cache is always enabled.`); } return serviceConfig; } /** * Build service objects based on credentials and forceType * @param {Array} credentials * @param {boolean} forceType * @param {ServiceConfig} serviceConfig * @returns {Service[]} */ function buildServices(credentials, forceType, serviceConfig) { const credentialsArray = Array.isArray(credentials) ? credentials : [credentials]; insertXsAppnameFromEnv(credentialsArray); if (forceType) { LOG.info(`forceType === ${forceType}. Creating ${forceType} service(s) from credentials.`); } switch (forceType) { case "XSUAA": return credentialsArray.map(c => new XsuaaService(c, serviceConfig)); case "XSA": return credentialsArray.map(c => new XsaService(c, serviceConfig)); case "IAS": return credentialsArray.map(c => new IdentityService(c, serviceConfig)); case "UAA": return credentialsArray.map(c => new UaaService(c, serviceConfig)); case null: case undefined: default: return credentialsArray.map(c => createServiceFromCredentials(c, serviceConfig)); } } /** * Backward-compatible filling of xsappname in credentials with environment variable XSAPPNAME if present. * @param {Array} credentials */ function insertXsAppnameFromEnv(credentials) { if (process.env.XSAPPNAME) { for (const c of credentials) { c.xsappname = process.env.XSAPPNAME; } } } module.exports = createSecurityContextV3;