From e7a96dd2d7ebcbd83340640c8ed14b29733d972b Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 10:48:38 +0200
Subject: [PATCH 01/25] component init

---
 .../link-account/link-account.component.css   |  1 +
 .../link-account/link-account.component.html  | 51 ++++++++++++++
 .../link-account.component.spec.ts            | 23 +++++++
 .../link-account/link-account.component.ts    | 66 +++++++++++++++++++
 4 files changed, 141 insertions(+)
 create mode 100644 src/app/welcome/link-account/link-account.component.css
 create mode 100644 src/app/welcome/link-account/link-account.component.html
 create mode 100644 src/app/welcome/link-account/link-account.component.spec.ts
 create mode 100644 src/app/welcome/link-account/link-account.component.ts

diff --git a/src/app/welcome/link-account/link-account.component.css b/src/app/welcome/link-account/link-account.component.css
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.css
@@ -0,0 +1 @@
+
diff --git a/src/app/welcome/link-account/link-account.component.html b/src/app/welcome/link-account/link-account.component.html
new file mode 100644
index 00000000..8497113e
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.html
@@ -0,0 +1,51 @@
+<div style="display: flex; justify-content: center;">
+    <div style="
+padding-bottom: 15px;
+ width: 60%
+" class="panel panel-default">
+        <div class="panel-heading">{{ 'ACCOUNT_LINKING.HEADER' | translate }}</div>
+        <div class="panel-body">
+            <form *ngIf="user"
+                  class="form-horizontal" #userDetailsForm="ngForm">
+                <div>
+                    <p>
+                        {{ 'ACCOUNT_LINKING.INFO' | translate }}
+                    </p>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">{{ 'USER_DETAILS.FIRST_NAME' | translate }}</label>
+                    <div class="col-sm-10">
+                        <p class="form-control-static">{{ user.firstname }}</p>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">{{ 'USER_DETAILS.LAST_NAME' | translate }}</label>
+                    <div class="col-sm-10">
+                        <p class="form-control-static">{{ user.lastname }}</p>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">{{ 'USER_DETAILS.EMAIL' | translate }}</label>
+                    <div class="col-sm-10">
+                        <p class="form-control-static">{{ user.email }}</p>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label for="password" class="col-sm-2 control-label">{{ 'PASSWORD.PASSWORD' | translate }}</label>
+                    <div class="col-sm-10">
+                        <input type="password" class="form-control" id="password"
+                               name="password" [(ngModel)]="password">
+                    </div>
+                </div>
+                <button type="submit" class="btn btn-primary"
+                        (click)="submit()">{{ 'PASSWORD.SUBMIT_BUTTON' | translate }}
+                </button>
+
+            </form>
+            <br>
+        </div>
+    </div>
+</div>
diff --git a/src/app/welcome/link-account/link-account.component.spec.ts b/src/app/welcome/link-account/link-account.component.spec.ts
new file mode 100644
index 00000000..9a780cda
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LinkAccountComponent } from './link-account.component';
+
+describe('LinkAccountComponent', () => {
+  let component: LinkAccountComponent;
+  let fixture: ComponentFixture<LinkAccountComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [LinkAccountComponent]
+    })
+    .compileComponents();
+    
+    fixture = TestBed.createComponent(LinkAccountComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/welcome/link-account/link-account.component.ts b/src/app/welcome/link-account/link-account.component.ts
new file mode 100644
index 00000000..b72bd90f
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.ts
@@ -0,0 +1,66 @@
+import {Component, OnDestroy, OnInit} from '@angular/core';
+import {User} from '../../model';
+import {ActivatedRoute, Router} from '@angular/router';
+import jwtDecode from 'jwt-decode';
+import {AuthService} from '../../auth/auth.service';
+
+
+@Component({
+    selector: 'app-link-account',
+    templateUrl: './link-account.component.html',
+    styleUrl: './link-account.component.css'
+})
+export class LinkAccountComponent implements OnInit, OnDestroy {
+    public user: User;
+    private token: string;
+    public password: string;
+
+    constructor(
+        private readonly route: ActivatedRoute,
+        private readonly authService: AuthService,
+        private router: Router
+    ) {
+    }
+
+    ngOnDestroy() {
+        if (!this.authService.isLogged()) {
+            this.authService.oidcLogout(this.token)
+        }
+    }
+
+    ngOnInit() {
+        this.route.queryParams.subscribe(param => {
+            this.token = param['oidc_token'];
+            const decoded: TokenPayload = jwtDecode<TokenPayload>(this.token);
+            this.user = new User();
+            this.user.username = decoded.sub;
+            this.user.firstname = decoded.given_name;
+            this.user.lastname = decoded.family_name;
+            this.user.email = decoded.email;
+        })
+
+    }
+
+    public submit(): void {
+        this.authService.oidcLinkingLogin(
+            this.token,
+            this.user.email,
+            this.password,
+            this.user.username,
+            this.user.firstname,
+            this.user.lastname,
+        ).subscribe(
+            () => {
+                this.router.navigate(['/']);
+            }
+        )
+    }
+}
+
+
+interface TokenPayload {
+    sub: string;
+    email: string;
+    given_name: string;
+    family_name: string;
+}
-- 
GitLab


From 59d957b0b4b2f2be9fc6de80f29e9bba7559bff9 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 10:48:45 +0200
Subject: [PATCH 02/25] component init

---
 src/app/welcome/welcome.module.ts | 67 ++++++++++++++++---------------
 1 file changed, 35 insertions(+), 32 deletions(-)

diff --git a/src/app/welcome/welcome.module.ts b/src/app/welcome/welcome.module.ts
index 08fc1c81..e212c4f0 100644
--- a/src/app/welcome/welcome.module.ts
+++ b/src/app/welcome/welcome.module.ts
@@ -20,38 +20,41 @@ import {TranslateModule} from '@ngx-translate/core';
 import {PasswordResetComponent} from './passwordreset/password-reset.component';
 import {PasswordStrengthMeterComponent} from 'angular-password-strength-meter';
 import {PolicySubpageComponent} from './policy-subpage/policy-subpage.component';
+import {LinkAccountComponent} from './link-account/link-account.component';
 
 @NgModule({
-  declarations: [
-    WelcomeComponent,
-    LoginComponent,
-    LogoutComponent,
-    RegistrationComponent,
-    ProfileComponent,
-    CompleteComponent,
-    TermsAcceptanceComponent,
-    PasswordResetComponent,
-    PolicySubpageComponent
-  ],
-  imports: [
-    FormsModule,
-    ReactiveFormsModule,
-    CommonModule,
-    RouterModule,
-    SharedModule,
-    PipesModule,
-    AppMarketModule,
-    PasswordStrengthMeterComponent,
-    TranslateModule.forChild()
-  ],
-  exports: [
-    WelcomeComponent
-  ],
-  providers: [
-    RegistrationService,
-    UserService,
-    ChangelogService,
-    ContentDisplayService
-  ]
+    declarations: [
+        WelcomeComponent,
+        LoginComponent,
+        LogoutComponent,
+        RegistrationComponent,
+        ProfileComponent,
+        CompleteComponent,
+        TermsAcceptanceComponent,
+        PasswordResetComponent,
+        PolicySubpageComponent,
+        LinkAccountComponent
+    ],
+    imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        CommonModule,
+        RouterModule,
+        SharedModule,
+        PipesModule,
+        AppMarketModule,
+        PasswordStrengthMeterComponent,
+        TranslateModule.forChild()
+    ],
+    exports: [
+        WelcomeComponent
+    ],
+    providers: [
+        RegistrationService,
+        UserService,
+        ChangelogService,
+        ContentDisplayService
+    ]
 })
