AAE-26321 Custom JWT storage service optional injection token (#10292)

* AAE-26321 Custom JWT storage service optional injection token

* AAE-26321 custom storage service test

* AAE-26321 Use only the injection token with factory

* AAE-26321 improve custom storage test
This commit is contained in:
Wojciech Duda
2024-10-09 17:06:30 +02:00
committed by GitHub
parent 9a9a9db628
commit 9e96fed71e
2 changed files with 89 additions and 60 deletions

View File

@@ -15,17 +15,34 @@
* limitations under the License.
*/
import { JwtHelperService } from './jwt-helper.service';
import { JWT_STORAGE_SERVICE, JwtHelperService } from './jwt-helper.service';
import { mockToken } from '../mock/jwt-helper.service.spec';
import { TestBed } from '@angular/core/testing';
import { StorageService } from '../../common';
import { OAuthStorage } from 'angular-oauth2-oidc';
const mockStorage = {
access_token: 'my-access_token',
id_token: 'my-id_token',
getItem(key: string) {
return this[key];
}
};
const mockCustomStorage = {
access_token: 'my-custom-access_token',
id_token: 'my-custom-id_token',
getItem(key: string) {
return this[key];
}
};
describe('JwtHelperService', () => {
let jwtHelperService: JwtHelperService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [JwtHelperService]
providers: [JwtHelperService, { provide: StorageService, useValue: mockStorage }]
});
jwtHelperService = TestBed.inject(JwtHelperService);
});
@@ -44,96 +61,104 @@ describe('JwtHelperService', () => {
});
describe('RealmRole ', () => {
it('Should be true if the realm_access contains the single role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role1'] }
});
const result = jwtHelperService.hasRealmRole('role1');
expect(result).toBeTruthy();
});
it('Should be true if the realm_access contains at least one of the roles', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role1'] }
});
const result = jwtHelperService.hasRealmRoles(['role1', 'role2']);
expect(result).toBeTruthy();
});
it('Should be false if the realm_access does not contain the role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role3'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role3'] }
});
const result = jwtHelperService.hasRealmRole('role1');
expect(result).toBeFalsy();
});
it('Should be false if the realm_access does not contain at least one of the roles', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role1'] }
});
const result = jwtHelperService.hasRealmRoles(['role3', 'role2']);
expect(result).toBeFalsy();
});
});
});
describe('ClientRole ', () => {
it('Should be true if the resource_access contains the single role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role1'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { fakeApp: { roles: ['role1'] } }
});
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1']);
expect(result).toBeTruthy();
});
it('Should be true if the resource_access contains at least one of the roles', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role1'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { fakeApp: { roles: ['role1'] } }
});
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
expect(result).toBeTruthy();
});
it('Should be false if the resource_access does not contain the role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role3'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { fakeApp: { roles: ['role3'] } }
});
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
expect(result).toBeFalsy();
});
it('Should be false if the resource_access does not contain the client role related to the app', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { anotherFakeApp: { roles: ['role1'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { anotherFakeApp: { roles: ['role1'] } }
});
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
expect(result).toBeFalsy();
});
});
});
});
describe('JwtHelperService with custom storage service', () => {
let jwtHelperService: JwtHelperService;
let defaultStorage: StorageService;
let customStorage: OAuthStorage;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
JwtHelperService,
{ provide: StorageService, useValue: mockStorage },
{ provide: JWT_STORAGE_SERVICE, useValue: mockCustomStorage }
]
});
jwtHelperService = TestBed.inject(JwtHelperService);
defaultStorage = TestBed.inject(StorageService);
customStorage = TestBed.inject(JWT_STORAGE_SERVICE);
});
it('should use the custom storage service', () => {
const customStorageGetItemSpy = spyOn(customStorage, 'getItem').and.callThrough();
const defaultStorageGetItemSpy = spyOn(defaultStorage, 'getItem').and.callThrough();
const result = jwtHelperService.getIdToken();
expect(customStorage).toBeDefined();
expect(customStorageGetItemSpy).toHaveBeenCalled();
expect(defaultStorageGetItemSpy).not.toHaveBeenCalled();
expect(result).toBe('my-custom-id_token');
});
});

View File

@@ -15,14 +15,19 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { inject, Injectable, InjectionToken } from '@angular/core';
import { OAuthStorage } from 'angular-oauth2-oidc';
import { StorageService } from '../../common/services/storage.service';
export const JWT_STORAGE_SERVICE = new InjectionToken<OAuthStorage>('JWT_STORAGE_SERVICE', {
providedIn: 'root',
factory: () => inject(StorageService)
});
@Injectable({
providedIn: 'root'
})
export class JwtHelperService {
static USER_NAME = 'name';
static FAMILY_NAME = 'family_name';
static GIVEN_NAME = 'given_name';
@@ -34,8 +39,7 @@ export class JwtHelperService {
static USER_PREFERRED_USERNAME = 'preferred_username';
static HXP_AUTHORIZATION = 'hxp_authorization';
constructor(private storageService: StorageService) {
}
private storageService: OAuthStorage = inject(JWT_STORAGE_SERVICE);
/**
* Decodes a JSON web token into a JS object.
@@ -85,7 +89,7 @@ export class JwtHelperService {
* @param key Key name of the field to retrieve
* @returns Value from the token
*/
getValueFromLocalToken<T>(key: string): T {
getValueFromLocalToken<T>(key: string): T {
return this.getValueFromToken(this.getAccessToken(), key) || this.getValueFromToken(this.getIdToken(), key);
}
@@ -114,7 +118,7 @@ export class JwtHelperService {
* @param key Key name of the field to retrieve
* @returns Value from the token
*/
getValueFromLocalIdToken<T>(key: string): T {
getValueFromLocalIdToken<T>(key: string): T {
return this.getValueFromToken(this.getIdToken(), key);
}
@@ -123,7 +127,7 @@ export class JwtHelperService {
*
* @returns id token
*/
getIdToken(): string {
getIdToken(): string {
return this.storageService.getItem(JwtHelperService.USER_ID_TOKEN);
}
@@ -186,7 +190,7 @@ export class JwtHelperService {
* @param rolesToCheck List of role names to check
* @returns True if it contains at least one of the given roles, false otherwise
*/
hasRealmRoles(rolesToCheck: string []): boolean {
hasRealmRoles(rolesToCheck: string[]): boolean {
return rolesToCheck.some((currentRole) => this.hasRealmRole(currentRole));
}
@@ -197,7 +201,7 @@ export class JwtHelperService {
* @param rolesToCheck List of role names to check
* @returns True if it contains at least one of the given roles, false otherwise
*/
hasRealmRolesForClientRole(clientName: string, rolesToCheck: string []): boolean {
hasRealmRolesForClientRole(clientName: string, rolesToCheck: string[]): boolean {
return rolesToCheck.some((currentRole) => this.hasClientRole(clientName, currentRole));
}