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

SF-3076 Simplify guest role sharing settings #2858

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions mongodb/Migrations/20210309-AddProjectRoleToShareKeys.mongodb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SF-1199 Include project role in share key
*
* Date: 9 March 2021
* Author: Raymond Luong
*
* For migrating share keys to have a role and expiration time
*
* Steps:
* 1. Open mongosh. Instructions for downloading the MongoDB Shell are here: https://docs.mongodb.com/manual/mongo/
* 2. Paste the following command into mongosh and hit 'enter'
* 3. If there are no errors MongoDB will report the number of documents updated
*/

use('xforge');

db.sf_project_secrets.updateMany(
{},
{ $set: { "shareKeys.$[elem].projectRole": "sf_community_checker" } },
{ arrayFilters: [ { "elem.projectRole": { $exists: false } } ] }
);

db.sf_project_secrets.updateMany(
{},
{ $set: { "shareKeys.$[elem].expirationTime": new Date(2021, 11) } },
{ arrayFilters: [ { "elem.expirationTime": { $exists: false } } ] }
);
24 changes: 24 additions & 0 deletions mongodb/Migrations/20230322-AddRecipientShareLinkType.mongodb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SF-1810 Allow inviting users without sending invitation email
*
* Date: 22 March 2023
* Author: Nigel Wells
*
* The new sharing method allows for specifying a recipient or anyone for the type of link
* Previously a recipient only link had an email address and would be treated differently
* This script sets any email share keys as a recipient key to ensure they aren't treated as a key for anyone
*/

use("xforge");

db.sf_project_secrets.updateMany(
{},
{
$set: {
"shareKeys.$[shareKey].shareLinkType": "recipient"
}
},
{
arrayFilters: [{ "shareKey.email": { $exists: true } }]
}
);
64 changes: 64 additions & 0 deletions mongodb/Migrations/20241127-AddCreatedByRoleToShareKeys.mongodb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* SF-3076 Simplify guest role sharing settings
*
* Date: 27 November 2024
* Author: Peter Chapman
*
* This script migrates the sf_project_secrets collection as follows:
*
* - Add the createdByRole property to shareKeys
* - Set createdByRole to 'pt_administrator' if createdByAdmin is true
* - Set createdByRole to projectRole if createdByAdmin is false or does not exist
* - Remove the createdByAdmin property
*
* This script can be run via the following command:
*
* mongosh 20241127-AddCreatedByRoleToShareKeys.mongodb
*/

use("xforge");

// 1. Migrate to the createdByRole filed
db.sf_project_secrets.updateMany(
{
"shareKeys.createdByRole": { $exists: false }
},
[
{
$set: {
shareKeys: {
$map: {
input: "$shareKeys",
as: "shareKey",
in: {
$mergeObjects: [
"$$shareKey",
{
createdByRole: {
$cond: {
if: { $eq: ["$$shareKey.createdByAdmin", true] },
then: "pt_administrator",
else: "$$shareKey.projectRole"
}
}
}
]
}
}
}
}
}
]
);

// 2. Remove the createdByAdmin field from share keys
db.sf_project_secrets.updateMany(
{
"shareKeys.createdByAdmin": { $exists: true }
},
{
$unset: {
"shareKeys.$[].createdByAdmin": ""
}
}
);
18 changes: 0 additions & 18 deletions mongodb/Migrations/AddRecipientShareLinkType.mongodb

This file was deleted.

18 changes: 0 additions & 18 deletions src/Migrations/ShareKeys/migrate-sharekeys.txt

This file was deleted.

