Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/global sharing #2929

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5a97b39
brought in first working ability to configure global read access by g…
rkboyce Apr 17, 2024
61dcae0
first working configuration of sharing using an authoring role
rkboyce Apr 22, 2024
8f46884
some fixes to global sharing after testing; moved the shared reader r…
rkboyce Apr 22, 2024
b99d5d5
started to implement the ability to hide the share-global buttons but…
rkboyce Apr 23, 2024
8396b4d
changed the 'shared artifact reader' role checked by Atlas to be the …
rkboyce May 6, 2024
0493316
added the configurable ability to restrict generation of a given coho…
rkboyce May 9, 2024
7e21378
feat: introduce a new permission instead of relying on a role
pieterlukasse May 10, 2024
649cc7e
fix: correct styling for sharing buttons on access modal ui
pieterlukasse May 14, 2024
0946559
fix: cleanup/remove unnecessary parts
pieterlukasse May 14, 2024
ca11cc3
Merge pull request #2 from uc-cdis/fix/final-global-sharing
rkboyce May 16, 2024
b24266b
fix: adjust permission name to "artifact:global:share:put"
pieterlukasse May 17, 2024
b162fd5
Merge pull request #3 from uc-cdis/fix/final-global-sharing_part2
rkboyce May 17, 2024
3bcffd5
fix: remove dead code
pieterlukasse Aug 23, 2024
058ace5
Merge pull request #7 from pieterlukasse/patch-1
rkboyce Sep 2, 2024
b8017b9
Merge branch 'OHDSI:master' into feature/global-sharing
rkboyce Sep 2, 2024
4872345
fix: better name for isPermittedGlobalShareArtifact
pieterlukasse May 17, 2024
01eca8c
Merge pull request #4 from uc-cdis/fix/owner_check_and_remove_dead_code
rkboyce Sep 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions js/components/security/access/configure-access-modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
readRoleOptions: readRoleOptions,
readRoleSearch: readRoleSearch,
writeRoleOptions: writeRoleOptions,
writeRoleSearch: writeRoleSearch
writeRoleSearch: writeRoleSearch,
shareFlag: shareFlag,
grantGlobalReadAccess: grantGlobalReadAccess,
revokeGlobalReadAccess: revokeGlobalReadAccess,
}">
<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', 'Grant WRITE access to role:')"></label>
<div class="input-group"
data-bind="css: classes({ element: 'new-access-btn-group', extra: ['new-access-btn-group'] })">
<input
Expand Down Expand Up @@ -48,8 +53,10 @@
</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>
<label data-bind="css: classes('new-access-label'), text: ko.i18n('common.configureAccessModal.addReadAccessToRole', 'Grant READ access to role:')"></label>
<div class="input-group"
data-bind="css: classes({ element: 'new-access-btn-group', extra: ['new-access-btn-group'] })">
<input
Expand Down Expand Up @@ -77,6 +84,34 @@
</div>
</div>
</div>


<div><br></div>
<!-- A BUTTON TO SHARE GLOBALLY -->
<div>
<label data-bind="css: classes('new-access-label'), text: ko.i18n('common.configureAccessModal.globalReadStatus', 'Status of global READ access:')"></label>
<div/>
<div class="btn-group" data-toggle="buttons">
<label data-bind="css: { active: !shareFlag()},
click: function () { shareFlag(false); grantGlobalReadAccess();},
clickBubble: false,
text: ko.i18n('common.configureAccessModal.globalReadStatusNotGranted', 'Granted')
"
class="btn btn-primary",
/>
<label data-bind="css: {
active: shareFlag()},
click: function () { shareFlag(true); revokeGlobalReadAccess();},
clickBubble: false,
text: ko.i18n('common.configureAccessModal.globalReadStatusIsGranted', 'Not Granted')
"
class="btn btn-primary",
/>
</div>
</div>

<div><br></div>

