diff --git a/api/src/test-util/testConfig.ts b/api/src/test-util/testConfig.ts index de8afa87..5621c242 100644 --- a/api/src/test-util/testConfig.ts +++ b/api/src/test-util/testConfig.ts @@ -95,7 +95,7 @@ export const testConfig: GramConfiguration = { componentClasses: classes.map(toComponentClass), identityProviders: [new DummyIdentityProvider()], reviewerProvider: testReviewerProvider, - authzProvider: new DefaultAuthzProvider(), + authzProvider: new DefaultAuthzProvider(dal), userProvider: testUserProvider, systemProvider: testSystemProvider, suggestionSources: [], diff --git a/config/default.ts b/config/default.ts index adf97d1d..34927d49 100644 --- a/config/default.ts +++ b/config/default.ts @@ -192,6 +192,7 @@ export const defaultConfig: GramConfiguration = { fallbackReviewer ), authzProvider: new StaticAuthzProvider( + dal, [sampleUsers[0].sub], [sampleUsers[1].sub], [sampleUsers[2].sub] diff --git a/config/providers/static/StaticAuthzProvider.ts b/config/providers/static/StaticAuthzProvider.ts index 08b41532..27e93414 100644 --- a/config/providers/static/StaticAuthzProvider.ts +++ b/config/providers/static/StaticAuthzProvider.ts @@ -1,14 +1,16 @@ import { DefaultAuthzProvider } from "@gram/core/dist/auth/DefaultAuthzProvider.js"; import { Role } from "@gram/core/dist/auth/models/Role.js"; +import { DataAccessLayer } from "@gram/core/dist/data/dal.js"; export class StaticAuthzProvider extends DefaultAuthzProvider { key: string = "static"; constructor( + dal: DataAccessLayer, public users: string[], public reviewers: string[], public admins: string[] ) { - super(); + super(dal); } async getRolesForUser(sub: string): Promise { diff --git a/core/src/auth/DefaultAuthzProvider.ts b/core/src/auth/DefaultAuthzProvider.ts index 384808ac..9b189499 100644 --- a/core/src/auth/DefaultAuthzProvider.ts +++ b/core/src/auth/DefaultAuthzProvider.ts @@ -1,5 +1,7 @@ +import { DataAccessLayer } from "../data/dal.js"; import Model from "../data/models/Model.js"; import { RequestContext } from "../data/providers/RequestContext.js"; +import { ReviewStatus } from "../data/reviews/Review.js"; import { systemProvider } from "../data/systems/systems.js"; import { AuthzProvider } from "./AuthzProvider.js"; import { AllPermissions, Permission } from "./authorization.js"; @@ -7,6 +9,8 @@ import { Role } from "./models/Role.js"; import { UserToken } from "./models/UserToken.js"; export class DefaultAuthzProvider implements AuthzProvider { + constructor(protected dal: DataAccessLayer) {} + async getPermissionsForSystem( ctx: RequestContext, systemId: string, @@ -54,34 +58,54 @@ export class DefaultAuthzProvider implements AuthzProvider { model: Model, user: UserToken ): Promise { + const review = await this.dal.reviewService.getByModelId(model.id!); if (model.systemId) { - return this.getPermissionsForSystem(ctx, model.systemId, user); + let systemPermissions = await this.getPermissionsForSystem( + ctx, + model.systemId, + user + ); + if (review?.status === ReviewStatus.Approved) { + systemPermissions = systemPermissions.filter( + (p) => p !== Permission.Write + ); + } + return systemPermissions; } if (user.roles.length === 0) return []; - if (user.roles.find((r) => r === Role.Admin)) return AllPermissions; + const permissions: Set = new Set(); + + if (user.roles.find((r) => r === Role.Admin)) { + AllPermissions.forEach((p) => permissions.add(p)); + } /** * Standalone models are mainly used for training. To avoid authz issues here we allow most things * by most users. Ideally here there should be some sharing system. */ - - const permissions: Permission[] = []; - if (user.roles.find((r) => r === Role.Reviewer)) { - permissions.push(Permission.Read, Permission.Review, Permission.Write); + [Permission.Read, Permission.Review, Permission.Write].forEach((p) => + permissions.add(p) + ); } if (user.roles.find((r) => r === Role.User)) { - permissions.push(Permission.Read, Permission.Write); + [Permission.Read, Permission.Write].forEach((p) => permissions.add(p)); } if (model.createdBy === user.sub) { - permissions.push(Permission.Read, Permission.Write, Permission.Delete); + [Permission.Read, Permission.Write, Permission.Delete].forEach((p) => + permissions.add(p) + ); } - return permissions; + if (review?.status === ReviewStatus.Approved) { + permissions.delete(Permission.Write); + } + + return [...permissions]; } async getRolesForUser(sub: string): Promise { diff --git a/core/src/auth/authorization.ts b/core/src/auth/authorization.ts index 1927717c..98748556 100644 --- a/core/src/auth/authorization.ts +++ b/core/src/auth/authorization.ts @@ -4,7 +4,6 @@ import { RequestContext } from "../data/providers/RequestContext.js"; import { NotFoundError } from "../util/errors.js"; import { AuthzError } from "./AuthzError.js"; import { AuthzProvider } from "./AuthzProvider.js"; -import { DefaultAuthzProvider } from "./DefaultAuthzProvider.js"; import { DummyAuthzProvider } from "./DummyAuthzProvider.js"; import { Role } from "./models/Role.js"; import { UserToken } from "./models/UserToken.js"; diff --git a/plugins/ldap/README.md b/plugins/ldap/README.md index e5fac585..a54749d2 100644 --- a/plugins/ldap/README.md +++ b/plugins/ldap/README.md @@ -6,6 +6,7 @@ Plugin for different providers using LDAP. Warning: these may require some extra - **LDAPGroupBasedAuthzProvider** - Performs lookups on logged in users and maps roles via groups. - **LDAPUserProvider** - Uses LDAP to provide information on users. - **LDAPGroupBasedReviewerProvider** - Provides reviewers based on one or more groups +- **LDAPTeamProvider** - Provides teams based on mapping attributes to teams ## Configuration diff --git a/plugins/ldap/src/LDAPGroupBasedAuthzProvider.ts b/plugins/ldap/src/LDAPGroupBasedAuthzProvider.ts index 9242eb1a..a85bd384 100644 --- a/plugins/ldap/src/LDAPGroupBasedAuthzProvider.ts +++ b/plugins/ldap/src/LDAPGroupBasedAuthzProvider.ts @@ -4,6 +4,7 @@ import { Role } from "@gram/core/dist/auth/models/Role.js"; import { LDAPClientSettings } from "./LDAPClientSettings.js"; import { connectLdapClient, ldapQueryOne } from "./lookup.js"; import { escapeFilterValue, getAttributeAsArray } from "./util.js"; +import { DataAccessLayer } from "@gram/core/dist/data/dal.js"; export interface LDAPAuthzProviderSettings { ldapSettings: LDAPClientSettings; @@ -22,8 +23,11 @@ export class LDAPGroupBasedAuthzProvider extends DefaultAuthzProvider implements AuthzProvider { - constructor(public settings: LDAPAuthzProviderSettings) { - super(); + constructor( + dal: DataAccessLayer, + public settings: LDAPAuthzProviderSettings + ) { + super(dal); } async getRolesForUser(sub: string): Promise {