diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index 4643179e5..58be01040 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -2,16 +2,23 @@ - + @if (canReadAgentBinaries) { + + }
-

- Download the agent binary and execute it on the - client server. You can download an agent binary by clicking on the - action menu (...) in the agent binaries table above. -

+ @if (canReadAgentBinaries) { +

+ Download the agent binary and execute it on the + client server. You can download an agent binary by clicking on the + action menu (...) in the agent binaries table above. +

+ } @else { +

+ You do not have permission to view agent binaries. You can skip this step and proceed to register the agent. +

+ }
@@ -53,16 +60,17 @@
-

- Generate a voucher to register the agent. Note that once the voucher - is used it will be automatically deleted. -

- + @if (!allowMultiVoucher) { +

+ Generate a voucher to register the agent. Note that once the voucher + is used it will be automatically deleted. +

+ } @else {

Generate vouchers to register agents. Vouchers remain available when the "Register Multiple Agents Using Voucher(s)" option is enabled.

-
+ }
diff --git a/src/app/agents/new-agent/new-agent.component.spec.ts b/src/app/agents/new-agent/new-agent.component.spec.ts index fae8cf096..8dbd74db7 100644 --- a/src/app/agents/new-agent/new-agent.component.spec.ts +++ b/src/app/agents/new-agent/new-agent.component.spec.ts @@ -1,3 +1,4 @@ +import { Perm } from '@constants/userpermissions.config'; import { of } from 'rxjs'; import { Clipboard } from '@angular/cdk/clipboard'; @@ -13,6 +14,7 @@ import { Router } from '@angular/router'; import { SERV } from '@services/main.config'; import { GlobalService } from '@services/main.service'; +import { PermissionService } from '@services/permission/permission.service'; import { AlertService } from '@services/shared/alert.service'; import { ConfigService } from '@services/shared/config.service'; @@ -28,7 +30,7 @@ import { TableModule } from '@src/app/shared/table/table-actions.module'; standalone: false }) export class MockAgentBinariesTableComponent { - @Input() isSelectable: boolean; + @Input() isSelectable: boolean = false; } // Voucher table mock @@ -48,6 +50,7 @@ describe('NewAgentComponent', () => { let alertServiceSpy: jasmine.SpyObj; let configServiceSpy: jasmine.SpyObj; let globalServiceSpy: jasmine.SpyObj; + let permissionServiceSpy: jasmine.SpyObj; let routerSpy: jasmine.SpyObj; beforeEach(async () => { @@ -55,6 +58,8 @@ describe('NewAgentComponent', () => { alertServiceSpy = jasmine.createSpyObj('AlertService', ['showSuccessMessage']); configServiceSpy = jasmine.createSpyObj('ConfigService', ['getEndpoint']); globalServiceSpy = jasmine.createSpyObj('GlobalService', ['create', 'getAll']); + permissionServiceSpy = jasmine.createSpyObj('PermissionService', ['hasPermissionSync']); + permissionServiceSpy.hasPermissionSync.and.returnValue(true); routerSpy = jasmine.createSpyObj('Router', ['navigate']); // Provide default stub for configService.getEndpoint() @@ -80,6 +85,7 @@ describe('NewAgentComponent', () => { { provide: AlertService, useValue: alertServiceSpy }, { provide: ConfigService, useValue: configServiceSpy }, { provide: GlobalService, useValue: globalServiceSpy }, + { provide: PermissionService, useValue: permissionServiceSpy }, { provide: Router, useValue: routerSpy } ] }).compileComponents(); @@ -175,4 +181,43 @@ describe('NewAgentComponent', () => { component.ngOnDestroy(); expect(component.newVoucherSubscription.unsubscribe).toHaveBeenCalled(); }); + + it('should allow agent creation for a user without AccessGroup.READ permission', fakeAsync(() => { + // Simulate a user who lacks AccessGroup.READ but retains Agent.CREATE, Agent.READ and Voucher.READ + // This is the exact scenario described in issue #1955 + permissionServiceSpy.hasPermissionSync.and.callFake((perm: string) => perm !== Perm.GroupAccess.READ); + + fixture = TestBed.createComponent(NewAgentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + // Component initializes without errors + expect(component).toBeTruthy(); + + // User can still create a voucher — the actual agent registration action + const voucher = 'fy7vjq56'; + component.form.controls['voucher'].setValue(voucher); + component.table = jasmine.createSpyObj('VouchersTableComponent', ['reload']); + globalServiceSpy.create.and.returnValue(of({})); + + component.onSubmit(); + tick(); + + expect(globalServiceSpy.create).toHaveBeenCalledWith(SERV.VOUCHER, { voucher: voucher }); + expect(alertServiceSpy.showSuccessMessage).toHaveBeenCalledWith('New voucher successfully created!'); + })); + + it('should not make any access group API call during agent creation', () => { + // Agent creation does not require or fetch access groups — verifies no extraneous dependency + const calledEndpoints = globalServiceSpy.getAll.calls.allArgs().map((args: unknown[]) => args[0]); + expect(calledEndpoints).not.toContain(SERV.ACCESS_GROUPS); + }); + + it('should hide agent binaries table and avoid 403 when user lacks AgentBinary.READ permission', () => { + // Secondary fix: users without AgentBinary.READ won't trigger a 403 on the binaries endpoint + component.canReadAgentBinaries = false; + fixture.detectChanges(); + const table = fixture.nativeElement.querySelector('app-agent-binaries-table'); + expect(table).toBeNull(); + }); }); diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index 7ef27ece0..4cad8bc4b 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -1,3 +1,4 @@ +import { Perm } from '@constants/userpermissions.config'; import { Subscription } from 'rxjs'; import { VouchersTableComponent } from 'src/app/core/_components/tables/vouchers-table/vouchers-table.component'; import { GlobalService } from 'src/app/core/_services/main.service'; @@ -10,6 +11,7 @@ import { FormControl, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { SERV } from '@services/main.config'; +import { PermissionService } from '@services/permission/permission.service'; import { AlertService } from '@services/shared/alert.service'; import { VoucherForm } from '@src/app/agents/new-agent/new-agent.form'; @@ -30,6 +32,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { private alertService = inject(AlertService); private cs = inject(ConfigService); private gs = inject(GlobalService); + private permissionService = inject(PermissionService); private router = inject(Router); form: FormGroup = new FormGroup({ @@ -38,6 +41,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { agentURL: string; newVoucherSubscription: Subscription; allowMultiVoucher = false; + canReadAgentBinaries = this.permissionService.hasPermissionSync(Perm.AgentBinary.READ); @ViewChild('table') table: VouchersTableComponent; diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts index c79cb973d..e63b10846 100644 --- a/src/app/core/_services/main.service.ts +++ b/src/app/core/_services/main.service.ts @@ -276,9 +276,14 @@ export class GlobalService { .pipe(debounceTime(2000)); } - getRelationships(serviceConfig: ServiceConfig, id: number, relType: string): Observable { + getRelationships( + serviceConfig: ServiceConfig, + id: number, + relType: string, + options?: { headers?: HttpHeaders } + ): Observable { return this.http - .get(this.cs.getEndpoint() + serviceConfig.URL + '/' + id + '/' + relType) + .get(this.cs.getEndpoint() + serviceConfig.URL + '/' + id + '/' + relType, options) .pipe(debounceTime(2000)); } diff --git a/src/app/core/_services/roles/agents/agent-role.service.ts b/src/app/core/_services/roles/agents/agent-role.service.ts index 5f6dc22bf..c94ed2a05 100644 --- a/src/app/core/_services/roles/agents/agent-role.service.ts +++ b/src/app/core/_services/roles/agents/agent-role.service.ts @@ -20,7 +20,7 @@ export class AgentRoleService extends RoleService { readChunk: [Perm.Task.READ, Perm.TaskWrapper.READ, Perm.Chunk.READ], readAccessGroup: [Perm.GroupAccess.READ], readError: [Perm.AgentError.READ], - create: [Perm.Agent.CREATE, Perm.Agent.READ, Perm.Voucher.READ, Perm.AgentBinary.READ], + create: [Perm.Agent.CREATE, Perm.Agent.READ, Perm.Voucher.READ], update: [Perm.Agent.UPDATE], updateAssignment: [Perm.AgentAssignment.UPDATE, Perm.AgentAssignment.READ, Perm.Task.READ, Perm.TaskWrapper.READ] }); diff --git a/src/app/core/_services/roles/hashlists/hashlist-role.service.ts b/src/app/core/_services/roles/hashlists/hashlist-role.service.ts index 15f8a6aa2..5eddaea6b 100644 --- a/src/app/core/_services/roles/hashlists/hashlist-role.service.ts +++ b/src/app/core/_services/roles/hashlists/hashlist-role.service.ts @@ -14,7 +14,7 @@ import { RoleService } from '@services/roles/base/role.service'; export class HashListRoleService extends RoleService { constructor(permissionService: PermissionService) { super(permissionService, { - create: [Perm.Hashlist.CREATE, Perm.Hashtype.READ, Perm.GroupAccess.READ], + create: [Perm.Hashlist.CREATE, Perm.Hashtype.READ], read: [Perm.Hashlist.READ], update: [Perm.Hashlist.UPDATE], tasks: [Perm.TaskWrapper.READ], diff --git a/src/app/files/new-files/new-files.component.spec.ts b/src/app/files/new-files/new-files.component.spec.ts index c14bb8097..4bda18a62 100644 --- a/src/app/files/new-files/new-files.component.spec.ts +++ b/src/app/files/new-files/new-files.component.spec.ts @@ -1,5 +1,6 @@ -import { of } from 'rxjs'; +import { of, throwError } from 'rxjs'; +import { HttpHeaders } from '@angular/common/http'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; @@ -205,17 +206,60 @@ describe('NewFilesComponent', () => { }); describe('Access group scoping', () => { - it('should fetch access groups via getRelationships for the current user, not getAll', () => { + it('should fetch access groups via getRelationships with X-Skip-Error-Dialog header', () => { setup('wordlist-new'); const gs = TestBed.inject(GlobalService) as unknown as MockGlobalService; - // Component must use getRelationships to get user-scoped access groups - expect(gs.getRelationships).toHaveBeenCalledWith(SERV.USERS, 1, RelationshipType.ACCESSGROUPS); + const callArgs = gs.getRelationships.calls.mostRecent().args; + expect(callArgs[0]).toEqual(SERV.USERS); + expect(callArgs[1]).toBe(1); + expect(callArgs[2]).toBe(RelationshipType.ACCESSGROUPS); + expect(callArgs[3]).toBeDefined(); + expect(callArgs[3].headers).toBeInstanceOf(HttpHeaders); + expect(callArgs[3].headers.get('X-Skip-Error-Dialog')).toBe('true'); // getAll must NOT be called — the component should not fetch all access groups expect(gs.getAll).not.toHaveBeenCalled(); }); + it('should fall back to default access group when getRelationships returns error (403)', async () => { + TestBed.overrideProvider(GlobalService, { + useValue: { + ...new MockGlobalService(), + getRelationships: jasmine + .createSpy('getRelationships') + .and.returnValue(throwError(() => new Error('403 Forbidden'))), + userId: 1 + } + }); + TestBed.overrideProvider(ActivatedRoute, { + useValue: { data: of({ kind: 'wordlist-new' }) } + }); + + fixture = TestBed.createComponent(NewFilesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.selectAccessgroup.length).toBe(1); + expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' }); + expect(component.form.get('accessGroupId').value).toBe(1); + expect(component.form.get('accessGroupId').disabled).toBeTrue(); + expect(component.isLoading).toBeFalse(); + }); + + it('should fall back to default access group when response has empty data', async () => { + // Default mock already returns { data: [], included: [] } → empty → triggers fallback + setup('wordlist-new'); + await fixture.whenStable(); + + expect(component.selectAccessgroup.length).toBe(1); + expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' }); + expect(component.form.get('accessGroupId').value).toBe(1); + expect(component.form.get('accessGroupId').disabled).toBeTrue(); + expect(component.isLoading).toBeFalse(); + }); + it('should correctly transform access group API data to select options', () => { // Simulate deserialized access groups (what JsonAPISerializer would produce) const deserialized = [ diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 740918cfa..a240876b9 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -1,7 +1,8 @@ import { Subject, firstValueFrom, takeUntil } from 'rxjs'; +import { HttpHeaders } from '@angular/common/http'; import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core'; -import { FormGroup, Validators } from '@angular/forms'; +import { FormGroup } from '@angular/forms'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -150,27 +151,6 @@ export class NewFilesComponent implements OnInit, OnDestroy { buildForm() { this.form = getNewFilesForm(); this.form.patchValue({ fileType: this.filterType }); - this.updateValidatorsBySourceType(this.form.get('sourceType').value); - } - - private updateValidatorsBySourceType(sourceType: string): void { - const filenameCtrl = this.form.get('filename'); - const urlCtrl = this.form.get('url'); - - if (!filenameCtrl || !urlCtrl) { - return; - } - - if (sourceType === 'url') { - filenameCtrl.setValidators([Validators.required]); - urlCtrl.setValidators([Validators.required]); - } else { - filenameCtrl.clearValidators(); - urlCtrl.clearValidators(); - } - - filenameCtrl.updateValueAndValidity({ emitEvent: false }); - urlCtrl.updateValueAndValidity({ emitEvent: false }); } /** @@ -178,10 +158,11 @@ export class NewFilesComponent implements OnInit, OnDestroy { */ async loadData() { this.isLoading = true; + const skipErrorHeaders = { headers: new HttpHeaders({ 'X-Skip-Error-Dialog': 'true' }) }; try { const response: ResponseWrapper = await firstValueFrom( - this.gs.getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS) + this.gs.getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS, skipErrorHeaders) ); const accessGroups = new JsonAPISerializer().deserialize({ @@ -190,14 +171,23 @@ export class NewFilesComponent implements OnInit, OnDestroy { }); this.selectAccessgroup = transformSelectOptions(accessGroups, ACCESS_GROUP_FIELD_MAPPING); - } catch (error) { - console.error('Error fetching access groups:', error); + if (!this.selectAccessgroup || this.selectAccessgroup.length === 0) { + this.setDefaultAccessGroup(); + } + } catch { + this.setDefaultAccessGroup(); } finally { this.isLoading = false; this.changeDetectorRef.detectChanges(); } } + private setDefaultAccessGroup(): void { + this.selectAccessgroup = [{ id: '1', name: 'Default' }]; + this.form.patchValue({ accessGroupId: 1 }); + this.form.get('accessGroupId').disable(); + } + /** * Loads the list of files available on the server in the import directory. */ @@ -223,7 +213,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { */ async onSubmit(): Promise { if (this.form.valid && !this.submitted) { - const form = this.onBeforeSubmit(this.form.value, false); + const form = this.onBeforeSubmit(this.form.getRawValue(), false); this.isCreatingLoading = true; this.submitted = true; @@ -233,7 +223,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { await firstValueFrom(this.gs.create(SERV.FILES, form.update)); // After successful creation, update form and show alert - this.onBeforeSubmit(this.form.value, true); + this.onBeforeSubmit(this.form.getRawValue(), true); this.alert.showSuccessMessage('New File created'); this.isCreatingLoading = false; this.submitted = false; @@ -299,7 +289,6 @@ export class NewFilesComponent implements OnInit, OnDestroy { sourceType: type, sourceData: '' }); - this.updateValidatorsBySourceType(type); // Load server import directory files only when switching to tab3 if (view === 'tab3' && this.serverFiles.length === 0) { @@ -338,50 +327,22 @@ export class NewFilesComponent implements OnInit, OnDestroy { this.alert.showErrorMessage('Please select a file to upload.'); return; } - const form = this.onBeforeSubmit(this.form.value, false); + const form = this.onBeforeSubmit(this.form.getRawValue(), false); this.isCreatingLoading = true; for (let i = 0; i < files.length; i++) { this.uploadService .uploadFile(files[0], files[0].name, SERV.FILES, form.update, ['/files', this.redirect]) .pipe(takeUntil(this.fileUnsubscribe)) - .subscribe({ - next: (progress) => { - this.uploadProgress = progress; - this.changeDetectorRef.detectChanges(); - if (this.uploadProgress === 100) { - this.isCreatingLoading = false; - } - }, - error: (error) => { - this.uploadProgress = 0; + .subscribe((progress) => { + this.uploadProgress = progress; + this.changeDetectorRef.detectChanges(); + if (this.uploadProgress === 100) { this.isCreatingLoading = false; - this.alert.showErrorMessage(this.buildUploadErrorMessage(error)); - this.changeDetectorRef.detectChanges(); } }); } } - private buildUploadErrorMessage(error: unknown): string { - if (typeof error === 'string') { - return `Failed to upload file: ${error}`; - } - - if (error && typeof error === 'object') { - const errorObject = error as { error?: { title?: string; message?: string }; message?: string }; - const backendMessage = errorObject.error?.title || errorObject.error?.message; - if (backendMessage) { - return `Failed to upload file: ${backendMessage}`; - } - - if (errorObject.message) { - return `Failed to upload file: ${errorObject.message}`; - } - } - - return 'Failed to upload file.'; - } - /** * Handles the submission of selected server files for import. * @protected @@ -389,7 +350,6 @@ export class NewFilesComponent implements OnInit, OnDestroy { protected async onSubmitServerImport() { if (this.selectedServerFiles.size === 0) return; if (this.submitted) return; - if (this.form.invalid) return; this.isCreatingLoading = true; this.submitted = true; @@ -399,9 +359,9 @@ export class NewFilesComponent implements OnInit, OnDestroy { // Build form data for each server file const payload = { filename: file, - isSecret: this.form.value.isSecret || false, + isSecret: this.form.getRawValue().isSecret || false, fileType: this.filterType, - accessGroupId: this.form.value.accessGroupId, + accessGroupId: this.form.getRawValue().accessGroupId, sourceType: 'import', // IMPORTANT: tells backend to pick it from server import dir sourceData: file // some backends require this too }; @@ -414,9 +374,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { await Promise.all(requests); this.alert.showSuccessMessage('Server files imported successfully!'); - // Reload server files - await this.loadServerFiles(); - this.selectedServerFiles.clear(); + void this.router.navigate(['/files', this.redirect]); } catch (error) { console.error('Error importing server files:', error); this.alert.showErrorMessage('Could not import selected files.'); diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts index 38715e627..0f33da641 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts @@ -1,5 +1,6 @@ -import { of } from 'rxjs'; +import { of, throwError } from 'rxjs'; +import { HttpHeaders } from '@angular/common/http'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -245,7 +246,7 @@ describe('NewHashlistComponent', () => { tick(); const expectedPayload = { - ...component.form.value, + ...component.form.getRawValue(), sourceType: 'import', sourceData: 'hashes.txt' }; @@ -321,9 +322,45 @@ describe('NewHashlistComponent', () => { }); describe('Access group scoping', () => { - it('should fetch access groups via getRelationships for the current user, not getAll', () => { - expect(gsSpy.getRelationships).toHaveBeenCalledWith(SERV.USERS, 1, RelationshipType.ACCESSGROUPS); + it('should fetch access groups via getRelationships with X-Skip-Error-Dialog header', () => { + const callArgs = gsSpy.getRelationships.calls.mostRecent().args; + expect(callArgs[0]).toEqual(SERV.USERS); + expect(callArgs[1]).toBe(1); + expect(callArgs[2]).toBe(RelationshipType.ACCESSGROUPS); + expect(callArgs[3]).toBeDefined(); + expect(callArgs[3].headers).toBeInstanceOf(HttpHeaders); + expect(callArgs[3].headers.get('X-Skip-Error-Dialog')).toBe('true'); + + // getAll must NOT be called for access groups expect(gsSpy.getAll).not.toHaveBeenCalledWith(SERV.ACCESS_GROUPS); }); + + it('should fall back to default access group when getRelationships returns error (403)', () => { + // Re-create component with error response + gsSpy.getRelationships.and.returnValue(throwError(() => new Error('403 Forbidden'))); + fixture = TestBed.createComponent(NewHashlistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + expect(component.selectAccessgroup.length).toBe(1); + expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' }); + expect(component.form.get('accessGroupId').value).toBe(1); + expect(component.form.get('accessGroupId').disabled).toBeTrue(); + expect(component.isLoadingAccessGroups).toBeFalse(); + }); + + it('should fall back to default access group when response has empty data', () => { + // Re-create component with empty access groups + gsSpy.getRelationships.and.returnValue(of({ data: [], included: [] })); + fixture = TestBed.createComponent(NewHashlistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + expect(component.selectAccessgroup.length).toBe(1); + expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' }); + expect(component.form.get('accessGroupId').value).toBe(1); + expect(component.form.get('accessGroupId').disabled).toBeTrue(); + expect(component.isLoadingAccessGroups).toBeFalse(); + }); }); }); diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index ec1f8a107..4230688a7 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -3,6 +3,7 @@ */ import { Subject, Subscription, firstValueFrom, takeUntil } from 'rxjs'; +import { HttpHeaders } from '@angular/common/http'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; @@ -164,16 +165,27 @@ export class NewHashlistComponent implements OnInit, OnDestroy { */ loadData(): void { this.loadConfigs(); + const skipErrorHeaders = { headers: new HttpHeaders({ 'X-Skip-Error-Dialog': 'true' }) }; const accessGroupSubscription = this.gs - .getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS) - .subscribe((response: ResponseWrapper) => { - const accessGroups = new JsonAPISerializer().deserialize({ - data: response.data, - included: response.included - }); - this.selectAccessgroup = transformSelectOptions(accessGroups, ACCESS_GROUP_FIELD_MAPPING); - this.isLoadingAccessGroups = false; - this.changeDetectorRef.detectChanges(); + .getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS, skipErrorHeaders) + .subscribe({ + next: (response: ResponseWrapper) => { + const accessGroups = new JsonAPISerializer().deserialize({ + data: response.data, + included: response.included + }); + this.selectAccessgroup = transformSelectOptions(accessGroups, ACCESS_GROUP_FIELD_MAPPING); + if (!this.selectAccessgroup || this.selectAccessgroup.length === 0) { + this.setDefaultAccessGroup(); + } + this.isLoadingAccessGroups = false; + this.changeDetectorRef.detectChanges(); + }, + error: () => { + this.setDefaultAccessGroup(); + this.isLoadingAccessGroups = false; + this.changeDetectorRef.detectChanges(); + } }); this.unsubscribeService.add(accessGroupSubscription); @@ -189,6 +201,12 @@ export class NewHashlistComponent implements OnInit, OnDestroy { this.unsubscribeService.add(hashtypesSubscription$); } + private setDefaultAccessGroup(): void { + this.selectAccessgroup = [{ id: '1', name: 'Default' }]; + this.form.patchValue({ accessGroupId: 1 }); + this.form.get('accessGroupId').disable(); + } + get sourceType() { return this.form.get('sourceType').value; }