Skip to content
Merged
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
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,15 @@ OSMT optionally supports role-based access, with these roles:
- Curator: an OSMT user with a curator role can update but not create RSDs and collections. This role is for someone who would publish and unpublish RSDs and Collections
- Viewer: an OSMT user with a viewer role is a logged-in user who can not make modifications to RSDs or Collections.

Role-based access is disabled by default. You can follow these steps to enable it.
Role-based access is disabled by default for the UI (front end) and REST (back end). Use these steps to enable roles.

* Note: if the role value is false, all endpoints will be exposed, make sure to enable this value to use Roles

Front End: In your [`auth-roles.ts`](ui/src/app/auth/auth-roles.ts) file, configure these values:
```text
BACKEND: application.properties file
app.enableRole=true

FRONTEND: ui/src/app/auth/auth-roles.ts
export const ENABLE_ROLES = true
export const ENABLE_ROLES = true
```

In your [`application.properties`](api/src/main/resources/config/application.properties) file, configure these values:
Back End: In your [`application.properties`](api/src/main/resources/config/application.properties) file, configure these values:
```
# Roles settings
app.enableRoles=true
Expand All @@ -216,6 +213,7 @@ osmt.security.role.curator=ROLE_Osmt_Curator
osmt.security.role.view=ROLE_Osmt_View
osmt.security.scope.read=SCOPE_osmt.read
```
* NOTE: if app.enableRoles=false, all endpoints will be accessible by any authenticated user.
* You can use these values, or you can provide your own based on your own authorization tooling. For Okta, you will need to use the uppercase `ROLE_` prefix on your role.
* `read` is a scope, not a role. This is for machine-to-machine access, rather than for authenticated OSMT users.

Expand Down
1 change: 1 addition & 0 deletions api/src/main/resources/config/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ app.baseUrl=http://localhost:8080

# Spring Boot will serve frontend files via port 8080
# If you are using ng serve to proxy static files built by Angular, set OSMT_FRONT_END_PORT to 4200
#app.frontEndPort=${OSMT_FRONT_END_PORT:4200}
app.frontEndPort=${OSMT_FRONT_END_PORT:8080}
app.frontendUrl=http://localhost:${app.frontEndPort}
app.security.cors.allowedOrigins=${app.baseUrl},${app.frontendUrl}
Expand Down
3 changes: 2 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
"scripts": {
"ng": "./node_modules/@angular/cli/bin/ng",
"start": "./node_modules/@angular/cli/bin/ng serve",
"start-debug": "./node_modules/@angular/cli/bin/ng serve --source-map",
"start-hotreload": "./node_modules/@angular/cli/bin/ng serve --live-reload",
"build": "./node_modules/@angular/cli/bin/ng build",
"build-prod": "./node_modules/@angular/cli/bin/ng build --prod",
"test": "./node_modules/@angular/cli/bin/ng test",
"lint": "./node_modules/@angular/cli/bin/ng lint",
"e2e": "./node_modules/@angular/cli/bin/ng e2e",
"ci-test": "./node_modules/@angular/cli/bin/ng test --no-watch --no-progress --karma-config=karma.ci.conf.js",
"clean": "rm -rf dist; rm -rf coverage; rm -rf reports; rm -rf test-results; rm -rf node_modules"
"clean": "rm -rf ../api/src/main/resources/ui/*; rm -rf coverage; rm -rf reports; rm -rf test-results; rm -rf node_modules"
},
"private": true,
"dependencies": {
Expand Down
24 changes: 15 additions & 9 deletions ui/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {ManageCollectionComponent} from "./collection/detail/manage-collection.c
import {PublishCollectionComponent} from "./collection/detail/publish-collection.component";
import {CollectionSkillSearchComponent} from "./collection/collection-skill-search.component";
import {BatchImportComponent} from "./richskill/import/batch-import.component";
import { OSMT_ADMIN, OSMT_CURATOR } from "./auth/auth-roles"
import { ActionByRoles, ButtonAction } from "./auth/auth-roles"


const routes: Routes = [
Expand All @@ -33,7 +33,7 @@ const routes: Routes = [
component: RichSkillFormComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN, OSMT_CURATOR]
roles: ActionByRoles.get(ButtonAction.SkillCreate)
},
canDeactivate: [FormDirtyGuard]
},
Expand All @@ -47,15 +47,18 @@ const routes: Routes = [
component: RichSkillFormComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN]
roles: ActionByRoles.get(ButtonAction.SkillUpdate)
},
canDeactivate: [FormDirtyGuard]
},
// clone skill
{path: "skills/:uuid/duplicate",
component: RichSkillFormComponent,
canActivate: [AuthGuard],
canDeactivate: [FormDirtyGuard]
canDeactivate: [FormDirtyGuard],
data: {
roles: ActionByRoles.get(ButtonAction.SkillCreate)
},
},
// manage skill
{path: "skills/:uuid/manage",
Expand All @@ -72,7 +75,7 @@ const routes: Routes = [
component: BatchImportComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN, OSMT_CURATOR]
roles: ActionByRoles.get(ButtonAction.SkillCreate)
},
},