-export class WelcomeModule {}
+export class WelcomeModule {
+}
-- 
GitLab


From ea12365f8e3cd185604b117cf48e830fc0d286d2 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 10:48:58 +0200
Subject: [PATCH 03/25] component init

---
 src/app/app.routes.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 610c10cc..31a968fe 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -6,6 +6,7 @@ import { WelcomeRoutes } from './welcome/welcome.routes';
 import {ServiceUnavailableRoutes} from './service-unavailable/service-unavailable.routes';
 import {PageNotFoundComponent} from './shared/page-not-found/page-not-found.component';
 import {LoginSuccessComponent} from './auth/login-success/login-success.component';
+import {LinkAccountComponent} from './welcome/link-account/link-account.component';
 
 const appRoutes: Routes = [
     ...WelcomeRoutes,
@@ -13,6 +14,7 @@ const appRoutes: Routes = [
     ...ServiceUnavailableRoutes,
     { path: 'notfound', component: PageNotFoundComponent },
     { path: 'login-success', component: LoginSuccessComponent },
+    { path: 'login-linking', component: LinkAccountComponent},
     { path: '**', redirectTo: '/welcome' },
 
 ];
-- 
GitLab


From 97b51919eef26808c4921befabbd89574a3e1c03 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 10:49:32 +0200
Subject: [PATCH 04/25] added new linking flow

---
 src/app/auth/auth.service.ts | 95 +++++++++++++++++++-----------------
 1 file changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts
index a3426c27..dcc55696 100644
--- a/src/app/auth/auth.service.ts
+++ b/src/app/auth/auth.service.ts
@@ -1,13 +1,11 @@
-import {BehaviorSubject, Observable, Subject, throwError as observableThrowError, of} from 'rxjs';
+import {BehaviorSubject, Observable, of, Subject, throwError as observableThrowError} from 'rxjs';
 import {catchError, debounceTime, map} from 'rxjs/operators';
 import {Injectable} from '@angular/core';
 import {AppConfigService, ConfigurationService} from '../service';
 import {JwtHelperService} from '@auth0/angular-jwt';
 import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {User} from '../model';
 import {ProfileService} from '../service/profile.service';
 import {Role, UserRole} from '../model/userrole';
-import {interval, Subscription} from 'rxjs';
 
 
 export class DomainRoles {
@@ -253,6 +251,49 @@ export class AuthService {
         return domainsWithRole;
     }
 
+    public oidcLinkingLogin(oidcToken: string,
+                            email: string,
+                            password: string,
+                            uuid: string,
+                            firstName: string,
+                            lastName: string) {
+        const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'});
+
+        return this.http.post(this.appConfig.config.apiUrl + '/oidc/link',
+            JSON.stringify(
+                {
+                    'oidcToken': oidcToken,
+                    'email': email,
+                    'password': password,
+                    'uuid': uuid,
+                    'firstName': firstName,
+                    'lastName': lastName,
+                }
+            ),
+            {headers: headers}).pipe(
+            debounceTime(1000),
+            map((res: Response) => {
+                    const token = res && res['token'];
+                    const oidcToken = res && res['oidcToken'];
+                    if (token) {
+                        this.storeToken(token);
+                        this.storeOidcToken(oidcToken);
+                        this.loginUsingSsoService = false;
+                        this.isLoggedInSubject.next(true);
+                        this.profileService.getRoles().subscribe(profile => {
+                            this.profile = profile
+                            this.storeRoles(profile);
+                            return true;
+                        })
+                    } else {
+                        this.isLoggedInSubject.next(false);
+                        return false;
+                    }
+                }
+            ),
+        )
+    }
+
     public login(username: string, password: string): Observable<boolean> {
         // hack so test instance modal is shown onl after login
         localStorage.setItem(this.appConfig.getTestInstanceModalKey(), 'True');
@@ -306,49 +347,6 @@ export class AuthService {
             }));
     }
 
-    public propagateSSOLogin(userid: string): Observable<boolean> {
-        console.debug('propagateSSOLogin');
-        console.debug('propagateSSOLogin ' + this.appConfig.config.apiUrl);
-        console.debug('propagateSSOLogin ' + this.appConfig.config.apiUrl + '/auth/sso/login');
-        console.debug('propagateSSOLogin ' + userid);
-        // hack so test instance modal is shown onl after login
-        localStorage.setItem(this.appConfig.getTestInstanceModalKey(), 'True');
-
-        if (this.maintenance) {
-            this.isLoggedInSubject.next(false);
-            return of(false);
-        }
-
-        const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'});
-        return this.http.post(this.appConfig.config.apiUrl + '/auth/sso/login',
-            JSON.stringify({'userid': userid}), {headers: headers}).pipe(
-            debounceTime(10000),
-            map((response: Response) => {
-                console.debug('SSO login response: ' + response);
-                // login successful if there's a jwt token in the response
-                const token = response && response['token'];
-
-                if (token) {
-                    this.storeToken(token);
-                    console.debug('SSO AUTH | User: ' + this.getUsername());
-                    console.debug('SSO AUTH | Domains: ' + this.getDomains());
-                    console.debug('SSO AUTH | Roles: ' + this.getRoles());
-                    console.debug('SSO AUTH | DomainRoles: ' + this.getDomainRoles());
-                    this.loginUsingSsoService = true;
-                    this.isLoggedInSubject.next(true);
-                    return true;
-                } else {
-                    // return false to indicate failed login
-                    this.isLoggedInSubject.next(false);
-                    return false;
-                }
-            }),
-            catchError((error) => {
-                console.error('SSO login error: ' + error.error['message']);
-                return observableThrowError(error);
-            }));
-    }
-
     public logout(): void {
         const oidcToken = this.getOidcToken();
         this.refresh = null;
@@ -366,6 +364,11 @@ export class AuthService {
         }
     }
 
