UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

161 lines (143 loc) 5.02 kB
const cds = require('../cds') const fs = require('fs') const path = require('path') const hardcoded = { Scopes: ['system-user', 'authenticated-user', 'registered-user', 'identified-user', 'internal-user', 'privileged-user', 'any'], Attributes: ['id', 'tenant', 'ext_attr'] } module.exports = function compile_to_xsuaa (model, options) { const base = getBaseModel(options) const scopes = {} const attributes = {} const findUserAttrInExpression = (expr, attributes = []) => { // find `$user` ref, but not e.g. `$self.$user` if (typeof expr === 'object' && expr.ref?.length && expr.ref[0] === '$user') { if (expr.ref.length === 2) { const attr = expr.ref[1] if (!/^[a-zA-Z0-9_]*$/.test(attr)) { // see https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG/ae8e8427ecdf407790d96dad93b5f723/517895a9612241259d6941dbf9ad81cb.html#attributes throw new Error(`Illegal attribute name '${attr}' in '${expr}'. Only characters a-z, A-Z, 0-9, _ are allowed.`) } if (!hardcoded.Attributes.includes(attr)) attributes.push(attr) } // Reject expressions like `$user.foo.bar`, as xsuaa attributes are primitive, not structured else if (expr.ref.length > 2) { throw new Error (`Structured attributes like '${expr.ref.join('.')}' are not supported`) } } else if (typeof expr === 'object') { // descend sub expressions etc. Object.values(expr).forEach(e => findUserAttrInExpression(e, attributes)) } else if (Array.isArray(expr)) { // descend arrays expr.forEach(e => findUserAttrInExpression(e, attributes)) } return attributes } /** * @param {string|object} condition - the condition object or string * @returns string[] - the attributes found in the condition */ const parseAttributes = (condition) => { // e.g. 'foo = $user.bar or baz = $user.boo' if (!condition) return [] let expr if (typeof condition === 'object') { if (!condition.xpr && !condition['=']) throw new Error(`Invalid expression: ${JSON.stringify(condition)}`) expr = condition.xpr } else { try { expr = cds.parse.expr(condition).xpr } catch (err) { throw new Error(`Invalid expression': ${err.message}`, { cause: err }) } } return findUserAttrInExpression(expr) } cds.reflect(model).foreach(def => { var reqAnnotations = def['@requires'] if(!Array.isArray(reqAnnotations)) reqAnnotations=[reqAnnotations] reqAnnotations.forEach(scope => { if (scope && !hardcoded.Scopes.includes(scope)) { scopes[scope] = scope } }) const annotationRes = def['@restrict'] if (annotationRes) { annotationRes.forEach(restriction => { var restrTo = restriction.to if(!Array.isArray(restrTo)) restrTo=[restrTo] restrTo.forEach(scope =>{ if (scope && !hardcoded.Scopes.includes(scope)) { scopes[scope] = scope } }) const lattributes = parseAttributes(restriction.where) lattributes.forEach(attr => attributes[attr] = true) }) } }) var roleTemplates = Object.keys(scopes).map((s) => { return { name: s, description: 'generated', 'scope-references': ['$XSAPPNAME.' + s], 'attribute-references': [] } }); if (Object.keys(attributes).length !== 0) { roleTemplates.push({ name: 'userattributes', description: 'generated', 'default-role-name': 'Attributes of a User', 'scope-references': [], 'attribute-references': Object.keys(attributes) }) } const generated = { scopes: Object.keys(scopes).map((s) => { return { name: '$XSAPPNAME.' + s, description: s } }), attributes: Object.keys(attributes).map((a) => { return { name: a, description: a, valueType: 's', valueRequired: false } }), 'role-templates': roleTemplates, 'authorities-inheritance': false } return mergeConfig(base, generated) } function mergeConfig(base, generated) { const result = base const mergeByName = (type) => { if (!generated[type]) return if (!result[type]) result[type] = [] result[type] = result[type].concat(generated[type].filter((g) => { if (!base[type].find) throw new Error(`Array expected for '${type}', but was: ${JSON.stringify(base)}`) return !(base[type].find((b) => b.name === g.name)) })) } mergeByName('scopes') mergeByName('attributes') mergeByName('role-templates') return result } function getBaseModel(options = {}) { if (typeof options.base === 'string') { try { return JSON.parse(options.base) // plain object } catch (err) { try { return JSON.parse(fs.readFileSync(path.resolve(options.base))); // file } catch (err2) { throw new Error('Neither a JSON object nor a file path: ' + options.base) } } } return options.base || {} }