diff --git a/common/taskana-common-test/src/test/java/pro/taskana/common/test/security/JaasExtensionTest.java b/common/taskana-common-test/src/test/java/pro/taskana/common/test/security/JaasExtensionTest.java index d62793a03b..633f19e098 100644 --- a/common/taskana-common-test/src/test/java/pro/taskana/common/test/security/JaasExtensionTest.java +++ b/common/taskana-common-test/src/test/java/pro/taskana/common/test/security/JaasExtensionTest.java @@ -141,7 +141,6 @@ void should_NotSetJaasSubject_When_AnnotationIsMissing_On_Test() { void should_SetJaasSubject_When_AnnotationExists_On_Test() { assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("user"); assertThat(CURRENT_USER_CONTEXT.getGroupIds()).isEmpty(); - assertThat(CURRENT_USER_CONTEXT.getPermissionIds()).isEmpty(); } @WithAccessId( @@ -151,30 +150,6 @@ void should_SetJaasSubject_When_AnnotationExists_On_Test() { void should_SetJaasSubjectWithGroups_When_AnnotationExistsWithGroups_On_Test() { assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("user"); assertThat(CURRENT_USER_CONTEXT.getGroupIds()).containsExactlyInAnyOrder("group1", "group2"); - assertThat(CURRENT_USER_CONTEXT.getPermissionIds()).isEmpty(); - } - - @WithAccessId( - user = "user", - permissions = {"permission1", "permission2"}) - @Test - void should_SetJaasSubjectWithPermissions_When_AnnotationExistsWithPermissions_On_Test() { - assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("user"); - assertThat(CURRENT_USER_CONTEXT.getGroupIds()).isEmpty(); - assertThat(CURRENT_USER_CONTEXT.getPermissionIds()) - .containsExactlyInAnyOrder("permission1", "permission2"); - } - - @WithAccessId( - user = "user", - groups = {"group1", "group2"}, - permissions = {"permission1", "permission2"}) - @Test - void should_SetJaasSubjectWithGroupsAndPerms_When_AnnotationExistsWithGroupsAndPerms_On_Test() { - assertThat(CURRENT_USER_CONTEXT.getUserid()).isEqualTo("user"); - assertThat(CURRENT_USER_CONTEXT.getGroupIds()).containsExactlyInAnyOrder("group1", "group2"); - assertThat(CURRENT_USER_CONTEXT.getPermissionIds()) - .containsExactlyInAnyOrder("permission1", "permission2"); } @WithAccessId(user = "user") diff --git a/lib/taskana-core/src/test/java/acceptance/workbasket/query/QueryWorkbasketByPermissionAccTest.java b/lib/taskana-core/src/test/java/acceptance/workbasket/query/QueryWorkbasketByPermissionAccTest.java index e97f0ddb96..2568bf3786 100644 --- a/lib/taskana-core/src/test/java/acceptance/workbasket/query/QueryWorkbasketByPermissionAccTest.java +++ b/lib/taskana-core/src/test/java/acceptance/workbasket/query/QueryWorkbasketByPermissionAccTest.java @@ -230,7 +230,7 @@ void should_GetAllTransferTargetsForSubjectUser_When_QueryingForMultiplePermissi assertThat(results).hasSize(3); } - @WithAccessId(user = "user-1-1", groups = GROUP_1_DN, permissions = PERM_1_DN) + @WithAccessId(user = "user-1-1", groups = {GROUP_1_DN, PERM_1_DN}) @Test void should_GetAllTransferTargetsForSubjectUserGroupPerm_When_QueryingForSinglePermission() { List results = @@ -242,7 +242,7 @@ void should_GetAllTransferTargetsForSubjectUserGroupPerm_When_QueryingForSingleP assertThat(results).hasSize(7); } - @WithAccessId(user = "user-1-1", groups = GROUP_1_DN, permissions = PERM_1_DN) + @WithAccessId(user = "user-1-1", groups = {GROUP_1_DN, PERM_1_DN}) @Test void should_GetAllTransferTargetsForSubjectUserGroupPerm_When_QueryingForMultiplePermissions() { List results = diff --git a/rest/taskana-rest-spring-example-common/src/main/resources/pro/taskana/example/rest/controllers/taskana-customization.json b/rest/taskana-rest-spring-example-common/src/main/resources/pro/taskana/example/rest/controllers/taskana-customization.json index 19230ab8eb..6cb1dcbe56 100644 --- a/rest/taskana-rest-spring-example-common/src/main/resources/pro/taskana/example/rest/controllers/taskana-customization.json +++ b/rest/taskana-rest-spring-example-common/src/main/resources/pro/taskana/example/rest/controllers/taskana-customization.json @@ -1,6 +1,9 @@ { "EN": { "workbaskets": { + "time": { + "debounceTime": 750 + }, "information": { "owner": { "lookupField": true diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/TaskanaEngineController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/TaskanaEngineController.java index d7d86e66f8..c100d714c6 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/TaskanaEngineController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/TaskanaEngineController.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import javax.naming.InvalidNameException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.http.ResponseEntity; @@ -15,6 +16,7 @@ import pro.taskana.common.api.ConfigurationService; import pro.taskana.common.api.TaskanaEngine; import pro.taskana.common.api.security.CurrentUserContext; +import pro.taskana.common.rest.ldap.LdapClient; import pro.taskana.common.rest.models.CustomAttributesRepresentationModel; import pro.taskana.common.rest.models.TaskanaUserInfoRepresentationModel; import pro.taskana.common.rest.models.VersionRepresentationModel; @@ -28,17 +30,19 @@ public class TaskanaEngineController { private final TaskanaEngine taskanaEngine; private final CurrentUserContext currentUserContext; private final ConfigurationService configurationService; + private final LdapClient ldapClient; @Autowired TaskanaEngineController( TaskanaConfiguration taskanaConfiguration, TaskanaEngine taskanaEngine, CurrentUserContext currentUserContext, - ConfigurationService configurationService) { + ConfigurationService configurationService, LdapClient ldapClient) { this.taskanaConfiguration = taskanaConfiguration; this.taskanaEngine = taskanaEngine; this.currentUserContext = currentUserContext; this.configurationService = configurationService; + this.ldapClient = ldapClient; } /** @@ -97,13 +101,18 @@ public ResponseEntity>> getClassificationCategoriesByTy * This endpoint computes all information of the current user. * * @return the information of the current user. + * @throws InvalidNameException if the dn is invalid. */ @GetMapping(path = RestEndpoints.URL_CURRENT_USER) @Transactional(readOnly = true, rollbackFor = Exception.class) - public ResponseEntity getCurrentUserInfo() { + public ResponseEntity getCurrentUserInfo() + throws InvalidNameException { TaskanaUserInfoRepresentationModel resource = new TaskanaUserInfoRepresentationModel(); resource.setUserId(currentUserContext.getUserid()); - resource.setGroupIds(currentUserContext.getGroupIds()); + Map> groupsAndPermissions = + ldapClient.searchAccessIdForGroupsAndPermissionsByDn(currentUserContext.getGroupIds()); + resource.setPermissionIds(groupsAndPermissions.get("permissions")); + resource.setGroupIds(groupsAndPermissions.get("groups")); taskanaConfiguration.getRoleMap().keySet().stream() .filter(taskanaEngine::isUserInRole) .forEach(resource.getRoles()::add); diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java index e1dc461daf..9f419b3f3b 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/ldap/LdapClient.java @@ -7,7 +7,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; @@ -235,6 +237,29 @@ public List searchGroupsByName(final String name) new GroupContextMapper()); } + public Map> searchAccessIdForGroupsAndPermissionsByDn(List dns) + throws InvalidNameException { + List accessIdsOfGroupsAndPermissions = new ArrayList<>(); + List permissions = new ArrayList<>(); + List accessIdsOfPermissions = new ArrayList<>(); + for (String groupOrPermission : dns) { + accessIdsOfGroupsAndPermissions.add(searchAccessIdByDn(groupOrPermission) + .getAccessId()); + } + for (String groupOrPermission : accessIdsOfGroupsAndPermissions) { + permissions.addAll(searchPermissionsByName(groupOrPermission)); + } + for (AccessIdRepresentationModel permission : permissions) { + accessIdsOfPermissions.add(permission.getAccessId()); + } + List accessIdsOfGroups = new ArrayList<>(accessIdsOfGroupsAndPermissions); + accessIdsOfGroups.removeAll(accessIdsOfPermissions); + Map> map = new HashMap<>(); + map.put("permissions", accessIdsOfPermissions); + map.put("groups", accessIdsOfGroups); + return map; + } + public List searchPermissionsByName(final String name) throws InvalidArgumentException { isInitOrFail(); diff --git a/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts b/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts index f810256df0..9cd9236823 100644 --- a/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts +++ b/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts @@ -4,6 +4,8 @@ import { TypeAheadComponent } from './type-ahead.component'; import { AccessIdsService } from '../../services/access-ids/access-ids.service'; import { of } from 'rxjs'; import { NgxsModule } from '@ngxs/store'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; @@ -19,6 +21,8 @@ describe('TypeAheadComponent with AccessId input', () => { let fixture: ComponentFixture; let debugElement: DebugElement; let component: TypeAheadComponent; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -30,7 +34,8 @@ describe('TypeAheadComponent with AccessId input', () => { MatTooltipModule, NoopAnimationsModule, FormsModule, - ReactiveFormsModule + ReactiveFormsModule, + HttpClientTestingModule ], declarations: [TypeAheadComponent], providers: [{ provide: AccessIdsService, useValue: accessIdService }] @@ -40,6 +45,8 @@ describe('TypeAheadComponent with AccessId input', () => { debugElement = fixture.debugElement; component = fixture.componentInstance; fixture.detectChanges(); + httpClient = TestBed.get(HttpClient); + httpTestingController = TestBed.get(HttpTestingController); })); it('should create component', () => { diff --git a/web/src/app/shared/components/type-ahead/type-ahead.component.ts b/web/src/app/shared/components/type-ahead/type-ahead.component.ts index 2848701405..16e52ea54e 100644 --- a/web/src/app/shared/components/type-ahead/type-ahead.component.ts +++ b/web/src/app/shared/components/type-ahead/type-ahead.component.ts @@ -2,11 +2,14 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, SimpleChange import { AccessIdsService } from '../../services/access-ids/access-ids.service'; import { Observable, Subject } from 'rxjs'; import { FormControl, FormGroup } from '@angular/forms'; +import { Customisation } from '../../../shared/models/customisation'; import { AccessId } from '../../models/access-id'; -import { take, takeUntil, distinctUntilChanged, debounceTime } from 'rxjs/operators'; +import { take, takeUntil, distinctUntilChanged, debounceTime, map } from 'rxjs/operators'; import { Select } from '@ngxs/store'; import { WorkbasketSelectors } from '../../store/workbasket-store/workbasket.selectors'; import { ButtonAction } from '../../../administration/models/button-action'; +import { HttpClient } from '@angular/common/http'; +const customisationUrl = 'environments/data-sources/taskana-customization.json'; @Component({ selector: 'taskana-shared-type-ahead', @@ -30,13 +33,14 @@ export class TypeAheadComponent implements OnInit, OnDestroy { name: string = ''; lastSavedAccessId: string = ''; filteredAccessIds: AccessId[] = []; + time: number; destroy$ = new Subject(); accessIdForm = new FormGroup({ accessId: new FormControl('') }); emptyAccessId: AccessId = { accessId: '', name: '' }; - constructor(private accessIdService: AccessIdsService) {} + constructor(private accessIdService: AccessIdsService, private httpClient: HttpClient) {} ngOnChanges(changes: SimpleChanges) { // currently needed because when saving, workbasket-details components sends old workbasket which reverts changes in this component @@ -46,6 +50,16 @@ export class TypeAheadComponent implements OnInit, OnDestroy { } ngOnInit() { + this.httpClient + .get(customisationUrl) + .pipe(take(1)) + .subscribe((customisation) => { + Object.keys(customisation).forEach((lang) => { + this.time = customisation[lang].workbaskets.time.debounceTime; + this.accessIdValueChanges(); + }); + }); + if (this.isDisabled) { this.accessIdForm.controls['accessId'].disable(); } @@ -56,9 +70,11 @@ export class TypeAheadComponent implements OnInit, OnDestroy { this.accessIdForm.controls['accessId'].setValue(this.lastSavedAccessId); } }); + } + accessIdValueChanges() { this.accessIdForm.controls['accessId'].valueChanges - .pipe(debounceTime(750), distinctUntilChanged(), takeUntil(this.destroy$)) + .pipe(debounceTime(this.time), distinctUntilChanged(), takeUntil(this.destroy$)) .subscribe(() => { const value = this.accessIdForm.controls['accessId'].value; if (value === '') { diff --git a/web/src/app/shared/models/customisation.ts b/web/src/app/shared/models/customisation.ts index 421c8a79ad..bbce6a658c 100644 --- a/web/src/app/shared/models/customisation.ts +++ b/web/src/app/shared/models/customisation.ts @@ -28,6 +28,7 @@ export interface ClassificationCategoryImages { export interface WorkbasketsCustomisation { information?: { owner: LookupField } & CustomFields; + time?: DebounceTime; 'access-items'?: AccessItemsCustomisation; } @@ -46,6 +47,10 @@ export interface LookupField { lookupField: boolean; } +export interface DebounceTime { + debounceTime: number; +} + export function getCustomFields(amount: number): OperatorFunction { return map((customisation) => [...Array(amount).keys()] diff --git a/web/src/environments/data-sources/taskana-customization.json b/web/src/environments/data-sources/taskana-customization.json index d89fb7158f..7e7c615cb8 100644 --- a/web/src/environments/data-sources/taskana-customization.json +++ b/web/src/environments/data-sources/taskana-customization.json @@ -1,6 +1,9 @@ { "EN": { "workbaskets": { + "time": { + "debounceTime": 750 + }, "information": { "owner": { "lookupField": true