+    public oidcLogout(oidcToken: string): void {
+        this.http.get(this.appConfig.config.apiUrl + '/oidc/logout/' + oidcToken).subscribe(() => {
+        })
+    }
+
     public isLogged(): boolean {
         const token = this.getToken();
         if (token == null) {
-- 
GitLab


From f0bd6b2dd5e250772460f504016b322684be7da4 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 11:19:47 +0200
Subject: [PATCH 05/25] test fixed

---
 .../link-account.component.spec.ts            | 68 ++++++++++++++-----
 .../link-account/link-account.component.ts    |  2 +-
 2 files changed, 52 insertions(+), 18 deletions(-)

diff --git a/src/app/welcome/link-account/link-account.component.spec.ts b/src/app/welcome/link-account/link-account.component.spec.ts
index 9a780cda..0b0a9f98 100644
--- a/src/app/welcome/link-account/link-account.component.spec.ts
+++ b/src/app/welcome/link-account/link-account.component.spec.ts
@@ -1,23 +1,57 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
 
-import { LinkAccountComponent } from './link-account.component';
+import {LinkAccountComponent} from './link-account.component';
+import {ActivatedRoute} from '@angular/router';
+import {of} from 'rxjs';
+import {AuthService} from '../../auth/auth.service';
+import {TranslateFakeLoader, TranslateLoader, TranslateModule} from '@ngx-translate/core';
 
 describe('LinkAccountComponent', () => {
-  let component: LinkAccountComponent;
-  let fixture: ComponentFixture<LinkAccountComponent>;
+    let component: LinkAccountComponent;
+    let fixture: ComponentFixture<LinkAccountComponent>;
 
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [LinkAccountComponent]
-    })
-    .compileComponents();
-    
-    fixture = TestBed.createComponent(LinkAccountComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
+    beforeEach(async () => {
+        await TestBed.configureTestingModule({
+            declarations: [LinkAccountComponent],
+            imports: [TranslateModule.forRoot({
+                loader: {
+                    provide: TranslateLoader,
+                    useClass: TranslateFakeLoader
+                }
+            })],
+            providers: [
+                {
+                    provide: ActivatedRoute,
+                    useValue: {
+                        queryParams: of({
+                            oidc_token: 'mocked.jwt.token'
+                        }),
+                        snapshot: {
+                            paramMap: {
+                                get: () => null
+                            }
+                        }
+                    }
+                },
+                {
+                    provide: AuthService,
+                    useValue: {
+                        isLogged: () => true,
+                        oidcLogout: jasmine.createSpy('oidcLogout'),
+                        oidcLinkingLogin: jasmine.createSpy('oidcLinkingLogin').and.returnValue(of({}))
+                    }
+                },
 
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
+            ]
+        })
+            .compileComponents();
+
+        fixture = TestBed.createComponent(LinkAccountComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
 });
diff --git a/src/app/welcome/link-account/link-account.component.ts b/src/app/welcome/link-account/link-account.component.ts
index b72bd90f..1f4f967c 100644
--- a/src/app/welcome/link-account/link-account.component.ts
+++ b/src/app/welcome/link-account/link-account.component.ts
@@ -18,7 +18,7 @@ export class LinkAccountComponent implements OnInit, OnDestroy {
     constructor(
         private readonly route: ActivatedRoute,
         private readonly authService: AuthService,
-        private router: Router
+        private readonly router: Router
     ) {
     }
 
-- 
GitLab


From 1cb3b94f10e6f6d3b2448f82f36f31c9f167e2f3 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 11:23:01 +0200
Subject: [PATCH 06/25] button fix

---
 src/app/welcome/link-account/link-account.component.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/app/welcome/link-account/link-account.component.html b/src/app/welcome/link-account/link-account.component.html
index 8497113e..fd27440e 100644
--- a/src/app/welcome/link-account/link-account.component.html
+++ b/src/app/welcome/link-account/link-account.component.html
@@ -41,7 +41,7 @@ padding-bottom: 15px;
                     </div>
                 </div>
                 <button type="submit" class="btn btn-primary"
-                        (click)="submit()">{{ 'PASSWORD.SUBMIT_BUTTON' | translate }}
+                        (click)="submit()">{{ 'ACCOUNT_LINKING.CONFIRM' | translate }}
                 </button>
 
             </form>
-- 
GitLab


From 0898c30085402da4289632a18b9324913794e4bb Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 11:23:47 +0200
Subject: [PATCH 07/25] button fix

---
 src/app/welcome/link-account/link-account.component.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/app/welcome/link-account/link-account.component.html b/src/app/welcome/link-account/link-account.component.html
index fd27440e..1ab303ed 100644
--- a/src/app/welcome/link-account/link-account.component.html
+++ b/src/app/welcome/link-account/link-account.component.html
@@ -1,6 +1,6 @@
 <div style="display: flex; justify-content: center;">
     <div style="
-padding-bottom: 15px;
+margin-top: 50px;
  width: 60%
 " class="panel panel-default">
         <div class="panel-heading">{{ 'ACCOUNT_LINKING.HEADER' | translate }}</div>
-- 
GitLab


From 6d220cb3ee6d6b5d2a4c759001a83ee910d377c8 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 13:07:59 +0200
Subject: [PATCH 08/25] added comment

---
 src/app/welcome/login/login.component.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/app/welcome/login/login.component.ts b/src/app/welcome/login/login.component.ts
index 62e1fc26..6971d776 100644
--- a/src/app/welcome/login/login.component.ts
+++ b/src/app/welcome/login/login.component.ts
@@ -64,8 +64,11 @@ export class LoginComponent implements OnInit {
         );
     }
 
+    // only for use in linking accounts
     public triggerOIDC() {
-        window.location.href = this.appConfig.getOidcUrl();
+        if (this.configuration.maintenance) {
+            window.location.href = this.appConfig.getOidcUrl();
+        }
     }
 
     public sendResetNotification() {
-- 
GitLab


From 3df0a175661d89d51236e4498f74836130ab7b18 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 13:08:20 +0200
Subject: [PATCH 09/25] added if statement

---
 src/app/auth/auth.service.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts
index dcc55696..56aa2c98 100644
--- a/src/app/auth/auth.service.ts
+++ b/src/app/auth/auth.service.ts
@@ -275,7 +275,7 @@ export class AuthService {
             map((res: Response) => {
                     const token = res && res['token'];
                     const oidcToken = res && res['oidcToken'];
-                    if (token) {
+                    if (token && oidcToken) {
                         this.storeToken(token);
                         this.storeOidcToken(oidcToken);
                         this.loginUsingSsoService = false;
-- 
GitLab


From c14b4a997c3a1d52d2807e9067c2daf0d14b760a Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Tue, 1 Apr 2025 13:34:47 +0200
Subject: [PATCH 10/25] increase coverage

---
 src/app/auth/auth.service.spec.ts | 80 +++++++++++++++++++++++++++----
 src/app/auth/auth.service.ts      | 18 -------
 2 files changed, 71 insertions(+), 27 deletions(-)

diff --git a/src/app/auth/auth.service.spec.ts b/src/app/auth/auth.service.spec.ts
index e9eef994..170ece28 100644
--- a/src/app/auth/auth.service.spec.ts
+++ b/src/app/auth/auth.service.spec.ts
@@ -3,19 +3,18 @@ import {TestBed, waitForAsync} from '@angular/core/testing';
 import {AuthService} from './auth.service';
 import {AppConfigService, ConfigurationService} from '../service';
 import {JwtHelperService} from '@auth0/angular-jwt';
-import {HttpClientTestingModule} from '@angular/common/http/testing';
+import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
 import {Role, UserRole} from '../model/userrole';
 import {ProfileService} from '../service/profile.service';
 import {Observable, of} from 'rxjs';
-import { Configuration } from '../model/configuration';
-import { HttpHandler } from '@angular/common/http';
+import {Configuration} from '../model/configuration';
 
 describe('Service: Auth', () => {
     let authService: AuthService;
     let appConfigServiceSpy: jasmine.SpyObj<AppConfigService>;
     let jwtHelperServiceSpy: jasmine.SpyObj<JwtHelperService>;
     let maintenanceServiceSpy: jasmine.SpyObj<ConfigurationService>;
-
+    let httpMock: HttpTestingController;
     let store: any = {};
 
     beforeEach(waitForAsync(() => {
@@ -23,7 +22,8 @@ describe('Service: Auth', () => {
             config: {
                 apiUrl: 'http://api.url',
                 tokenName: 'token',
-            }
+            },
+            getTestInstanceModalKey: () => 'testModalKey'
         };
         const jwtSpy = jasmine.createSpyObj('JwtHelperService', ['decodeToken', 'isTokenExpired']);
         jwtSpy.decodeToken.and.returnValue({
@@ -41,19 +41,19 @@ describe('Service: Auth', () => {
 
         class MockConfigurationService {
             protected uri: string;
-        
+
             constructor() {
                 this.uri = 'http://localhost/api';
             }
-        
+
             public getApiUrl(): string {
                 return 'http://localhost/api';
             }
-        
+
             public getConfiguration(): Observable<Configuration> {
                 return of<Configuration>();
             }
-        
+
             public updateConfiguration(configuration: Configuration): Observable<any> {
                 return of<Configuration>();
             }
@@ -84,6 +84,7 @@ describe('Service: Auth', () => {
             ],
         });
 
+        httpMock = TestBed.inject(HttpTestingController)
         authService = TestBed.get(AuthService);
         authService.profile = [userRole, userRole2]
         appConfigServiceSpy = TestBed.get(AppConfigService);
@@ -104,6 +105,10 @@ describe('Service: Auth', () => {
         });
 
     }));
+    afterEach(() => {
+        httpMock.verify();
+        store = {};
+    });
 
     it('should create service', () => {
         expect(authService).toBeTruthy();
@@ -180,8 +185,11 @@ describe('Service: Auth', () => {
     });
 
     it('should remove token on logout', () => {
+        store['oidc-token'] = 'some-oidc-token';
         authService.logout();
         expect(store['token']).not.toBeDefined();
+        const req = httpMock.expectOne('http://api.url/oidc/logout/some-oidc-token');
+        req.flush({});
     });
 
     it('should be logged in when token is present and valid', () => {
@@ -197,4 +205,58 @@ describe('Service: Auth', () => {
         expect(r).toEqual(false);
     });
 
+    it('should store token and oidc token in localStorage', () => {
+        authService.storeToken('abc123');
+        expect(store['token']).toEqual('abc123');
+
+        authService.storeOidcToken('oidc456');
+        expect(store['oidc-token']).toEqual('oidc456');
+    });
+
+    it('should remove roles from localStorage', () => {
+        store['rolesToken'] = 'some_roles';
+        authService.removeRoles();
+        expect(store['rolesToken']).toBeUndefined();
+    });
+
+    it('should load and parse roles from localStorage', () => {
+        const roles = [{domainId: 1, role: Role.ROLE_USER, domainName: 'x'}];
+        store['rolesToken'] = JSON.stringify(roles);
+
+        const result = authService.loadRoles();
+        expect(result.length).toEqual(1);
+        expect(result[0].role).toEqual(Role.ROLE_USER);
+    });
+
+    it('should assign loaded roles to profile', () => {
+        const roles = [{domainId: 2, role: Role.ROLE_DOMAIN_ADMIN, domainName: 'x'}];
+        store['rolesToken'] = JSON.stringify(roles);
+        authService.loadAndSaveRoles();
+        expect(authService.profile[0].role).toEqual(Role.ROLE_DOMAIN_ADMIN);
+    });
+    it('should stringify and store roles', () => {
+        const roles = [new UserRole()];
+        roles[0].domainId = 1;
+        roles[0].role = Role.ROLE_USER;
+        roles[0].domainName = 'dom1';
+
+        authService.storeRoles(roles);
+        expect(store['rolesToken']).toContain('ROLE_USER');
+    });
+    it('should get global role from token', () => {
+        const result = authService.getGlobalRole();
+        expect(result).toContain('ROLE_SYSTEM_ADMIN');
+    });
+
+    it('should handle login error with catchError', waitForAsync(() => {
+        authService.login('user', 'pass').subscribe({
+            next: () => fail('Expected error'),
+            error: (err) => {
+                expect(err.status).toEqual(401);
+            }
+        });
+
+        const req = httpMock.expectOne('http://api.url/auth/basic/login');
+        req.flush({ message: 'Invalid credentials' }, { status: 401, statusText: 'Unauthorized' });
+    }));
 });
diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts
index 56aa2c98..6156c8e4 100644
--- a/src/app/auth/auth.service.ts
+++ b/src/app/auth/auth.service.ts
@@ -15,10 +15,6 @@ export class DomainRoles {
     ) {
     }
 
-    public getDomainId(): number {
-        return this.domainId;
-    }
-
     public getRoles(): string[] {
         return this.roles;
     }
@@ -181,15 +177,6 @@ export class AuthService {
         return this.jwtHelper.decodeToken(token).global_role;
     }
 
-    public getDomainsRoles() {
-        const token = this.getToken();
-        if (token == null) {
-            return null;
-        }
-        return this.jwtHelper.decodeToken(token).roles;
-
-    }
-
     public getDomainRoles(): Map<number, DomainRoles> {
         const domainRolesMap: Map<number, DomainRoles> = new Map<number, DomainRoles>();
 
@@ -377,11 +364,6 @@ export class AuthService {
         return (token ? !this.jwtHelper.isTokenExpired(token) : false);
     }
 
-    get isLoggedIn$(): Observable<boolean> {
-        return this.isLoggedInSubject.pipe(
-            debounceTime(100), // use debounceTime to aggregate multiple emissions https://rxjs.dev/api/operators/debounceTime
-        );
-    }
 
     public getDomainIds(): number[] {
         return Array.from(new Set(this.profile.map(ur => ur.domainId)));
-- 
GitLab


From 89f9cf2bbac5bf2ec09ffa00db2eb858d3bcf187 Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Wed, 2 Apr 2025 12:27:07 +0200
Subject: [PATCH 11/25] fixed `if` statement

---
 src/app/welcome/login/login.component.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/app/welcome/login/login.component.ts b/src/app/welcome/login/login.component.ts
index 6971d776..0a4481b6 100644
--- a/src/app/welcome/login/login.component.ts
+++ b/src/app/welcome/login/login.component.ts
@@ -66,7 +66,7 @@ export class LoginComponent implements OnInit {
 
     // only for use in linking accounts
     public triggerOIDC() {
-        if (this.configuration.maintenance) {
+        if (!this.configuration.maintenance) {
             window.location.href = this.appConfig.getOidcUrl();
         }
     }
-- 
GitLab


From 82c3e817d72b479f36f553cbfc167bb3a07223fe Mon Sep 17 00:00:00 2001
From: pkazimierowski <pkazimierowski@man.poznan.pl>
Date: Wed, 2 Apr 2025 13:36:00 +0200
Subject: [PATCH 12/25] added error handle

---
 .../link-account/link-account.component.html  |  2 +-
 .../link-account/link-account.component.ts    | 20 ++++++++++++++++++-
 2 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/src/app/welcome/link-account/link-account.component.html b/src/app/welcome/link-account/link-account.component.html
index 1ab303ed..2d752052 100644
--- a/src/app/welcome/link-account/link-account.component.html
+++ b/src/app/welcome/link-account/link-account.component.html
@@ -43,7 +43,7 @@ margin-top: 50px;
                 <button type="submit" class="btn btn-primary"
                         (click)="submit()">{{ 'ACCOUNT_LINKING.CONFIRM' | translate }}
                 </button>
-
+                <div *ngIf="error" class="alert alert-danger" style="margin-top: 20px">{{error}}</div>
             </form>
             <br>
         </div>
diff --git a/src/app/welcome/link-account/link-account.component.ts b/src/app/welcome/link-account/link-account.component.ts
index 1f4f967c..cb60e030 100644
--- a/src/app/welcome/link-account/link-account.component.ts
+++ b/src/app/welcome/link-account/link-account.component.ts
@@ -3,6 +3,7 @@ import {User} from '../../model';
 import {ActivatedRoute, Router} from '@angular/router';
 import jwtDecode from 'jwt-decode';
 import {AuthService} from '../../auth/auth.service';
+import {TranslateService} from '@ngx-translate/core';
 
 
 @Component({
@@ -14,11 +15,13 @@ export class LinkAccountComponent implements OnInit, OnDestroy {
     public user: User;
     private token: string;
     public password: string;
+    public error: string;
 
     constructor(
         private readonly route: ActivatedRoute,
         private readonly authService: AuthService,
-        private readonly router: Router
+        private readonly router: Router,
+        private translate: TranslateService,
     ) {
     }
 
@@ -52,9 +55,24 @@ export class LinkAccountComponent implements OnInit, OnDestroy {
         ).subscribe(
             () => {
                 this.router.navigate(['/']);
+            },
+            err => {
+                this.error = this.translate.instant(this.getMessage(err));
             }
         )
     }
+    private getMessage(err: any): string {
+        switch (err['status']) {
+            case 401:
+                return 'LOGIN.LOGIN_FAILURE_MESSAGE';
+            case 406:
+                return 'LOGIN.APPLICATION_UNDER_MAINTENANCE_MESSAGE';
+            case 409:
+                return 'GENERIC_MESSAGE.UNAVAILABLE_MESSAGE';
+            default:
+                return 'GENERIC_MESSAGE.UNAVAILABLE_MESSAGE';
+        }
+    }
 }
 
 
-- 
GitLab


From 5e073a2b51fb548a51cc4a77616a98f759afb864 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20=C5=81opatowski?= <llopat@man.poznan.pl>
Date: Wed, 2 Apr 2025 16:18:18 +0200
Subject: [PATCH 13/25] Updated version to 1.7.0

---
 build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle b/build.gradle
index a443e538..508d65aa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ plugins {
 	id "org.sonarqube" version "3.2.0"
 }
 
-version = '1.7.0-SNAPSHOT'
+version = '1.7.0'
 
 task buildGUI(type: Exec) {
 	println 'Building using Angular CLI'
-- 
GitLab


From e382c18715720f96189f8136491918222b5d3443 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Mon, 7 Apr 2025 10:55:01 +0200
Subject: [PATCH 14/25] show error message on submit

---
 .../appmarket/domains/domain/domain.component.html    |  5 +++++
 src/app/appmarket/domains/domain/domain.component.ts  | 11 +++++++++--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/app/appmarket/domains/domain/domain.component.html b/src/app/appmarket/domains/domain/domain.component.html
index 00208f2f..c9c32db1 100644
--- a/src/app/appmarket/domains/domain/domain.component.html
+++ b/src/app/appmarket/domains/domain/domain.component.html
@@ -260,6 +260,11 @@
 		<div class="flex justify-content-end">
 			<button *ngIf="!isInMode(ComponentMode.VIEW)" type="submit" class="btn btn-primary" [disabled]="!domainForm.form.valid">{{ 'DOMAIN_DETAILS.SUBMIT_BUTTON' | translate }}</button>
 		</div>
+
+		<br *ngIf="errorMessage">
+      <div class="alert alert-danger text-left" *ngIf="errorMessage">
+        {{errorMessage}}
+      </div>
 	</form>
 </div>
 
diff --git a/src/app/appmarket/domains/domain/domain.component.ts b/src/app/appmarket/domains/domain/domain.component.ts
index eecd96f7..5cf1f5e0 100644
--- a/src/app/appmarket/domains/domain/domain.component.ts
+++ b/src/app/appmarket/domains/domain/domain.component.ts
@@ -9,7 +9,7 @@ import {User} from '../../../model';
 import {Observable, of} from 'rxjs';
 import {UserRole} from '../../../model/userrole';
 import {AuthService} from '../../../auth/auth.service';
-import {ModalComponent} from '../../../shared';
+import {ModalComponent} from '../../../shared'; 
 import {map, shareReplay, take} from 'rxjs/operators';
 import {DcnDeploymentType} from '../../../model/dcndeploymenttype';
 import {CustomerNetwork} from '../../../model/customernetwork';
@@ -48,6 +48,8 @@ export class DomainComponent extends BaseComponent implements OnInit {
 
     public annotations : Observable<DomainAnnotation[]> = of([]);
 
+    public errorMessage = "";
+
     constructor(public domainService: DomainService,
                 protected userService: UserService,
                 private router: Router,
@@ -103,7 +105,12 @@ export class DomainComponent extends BaseComponent implements OnInit {
         if (this.domainId !== undefined) {
             this.updateExistingDomain();
         } else {
-            this.domainService.add(this.domain).subscribe(() => this.router.navigate(['admin/domains/']));
+            this.domainService.add(this.domain).subscribe(() => {
+                this.router.navigate(['admin/domains/'])
+        }, err => {
+            console.error(err);
+            this.errorMessage = err.message;
+        });
         }
         this.domainService.setUpdateRequiredFlag(true);
     }
-- 
GitLab


From 107b443c73582a887d1a106663665e9adda2f30e Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Mon, 7 Apr 2025 10:59:37 +0200
Subject: [PATCH 15/25] delete hashed token from tabel

---
 .../users/access-token/access-tokens.component.html  | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/app/shared/users/access-token/access-tokens.component.html b/src/app/shared/users/access-token/access-tokens.component.html
index 9800061a..6ad21e04 100644
--- a/src/app/shared/users/access-token/access-tokens.component.html
+++ b/src/app/shared/users/access-token/access-tokens.component.html
@@ -6,22 +6,20 @@
             <tr>
                 <th scope="col">{{'TOKENS.TABLE.ID' | translate}}</th>
                 <th scope="col">{{'TOKENS.TABLE.NAME' | translate}}</th>
-                <th scope="col">{{'TOKENS.TABLE.VALUE' | translate}}</th>
                 <th scope="col">{{'TOKENS.TABLE.VALID' | translate}}</th>
                 <th scope="col">{{'TOKENS.TABLE.ACTIONS' | translate}}</th>
             </tr>
             </thead>
             <tbody>
             <tr *ngFor="let token of tokensList">
-                <td>{{token.id}}</td>
-                <td>{{token.name}}</td>
-                <td>{{token.tokenValue}}</td>
-                <td>{{token.valid}}</td>
-                <td *ngIf="token.valid">
+                <td style="width: 25%;">{{token.id}}</td>
+                <td style="width: 25%;">{{token.name}}</td>
+                <td style="width: 25%;">{{token.valid}}</td>
+                <td style="width: 25%;" *ngIf="token.valid">
                     <button type="button" class="btn btn-danger"
                             (click)="invalidate(token.id)">{{'TOKENS.BUTTON_INVALIDATE' | translate}}</button>
                 </td>
-                <td *ngIf="!token.valid">
+                <td style="width: 25%;" *ngIf="!token.valid">
                     <button type="button" class="btn btn-danger"
                             (click)="deleteToken(token.id)">{{'TOKENS.BUTTON_DELETE' | translate}}</button>
                 </td>
-- 
GitLab


From 2d8508c0a13aa993f9ce964e9a807c5cb17e1703 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Thu, 10 Apr 2025 14:13:56 +0200
Subject: [PATCH 16/25] fix problem with displaying error based on structure

---
 src/app/appmarket/domains/domain/domain.component.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/app/appmarket/domains/domain/domain.component.ts b/src/app/appmarket/domains/domain/domain.component.ts
index 5cf1f5e0..b25fb1cc 100644
--- a/src/app/appmarket/domains/domain/domain.component.ts
+++ b/src/app/appmarket/domains/domain/domain.component.ts
@@ -109,7 +109,10 @@ export class DomainComponent extends BaseComponent implements OnInit {
                 this.router.navigate(['admin/domains/'])
         }, err => {
             console.error(err);
-            this.errorMessage = err.message;
+            console.warn("error", err.statusCode)
+            if(err.statusCode !== 409 && err?.message !== undefined) this.errorMessage = err.message;
+            else this.errorMessage = err;
+    
         });
         }
         this.domainService.setUpdateRequiredFlag(true);
-- 
GitLab


From 5656161dd6620407d3f619e67bc9947fe79114db Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Thu, 10 Apr 2025 14:14:46 +0200
Subject: [PATCH 17/25] delete log

---
 src/app/appmarket/domains/domain/domain.component.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/app/appmarket/domains/domain/domain.component.ts b/src/app/appmarket/domains/domain/domain.component.ts
index b25fb1cc..d74fcedd 100644
--- a/src/app/appmarket/domains/domain/domain.component.ts
+++ b/src/app/appmarket/domains/domain/domain.component.ts
@@ -109,7 +109,6 @@ export class DomainComponent extends BaseComponent implements OnInit {
                 this.router.navigate(['admin/domains/'])
         }, err => {
             console.error(err);
-            console.warn("error", err.statusCode)
             if(err.statusCode !== 409 && err?.message !== undefined) this.errorMessage = err.message;
             else this.errorMessage = err;
     
-- 
GitLab


From 63bf8b1efe83823fc6d4aa31e3fffdeb8e8c26be Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 11 Apr 2025 13:41:07 +0200
Subject: [PATCH 18/25] use roleExluded, fix domain name

---
 .../appinstancelist/appinstancelist.component.html    |  4 ++--
 .../appinstancelist/appinstancelist.component.ts      | 11 -----------
 src/app/model/app-instance.ts                         |  1 +
 src/app/shared/navbar/navbar.component.html           |  3 ++-
 4 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.html b/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.html
index 88caaeec..d97bd6f0 100644
--- a/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.html
+++ b/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.html
@@ -78,7 +78,7 @@
                 <td class="col-lg-1 col-md-1">{{appInstance?.applicationVersion}}</td>
                 <td class="col-lg-2 col-md-2"
                     *ngIf="domainId === undefined || domainId === domainService.getGlobalDomainId()">
-                    {{getDomainNameById(appInstance?.domainId)}}
+                    {{appInstance?.domainName}}
                 </td>
                 <td class="col-lg-1 col-md-1">{{appInstance?.owner?.username}}</td>
                 <td class="col-lg-2 col-md-2">{{appInstance?.createdAt | localDate:'dd-MM-yyyy HH:mm'}}</td>
@@ -128,7 +128,7 @@
                 <td class="col-lg-2 col-md-2">{{appInstance?.applicationName}}</td>
                 <td class="col-lg-1 col-md-1"
                     *ngIf="domainId === undefined || domainId === domainService.getGlobalDomainId()">
-                    {{getDomainNameById(appInstance?.domainId)}}</td>
+                    {{appInstance?.domainName}}</td>
                 <td class="col-lg-1 col-md-1">{{appInstance?.owner?.username}}</td>
                 <td class="col-lg-2 col-md-2">{{appInstance?.createdAt | localDate:'dd-MM-yyyy HH:mm'}}</td>
                 <td class="col-lg-3 col-md-3">{{ translateState(appInstance?.state) }}</td>
diff --git a/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.ts b/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.ts
index 1a95ba43..b02be4cc 100644
--- a/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.ts
+++ b/src/app/appmarket/appinstance/appinstancelist/appinstancelist.component.ts
@@ -52,8 +52,6 @@ export class AppInstanceListComponent implements OnInit {
     public selectedUsername: string;
     public domainId = 0;
 
-    public domains: Domain[] = [];
-
     public searchValue = '';
     public selectionOptions = [
         { label: this.translateEnum(AppInstanceListSelection.ALL), value: AppInstanceListSelection.ALL },
@@ -73,9 +71,6 @@ export class AppInstanceListComponent implements OnInit {
 
     ngOnInit() {
         this.sessionService.registerCulture(this.translateService.currentLang);
-        this.domainService.getAll().subscribe(result => {
-            this.domains.push(...result);
-        });
         const i = sessionStorage.getItem(this.item_number_key);
         if (i) {
             this.maxItemsOnPage = +i;
@@ -110,12 +105,6 @@ export class AppInstanceListComponent implements OnInit {
 
     }
 
-    public getDomainNameById(id: number): string {
-        if (this.domains === undefined) {
-            return 'none';
-        }
-        return this.domains.find(value => value.id === id).name;
-    }
 
     public translateEnum(value: AppInstanceListSelection): string {
         let outValue = '';
diff --git a/src/app/model/app-instance.ts b/src/app/model/app-instance.ts
index 76edca2f..27119dad 100644
--- a/src/app/model/app-instance.ts
+++ b/src/app/model/app-instance.ts
@@ -28,6 +28,7 @@ export class AppInstance {
 
   public id: number = undefined;
   public domainId: number = undefined;
+  public domainName: string = undefined;
   public applicationId: number = undefined;
   public applicationName: string = undefined;
   public applicationVersion: string = undefined;
diff --git a/src/app/shared/navbar/navbar.component.html b/src/app/shared/navbar/navbar.component.html
index cead22d4..d41a8c16 100644
--- a/src/app/shared/navbar/navbar.component.html
+++ b/src/app/shared/navbar/navbar.component.html
@@ -77,7 +77,8 @@
                         <li *roles="['ROLE_SYSTEM_ADMIN']"><a
                                 [routerLink]="['/admin/users']">{{ 'NAVBAR.USERS' | translate }}</a>
                         </li>
-                        <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_DOMAIN_ADMIN']"><a
+                        <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_DOMAIN_ADMIN']" >
+                            <a *rolesExcluded="['ROLE_SYSTEM_ADMIN']"
                                 [routerLink]="['/domain/users']">{{ 'NAVBAR.DOMAIN_USERS' | translate }}</a>
                         </li>
                         <li *roles="['ROLE_SYSTEM_ADMIN']"><a
-- 
GitLab


From 570f35eaed6d155396a11bdd336e54800771e76a Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 11 Apr 2025 13:56:34 +0200
Subject: [PATCH 19/25] fix test

---
 .../appinstance/appinstance/appinstance.component.spec.ts        | 1 +
 .../modals/add-members-modal/add-members-modal.component.spec.ts | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts b/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts
index 449459e4..3634c03e 100644
--- a/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts
+++ b/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts
@@ -186,6 +186,7 @@ describe('Component: AppInstance', () => {
         createdAt: new Date(),
         descriptiveDeploymentId: 'test-oxidized-48',
         domainId: 4,
+        domainName: "Test Domain",
         id: 1,
         internalId: 'eccbaf70-7fdd-401a-bb3e-b8659bcfbdff',
         name: 'oxi-virt-1',
diff --git a/src/app/appmarket/appinstance/modals/add-members-modal/add-members-modal.component.spec.ts b/src/app/appmarket/appinstance/modals/add-members-modal/add-members-modal.component.spec.ts
index 4a613f13..0e2900d4 100644
--- a/src/app/appmarket/appinstance/modals/add-members-modal/add-members-modal.component.spec.ts
+++ b/src/app/appmarket/appinstance/modals/add-members-modal/add-members-modal.component.spec.ts
@@ -32,6 +32,7 @@ describe('AddMembersModalComponent', () => {
         createdAt: new Date(),
         descriptiveDeploymentId: 'test-oxidized-48',
         domainId: 4,
+        domainName: "Test Domain",
         id: 1,
         internalId: 'eccbaf70-7fdd-401a-bb3e-b8659bcfbdff',
         name: 'oxi-virt-1',
-- 
GitLab


From 1cd9c8b4dcd4de755dbe98748be5647d994a8540 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 11 Apr 2025 15:42:31 +0200
Subject: [PATCH 20/25] move role exluded to diffrent directive

---
 .../login-success/login-success.component.ts  |  1 +
 src/app/directive/roles-exluded.directive.ts  | 30 +++++++++++
 src/app/directive/roles.directive.ts          | 52 +++----------------
 src/app/shared/navbar/navbar.component.html   |  9 ++--
 src/app/shared/shared.module.ts               |  9 +++-
 5 files changed, 50 insertions(+), 51 deletions(-)
 create mode 100644 src/app/directive/roles-exluded.directive.ts

diff --git a/src/app/auth/login-success/login-success.component.ts b/src/app/auth/login-success/login-success.component.ts
index e7fec74d..4d5444dc 100644
--- a/src/app/auth/login-success/login-success.component.ts
+++ b/src/app/auth/login-success/login-success.component.ts
@@ -25,6 +25,7 @@ export class LoginSuccessComponent implements OnInit {
             if (refreshToken) {
                 this.authService.storeOidcToken(oidcToken);
             }
+            this.authService.loadUser();
             this.router.navigate(['/'])
         })
 
diff --git a/src/app/directive/roles-exluded.directive.ts b/src/app/directive/roles-exluded.directive.ts
new file mode 100644
index 00000000..6b003ab5
--- /dev/null
+++ b/src/app/directive/roles-exluded.directive.ts
@@ -0,0 +1,30 @@
+import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
+import { AuthService } from '../auth/auth.service';
+
+@Directive({
+  selector: '[rolesExcluded]'
+})
+export class RolesExcludedDirective {
+  private _excluded: Array<string> = [];
+
+  constructor(
+    private _templateRef: TemplateRef<any>,
+    private _viewContainer: ViewContainerRef,
+    private authService: AuthService
+  ) {}
+
+  @Input() set rolesExcluded(excludedRoles: Array<string>) {
+    this._excluded = excludedRoles;
+    this.updateState();
+  }
+
+  private updateState() {
+    this._viewContainer.clear();
+
+    const hasExcludedRole = this._excluded.some(role => this.authService.hasRole(role));
+    if (!hasExcludedRole) {
+      // If user has exluded role hide the element 
+      this._viewContainer.createEmbeddedView(this._templateRef);
+    } 
+  }
+}
\ No newline at end of file
diff --git a/src/app/directive/roles.directive.ts b/src/app/directive/roles.directive.ts
index 68f2e123..c2155e3c 100644
--- a/src/app/directive/roles.directive.ts
+++ b/src/app/directive/roles.directive.ts
@@ -3,10 +3,7 @@ import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
 
 class RoleState {
     public allowed: Array<string> = new Array<string>();
-    public excluded: Array<string> = new Array<string>()
 }
-
-
 @Directive({
     selector: '[roles]',
     inputs: ['roles']
@@ -15,8 +12,6 @@ export class RolesDirective {
 
     private _allowed: Array<string> = new Array<string>();
 
-    private _excluded: Array<string> = new Array<string>();
-
     constructor(private _templateRef: TemplateRef<any>,
                 private _viewContainer: ViewContainerRef,
                 private authService: AuthService) {
@@ -26,53 +21,18 @@ export class RolesDirective {
     @Input() set roles(allowedRoles: Array<string>) {
         this._allowed = allowedRoles;
         this.updateState({
-            allowed: this._allowed,
-            excluded: this._excluded
-        })
-    }
-
-    // Excluded roles have priority than allowed roles
-    // If user have excluded role template would not be shown
-
-    @Input() set rolesExcluded(excluded: Array<string>) {
-        this._excluded = excluded;
-        this.updateState({
-            allowed: this._allowed,
-            excluded: this._excluded
+            allowed: this._allowed
         })
     }
 
     updateState(state: RoleState) {
         this._viewContainer.clear();
-
-        let show: boolean = false;
-        let notAllowed: boolean = false;
-
-        const allowedRoles = state.allowed;
-
-        for (let exclude of state.excluded) {
-            if (this.authService.hasRole(exclude)) {
-                notAllowed = true;
-                break;
-            }
+    
+    
+        const hasAllowedRole = state.allowed.some(role => this.authService.hasRole(role));
+        if (hasAllowedRole) {
+            this._viewContainer.createEmbeddedView(this._templateRef);
         }
-        if (notAllowed) {
-            this._viewContainer.clear();
-        } else {
-            for (let allowedRole of allowedRoles) {
-                if (this.authService.hasRole(allowedRole)) {
-                    show = true;
-                    break;
-                }
-            }
-
-            if (show) {
-                this._viewContainer.createEmbeddedView(this._templateRef);
-            } else {
-                this._viewContainer.clear();
-            }
-        }
-
     }
 
 }
diff --git a/src/app/shared/navbar/navbar.component.html b/src/app/shared/navbar/navbar.component.html
index d41a8c16..11b5cb2e 100644
--- a/src/app/shared/navbar/navbar.component.html
+++ b/src/app/shared/navbar/navbar.component.html
@@ -77,10 +77,13 @@
                         <li *roles="['ROLE_SYSTEM_ADMIN']"><a
                                 [routerLink]="['/admin/users']">{{ 'NAVBAR.USERS' | translate }}</a>
                         </li>
-                        <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_DOMAIN_ADMIN']" >
-                            <a *rolesExcluded="['ROLE_SYSTEM_ADMIN']"
-                                [routerLink]="['/domain/users']">{{ 'NAVBAR.DOMAIN_USERS' | translate }}</a>
+                        
+                        <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_DOMAIN_ADMIN']">
+                                <a *rolesExcluded="['ROLE_SYSTEM_ADMIN']"
+                                    [routerLink]="['/domain/users']">{{ 'NAVBAR.DOMAIN_USERS' | translate }}</a>
+                           
                         </li>
+                    
                         <li *roles="['ROLE_SYSTEM_ADMIN']"><a
                                 [routerLink]="['/admin/languages']">{{ 'NAVBAR.LANGUAGES' | translate }}</a>
                         </li>
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index a6243e01..ae644275 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -1,6 +1,6 @@
 import {DefaultLogo} from '../directive/defaultlogo.directive';
 import {RolesDirective} from '../directive/roles.directive';
-import {NgModule} from '@angular/core';
+import {NgModule, NO_ERRORS_SCHEMA} from '@angular/core';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {CommonModule, DatePipe} from '@angular/common';
 
@@ -65,6 +65,7 @@ import { InputGroupModule } from 'primeng/inputgroup';
 import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
 import { ButtonModule } from 'primeng/button';
 import { BrowserModule } from '@angular/platform-browser';
+import { RolesExcludedDirective } from '../directive/roles-exluded.directive';
 
 
 @NgModule({
@@ -103,6 +104,7 @@ import { BrowserModule } from '@angular/platform-browser';
         NavbarComponent,
         DefaultLogo,
         RolesDirective,
+        RolesExcludedDirective,
         MinLengthDirective,
         MaxLengthDirective,
         SearchComponent,
@@ -179,13 +181,16 @@ import { BrowserModule } from '@angular/platform-browser';
         ModalTestInstanceComponent,
         ModalNotificationSendComponent,
         DomainRolesDirective,
+        RolesExcludedDirective,
         SshKeysComponent,
         ModalProvideSshKeyComponent,
         PreferencesComponent,
         SortableHeaderDirective,
         DomainNamespaceAnnotationsComponent,
         AccessTokensComponent
-    ]
+    ],
+    schemas: [NO_ERRORS_SCHEMA], // Dodanie schematu
+
 })
 export class SharedModule {
 }
-- 
GitLab


From 2414c23be391925e439e2acb8fb39d08d1d8f410 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Mon, 14 Apr 2025 10:29:10 +0200
Subject: [PATCH 21/25] fix user call based on mode

---
 .../shared/users/list/userslist.component.ts    | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/app/shared/users/list/userslist.component.ts b/src/app/shared/users/list/userslist.component.ts
index 7394575d..1ae3116b 100644
--- a/src/app/shared/users/list/userslist.component.ts
+++ b/src/app/shared/users/list/userslist.component.ts
@@ -101,11 +101,20 @@ export class UsersListComponent extends BaseComponent implements OnInit, OnChang
     }
 
     public getAllDomain() {
-        this.domainService.getAll().subscribe(domains => {
-            domains.forEach(domain => {
-                this.domainCache.setData(domain.id, domain)
+        if(this.domainMode) {
+            this.domainService.getMyDomains().subscribe(domains => {
+                domains.forEach(domain => { 
+                    this.domainCache.setData(domain.id, domain)
+                }
+            ) })
+        } else {
+            this.domainService.getAll().subscribe(domains => {
+                domains.forEach(domain => {
+                    this.domainCache.setData(domain.id, domain)
+                })
             })
-        })
+        }
+       
     }
 
     public getDomainName(domainId: number): Observable<string> {
-- 
GitLab


From 80d8c5424cb915ed9cb254de69658f68e33af808 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Mon, 14 Apr 2025 10:42:59 +0200
Subject: [PATCH 22/25] add group domain admin

---
 src/app/appmarket/appdetails/appdetails.component.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/app/appmarket/appdetails/appdetails.component.ts b/src/app/appmarket/appdetails/appdetails.component.ts
index e5518794..cb156314 100644
--- a/src/app/appmarket/appdetails/appdetails.component.ts
+++ b/src/app/appmarket/appdetails/appdetails.component.ts
@@ -146,7 +146,8 @@ export class AppDetailsComponent implements OnInit {
         }
 
         return this.authService.hasRole(Role[Role.ROLE_SYSTEM_ADMIN])
-            || this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_DOMAIN_ADMIN]);
+            || this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_DOMAIN_ADMIN])
+            || this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_GROUP_DOMAIN_ADMIN]);
     }
 
     public isApplicationEnabledInDomain(): boolean {
-- 
GitLab


From e54db25177602c0a0590d5525cbac23c1f87a04d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20=C5=81opatowski?= <llopat@man.poznan.pl>
Date: Mon, 14 Apr 2025 16:21:45 +0200
Subject: [PATCH 23/25] Updated version to 1.7.1

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 881b777c..2a4d1793 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "nmaas-portal",
-  "version": "1.7.0",
+  "version": "1.7.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "nmaas-portal",
-      "version": "1.7.0",
+      "version": "1.7.1",
       "license": "Apache 2.0",
       "dependencies": {
         "@angular/animations": "17.3.12",
diff --git a/package.json b/package.json
index 9402af95..8f55d7ac 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "nmaas-portal",
-  "version": "1.7.0",
+  "version": "1.7.1",
   "license": "Apache 2.0",
   "angular-cli": {},
   "scripts": {
-- 
GitLab


From 2b999125bc407010b5381226dffa8585cc8acb9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20=C5=81opatowski?= <llopat@man.poznan.pl>
Date: Mon, 14 Apr 2025 16:34:52 +0200
Subject: [PATCH 24/25] Added Mend scanning CI job

---
 .gitlab-ci.yml | 24 +++++++++++++++++++++++-
 ws/run_ws.sh   |  7 -------
 2 files changed, 23 insertions(+), 8 deletions(-)
 delete mode 100644 ws/run_ws.sh

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ca9b9640..73cc5798 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,7 @@ stages:
   - test
   - sonar
   - build
+  - mend
 
 test:
   stage: test
@@ -64,4 +65,25 @@ build_and_push_release_image:
       docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REPOSITORY
       docker build -t $DOCKER_REPOSITORY_LOCAL:$IMAGE_TAG .
       docker push $DOCKER_REPOSITORY_LOCAL:$IMAGE_TAG
-      docker logout $DOCKER_REPOSITORY
\ No newline at end of file
+      docker logout $DOCKER_REPOSITORY
+
+mend:
+  stage: mend
+  image: openjdk:17-jdk-slim
+  only:
+    - /^release/
+  variables:
+    PRODUCT_NAME: "nmaas"
+    PROJECT_NAME: "nmaas-portal"
+  script:
+    - |
+      export PRODUCT_VERSION=$(echo $CI_COMMIT_BRANCH | cut -c 9-)
+      export PROJECT_VERSION=$PRODUCT_VERSION
+      apt-get update && apt-get install -y curl gnupg
+      curl -sL https://deb.nodesource.com/setup_11.x  | bash -
+      apt-get -y install nodejs
+      npm install -g @angular/cli
+      npm ci
+      chmod +x ./gradlew
+      curl -LJO https://github.com/whitesource/unified-agent-distribution/releases/latest/download/wss-unified-agent.jar
+      java -jar wss-unified-agent.jar -userKey ${MEND_USER_KEY} -apiKey ${MEND_API_KEY} -projectVersion ${PROJECT_VERSION} -project ${PROJECT_NAME} -productVersion ${PRODUCT_VERSION} -product ${PRODUCT_NAME} -c ./ws/ws.config -d ./
\ No newline at end of file
diff --git a/ws/run_ws.sh b/ws/run_ws.sh
deleted file mode 100644
index 8763a587..00000000
--- a/ws/run_ws.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-echo "Downloading WhiteSource agent..."
-curl -LJO https://github.com/whitesource/unified-agent-distribution/releases/latest/download/wss-unified-agent.jar
-
-echo "Running WhiteSource scan..."
-java -jar wss-unified-agent.jar -userKey ${USER_KEY} -apiKey ${API_KEY} -projectVersion ${PROJECT_VERSION} -projectToken ${PROJECT_TOKEN} -productVersion ${PRODUCT_VERSION} -productToken ${PRODUCT_TOKEN} -c ws.config -d ../
\ No newline at end of file
-- 
GitLab


From 4dd4bae59e2f6b33e70eba7d9d3326ab13992d00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20=C5=81opatowski?= <llopat@man.poznan.pl>
Date: Tue, 15 Apr 2025 08:33:30 +0200
Subject: [PATCH 25/25] Fix in Mend scanning CI job

---
 .gitlab-ci.yml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 73cc5798..525ea473 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -79,9 +79,7 @@ mend:
     - |
       export PRODUCT_VERSION=$(echo $CI_COMMIT_BRANCH | cut -c 9-)
       export PROJECT_VERSION=$PRODUCT_VERSION
-      apt-get update && apt-get install -y curl gnupg
-      curl -sL https://deb.nodesource.com/setup_11.x  | bash -
-      apt-get -y install nodejs
+      apt-get update && apt-get install -y curl nodejs npm
       npm install -g @angular/cli
       npm ci
       chmod +x ./gradlew
-- 
GitLab