Skip to content

Commit

Permalink
Merge pull request #4425 from beyondessential/release-2023-15
Browse files Browse the repository at this point in the history
Release 2023 15
  • Loading branch information
biaoli0 authored Apr 13, 2023
2 parents 8c345a6 + 67d3e50 commit 8a890f0
Show file tree
Hide file tree
Showing 302 changed files with 8,661 additions and 4,543 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"packages/report-server/**",
"packages/server-boilerplate/**",
"packages/supserset-api/**",
"packages/tsutils/**"
"packages/tsutils/**",
"packages/types/**"
],
"extends": "@beyondessential/ts",
"parserOptions": {
Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ignore auto-generated files
packages/types/src/schemas/schemas.ts
packages/types/src/types/models.ts
7 changes: 2 additions & 5 deletions codeship-services.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# 'validation' service checks:
# - branch name
# - that there are no exclusive tests
# - TODO: strip this down to a tiny image that just checks branch name, and use eslint no-exclusive-tests flag
# at lint step after RN-201
# - TODO: use eslint no-exclusive-tests flag at lint step after RN-201
validation:
build:
dockerfile: ./packages/devops/ci/tupaia-ci-cd.Dockerfile
cached: true
default_cache_branch: 'dev'
dockerfile: ./packages/devops/ci/validation.Dockerfile
encrypted_dockercfg_path: ./packages/devops/ci/dockercfg.encrypted

testing:
Expand Down
2 changes: 2 additions & 0 deletions codeship-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
steps:
- name: Validate branch name
command: './packages/devops/scripts/ci/validateBranchName.sh'
- name: Validate new migrations
command: './packages/devops/scripts/ci/validateNewMigrations.sh'
- name: Validate tests
command: './packages/devops/scripts/ci/validateTests.sh'
- type: serial
Expand Down
132 changes: 132 additions & 0 deletions packages/access-policy/src/AccessPolicy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Tupaia
* Copyright (c) 2017 - 2020 Beyond Essential Systems Pty Ltd
*/

export class AccessPolicy {
constructor(policy) {
this.policy = typeof policy === 'string' ? JSON.parse(policy) : policy;
this.validate();
this.cachedPermissionGroupSets = {};
}

validate() {
if (!this.policy) {
throw new Error('Cannot instantiate an AccessPolicy without providing the policy details');
}
const permissionGroupLists = Object.values(this.policy);
if (permissionGroupLists.length === 0) {
throw new Error('At least one entity should be specified in an access policy');
}
if (permissionGroupLists.some(permissionGroups => !Array.isArray(permissionGroups))) {
throw new Error(
'Each entity should contain an array of permissionGroups for which the user has access',
);
}
}

/**
* Check if the user has access to a given permission group for the given entity
*
* @param {string} [entity]
* @param {string} [permissionGroup]
*
* @returns {boolean} Whether or not the user has access to any of the entities, optionally for
* the given permission group
*/
allows(entity, permissionGroup) {
return this.allowsSome([entity], permissionGroup);
}

/**
* Check if the user has access to a given permission group for all of a given set of entities.
* @param {*} entities
* @param {*} permissionGroup
*/
allowsAll(entities, permissionGroup) {
if (!entities || !entities.length) {
return false;
}
return entities.every(entity => this.allows(entity, permissionGroup));
}

/**
* Check if the user has access to a given permission group for any of a given set of entities e.g.
* - has access to some of the given entities with the given permission group
* accessPolicy.allowsSome(['DL', 'DL_North'], 'Donor');
*
* - has access to the given entities with some permission group
* accessPolicy.allowsSome(['DL']);
*
* @param {string[]} [entities]
* @param {string} [permissionGroup]
*
* @returns {boolean} Whether or not the user has access to any of the entities, optionally for
* the given permission group
*/
allowsSome(entities, permissionGroup) {
if (!entities && !permissionGroup) {
return false;
}
if (!permissionGroup) {
return entities.some(entityCode => !!this.policy[entityCode]);
}

const allowedPermissionGroups = this.getPermissionGroupsSet(entities);
return allowedPermissionGroups.has(permissionGroup);
}

/**
* Returns true if the access policy grants access to any entity with the specified permission
* group
* @param {string} permissionGroup
*/
allowsAnywhere(permissionGroup) {
if (!permissionGroup) {
throw new Error('Must provide a permission group when checking allowsAnywhere');
}
return this.getPermissionGroupsSet().has(permissionGroup);
}

/**
* Return permission groups the user has access to for the given entities (or all permission
* groups they can access if no entities provided)
*
* @param {string[]} [entities]
*
* @returns {string[]} The permission groups, e.g ['Admin', 'Donor']
*/
getPermissionGroups(entities) {
return [...this.getPermissionGroupsSet(entities)];
}

getPermissionGroupsSet(requestedEntities) {
// if no specific entities were requested, fetch the permissions for all of them
const entities = requestedEntities || Object.keys(this.policy);
// cache this part, as it is run often and is the most expensive operation
const cacheKey = `permissions-${entities.join('-')}`;
if (!this.cachedPermissionGroupSets[cacheKey]) {
const permissionGroups = new Set();
entities.forEach(entityCode => {
if (this.policy[entityCode]) {
this.policy[entityCode].forEach(r => permissionGroups.add(r));
}
});
this.cachedPermissionGroupSets[cacheKey] = permissionGroups;
}
return this.cachedPermissionGroupSets[cacheKey];
}

/**
* Return entities the user has access to the given permission group for
*
* @param {string} [permissionGroup]
*
* @returns entities[] The entity objects
*/
getEntitiesAllowed(permissionGroup) {
const allEntityCodes = Object.keys(this.policy);
if (!permissionGroup) return allEntityCodes;
return allEntityCodes.filter(e => this.allows(e, permissionGroup));
}
}
2 changes: 1 addition & 1 deletion packages/access-policy/src/__tests__/AccessPolicy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (c) 2017 Beyond Essential Systems Pty Ltd
*/

import { AccessPolicy } from '..';
import { AccessPolicy } from '../AccessPolicy';

const policy = {
DL: ['Public'],
Expand Down
128 changes: 1 addition & 127 deletions packages/access-policy/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,130 +3,4 @@
* Copyright (c) 2017 - 2020 Beyond Essential Systems Pty Ltd
*/

export class AccessPolicy {
constructor(policy) {
this.policy = typeof policy === 'string' ? JSON.parse(policy) : policy;
this.validate();
this.cachedPermissionGroupSets = {};
}

validate() {
if (!this.policy) {
throw new Error('Cannot instantiate an AccessPolicy without providing the policy details');
}
const permissionGroupLists = Object.values(this.policy);
if (permissionGroupLists.length === 0) {
throw new Error('At least one entity should be specified in an access policy');
}
if (permissionGroupLists.some(permissionGroups => !Array.isArray(permissionGroups))) {
throw new Error(
'Each entity should contain an array of permissionGroups for which the user has access',
);
}
}

/**
* Check if the user has access to a given permission group for the given entity
*
* @param {string} [entity]
* @param {string} [permissionGroup]
*
* @returns {boolean} Whether or not the user has access to any of the entities, optionally for
* the given permission group
*/
allows(entity, permissionGroup) {
return this.allowsSome([entity], permissionGroup);
}

/**
* Check if the user has access to a given permission group for all of a given set of entities.
* @param {*} entities
* @param {*} permissionGroup
*/
allowsAll(entities, permissionGroup) {
if (!entities || !entities.length) {
return false;
}
return entities.every(entity => this.allows(entity, permissionGroup));
}

/**
* Check if the user has access to a given permission group for any of a given set of entities e.g.
* - has access to some of the given entities with the given permission group
* accessPolicy.allowsSome(['DL', 'DL_North'], 'Donor');
*
* - has access to the given entities with some permission group
* accessPolicy.allowsSome(['DL']);
*
* @param {string[]} [entities]
* @param {string} [permissionGroup]
*
* @returns {boolean} Whether or not the user has access to any of the entities, optionally for
* the given permission group
*/
allowsSome(entities, permissionGroup) {
if (!entities && !permissionGroup) {
return false;
}
if (!permissionGroup) {
return entities.some(entityCode => !!this.policy[entityCode]);
}

const allowedPermissionGroups = this.getPermissionGroupsSet(entities);
return allowedPermissionGroups.has(permissionGroup);
}

/**
* Returns true if the access policy grants access to any entity with the specified permission
* group
* @param {string} permissionGroup
*/
allowsAnywhere(permissionGroup) {
if (!permissionGroup) {
throw new Error('Must provide a permission group when checking allowsAnywhere');
}
return this.getPermissionGroupsSet().has(permissionGroup);
}

/**
* Return permission groups the user has access to for the given entities (or all permission
* groups they can access if no entities provided)
*
* @param {string[]} [entities]
*
* @returns {string[]} The permission groups, e.g ['Admin', 'Donor']
*/
getPermissionGroups(entities) {
return [...this.getPermissionGroupsSet(entities)];
}

getPermissionGroupsSet(requestedEntities) {
// if no specific entities were requested, fetch the permissions for all of them
const entities = requestedEntities || Object.keys(this.policy);
// cache this part, as it is run often and is the most expensive operation
const cacheKey = `permissions-${entities.join('-')}`;
if (!this.cachedPermissionGroupSets[cacheKey]) {
const permissionGroups = new Set();
entities.forEach(entityCode => {
if (this.policy[entityCode]) {
this.policy[entityCode].forEach(r => permissionGroups.add(r));
}
});
this.cachedPermissionGroupSets[cacheKey] = permissionGroups;
}
return this.cachedPermissionGroupSets[cacheKey];
}

/**
* Return entities the user has access to the given permission group for
*
* @param {string} [permissionGroup]
*
* @returns entities[] The entity objects
*/
getEntitiesAllowed(permissionGroup) {
const allEntityCodes = Object.keys(this.policy);
if (!permissionGroup) return allEntityCodes;
return allEntityCodes.filter(e => this.allows(e, permissionGroup));
}
}
export { AccessPolicy } from './AccessPolicy';
30 changes: 30 additions & 0 deletions packages/admin-panel-server/examples.http
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,33 @@ Authorization: {{authorization}}
}
}
}

### Fetch Data Table Preview Data
POST {{baseUrl}}/fetchDataTablePreviewData
content-type: {{contentType}}
Authorization: {{authorization}}

{
"previewConfig": {
"code": "data_lake_db_test",
"type": "sql",
"config": {
"sql": "SELECT * FROM analytics WHERE entity_code LIKE :entityCode",
"externalDatabaseConnectionCode": "DATA_LAKE_DB",
"additionalParams": [
{
"name": "entityCode",
"config": {
"type": "string"
}
}
]
},
"runtimeParams": {
"entityCode": "AU_SA%"
},
"permission_groups": [
"*"
]
}
}
Loading

0 comments on commit 8a890f0

Please sign in to comment.