@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
161 lines (143 loc) • 5.02 kB
JavaScript
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 || {}
}