Skip to content

Commit

Permalink
Add read permissions (#2849)
Browse files Browse the repository at this point in the history
Adding the ability to see read permissions alongside of write permissions
Added flag enablePermissionManagement to control UI element to assign permissions.
  • Loading branch information
rkboyce committed Jul 31, 2023
1 parent 8f54f70 commit b592e21
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 80 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*~
.idea
/web.config
/node_modules/
Expand Down
64 changes: 49 additions & 15 deletions js/components/security/access/configure-access-modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,80 @@
data: {
classes: classes,
isLoading: isLoading,
roleName: roleName,
columns: columns,
accessList: accessList,
readRoleName: readRoleName,
writeRoleName: writeRoleName,
writeAccessColumns: writeAccessColumns,
readAccessColumns: readAccessColumns,
writeAccessList: writeAccessList,
readAccessList: readAccessList,
grantAccess: grantAccess,
revokeRoleAccess: revokeRoleAccess,
roleOptions: roleOptions,
roleSearch: roleSearch
readRoleOptions: readRoleOptions,
readRoleSearch: readRoleSearch,
writeRoleOptions: writeRoleOptions,
writeRoleSearch: writeRoleSearch
}">
<loading data-bind="css: classes('loading-panel'), visible: isLoading()" params="status: ko.i18n('common.configureAccessModal.loadingAccessList', 'Loading access list...')"></loading>
<div data-bind="css: classes()">
<div data-bind="if: !isLoading()">
<div data-bind="css: classes('new-access')">
<label data-bind="css: classes('new-access-label'), text: ko.i18n('common.configureAccessModal.addWriteAccessToRole', 'Add write access to role:')"></label>
<label data-bind="css: classes('new-access-label'), text: ko.i18n('common.configureAccessModal.addWriteAccessToRole', 'Add WRITE access to role:')"></label>
<div class="input-group"
data-bind="css: classes({ element: 'new-access-btn-group', extra: ['new-access-btn-group'] })">
<input
class="form-control"
data-bind="
textInput: roleSearch,
value: roleName,
textInput: writeRoleSearch,
value: writeRoleName,
eventType: 'blur',
ko_autocomplete: { source: roleOptions(), minLength: 0, maxShowItems: 10, scroll: true }"
ko_autocomplete: { source: writeRoleOptions(), minLength: 0, maxShowItems: 10, scroll: true }"
>
<span class="input-group-btn">
<button class="btn btn-primary" type="button" data-bind="click: grantAccess, attr: { disabled: !(roleName() && roleName().length) }, text: ko.i18n('common.add', 'Add')"></button>
<button class="btn btn-primary" type="button" data-bind="click: grantAccess.bind($data,'WRITE'), attr: { disabled: !(writeRoleName() && writeRoleName().length) }, text: ko.i18n('common.add', 'Add')"></button>
</span>
</div>
<div data-bind="css: classes('access-list')">
<label data-bind="css: classes('access-list-label'), text: ko.i18n('common.configureAccessModal.rolesWithWriteAccess', 'Roles with write access:')"></label>
<label data-bind="css: classes('access-list-label'), text: ko.i18n('common.configureAccessModal.rolesWithWriteAccess', 'Roles with WRITE access:')"></label>
<div>
<table data-bind="
css: classes({ element: 'access-table', extra: ['table', 'table-bordered', 'table-hover'] }),
dataTable:{
data: accessList,
options: {columns: columns, language: ko.i18n('datatable.language')},
data: writeAccessList,
options: {columns: writeAccessColumns, language: ko.i18n('datatable.language')},
}
"/>
</div>
</div>
</div>
</div>
<div data-bind="css: classes('new-access')">
<label data-bind="css: classes('new-access-label'), text: ko.i18n('common.configureAccessModal.addReadAccessToRole', 'Add READ access to role:')"></label>
<div class="input-group"
data-bind="css: classes({ element: 'new-access-btn-group', extra: ['new-access-btn-group'] })">
<input
class="form-control"
data-bind="
textInput: readRoleSearch,
value: readRoleName,
eventType: 'blur',
ko_autocomplete: { source: readRoleOptions(), minLength: 0, maxShowItems: 10, scroll: true }"
>
<span class="input-group-btn">
<button class="btn btn-primary" type="button" data-bind="click: grantAccess.bind($data,'READ'), attr: { disabled: !(readRoleName() && readRoleName().length) }, text: ko.i18n('common.add', 'Add')"></button>
</span>
</div>
<div data-bind="css: classes('access-list')">
<label data-bind="css: classes('access-list-label'), text: ko.i18n('common.configureAccessModal.rolesWithReadAccess', 'Roles with READ access:')"></label>
<div>
<table data-bind="
css: classes({ element: 'access-table', extra: ['table', 'table-bordered', 'table-hover'] }),
dataTable:{
data: readAccessList,
options: {columns: readAccessColumns, language: ko.i18n('datatable.language')},
}
"/>
</div>
</div>
</div>
</div>
</div>
</atlas-modal>
</atlas-modal>
100 changes: 72 additions & 28 deletions js/components/security/access/configure-access-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,80 +19,124 @@ define([

this.isModalShown = params.isModalShown;
this.isLoading = ko.observable(false);
this.accessList = ko.observable([]);
this.roleName = ko.observable();

this.roleSuggestions = ko.observable([]);
this.roleOptions = ko.computed(() => this.roleSuggestions().map(r => r.name));
this.roleSearch = ko.observable();
this.roleSearch.subscribe(str => this.loadRoleSuggestions(str));
this.writeRoleName = ko.observable();
this.writeAccessList = ko.observable([]);
this.writeRoleSuggestions = ko.observable([]);
this.writeRoleOptions = ko.computed(() => this.writeRoleSuggestions().map(r => r.name));
this.writeRoleSearch = ko.observable();
this.writeRoleSearch.subscribe(str => this.loadWriteRoleSuggestions(str));

this.readAccessList = ko.observable([]);
this.readRoleName = ko.observable();
this.readRoleSuggestions = ko.observable([]);
this.readRoleOptions = ko.computed(() => this.readRoleSuggestions().map(r => r.name));
this.readRoleSearch = ko.observable();
this.readRoleSearch.subscribe(str => this.loadReadRoleSuggestions(str));

this.isOwnerFn = params.isOwnerFn;
this.grantAccessFn = params.grantAccessFn;
this.loadAccessListFn = params.loadAccessListFn;
this.revokeAccessFn = params.revokeAccessFn;
this.loadRoleSuggestionsFn = params.loadRoleSuggestionsFn;

this.columns = [
this.readAccessColumns = [
{
class: this.classes('access-tbl-col-id'),
title: ko.i18n('readAccessColumns.id', 'ID'),
data: 'id'
},
{
class: this.classes('access-tbl-col-name'),
title: ko.i18n('readAccessColumns.name', 'Name'),
data: 'name'
},
{
class: this.classes('access-tbl-col-action'),
title: ko.i18n('readAccessColumns.action', 'Action'),
render: (s, p, d) => !this.isOwnerFn(d.name) ? `<a data-bind="css: '${this.classes('revoke-link')}', click: revoke, text: ko.i18n('common.configureAccessModal.revoke', 'Revoke')"></a>` : '-'
}
];

this.writeAccessColumns = [
{
class: this.classes('access-tbl-col-id'),
title: ko.i18n('columns.id', 'ID'),
title: ko.i18n('writeAccessColumns.id', 'ID'),
data: 'id'
},
{
class: this.classes('access-tbl-col-name'),
title: ko.i18n('columns.name', 'Name'),
title: ko.i18n('writeAccessColumns.name', 'Name'),
data: 'name'
},
{
class: this.classes('access-tbl-col-action'),
title: ko.i18n('columns.action', 'Action'),
title: ko.i18n('writeAccessColumns.action', 'Action'),
render: (s, p, d) => !this.isOwnerFn(d.name) ? `<a data-bind="css: '${this.classes('revoke-link')}', click: revoke, text: ko.i18n('common.configureAccessModal.revoke', 'Revoke')"></a>` : '-'
}
];

this.isModalShown.subscribe(open => !!open && this.loadAccessList());
}

async _loadAccessList() {
let accessList = await this.loadAccessListFn();
accessList = accessList.map(a => ({ ...a, revoke: () => this.revokeRoleAccess(a.id) }));
this.accessList(accessList);
async _loadReadAccessList() {
let accessList = await this.loadAccessListFn('READ');
accessList = accessList.map(a => ({ ...a, revoke: () => this.revokeRoleAccess(a.id, 'READ') }));
this.readAccessList(accessList);
}

async _loadWriteAccessList() {
let accessList = await this.loadAccessListFn('WRITE');
accessList = accessList.map(a => ({ ...a, revoke: () => this.revokeRoleAccess(a.id, 'WRITE') }));
this.writeAccessList(accessList);
}

async loadRoleSuggestions() {
const res = await this.loadRoleSuggestionsFn(this.roleSearch());
this.roleSuggestions(res);
async loadReadRoleSuggestions() {
const res = await this.loadRoleSuggestionsFn(this.readRoleSearch());
this.readRoleSuggestions(res);
}

async loadWriteRoleSuggestions() {
const res = await this.loadRoleSuggestionsFn(this.writeRoleSearch());
this.writeRoleSuggestions(res);
}

async loadAccessList() {
this.isLoading(false);
this.isLoading(true);
try {
await this._loadAccessList();
await this._loadReadAccessList();
await this._loadWriteAccessList();
} catch (ex) {
console.log(ex);
}
this.isLoading(false);
}

async grantAccess() {
async grantAccess(perm_type) {
this.isLoading(true);
try {
const role = this.roleSuggestions().find(r => r.name === this.roleName());
await this.grantAccessFn(role.id);
await this._loadAccessList();
this.roleName('');
if (perm_type == 'WRITE'){
const role = this.writeRoleSuggestions().find(r => r.name === this.writeRoleName());
await this.grantAccessFn(role.id,'WRITE');
await this._loadWriteAccessList();
this.writeRoleName('');
} else {
const role = this.readRoleSuggestions().find(r => r.name === this.readRoleName());
await this.grantAccessFn(role.id,'READ');
await this._loadReadAccessList();
this.readRoleName('');
}
} catch (ex) {
console.log(ex);
}
this.isLoading(false);
}

async revokeRoleAccess(roleId) {
async revokeRoleAccess(roleId, perm_type) {
this.isLoading(true);
try {
await this.revokeAccessFn(roleId);
await this._loadAccessList();
try {
await this.revokeAccessFn(roleId, perm_type);
await this.loadAccessList();
} catch (ex) {
console.log(ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
<!-- ko ifnot: isNewEntity() -->
<button type="button" class="btn btn-primary" data-bind="click: () => isTagsModalShown(!isTagsModalShown()), visible: isEditPermitted() && !previewVersion(), css: { disabled: isProcessing() }, title: ko.i18n('common.tags', 'Tags')"><i class="fa fa-tags"></i></button>
<button type="button" class="btn btn-primary" data-bind="visible: !previewVersion(), click: copyCc, css: {disabled: !canCopy() || isProcessing() }, title: , title: ko.i18n('common.createACopy', 'Create a copy')"><i class="fa fa-copy"></i></button>

<!-- ko if: enablePermissionManagement -->
<button class="btn btn-primary" data-bind="visible: isOwner() && !previewVersion(), click: () => isAccessModalShown(!isAccessModalShown()), title: ko.i18n('common.configureAccess', 'Configure access')">
<i class="fa fa-lock"></i>
</button>
<button type="button" class="btn btn-danger" data-bind="visible: !previewVersion(), click: deleteCc, css: {disabled: !$component.isDeletePermitted() || isProcessing() }"><i class="fa fa-trash-alt"></i></button>
<!-- /ko -->

<button type="button" class="btn btn-danger" data-bind="visible: !previewVersion(), click: deleteCc, css: {disabled: !$component.isDeletePermitted() || isProcessing() }"><i class="fa fa-trash-alt"></i></button>
<!-- /ko -->
</div>
</div>
Expand Down Expand Up @@ -103,4 +107,4 @@
loadAvailableTagsFn: $component.loadAvailableTags,
checkAssignPermissionFn: $component.checkAssignPermission,
checkUnassignPermissionFn: $component.checkUnassignPermission
"></tags-modal>
"></tags-modal>
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ define([
this.areStratasNamesEmpty = ko.observable();
this.duplicatedStrataNames = ko.observable([]);

this.enablePermissionManagement = ko.pureComputed(() => {
return config.enablePermissionManagement;
});

this.designDirtyFlag = sharedState.CohortCharacterization.dirtyFlag;
this.loading = ko.observable(false);
this.defaultName = ko.unwrap(constants.newEntityNames.characterization);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
<button class="btn btn-primary" data-bind="click: closeAnalysis, enable: !isProcessing(), title: ko.i18n('common.close', 'Close')"><i class="fa fa-times"></i></button>
<!-- ko ifnot: isNewEntity -->
<button type="button" class="btn btn-primary" data-bind="click: copyFeatureAnalysis, css: {disabled: !canCopy() || isProcessing() }, title: ko.i18n('common.createACopy', 'Create a copy')"><i class="fa fa-copy"></i></button>

<!-- ko if: enablePermissionManagement -->
<button class="btn btn-primary" data-bind="visible: isOwner, enable: !isProcessing(), click: () => isAccessModalShown(!isAccessModalShown()), title: ko.i18n('common.configureAccess', 'Configure access')">
<i class="fa fa-lock"></i>
</button>
<!-- /ko -->

<button class="btn btn-danger" data-bind="click: deleteFeature, enable: canDelete() && !isProcessing(), title: ko.i18n('common.delete', 'Delete')"><i class="fa fa-trash-alt"></i></button>
<!-- /ko -->
</div>
Expand Down Expand Up @@ -53,4 +57,4 @@
grantAccessFn: $component.grantAccess,
revokeAccessFn: $component.revokeAccess,
loadRoleSuggestionsFn: $component.loadAccessRoleSuggestions
"></configure-access-modal>
"></configure-access-modal>
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ define([
});
this.editorClasses = ko.computed(() => this.classes({ element: 'content', modifiers: this.canEdit() ? '' : 'disabled' }))

this.enablePermissionManagement = ko.pureComputed(() => {
return config.enablePermissionManagement;
});

this.selectedTabKey = ko.observable();
this.componentParams = ko.observable({
...params,
Expand Down
9 changes: 6 additions & 3 deletions js/pages/cohort-definitions/cohort-definition-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
data-bind="visible: !previewVersion(), title: ko.i18n('cohortDefinitions.cohortDefinitionManager.createCopyCohortTitle', 'Create a copy of this cohort definition'), click: copy, enable: canCopy() && !isProcessing()"><i
class="fa fa-copy"></i></button>
<button class="btn btn-primary"
data-bind="visible: !previewVersion(), title: ko.i18n('cohortDefinitions.cohortDefinitionManager.getLinkCohortTitle', 'Get a link to this cohort definition'), enable: !dirtyFlag().isDirty() && !isProcessing(), click: function () { $component.cohortLinkModalOpened(true) }"><i
class="fa fa-link"></i></button>
data-bind="visible: !previewVersion(), title: ko.i18n('cohortDefinitions.cohortDefinitionManager.getLinkCohortTitle', 'Get a link to this cohort definition'), enable: !dirtyFlag().isDirty() && !isProcessing(), click: function () { $component.cohortLinkModalOpened(true) }"><i class="fa fa-link"></i></button>

<!-- ko if: enablePermissionManagement -->
<button class="btn btn-primary"
data-bind="title: ko.i18n('common.configureAccess', 'Configure access'), visible: isOwner() && !previewVersion(), click: () => isAccessModalShown(!isAccessModalShown())">
<i class="fa fa-lock"></i>
</button>
<!-- /ko -->

<!-- ko if: !isRunning() -->
<button class="btn btn-danger"
data-bind="visible: !previewVersion(), title: ko.i18n('common.delete', 'Delete'), click: $component.delete, enable: canDelete() && !isProcessing()"><i
Expand Down Expand Up @@ -790,4 +793,4 @@ <h3 data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.a
</div>
</script>

<!-- /ko -->
<!-- /ko -->
7 changes: 5 additions & 2 deletions js/pages/cohort-definitions/cohort-definition-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,13 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
super(params);

this.previewVersion = sharedState.CohortDefinition.previewVersion;

this.pollTimeoutId = null;
this.authApi = authApi;
this.config = config;
this.config = config;
this.enablePermissionManagement = ko.pureComputed(() => {
return config.enablePermissionManagement;
});
this.relatedSourcecodesOptions = globalConstants.relatedSourcecodesOptions;
this.commonUtils = commonUtils;
this.isLoading = ko.observable(false);
Expand Down
4 changes: 4 additions & 0 deletions js/pages/concept-sets/conceptset-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
<!-- ko if: $component.currentConceptSet().id != null && $component.currentConceptSet().id != 0 -->
<button type="button" class="btn btn-primary" data-bind="click: () => isTagsModalShown(!isTagsModalShown()), visible: canEdit() && !previewVersion(), css: { disabled: isProcessing() }, title: ko.i18n('common.tags', 'Tags')"><i class="fa fa-tags"></i></button>
<button type="button" class="btn btn-primary" data-bind="visible: !previewVersion(), click: optimize, css: { disabled: !canOptimize() || isProcessing() }, text: ko.i18n('cs.manager.optimize', 'Optimize')"></button>

<!-- ko if: enablePermissionManagement -->
<button class="btn btn-primary" data-bind="visible: !previewVersion() && isOwner(), click: () => isAccessModalShown(!isAccessModalShown()), title: ko.i18n('common.configureAccess', 'Configure access')">
<i class="fa fa-lock"></i>
</button>
<!-- /ko -->

<button type="button" class="btn btn-danger" data-bind="visible: !previewVersion(), click: $component.delete, css: { disabled: !canDelete() || isProcessing() }, title: ko.i18n('common.delete', 'Delete')"><i class="fa fa-trash-alt"></i></button>
<!-- /ko -->
</div>
Expand Down

0 comments on commit b592e21

Please sign in to comment.