| /** |
| * |
| * Copyright (c) 2020 Silicon Labs |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * This module contains the API for templating. For more detailed instructions, read {@tutorial template-tutorial} |
| * |
| * @module Templating API: Access helpers |
| */ |
| |
| const queryAccess = require('../db/query-access') |
| const templateUtil = require('./template-util') |
| const dbEnum = require('../../src-shared/db-enum') |
| |
| /** |
| * Collects the default access list |
| * |
| * @param {*} ctx |
| * @param {*} entityType |
| * @returns Promise of default access |
| */ |
| async function collectDefaultAccessList(ctx, entityType) { |
| let packageIds = await templateUtil.ensureZclPackageIds(ctx) |
| let defaultAccess = await queryAccess.selectDefaultAccess( |
| ctx.global.db, |
| packageIds, |
| entityType |
| ) |
| return defaultAccess |
| } |
| |
| /** |
| * Get Access List based on on given options. |
| * |
| * @param {*} ctx |
| * @param {*} options |
| * @returns Access List |
| */ |
| async function collectAccesslist(ctx, options) { |
| let entityType = null |
| let includeDefault = true |
| |
| if ('entity' in options.hash) { |
| entityType = options.hash.entity |
| } else { |
| entityType = ctx.entityType |
| } |
| |
| if ('includeDefault' in options.hash) { |
| includeDefault = options.hash.includeDefault == 'true' |
| } |
| |
| if (entityType == null) { |
| throw new Error( |
| 'Access helper requires entityType, either from context, or from the entity="<entityType>" option.' |
| ) |
| } |
| |
| let accessList |
| |
| switch (entityType) { |
| case 'attribute': |
| accessList = await queryAccess.selectAttributeAccess( |
| ctx.global.db, |
| ctx.id |
| ) |
| break |
| case 'command': |
| accessList = await queryAccess.selectCommandAccess(ctx.global.db, ctx.id) |
| break |
| case 'event': |
| accessList = await queryAccess.selectEventAccess(ctx.global.db, ctx.id) |
| break |
| default: |
| throw new Error( |
| `Entity type ${entityType} not supported. Requires: attribute/command/event.` |
| ) |
| } |
| |
| if (includeDefault) { |
| let defaultAccess = await collectDefaultAccessList(ctx, entityType) |
| accessList.push(...defaultAccess) |
| } |
| |
| return accessList |
| } |
| |
| /** |
| * This helper creates a context for the aggregates of access. |
| * |
| * @param {*} options |
| */ |
| async function access_aggregate(options) { |
| let packageIds = await templateUtil.ensureZclPackageIds(this) |
| let accessList = await collectAccesslist(this, options) |
| let ignoreEmpty |
| if ('ignoreEmpty' in options.hash) { |
| ignoreEmpty = options.hash.ignoreEmpty == 'true' |
| } else { |
| ignoreEmpty = false |
| } |
| |
| let allOps = await queryAccess.selectAccessOperations( |
| this.global.db, |
| packageIds |
| ) |
| let allMods = await queryAccess.selectAccessModifiers( |
| this.global.db, |
| packageIds |
| ) |
| let allRoles = await queryAccess.selectAccessRoles(this.global.db, packageIds) |
| let roleLevels = {} |
| allRoles.forEach((r) => { |
| roleLevels[r.name] = r.level |
| }) |
| |
| let aggregate = { |
| count: accessList.length |
| } |
| |
| allOps.forEach((r) => { |
| aggregate[r.name + 'Highest'] = 'NONE' |
| aggregate[r.name + 'Lowest'] = 'NONE' |
| }) |
| allMods.forEach((r) => { |
| aggregate[r.name] = false |
| }) |
| |
| accessList.forEach((a) => { |
| let role = a.role |
| let operation = a.operation |
| let accessModifier = a.accessModifier |
| if (accessModifier != null) { |
| aggregate[accessModifier] = true |
| } |
| if (role != null) { |
| if (aggregate[operation + 'Highest'] === 'NONE') { |
| aggregate[operation + 'Highest'] = role |
| aggregate[operation + 'Lowest'] = role |
| } else { |
| let highestRole = aggregate[operation + 'Highest'] |
| let lowestRole = aggregate[operation + 'Lowest'] |
| if (roleLevels[role] < roleLevels[lowestRole]) { |
| aggregate[operation + 'Lowest'] = role |
| } |
| if (roleLevels[role] > roleLevels[highestRole]) { |
| aggregate[operation + 'Highest'] = role |
| } |
| } |
| } |
| }) |
| |
| let blocks |
| if (ignoreEmpty && aggregate.count == 0) { |
| blocks = [] |
| } else { |
| blocks = [aggregate] |
| } |
| let p = templateUtil.collectBlocks(blocks, options, this) |
| return templateUtil.templatePromise(this.global, p) |
| } |
| |
| /** |
| * Access helper iterates across all the access triplets associated with the element. |
| * For each element, context contains role, operation, accessModifier. |
| * Additionally it creates booleans hasRole, hasOperation and hasAccessModifier |
| * and hasAtLeastOneAccessElement and hasAllAccessElements |
| * @param {*} options |
| */ |
| async function access(options) { |
| let accessList = await collectAccesslist(this, options) |
| |
| accessList.forEach((element) => { |
| element.hasRole = element.role != null && element.role.length > 0 |
| element.hasOperation = |
| element.operation != null && element.operation.length > 0 |
| element.hasAccessModifier = |
| element.accessModifier != null && element.accessModifier.length > 0 |
| element.hasAllAccessElements = |
| element.hasRole && element.hasOperation && element.hasAccessModifier |
| element.hasAtLeastOneAccessElement = |
| element.hasRole || element.hasOperation || element.hasAccessModifier |
| }) |
| |
| let p = templateUtil.collectBlocks(accessList, options, this) |
| return templateUtil.templatePromise(this.global, p) |
| } |
| |
| /** |
| * Get the access list information. |
| * |
| * @param {*} options |
| * @returns access list |
| */ |
| async function default_access(options) { |
| let entityType = null |
| |
| if ('entity' in options.hash) { |
| entityType = options.hash.entity |
| } else { |
| entityType = ctx.entityType |
| } |
| |
| if (entityType == null) { |
| throw new Error( |
| 'Access helper requires entityType, either from context, or from the entity="<entityType>" option.' |
| ) |
| } |
| |
| let accessList = await collectDefaultAccessList(this, entityType) |
| accessList.forEach((element) => { |
| element.hasRole = element.role != null && element.role.length > 0 |
| element.hasOperation = |
| element.operation != null && element.operation.length > 0 |
| element.hasAccessModifier = |
| element.accessModifier != null && element.accessModifier.length > 0 |
| element.hasAllAccessElements = |
| element.hasRole && element.hasOperation && element.hasAccessModifier |
| element.hasAtLeastOneAccessElement = |
| element.hasRole || element.hasOperation || element.hasAccessModifier |
| }) |
| |
| let p = templateUtil.collectBlocks(accessList, options, this) |
| return templateUtil.templatePromise(this.global, p) |
| } |
| |
| /** |
| * Determines the access role for a given entity and operation. |
| * |
| * @param {*} options |
| * @returns {string} The access role. |
| */ |
| async function chip_get_access_role(options) { |
| if (!('op' in options.hash)) { |
| throw new Error('Access helper requires op from the op="<op>" option.') |
| } |
| |
| const op = options.hash.op |
| const accessList = await collectAccesslist(this, options) |
| const accessForOp = accessList.find((a) => a.operation === op) |
| |
| if (accessForOp?.role) { |
| return accessForOp.role |
| } |
| |
| if ('default' in options.hash) { |
| return options.hash.default |
| } |
| |
| return '' |
| } |
| |
| exports.access = access |
| exports.access_aggregate = access_aggregate |
| exports.default_access = default_access |
| exports.chip_get_access_role = chip_get_access_role |