</div>
</div>
</atlas-modal>
41 changes: 37 additions & 4 deletions js/components/security/access/configure-access-modal.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
define([
'knockout',
'text!./configure-access-modal.html',
'text!./configure-access-modal.html',
'components/Component',
'utils/CommonUtils',
'utils/AutoBind',
'less!./configure-access-modal.less',
'databindings',
], function (
ko,
view,
view,
Component,
commonUtils,
AutoBind
Expand All @@ -34,6 +34,8 @@ define([
this.readRoleSearch = ko.observable();
this.readRoleSearch.subscribe(str => this.loadReadRoleSuggestions(str));

this.shareFlag = ko.observable(true);

this.isOwnerFn = params.isOwnerFn;
this.grantAccessFn = params.grantAccessFn;
this.loadAccessListFn = params.loadAccessListFn;
Expand Down Expand Up @@ -109,7 +111,14 @@ define([
} catch (ex) {
console.log(ex);
}
this.isLoading(false);
this.isLoading(false);

// update shareFlag depending on if the shared artifacts reader role is in readAccessList
function testForGlobalRead(value, index, array) {
return value.id === 1; // the 'public' role that every use should have
rkboyce marked this conversation as resolved.
Show resolved Hide resolved
}
let tst = this.readAccessList().some(testForGlobalRead);
this.shareFlag(tst);
}

async grantAccess(perm_type) {
Expand All @@ -124,7 +133,6 @@ define([
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);
Expand All @@ -142,6 +150,31 @@ define([
}
this.isLoading(false);
}


async grantGlobalReadAccess() {
this.isLoading(true);
try {
console.log('grantGlobalReadAccess function called to grant read permissions!! shareflag: ' + this.shareFlag());
await this.grantAccessFn('1','READ'); // 1 is the 'public' role, a SYSTEM role every user should have
await this.loadAccessList();
} catch (ex) {
console.log(ex);
}
this.isLoading(false);
}

async revokeGlobalReadAccess() {
this.isLoading(true);
try {
console.log('revokeGlobalReadAccess function called to REVOKE read permissions!! shareflag: ' + this.shareFlag());
await this.revokeAccessFn('1','READ'); // 1 is the 'public' role, a SYSTEM role every user should have
await this.loadAccessList();
} catch (ex) {
console.log(ex);
}
this.isLoading(false);
}
}

return commonUtils.build('configure-access-modal', ConfigureAccessModal, view);
Expand Down
14 changes: 7 additions & 7 deletions js/pages/cohort-definitions/cohort-definition-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<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>

<!-- ko if: enablePermissionManagement -->
<!-- ko if: (enablePermissionManagement() === true && userCanShare() === true) -->
<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>
Expand Down Expand Up @@ -77,20 +77,20 @@
</li>

<li role="presentation"
data-bind="visible: !previewVersion(), css: { active: $component.tabMode() == 'samples' }, click: clickSampleTab">
<a data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.tabs.samples', 'Samples')"></a>
data-bind="visible: !previewVersion(), css: { active: $component.tabMode() == 'samples' }, click: clickSampleTab">
<a data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.tabs.samples', 'Samples')"></a>
</li>

<li role="presentation"
data-bind="visible: !previewVersion(), css: { active: $component.tabMode() == 'reporting' }, click: function() { $component.selectTab('reporting'); }">
<a data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.tabs.reporting', 'Reporting')"></a>
</li>
<!--

<li role="presentation"
data-bind="visible: !previewVersion(), css: { active: $component.tabMode() == 'explore' }, click: function() { $component.selectTab('explore'); }">
<a data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.tabs.explore', 'Explore')"></a>
data-bind="visible: !previewVersion(), css: { active: $component.tabMode() == 'explore' }, click: function() { $component.selectTab('explore'); }">
<a data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.tabs.explore', 'Explore')"></a>
</li>
-->

rkboyce marked this conversation as resolved.
Show resolved Hide resolved
<li role="presentation"
data-bind="visible: !previewVersion(), css: { active: $component.tabMode() == 'export' }, click: () => { $component.selectTab('export'); refreshPrintFriendly(); }">
<a data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.tabs.export', 'Export')"></a>
Expand Down
29 changes: 24 additions & 5 deletions js/pages/cohort-definitions/cohort-definition-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
'appConfig',
'components/cohortbuilder/CohortDefinition',
'services/CohortDefinition',
'services/ShareRoleCheck',
'services/MomentAPI',
'services/ConceptSet',
'services/Permission',
Expand Down Expand Up @@ -58,14 +59,15 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
'utilities/sql',
'components/conceptset/conceptset-list',
'components/name-validation',
'components/versions/versions'
'components/versions/versions',
], function (
$,
ko,
view,
config,
CohortDefinition,
cohortDefinitionService,
cohortDefinitionService,
shareRoleCheck,
momentApi,
conceptSetService,
PermissionService,
Expand Down Expand Up @@ -99,7 +101,7 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
globalConstants,
constants,
{ entityType },
conceptSetUtils,
conceptSetUtils,
) {
const includeKeys = ["UseEventEnd"];
function pruneJSON(key, value) {
Expand Down Expand Up @@ -198,7 +200,24 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
this.pollTimeoutId = null;
this.authApi = authApi;
this.config = config;
this.enablePermissionManagement = config.enablePermissionManagement;

this.enablePermissionManagement = ko.observable(false);
this.enablePermissionManagement(config.enablePermissionManagement);

this.userCanShare = ko.observable(false);
if (config.permissionManagementRoleId === "") {
this.userCanShare(true);
} else {
shareRoleCheck.checkIfRoleCanShare(authApi.subject(), config.permissionManagementRoleId)
.then(res=>{
this.userCanShare(res);
})
.catch(error => {
console.error(error);
alert(ko.i18n('cohortDefinitions.cohortDefinitionManager.shareRoleCheck', 'Error when determining if user can share cohorts')());
});
}

this.relatedSourcecodesOptions = globalConstants.relatedSourcecodesOptions;
this.commonUtils = commonUtils;
this.isLoading = ko.observable(false);
Expand Down Expand Up @@ -914,7 +933,7 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
}

// METHODS

startPolling(cd, source) {
this.pollId = PollService.add({
callback: () => this.queryHeraclesJob(cd, source),
Expand Down
2 changes: 1 addition & 1 deletion js/pages/concept-sets/conceptset-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<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 -->
<!-- ko if: (enablePermissionManagement() === true && userCanShare() === true) -->
<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>
Expand Down
26 changes: 23 additions & 3 deletions js/pages/concept-sets/conceptset-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ define([
'./const',
'const',
'components/conceptset/utils',
'services/Vocabulary',
'services/Vocabulary',
'services/ShareRoleCheck',
'services/Permission',
'services/Tags',
'components/security/access/const',
Expand Down Expand Up @@ -55,7 +56,8 @@ define([
constants,
globalConstants,
utils,
vocabularyAPI,
vocabularyAPI,
shareRoleCheck,
GlobalPermissionService,
TagsService,
{ entityType },
Expand Down Expand Up @@ -174,7 +176,25 @@ define([
this.canCopy = ko.computed(() => {
return this.currentConceptSet() && this.currentConceptSet().id > 0;
});
this.enablePermissionManagement = config.enablePermissionManagement;

this.enablePermissionManagement = ko.observable(false);
this.enablePermissionManagement(config.enablePermissionManagement);

this.userCanShare = ko.observable(false);
if (config.permissionManagementRoleId === "") {
this.userCanShare(true);
} else {
shareRoleCheck.checkIfRoleCanShare(authApi.subject(), config.permissionManagementRoleId)
.then(res=>{
this.userCanShare(res);
})
.catch(error => {
console.error(error);
alert(ko.i18n('conceptSets.conceptSetManager.shareRoleCheck', 'Error when determining if user can share concept sets')());
});
}


this.isSaving = ko.observable(false);
this.isDeleting = ko.observable(false);
this.isOptimizing = ko.observable(false);
Expand Down
14 changes: 12 additions & 2 deletions js/services/AuthAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,18 @@ define(function(require, exports) {
}

var isPermittedGenerateCohort = function(cohortId, sourceKey) {
return isPermitted('cohortdefinition:' + cohortId + ':generate:' + sourceKey + ':get') &&
isPermitted('cohortdefinition:' + cohortId + ':info:get');
var v = isPermitted('cohortdefinition:' + cohortId + ':generate:' + sourceKey + ':get') &&
isPermitted('cohortdefinition:' + cohortId + ':info:get');

// By default, everyone can generate any artifact they have
// permission to read. If a permissionManagementRoleId has
// been assigned, (non- empty string assignment), the default
// generate functionality is not desired. Rather, users will have to
// have a role that allows them to update the specific cohort definition.
if (config.permissionManagementRoleId !== ""){
v = v && isPermitted('cohortdefinition:' + cohortId + ':put')
}
return v
}

var isPermittedReadCohortReport = function(cohortId, sourceKey) {
Expand Down
32 changes: 32 additions & 0 deletions js/services/ShareRoleCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
define(function (require, exports) {
var $ = require('jquery');
var constants = require('const');
const httpService = require('services/http');

async function getRoleUsers(subject, permissionManagementRoleId) {
return await httpService.doGet(constants.apiPaths.roleUsers(permissionManagementRoleId))
rkboyce marked this conversation as resolved.
Show resolved Hide resolved
.then(({ data = [] }) => data)
.catch((er) => {
console.error('ERROR: Can\'t find users with permissionManagementRoleId: ' + permissionManagementRoleId);
});
};

async function checkIfRoleCanShare(subject, permissionManagementRoleId) {
var isAbleToShare = false;
const roleUsers = await getRoleUsers(subject, permissionManagementRoleId);
console.log("INFO: roleUsers:" + roleUsers.toString());

roleUsers.forEach((user) => {
console.log("INFO: user.login of user that has the permissionManagementRoleId " + permissionManagementRoleId + ": " + user.login + "; subject (currently logged in user): " + subject);
if (subject == user.login){
isAbleToShare = true;
}
});
console.log("INFO: isAbleToShare: " + isAbleToShare);
return isAbleToShare;
};

return {
checkIfRoleCanShare,
};
});
Loading