Expand All @@ -83,7 +86,7 @@ const routes: Routes = [
component: CollectionFormComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN, OSMT_CURATOR]
roles: ActionByRoles.get(ButtonAction.CollectionCreate)
},
canDeactivate: [FormDirtyGuard]
},
Expand All @@ -97,7 +100,7 @@ const routes: Routes = [
component: CollectionFormComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN]
roles: ActionByRoles.get(ButtonAction.CollectionUpdate)
},
canDeactivate: [FormDirtyGuard]
},
Expand All @@ -111,21 +114,24 @@ const routes: Routes = [
component: PublishCollectionComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN]
roles: ActionByRoles.get(ButtonAction.CollectionPublish)
},
},
// find skills to add to a collection
{path: "collections/:uuid/add-skills",
component: CollectionSkillSearchComponent,
canActivate: [AuthGuard],
data: {
roles: [OSMT_ADMIN]
roles: ActionByRoles.get(ButtonAction.CollectionSkillsUpdate)
},
},
// find a collection to add a selection of skills to
{path: "collections/add-skills",
component: AddSkillsCollectionComponent,
canActivate: [AuthGuard],
data: {
roles: ActionByRoles.get(ButtonAction.CollectionSkillsUpdate)
},
},
// collections library
{path: "collections",
Expand Down
22 changes: 21 additions & 1 deletion ui/src/app/auth/auth-roles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
// Default values of OSMT Roles
export const OSMT_ADMIN = "ROLE_Osmt_Admin"
export const OSMT_CURATOR = "ROLE_Osmt_Curator"

export const ENABLE_ROLES = false

export enum ButtonAction {
SkillUpdate,
SkillCreate,
SkillPublish,
CollectionUpdate,
CollectionCreate,
CollectionPublish,
CollectionSkillsUpdate
}

export const ActionByRoles = new Map<number, string[]>([
[ButtonAction.SkillUpdate, [OSMT_ADMIN, OSMT_CURATOR]],
[ButtonAction.SkillCreate, [OSMT_ADMIN, OSMT_CURATOR]],
[ButtonAction.SkillPublish, [OSMT_ADMIN]],
[ButtonAction.CollectionUpdate, [OSMT_ADMIN, OSMT_CURATOR]],
[ButtonAction.CollectionCreate, [OSMT_ADMIN, OSMT_CURATOR]],
[ButtonAction.CollectionPublish, [OSMT_ADMIN]],
[ButtonAction.CollectionSkillsUpdate, [OSMT_ADMIN]],
]);

20 changes: 19 additions & 1 deletion ui/src/app/auth/auth-service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {ENABLE_ROLES, ButtonAction, ActionByRoles} from "./auth-roles";
import { Injectable } from "@angular/core"
import { Router } from "@angular/router"
import { DEFAULT_INTERRUPTSOURCES, Idle } from "@ng-idle/core"
import { Keepalive } from "@ng-idle/keepalive"
import { Whitelabelled } from "../../whitelabel"
import { IAuthService } from "./iauth-service"


export const STORAGE_KEY_TOKEN = "OSMT.AuthService.accessToken"
export const STORAGE_KEY_RETURN = "OSMT.AuthService.return"
export const STORAGE_KEY_ROLE = "OSMT.AuthService.role"
Expand Down Expand Up @@ -72,6 +72,24 @@ export class AuthService extends Whitelabelled implements IAuthService {
return localStorage.getItem(STORAGE_KEY_ROLE) as string
}

hasRole(requiredRoles: string[], userRoles: string[]): boolean {
for (const role of userRoles) {
if (requiredRoles?.indexOf(role) !== -1) {
return true
}
}
return false
}

isEnabledByRoles(buttonAction : ButtonAction): boolean {
if (ENABLE_ROLES) {
const allowedRoles = ActionByRoles.get(buttonAction) ?? [];
const userRoles = this.getRole()?.split(",");
return this.hasRole(allowedRoles, userRoles);
}
return true;
}

private watchForIdle(): void {
this.idle.setIdle(this.whitelabel.idleTimeoutInSeconds)
this.idle.setTimeout(1)
Expand Down
4 changes: 2 additions & 2 deletions ui/src/app/auth/auth.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AuthService } from "./auth-service"
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot} from "@angular/router"
import { HttpClientTestingModule } from "@angular/common/http/testing"
import { AuthServiceStub, RouterStub } from "../../../test/resource/mock-stubs"
import { ENABLE_ROLES, OSMT_ADMIN, OSMT_CURATOR } from "./auth-roles"
import {ActionByRoles, ButtonAction, ENABLE_ROLES} from "./auth-roles"


