Skip to content

Commit 688fe2f

Browse files
committed
WFPREV-797 Extra scope check for WFPREV_CREATOR
1 parent a69de4a commit 688fe2f

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

client/wfprev-war/src/main/angular/src/app/services/token.service.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,45 @@ describe('TokenService', () => {
406406
expect(service.getIdir()).toBe('');
407407
});
408408
});
409+
410+
describe('hasAllScopesFromHash (scope validation)', () => {
411+
const call = (hash: string, required: string[]) =>
412+
(service as any).hasAllScopesFromHash(hash, required);
413+
414+
afterEach(() => {
415+
// clean up hash after each test
416+
window.history.pushState({}, '', '/');
417+
});
418+
419+
it('returns true when all explicit scopes are present (space encoded as %20)', () => {
420+
const hash = '#access_token=t&scope=FOO%20BAR%20BAZ';
421+
expect(call(hash, ['FOO', 'BAR'])).toBeTrue();
422+
});
423+
424+
it('returns true when scopes are + separated (IdPs may encode spaces as +)', () => {
425+
const hash = '#access_token=t&scope=FOO+BAR+BAZ';
426+
expect(call(hash, ['FOO', 'BAR'])).toBeTrue();
427+
});
428+
429+
it('supports wildcard prefix (WFDM.*) when at least one WFDM scope is present', () => {
430+
const hash = '#access_token=t&scope=WFDM.CREATE_FILE%20WFPREV.GET_TOPLEVEL';
431+
expect(call(hash, ['WFDM.*'])).toBeTrue();
432+
});
433+
434+
it('fails wildcard check when no scope matches the prefix', () => {
435+
const hash = '#access_token=t&scope=WFPREV.GET_TOPLEVEL';
436+
expect(call(hash, ['WFDM.*'])).toBeFalse();
437+
});
438+
439+
it('returns false if an explicit required scope is missing', () => {
440+
const hash = '#access_token=t&scope=FOO%20BAR';
441+
expect(call(hash, ['FOO', 'MISSING'])).toBeFalse();
442+
});
443+
444+
it('treats empty required list as true (noop)', () => {
445+
const hash = '#access_token=t&scope=ANY';
446+
expect(call(hash, [])).toBeFalse();
447+
});
448+
});
449+
409450
});

client/wfprev-war/src/main/angular/src/app/services/token.service.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ export class TokenService {
4949

5050
public async checkForToken(redirectUri?: string, lazyAuth = false, allowLocalExpiredToken = false): Promise<void> {
5151
const hash = globalThis.location?.hash;
52+
const authScopes: string[] = this.appConfigService?.getConfig()?.webade?.authScopes as unknown as string[];
5253

5354
if (hash?.includes('access_token')) {
55+
if (!this.hasAllScopesFromHash(hash, authScopes)) {
56+
this.router.navigate(['/' + ResourcesRoutes.ERROR_PAGE]);
57+
return;
58+
}
5459
this.parseToken(hash);
5560
} else if (this.useLocalStore && !navigator.onLine) {
5661
let tokenStore = localStorage.getItem(this.LOCAL_STORAGE_KEY);
@@ -78,6 +83,37 @@ export class TokenService {
7883
}
7984
}
8085

86+
private hasAllScopesFromHash(hash: string | undefined, required: string[] = []): boolean {
87+
if (!hash || required.length === 0) return false;
88+
89+
const params = new URLSearchParams(hash.replace(/^#/, ''));
90+
let scopeParam = params.get('scope') || '';
91+
92+
scopeParam = scopeParam.replace(/\+/g, ' ');
93+
94+
const grantedList = scopeParam.split(/\s+/).filter(Boolean);
95+
const grantedSet = new Set(grantedList);
96+
97+
const missing: string[] = [];
98+
99+
for (const req of required) {
100+
// handle wildcard pattern like "WFDM.*"
101+
if (req.endsWith('.*')) {
102+
const prefix = req.slice(0, -1);
103+
const hasAny = grantedList.some(g => g.startsWith(prefix));
104+
if (!hasAny) missing.push(req);
105+
} else {
106+
if (!grantedSet.has(req)) missing.push(req);
107+
}
108+
}
109+
110+
if (missing.length > 0) {
111+
return false;
112+
}
113+
return true;
114+
}
115+
116+
81117
public isTokenExpired(token: any): boolean {
82118
if (token?.exp) {
83119
const expiryDate = moment.unix(token.exp);

0 commit comments

Comments
 (0)