10 changes: 7 additions & 3 deletions src/RealtimeServer/common/models/project-rights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export enum Operation {
// See https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
export type ProjectRight = [domain: string, operation: `${Operation}`];

/**
* NOTE: When updating this class, be sure to update SFProjectRights in C#.
*/
export class ProjectRights {
private readonly rights = new Map<string, string[]>();

Expand All @@ -27,9 +30,10 @@ export class ProjectRights {
}

hasRight(project: Project, userId: string, projectDomain: string, operation: Operation, data?: OwnedData): boolean {
const rights = (this.rights.get(project.userRoles[userId]) || []).concat(
(project.userPermissions || {})[userId] || []
);
const userRole: string = project.userRoles[userId];
const rights = (this.rights.get(userRole) || [])
.concat((project.userPermissions || {})[userId] || [])
.concat((project.rolePermissions || {})[userRole] || []);

if (rights.includes(this.joinRight(projectDomain, operation))) {
return operation === Operation.Create && userId != null && data != null ? userId === data.ownerRef : true;
Expand Down
1 change: 1 addition & 0 deletions src/RealtimeServer/common/models/project.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface Project {
name: string;
rolePermissions: { [role: string]: string[] };
userRoles: { [userRef: string]: string };
userPermissions: { [userRef: string]: string[] };
/** Whether the project has its capability to synchronize project data turned off. */
Expand Down
2 changes: 2 additions & 0 deletions src/RealtimeServer/common/realtime-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ describe('RealtimeServer', () => {
userRoles: {
user01: 'user'
},
rolePermissions: {},
userPermissions: {}
});
await submitOp(userConn, PROJECTS_COLLECTION, 'project02', []);
Expand Down Expand Up @@ -659,6 +660,7 @@ class TestEnvironment {
userRoles: {
user01: 'admin'
},
rolePermissions: {},
userPermissions: {}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ class TestEnvironment {
userOwn: 'userOwn',
observer: 'observer'
},
rolePermissions: {},
userPermissions: {}
});

Expand Down
1 change: 1 addition & 0 deletions src/RealtimeServer/common/services/project-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class TestEnvironment {
projectAdmin: 'admin',
user: 'user'
},
rolePermissions: {},
userPermissions: {}
});
}
Expand Down
12 changes: 12 additions & 0 deletions src/RealtimeServer/common/services/project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ export abstract class ProjectService<T extends Project = Project> extends JsonDo
name: {
bsonType: 'string'
},
rolePermissions: {
bsonType: 'object',
patternProperties: {
'^[a-z_]+$': {
bsonType: 'array',
items: {
bsonType: 'string'
}
}
},
additionalProperties: false
},
userRoles: {
bsonType: 'object',
patternProperties: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export enum CheckingAnswerExport {
export interface CheckingConfig {
checkingEnabled: boolean;
usersSeeEachOthersResponses: boolean;
shareEnabled: boolean;
answerExportMethod: CheckingAnswerExport;
noteTagId?: number;
hideCommunityCheckingText?: boolean;
Expand Down
104 changes: 8 additions & 96 deletions src/RealtimeServer/scriptureforge/models/sf-project-rights.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Operation, ProjectRight, ProjectRights } from '../../common/models/project-rights';
import rightsByRole from '../rightsByRole.json';
import { SFProjectRole } from './sf-project-role';

export enum SFProjectDomain {
Expand All @@ -16,108 +17,19 @@ export enum SFProjectDomain {
Notes = 'notes',
TextAudio = 'text_audio',
TrainingData = 'training_data',
Drafts = 'drafts'
Drafts = 'drafts',
UserInvites = 'user_invites'
}

// See https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
// Domain is marked optional because each role does not need to list the domains exhaustively
const rightsByRole: Record<SFProjectRole, { [domain in `${SFProjectDomain}`]?: `${Operation}`[] }> = {
sf_observer: {
project_user_configs: ['view_own', 'edit_own'],
texts: ['view'],
sf_note_threads: ['view'],
notes: ['view'],
text_audio: ['view']
},
pt_observer: {
project_user_configs: ['view_own', 'edit_own'],
project: ['view'],
texts: ['view'],
questions: ['view'],
answers: ['view'],
answer_status: ['view'],
answer_comments: ['view'],
likes: ['view'],
biblical_terms: ['view'],
pt_note_threads: ['view'],
sf_note_threads: ['view'],
notes: ['view'],
text_audio: ['view']
},
sf_commenter: {
project_user_configs: ['view_own', 'edit_own'],
texts: ['view'],
sf_note_threads: ['view', 'create', 'delete_own'],
notes: ['view', 'create', 'edit_own', 'delete_own'],
text_audio: ['view']
},
sf_community_checker: {
project_user_configs: ['view_own', 'edit_own'],
texts: ['view'],
questions: ['view'],
answers: ['view', 'create', 'edit_own', 'delete_own'],
answer_status: ['view'],
answer_comments: ['view', 'create', 'edit_own', 'delete_own'],
likes: ['view', 'create', 'delete_own'],
text_audio: ['view']
},
pt_consultant: {
project_user_configs: ['view_own', 'edit_own'],
project: ['view'],
texts: ['view'],
questions: ['view'],
answers: ['view'],
answer_status: ['view'],
answer_comments: ['view'],
likes: ['view'],
biblical_terms: ['view'],
pt_note_threads: ['view', 'create', 'edit', 'delete_own'],
sf_note_threads: ['view', 'create', 'edit', 'delete_own'],
notes: ['view', 'create', 'edit_own', 'delete_own'],
text_audio: ['view']
},
pt_translator: {
project_user_configs: ['view_own', 'edit_own'],
project: ['view'],
texts: ['view', 'edit'],
questions: ['view'],
answers: ['view', 'create', 'edit_own', 'delete_own'],
answer_comments: ['view', 'create', 'edit_own', 'delete_own'],
answer_status: ['view'],
likes: ['view', 'create', 'delete_own'],
biblical_terms: ['view', 'edit'],
pt_note_threads: ['view', 'create', 'edit', 'delete_own'],
sf_note_threads: ['view', 'create', 'edit', 'delete_own'],
notes: ['view', 'create', 'edit_own', 'delete_own'],
text_audio: ['view'],
training_data: ['view', 'create', 'edit_own', 'delete_own'],
drafts: ['view']
},
pt_administrator: {
project_user_configs: ['view_own', 'edit_own'],
project: ['view'],
texts: ['view', 'edit'],
questions: ['view', 'create', 'edit', 'delete'],
answers: ['view', 'create', 'delete', 'edit_own'],
answer_comments: ['view', 'create', 'edit_own', 'delete'],
answer_status: ['view', 'edit'],
likes: ['view', 'create', 'delete_own'],
biblical_terms: ['view', 'edit'],
pt_note_threads: ['view', 'create', 'edit', 'delete'],
sf_note_threads: ['view', 'create', 'edit', 'delete'],
notes: ['view', 'create', 'edit_own', 'delete'],
text_audio: ['view', 'edit', 'create', 'delete'],
training_data: ['view', 'create', 'edit', 'delete'],
drafts: ['view']
},
none: {}
};

export class SFProjectRights extends ProjectRights {
constructor() {
super();

for (const [role, rights] of Object.entries(rightsByRole)) {
// See https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
// Domain is marked optional because each role does not need to list the domains exhaustively
for (const [role, rights] of Object.entries(
rightsByRole as Record<SFProjectRole, { [domain in `${SFProjectDomain}`]?: `${Operation}`[] }>
)) {
const rightsForRole: ProjectRight[] = [];
for (const [domain, operations] of Object.entries(rights)) {
for (const operation of operations) rightsForRole.push([domain, operation]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SFProject, SFProjectProfile } from './sf-project';
function testProjectProfile(ordinal: number): SFProjectProfile {
return {
name: `Test project ${ordinal}`,
rolePermissions: {},
userRoles: {},
userPermissions: {},
syncDisabled: false,
Expand All @@ -15,7 +16,6 @@ function testProjectProfile(ordinal: number): SFProjectProfile {
isRightToLeft: false,
translateConfig: {
translationSuggestionsEnabled: false,
shareEnabled: false,
preTranslate: false,
defaultNoteTagId: 1,
draftConfig: {
Expand All @@ -31,7 +31,6 @@ function testProjectProfile(ordinal: number): SFProjectProfile {
checkingConfig: {
checkingEnabled: true,
usersSeeEachOthersResponses: true,
shareEnabled: false,
answerExportMethod: CheckingAnswerExport.MarkedForExport,
hideCommunityCheckingText: false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export interface DraftConfig {
export interface TranslateConfig {
translationSuggestionsEnabled: boolean;
source?: TranslateSource;
shareEnabled: boolean;
defaultNoteTagId?: number;
preTranslate: boolean;
draftConfig: DraftConfig;
Expand Down
Loading
Loading