describe("AuthGuard", () => {
Expand Down Expand Up @@ -38,7 +38,7 @@ describe("AuthGuard", () => {
it("should return true", () => {
// Arrange
const route = Object.assign({}, ActivatedRouteSnapshot.prototype, {
data: {roles: [OSMT_ADMIN, OSMT_CURATOR]}
data: {roles: ActionByRoles.get(ButtonAction.SkillCreate)}
})

// Act and Assert
Expand Down
11 changes: 1 addition & 10 deletions ui/src/app/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class AuthGuard implements CanActivate {
const requiredRoles = route.data?.roles
if (requiredRoles) {
const userRoles = this.authService.getRole()?.split(",")
if (!ENABLE_ROLES || this.hasRole(requiredRoles, userRoles)) {
if (!ENABLE_ROLES || this.authService.hasRole(requiredRoles, userRoles)) {
return true
}
this.toastService.showToast("Whoops!", "You need permission to perform this action. If this seems to be an error, please contact your OSMT administrator.")
Expand All @@ -31,13 +31,4 @@ export class AuthGuard implements CanActivate {
return false
}

private hasRole(requiredRoles: string[], userRoles: string[]): boolean {
for (const role of userRoles) {
if (requiredRoles?.indexOf(role) !== -1) {
return true
}
}
return false
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { AppConfig } from "src/app/app.config"
import { EnvironmentService } from "src/app/core/environment.service"
import { ActivatedRouteStubSpec } from "test/util/activated-route-stub.spec"
import { createMockCollectionSummary, createMockPaginatedCollections } from "../../../test/resource/mock-data"
import { CollectionServiceStub, SearchServiceStub } from "../../../test/resource/mock-stubs"
import {AuthServiceStub, CollectionServiceStub, SearchServiceStub} from "../../../test/resource/mock-stubs"
import { PublishStatus } from "../PublishStatus"
import { ApiAdvancedSearch, ApiSearch } from "../richskill/service/rich-skill-search.service"
import { SearchService } from "../search/search.service"
import { ToastService } from "../toast/toast.service"
import { CollectionSearchResultsComponent } from "./collection-search-results.component"
import { CollectionService } from "./service/collection.service"
import {AuthService} from "../auth/auth-service";


export function createComponent(T: Type<CollectionSearchResultsComponent>, f?: () => void): Promise<void> {
Expand Down Expand Up @@ -65,6 +66,7 @@ describe("CollectionSearchResultsComponent", () => {
{ provide: CollectionService, useClass: CollectionServiceStub },
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: Router, useValue: routerSpy },
{ provide: AuthService, useClass: AuthServiceStub },
]
})
.compileComponents()
Expand Down Expand Up @@ -208,6 +210,7 @@ describe("RichCollectionSearchResultsComponent with latestSearch", () => {
{ provide: CollectionService, useClass: CollectionServiceStub },
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: Router, useValue: routerSpy },
{ provide: AuthService, useClass: AuthServiceStub },
]
})
.compileComponents()
Expand Down Expand Up @@ -257,6 +260,7 @@ describe("RichCollectionSearchResultsComponent with params", () => {
{ provide: CollectionService, useClass: CollectionServiceStub },
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: Router, useValue: routerSpy },
{ provide: AuthService, useClass: AuthServiceStub },
]
})
.compileComponents()
Expand Down
6 changes: 4 additions & 2 deletions ui/src/app/collection/collection-search-results.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {CollectionService} from "./service/collection.service";
import {ApiSkillSummary, ICollectionSummary} from "../richskill/ApiSkillSummary";
import {determineFilters} from "../PublishStatus";
import {Title} from "@angular/platform-browser";
import {AuthService} from "../auth/auth-service";

@Component({
selector: "app-collection-search-results",
Expand All @@ -29,9 +30,10 @@ export class CollectionSearchResultsComponent extends CollectionsListComponent i
protected collectionService: CollectionService,
protected searchService: SearchService,
protected route: ActivatedRoute,
protected titleService: Title
protected titleService: Title,
protected authService: AuthService
) {
super(router, toastService, collectionService)
super(router, toastService, collectionService, authService)
this.searchService.searchQuery$.subscribe(apiSearch => this.handleNewSearch(apiSearch) )
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { async, ComponentFixture, TestBed } from "@angular/core/testing"
import { Title } from "@angular/platform-browser"
import { RouterTestingModule } from "@angular/router/testing"
import { createMockPaginatedSkills, createMockSkillSummary } from "../../../test/resource/mock-data"
import { CollectionServiceStub, RichSkillServiceStub } from "../../../test/resource/mock-stubs"
import {AuthServiceStub, CollectionServiceStub, RichSkillServiceStub} from "../../../test/resource/mock-stubs"
import { AppConfig } from "../app.config"
import { EnvironmentService } from "../core/environment.service"
import { ApiSearch, PaginatedSkills } from "../richskill/service/rich-skill-search.service"
Expand All @@ -14,6 +14,7 @@ import { TableActionDefinition } from "../table/skills-library-table/has-action-
import { ToastService } from "../toast/toast.service"
import { CollectionSkillSearchComponent } from "./collection-skill-search.component"
import { CollectionService } from "./service/collection.service"
import {AuthService} from "../auth/auth-service";


export function createComponent(T: Type<CollectionSkillSearchComponent>): Promise<void> {
Expand Down Expand Up @@ -52,6 +53,7 @@ describe("CollectionSkillSearchComponent", () => {
ToastService,
{ provide: CollectionService, useClass: CollectionServiceStub },
{ provide: RichSkillService, useClass: RichSkillServiceStub },
{ provide: AuthService, useClass: AuthServiceStub },
]
})
.compileComponents()
Expand Down
6 changes: 4 additions & 2 deletions ui/src/app/collection/collection-skill-search.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {TableActionDefinition} from "../table/skills-library-table/has-action-de
import {ApiSkillSummary} from "../richskill/ApiSkillSummary";
import {SkillsListComponent} from "../richskill/list/skills-list.component";
import {ApiTaskResult} from "../task/ApiTaskResult";
import {AuthService} from "../auth/auth-service";

@Component({
selector: "app-collection-skill-search",
Expand Down Expand Up @@ -40,9 +41,10 @@ export class CollectionSkillSearchComponent extends SkillsListComponent implemen
protected location: Location,
protected collectionService: CollectionService,
protected richSkillService: RichSkillService,
protected toastService: ToastService
protected toastService: ToastService,
protected authService: AuthService,
) {
super(router, richSkillService, toastService)
super(router, richSkillService, toastService, authService)
this.titleService.setTitle(`Add RSDs to Collection | ${this.whitelabel.toolName}`)

this.uuidParam = this.route.snapshot.paramMap.get("uuid") || undefined
Expand Down
4 changes: 3 additions & 1 deletion ui/src/app/collection/collections-list.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { Component, Type } from "@angular/core"
import { async, ComponentFixture, TestBed } from "@angular/core/testing"
import { RouterTestingModule } from "@angular/router/testing"
import { createMockCollectionSummary, createMockPaginatedCollections } from "../../../test/resource/mock-data"
import { CollectionServiceStub } from "../../../test/resource/mock-stubs"
import {AuthServiceStub, CollectionServiceStub} from "../../../test/resource/mock-stubs"
import { PublishStatus } from "../PublishStatus"
import { ApiSortOrder } from "../richskill/ApiSkill"
import { ApiSearch, PaginatedCollections } from "../richskill/service/rich-skill-search.service"
import { ToastService } from "../toast/toast.service"
import { CollectionsListComponent } from "./collections-list.component"
import { CollectionService } from "./service/collection.service"
import {AuthService} from "../auth/auth-service";


@Component({
Expand Down Expand Up @@ -61,6 +62,7 @@ describe("CollectionsListComponent", () => {
providers: [
ToastService,
{ provide: CollectionService, useClass: CollectionServiceStub },
{ provide: AuthService, useClass: AuthServiceStub },
]
})

Expand Down
Loading