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. * 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 { mockToken } from '../mock/jwt-helper.service.spec';
import { TestBed } from '@angular/core/testing'; 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', () => { describe('JwtHelperService', () => {
let jwtHelperService: JwtHelperService; let jwtHelperService: JwtHelperService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [JwtHelperService] providers: [JwtHelperService, { provide: StorageService, useValue: mockStorage }]
}); });
jwtHelperService = TestBed.inject(JwtHelperService); jwtHelperService = TestBed.inject(JwtHelperService);
}); });
@@ -44,12 +61,8 @@ describe('JwtHelperService', () => {
}); });
describe('RealmRole ', () => { describe('RealmRole ', () => {
it('Should be true if the realm_access contains the single role', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] } realm_access: { roles: ['role1'] }
}); });
@@ -58,10 +71,7 @@ describe('JwtHelperService', () => {
}); });
it('Should be true if the realm_access contains at least one of the roles', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] } realm_access: { roles: ['role1'] }
}); });
@@ -70,9 +80,7 @@ describe('JwtHelperService', () => {
}); });
it('Should be false if the realm_access does not contain the role', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role3'] } realm_access: { roles: ['role3'] }
}); });
const result = jwtHelperService.hasRealmRole('role1'); const result = jwtHelperService.hasRealmRole('role1');
@@ -80,9 +88,7 @@ describe('JwtHelperService', () => {
}); });
it('Should be false if the realm_access does not contain at least one of the roles', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] } realm_access: { roles: ['role1'] }
}); });
const result = jwtHelperService.hasRealmRoles(['role3', 'role2']); const result = jwtHelperService.hasRealmRoles(['role3', 'role2']);
@@ -91,12 +97,8 @@ describe('JwtHelperService', () => {
}); });
describe('ClientRole ', () => { describe('ClientRole ', () => {
it('Should be true if the resource_access contains the single role', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role1'] } } resource_access: { fakeApp: { roles: ['role1'] } }
}); });
@@ -105,10 +107,7 @@ describe('JwtHelperService', () => {
}); });
it('Should be true if the resource_access contains at least one of the roles', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role1'] } } resource_access: { fakeApp: { roles: ['role1'] } }
}); });
@@ -117,9 +116,7 @@ describe('JwtHelperService', () => {
}); });
it('Should be false if the resource_access does not contain the role', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role3'] } } resource_access: { fakeApp: { roles: ['role3'] } }
}); });
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']); const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
@@ -127,9 +124,7 @@ describe('JwtHelperService', () => {
}); });
it('Should be false if the resource_access does not contain the client role related to the app', () => { 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({
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { anotherFakeApp: { roles: ['role1'] } } resource_access: { anotherFakeApp: { roles: ['role1'] } }
}); });
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']); const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
@@ -137,3 +132,33 @@ describe('JwtHelperService', () => {
}); });
}); });
}); });
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. * 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'; import { StorageService } from '../../common/services/storage.service';
export const JWT_STORAGE_SERVICE = new InjectionToken<OAuthStorage>('JWT_STORAGE_SERVICE', {
providedIn: 'root',
factory: () => inject(StorageService)
});
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class JwtHelperService { export class JwtHelperService {
static USER_NAME = 'name'; static USER_NAME = 'name';
static FAMILY_NAME = 'family_name'; static FAMILY_NAME = 'family_name';
static GIVEN_NAME = 'given_name'; static GIVEN_NAME = 'given_name';
@@ -34,8 +39,7 @@ export class JwtHelperService {
static USER_PREFERRED_USERNAME = 'preferred_username'; static USER_PREFERRED_USERNAME = 'preferred_username';
static HXP_AUTHORIZATION = 'hxp_authorization'; 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. * Decodes a JSON web token into a JS object.
@@ -186,7 +190,7 @@ export class JwtHelperService {
* @param rolesToCheck List of role names to check * @param rolesToCheck List of role names to check
* @returns True if it contains at least one of the given roles, false otherwise * @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)); return rolesToCheck.some((currentRole) => this.hasRealmRole(currentRole));
} }
@@ -197,7 +201,7 @@ export class JwtHelperService {
* @param rolesToCheck List of role names to check * @param rolesToCheck List of role names to check
* @returns True if it contains at least one of the given roles, false otherwise * @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)); return rolesToCheck.some((currentRole) => this.hasClientRole(clientName, currentRole));
} }