-
Notifications
You must be signed in to change notification settings - Fork 21
feat(admin-ui): revamp Jans Lock module as per Figma #2705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 13 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
d569e30
feat(admin-ui): revamp User Claims module as per Figma (#2632)
faisalsiddique4400 50c48c2
feat(admin-ui): merge main branch into user claims
faisalsiddique4400 5ff9e8a
feat(admin-ui): resolve code rabbit issues
faisalsiddique4400 bcc8b6b
Merge branch 'main' into admin-ui-issue-2632
faisalsiddique4400 064823c
feat(admin-ui): resolve code rabbit issues
faisalsiddique4400 391ec54
Merge branch 'admin-ui-issue-2632' of github-faisal:GluuFederation/fl…
faisalsiddique4400 8d344f0
feat(admin-ui): resolve code rabbit issues
faisalsiddique4400 2d4ae86
feat(admin-ui): resolve code rabbit issues
faisalsiddique4400 97f4b8e
feat(admin-ui): resolve code rabbit issues
faisalsiddique4400 0629b93
feat(admin-ui): resolve code rabbit issues
faisalsiddique4400 fe2251c
jans lock theme fixes
faisalsiddique4400 892c3a9
test files added
faisalsiddique4400 52468df
test files added
faisalsiddique4400 c5fd604
code rabbit fixes
faisalsiddique4400 3986e98
code rabbit fixes
faisalsiddique4400 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
46 changes: 46 additions & 0 deletions
46
admin-ui/app/cedarling/__tests__/client/CedarlingClient.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { cedarlingClient } from '@/cedarling/client/CedarlingClient' | ||
| import type { TokenAuthorizationRequest } from '@/cedarling' | ||
|
|
||
| // The WASM module is mocked via __mocks__/@janssenproject/cedarling_wasm.ts | ||
|
|
||
| describe('cedarlingClient', () => { | ||
| it('exports initialize and token_authorize methods', () => { | ||
| expect(typeof cedarlingClient.initialize).toBe('function') | ||
| expect(typeof cedarlingClient.token_authorize).toBe('function') | ||
| }) | ||
|
|
||
| describe('initialize', () => { | ||
| it('initializes without error', async () => { | ||
| await expect(cedarlingClient.initialize({})).resolves.toBeUndefined() | ||
| }) | ||
|
|
||
| it('does not re-initialize when already initialized', async () => { | ||
| await cedarlingClient.initialize({}) | ||
| await expect(cedarlingClient.initialize({})).resolves.toBeUndefined() | ||
| }) | ||
| }) | ||
|
|
||
| describe('token_authorize', () => { | ||
| const request: TokenAuthorizationRequest = { | ||
| tokens: { | ||
| access_token: 'test-access-token', | ||
| id_token: 'test-id-token', | ||
| userinfo_token: 'test-userinfo-token', | ||
| }, | ||
| action: 'Gluu::Flex::AdminUI::Action::"read"', | ||
| resource: { | ||
| cedar_entity_mapping: { | ||
| entity_type: 'Gluu::Flex::AdminUI::Resources::Features', | ||
| id: 'Dashboard', | ||
| }, | ||
| }, | ||
| context: {}, | ||
| } | ||
|
|
||
| it('returns authorization response after initialization', async () => { | ||
| await cedarlingClient.initialize({}) | ||
| const response = await cedarlingClient.token_authorize(request) | ||
| expect(response).toHaveProperty('decision') | ||
| }) | ||
| }) | ||
| }) | ||
59 changes: 59 additions & 0 deletions
59
admin-ui/app/cedarling/__tests__/constants/resourceScopes.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { CEDAR_RESOURCE_SCOPES, CEDARLING_CONSTANTS } from '@/cedarling/constants/resourceScopes' | ||
| import { ADMIN_UI_RESOURCES } from '@/cedarling/utility' | ||
| import type { AdminUiFeatureResource } from '@/cedarling' | ||
|
|
||
| describe('CEDAR_RESOURCE_SCOPES', () => { | ||
| const allResources = Object.keys(ADMIN_UI_RESOURCES) as AdminUiFeatureResource[] | ||
|
|
||
| it('has an entry for every AdminUiFeatureResource', () => { | ||
| allResources.forEach((resource) => { | ||
| expect(CEDAR_RESOURCE_SCOPES).toHaveProperty(resource) | ||
| }) | ||
| }) | ||
|
|
||
| it('has no extra keys beyond AdminUiFeatureResource', () => { | ||
| expect(Object.keys(CEDAR_RESOURCE_SCOPES).sort()).toEqual(allResources.sort()) | ||
| }) | ||
|
|
||
| it.each(allResources)('%s has non-empty scope entries', (resource) => { | ||
| const scopes = CEDAR_RESOURCE_SCOPES[resource] | ||
| expect(Array.isArray(scopes)).toBe(true) | ||
| expect(scopes.length).toBeGreaterThan(0) | ||
| }) | ||
|
|
||
| it.each(allResources)('%s scope entries have correct resourceId', (resource) => { | ||
| const scopes = CEDAR_RESOURCE_SCOPES[resource] | ||
| scopes.forEach((scope) => { | ||
| expect(scope.resourceId).toBe(resource) | ||
| expect(typeof scope.permission).toBe('string') | ||
| expect(scope.permission.length).toBeGreaterThan(0) | ||
| }) | ||
| }) | ||
|
|
||
| it('Lock has read and write scopes', () => { | ||
| const lockScopes = CEDAR_RESOURCE_SCOPES.Lock | ||
| expect(lockScopes).toHaveLength(2) | ||
| const permissions = lockScopes.map((s) => s.permission) | ||
| expect(permissions.some((p) => p.includes('read') || p.includes('lock'))).toBe(true) | ||
| }) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| it('SMTP has read, write, and delete scopes', () => { | ||
| const smtpScopes = CEDAR_RESOURCE_SCOPES.SMTP | ||
| expect(smtpScopes).toHaveLength(3) | ||
| }) | ||
|
|
||
| it('Dashboard has stat read scopes', () => { | ||
| const dashScopes = CEDAR_RESOURCE_SCOPES.Dashboard | ||
| expect(dashScopes).toHaveLength(2) | ||
| }) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
|
|
||
| describe('CEDARLING_CONSTANTS', () => { | ||
| it('has ACTION_TYPE with correct prefix', () => { | ||
| expect(CEDARLING_CONSTANTS.ACTION_TYPE).toBe('Gluu::Flex::AdminUI::Action::') | ||
| }) | ||
|
|
||
| it('has RESOURCE_TYPE with correct value', () => { | ||
| expect(CEDARLING_CONSTANTS.RESOURCE_TYPE).toBe('Gluu::Flex::AdminUI::Resources::Features') | ||
| }) | ||
| }) | ||
16 changes: 16 additions & 0 deletions
16
admin-ui/app/cedarling/__tests__/enums/CedarlingLogType.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { CedarlingLogType } from '@/cedarling/enums/CedarlingLogType' | ||
|
|
||
| describe('CedarlingLogType', () => { | ||
| it('has OFF value', () => { | ||
| expect(CedarlingLogType.OFF).toBe('off') | ||
| }) | ||
|
|
||
| it('has STD_OUT value', () => { | ||
| expect(CedarlingLogType.STD_OUT).toBe('std_out') | ||
| }) | ||
|
|
||
| it('has exactly 2 values', () => { | ||
| const values = Object.values(CedarlingLogType) | ||
| expect(values).toHaveLength(2) | ||
| }) | ||
| }) |
223 changes: 223 additions & 0 deletions
223
admin-ui/app/cedarling/__tests__/hooks/useCedarling.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| import { renderHook, act } from '@testing-library/react' | ||
| import React from 'react' | ||
| import { Provider } from 'react-redux' | ||
| import { combineReducers, configureStore } from '@reduxjs/toolkit' | ||
| import { useCedarling } from '@/cedarling/hooks/useCedarling' | ||
| import type { CedarPermissionsState } from '@/cedarling' | ||
|
|
||
| jest.mock('@/cedarling/client', () => ({ | ||
| cedarlingClient: { | ||
| initialize: jest.fn().mockResolvedValue(undefined), | ||
| token_authorize: jest.fn().mockResolvedValue({ decision: true }), | ||
| }, | ||
| })) | ||
|
|
||
| const createStore = ( | ||
| overrides: { | ||
| authState?: Record<string, string | string[] | undefined> | ||
| cedarState?: Partial<CedarPermissionsState> | ||
| } = {}, | ||
| ) => { | ||
| const defaultAuth = { | ||
| userinfo_jwt: 'test-userinfo-jwt', | ||
| idToken: 'test-id-token', | ||
| jwtToken: 'test-access-token', | ||
| permissions: [], | ||
| ...overrides.authState, | ||
| } | ||
|
|
||
| const defaultCedar: CedarPermissionsState = { | ||
| permissions: {}, | ||
| loading: false, | ||
| error: null, | ||
| initialized: true, | ||
| isInitializing: false, | ||
| cedarFailedStatusAfterMaxTries: null, | ||
| policyStoreJson: '', | ||
| ...overrides.cedarState, | ||
| } | ||
|
|
||
| return configureStore({ | ||
| reducer: combineReducers({ | ||
| authReducer: (state = defaultAuth) => state, | ||
| cedarPermissions: (state = defaultCedar) => state, | ||
| noReducer: (state = {}) => state, | ||
| }), | ||
| }) | ||
| } | ||
|
|
||
| const createWrapper = (store: ReturnType<typeof createStore>) => { | ||
| const Wrapper = ({ children }: { children: React.ReactNode }) => | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| React.createElement(Provider, { store } as React.ComponentProps<typeof Provider>, children) | ||
| return Wrapper | ||
| } | ||
|
|
||
| describe('useCedarling', () => { | ||
| describe('hasCedarReadPermission', () => { | ||
| it('returns undefined when no cached permission', () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarReadPermission('Dashboard')).toBeUndefined() | ||
| }) | ||
|
|
||
| it('returns cached read permission', () => { | ||
| const store = createStore({ | ||
| cedarState: { permissions: { 'Dashboard::read': true } }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarReadPermission('Dashboard')).toBe(true) | ||
| }) | ||
|
|
||
| it('returns false for denied read permission', () => { | ||
| const store = createStore({ | ||
| cedarState: { permissions: { 'Users::read': false } }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarReadPermission('Users')).toBe(false) | ||
| }) | ||
| }) | ||
|
|
||
| describe('hasCedarWritePermission', () => { | ||
| it('returns undefined when no cached permission', () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarWritePermission('Dashboard')).toBeUndefined() | ||
| }) | ||
|
|
||
| it('returns cached write permission', () => { | ||
| const store = createStore({ | ||
| cedarState: { permissions: { 'SMTP::write': true } }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarWritePermission('SMTP')).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('hasCedarDeletePermission', () => { | ||
| it('returns undefined when no cached permission', () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarDeletePermission('Scripts')).toBeUndefined() | ||
| }) | ||
|
|
||
| it('returns cached delete permission', () => { | ||
| const store = createStore({ | ||
| cedarState: { permissions: { 'Scripts::delete': true } }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.hasCedarDeletePermission('Scripts')).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('authorize', () => { | ||
| it('returns not authorized when cedarling is not initialized', async () => { | ||
| const store = createStore({ | ||
| cedarState: { initialized: false, isInitializing: true }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| let authResult: { isAuthorized: boolean; error?: string } = { isAuthorized: false } | ||
| await act(async () => { | ||
| authResult = await result.current.authorize([ | ||
| { permission: 'https://example.com/read', resourceId: 'Dashboard' }, | ||
| ]) | ||
| }) | ||
| expect(authResult.isAuthorized).toBe(false) | ||
| expect(authResult.error).toContain('not yet initialized') | ||
| }) | ||
|
|
||
| it('returns not authorized when tokens are missing', async () => { | ||
| const store = createStore({ | ||
| authState: { userinfo_jwt: undefined, idToken: undefined, jwtToken: undefined }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| let authResult: { isAuthorized: boolean; error?: string } = { isAuthorized: false } | ||
| await act(async () => { | ||
| authResult = await result.current.authorize([ | ||
| { permission: 'https://example.com/read', resourceId: 'Dashboard' }, | ||
| ]) | ||
| }) | ||
| expect(authResult.isAuthorized).toBe(false) | ||
| expect(authResult.error).toContain('tokens are missing') | ||
| }) | ||
|
|
||
| it('returns not authorized for empty scope array', async () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| let authResult: { isAuthorized: boolean } = { isAuthorized: false } | ||
| await act(async () => { | ||
| authResult = await result.current.authorize([]) | ||
| }) | ||
| expect(authResult.isAuthorized).toBe(false) | ||
| }) | ||
|
|
||
| it('returns cached decision without calling API', async () => { | ||
| const store = createStore({ | ||
| cedarState: { permissions: { 'Dashboard::read': true } }, | ||
| }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| let authResult: { isAuthorized: boolean } = { isAuthorized: false } | ||
| await act(async () => { | ||
| authResult = await result.current.authorize([ | ||
| { permission: 'https://example.com/read', resourceId: 'Dashboard' }, | ||
| ]) | ||
| }) | ||
| expect(authResult.isAuthorized).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('authorizeHelper', () => { | ||
| it('returns empty array for empty scopes', async () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| let results: { isAuthorized: boolean }[] = [] | ||
| await act(async () => { | ||
| results = await result.current.authorizeHelper([]) | ||
| }) | ||
| expect(results).toEqual([]) | ||
| }) | ||
|
|
||
| it('returns empty array for undefined-like input', async () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| let results: { isAuthorized: boolean }[] = [] | ||
| await act(async () => { | ||
| results = await result.current.authorizeHelper([]) | ||
| }) | ||
| expect(results).toEqual([]) | ||
| }) | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }) | ||
|
|
||
| describe('hook return shape', () => { | ||
| it('returns all expected properties', () => { | ||
| const store = createStore() | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
|
|
||
| expect(typeof result.current.authorize).toBe('function') | ||
| expect(typeof result.current.authorizeHelper).toBe('function') | ||
| expect(typeof result.current.hasCedarReadPermission).toBe('function') | ||
| expect(typeof result.current.hasCedarWritePermission).toBe('function') | ||
| expect(typeof result.current.hasCedarDeletePermission).toBe('function') | ||
| expect(typeof result.current.isLoading).toBe('boolean') | ||
| expect(result.current.error).toBeNull() | ||
| }) | ||
|
|
||
| it('reflects loading state from store', () => { | ||
| const store = createStore({ cedarState: { loading: true } }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.isLoading).toBe(true) | ||
| }) | ||
|
|
||
| it('reflects error state from store', () => { | ||
| const store = createStore({ cedarState: { error: 'test error' } }) | ||
| const { result } = renderHook(() => useCedarling(), { wrapper: createWrapper(store) }) | ||
| expect(result.current.error).toBe('test error') | ||
| }) | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.