X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/ba71587fc2058276b9b70c342b990aa6508690a9..1cbc8f98d5b9591f99b3f2c6efcaceaf04ab4831:/src/store/auth/auth-action.test.ts diff --git a/src/store/auth/auth-action.test.ts b/src/store/auth/auth-action.test.ts index 801d9e33..cba93965 100644 --- a/src/store/auth/auth-action.test.ts +++ b/src/store/auth/auth-action.test.ts @@ -2,138 +2,356 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { authReducer, AuthState } from "./auth-reducer"; -import { AuthAction, initAuth } from "./auth-action"; -import { - API_TOKEN_KEY, - USER_EMAIL_KEY, - USER_FIRST_NAME_KEY, - USER_LAST_NAME_KEY, - USER_OWNER_UUID_KEY, - USER_UUID_KEY, - USER_IS_ADMIN, - USER_IS_ACTIVE, - USER_USERNAME, - USER_PREFS -} from "~/services/auth-service/auth-service"; +import { getNewExtraToken, initAuth } from "./auth-action"; +import { API_TOKEN_KEY } from "services/auth-service/auth-service"; import 'jest-localstorage-mock'; -import { createServices } from "~/services/services"; +import { ServiceRepository, createServices } from "services/services"; import { configureStore, RootStore } from "../store"; -import createBrowserHistory from "history/createBrowserHistory"; -import { mockConfig } from '~/common/config'; -import { ApiActions } from "~/services/api/api-actions"; -import { ACCOUNT_LINK_STATUS_KEY } from '~/services/link-account-service/link-account-service'; +import { createBrowserHistory } from "history"; +import { mockConfig } from 'common/config'; +import { ApiActions } from "services/api/api-actions"; +import { ACCOUNT_LINK_STATUS_KEY } from 'services/link-account-service/link-account-service'; +import Axios, { AxiosInstance } from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { ImportMock } from 'ts-mock-imports'; +import * as servicesModule from "services/services"; +import * as authActionSessionModule from "./auth-action-session"; +import { SessionStatus } from "models/session"; +import { getRemoteHostConfig } from "./auth-action-session"; describe('auth-actions', () => { - let reducer: (state: AuthState | undefined, action: AuthAction) => any; + let axiosInst: AxiosInstance; + let axiosMock: MockAdapter; + let store: RootStore; + let services: ServiceRepository; + const config: any = {}; const actions: ApiActions = { progressFn: (id: string, working: boolean) => { }, errorFn: (id: string, message: string) => { } }; + let importMocks: any[]; beforeEach(() => { - store = configureStore(createBrowserHistory(), createServices(mockConfig({}), actions)); + axiosInst = Axios.create({ headers: {} }); + axiosMock = new MockAdapter(axiosInst); + services = createServices(mockConfig({}), actions, axiosInst); + store = configureStore(createBrowserHistory(), services, config); localStorage.clear(); - reducer = authReducer(createServices(mockConfig({}), actions)); + importMocks = []; }); - it('should initialise state with user and api token from local storage', () => { + afterEach(() => { + importMocks.map(m => m.restore()); + }); - // Only test the case when a link account operation is not being cancelled + it('creates an extra token', async () => { + axiosMock + .onGet("/users/current") + .reply(200, { + email: "test@test.com", + first_name: "John", + last_name: "Doe", + uuid: "zzzzz-tpzed-abcefg", + owner_uuid: "ownerUuid", + is_admin: false, + is_active: true, + username: "jdoe", + prefs: {} + }) + .onGet("/api_client_authorizations/current") + .reply(200, { + expires_at: "2140-01-01T00:00:00.000Z", + api_token: 'extra token', + }) + .onPost("/api_client_authorizations") + .replyOnce(200, { + uuid: 'zzzzz-gj3su-xxxxxxxxxx', + apiToken: 'extra token', + }) + .onPost("/api_client_authorizations") + .reply(200, { + uuid: 'zzzzz-gj3su-xxxxxxxxxx', + apiToken: 'extra additional token', + }); + + importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services)); sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0"); localStorage.setItem(API_TOKEN_KEY, "token"); - localStorage.setItem(USER_EMAIL_KEY, "test@test.com"); - localStorage.setItem(USER_FIRST_NAME_KEY, "John"); - localStorage.setItem(USER_LAST_NAME_KEY, "Doe"); - localStorage.setItem(USER_UUID_KEY, "zzzzz-tpzed-abcefg"); - localStorage.setItem(USER_USERNAME, "username"); - localStorage.setItem(USER_PREFS, JSON.stringify({})); - localStorage.setItem(USER_OWNER_UUID_KEY, "ownerUuid"); - localStorage.setItem(USER_IS_ADMIN, JSON.stringify(false)); - localStorage.setItem(USER_IS_ACTIVE, JSON.stringify(true)); const config: any = { - rootUrl: "https://zzzzz.arvadosapi.com", + rootUrl: "https://zzzzz.example.com", uuidPrefix: "zzzzz", - remoteHosts: { xc59z: "xc59z.arvadosapi.com" }, + remoteHosts: { }, + apiRevision: 12345678, + clusterConfig: { + Login: { LoginCluster: "" }, + }, }; - store.dispatch(initAuth(config)); + // Set up auth, confirm that no extra token was requested + await store.dispatch(initAuth(config)) + expect(store.getState().auth.apiToken).toBeDefined(); + expect(store.getState().auth.extraApiToken).toBeUndefined(); - expect(store.getState().auth).toEqual({ - apiToken: "token", - sshKeys: [], - homeCluster: "zzzzz", - localCluster: "zzzzz", - loginCluster: undefined, - remoteHostsConfig: { - "zzzzz": { - "remoteHosts": { - "xc59z": "xc59z.arvadosapi.com", - }, - "rootUrl": "https://zzzzz.arvadosapi.com", - "uuidPrefix": "zzzzz", - }, - }, - remoteHosts: { - zzzzz: "zzzzz.arvadosapi.com", - xc59z: "xc59z.arvadosapi.com" + // Ask for an extra token + await store.dispatch(getNewExtraToken()); + expect(store.getState().auth.apiToken).toBeDefined(); + expect(store.getState().auth.extraApiToken).toBeDefined(); + const extraToken = store.getState().auth.extraApiToken; + + // Ask for a cached extra token + await store.dispatch(getNewExtraToken(true)); + expect(store.getState().auth.extraApiToken).toBe(extraToken); + + // Ask for a new extra token, should make a second api request + await store.dispatch(getNewExtraToken(false)); + expect(store.getState().auth.extraApiToken).toBeDefined(); + expect(store.getState().auth.extraApiToken).not.toBe(extraToken); + }); + + it('requests remote token data to login cluster', async () => { + const localClusterTokenExpiration = "2020-01-01T00:00:00.000Z"; + const loginClusterTokenExpiration = "2140-01-01T00:00:00.000Z"; + axiosMock + .onGet("/users/current") + .reply(200, { + email: "test@test.com", + first_name: "John", + last_name: "Doe", + uuid: "zzzz1-tpzed-abcefg", + owner_uuid: "ownerUuid", + is_admin: false, + is_active: true, + username: "jdoe", + prefs: {} + }) + .onGet("https://zzzz1.example.com/discovery/v1/apis/arvados/v1/rest") + .reply(200, { + baseUrl: "https://zzzz1.example.com/arvados/v1", + keepWebServiceUrl: "", + keepWebInlineServiceUrl: "", + remoteHosts: {}, + rootUrl: "https://zzzz1.example.com", + uuidPrefix: "zzzz1", + websocketUrl: "", + workbenchUrl: "", + workbench2Url: "", + revision: 12345678 + }) + // Local cluster -- cached token + .onGet("https://zzzzz.example.com/arvados/v1/api_client_authorizations/current") + .reply(200, { + uuid: 'zzzz1-gj3su-aaaaaaa', + expires_at: localClusterTokenExpiration, + api_token: 'tokensecret', + }) + // Login cluster -- authoritative token copy + .onGet("https://zzzz1.example.com/arvados/v1/api_client_authorizations/current") + .reply(200, { + uuid: 'zzzz1-gj3su-aaaaaaa', + expires_at: loginClusterTokenExpiration, + api_token: 'tokensecret', + }); + + const config: any = { + rootUrl: "https://zzzzz.example.com", + uuidPrefix: "zzzzz", + remoteHosts: { zzzz1: "zzzz1.example.com" }, + apiRevision: 12345678, + clusterConfig: { + Login: { LoginCluster: "zzzz1" }, }, - sessions: [{ - "active": true, - "baseUrl": undefined, - "clusterId": "zzzzz", - "email": "test@test.com", - "loggedIn": true, - "remoteHost": "https://zzzzz.arvadosapi.com", - "status": 2, - "token": "token", - "username": "John Doe" - }, { - "active": false, - "baseUrl": "", - "clusterId": "xc59z", - "email": "", - "loggedIn": false, - "remoteHost": "xc59z.arvadosapi.com", - "status": 1, - "token": "", - "username": "" - }], - user: { + }; + + const remoteHostConfig = await getRemoteHostConfig(config.remoteHosts.zzzz1, axiosInst); + expect(remoteHostConfig).not.toBeFalsy; + services = createServices(remoteHostConfig!, actions, axiosInst); + + importMocks.push(ImportMock.mockFunction(authActionSessionModule, 'getRemoteHostConfig', remoteHostConfig)); + importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services)); + + sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0"); + localStorage.setItem(API_TOKEN_KEY, "v2/zzzz1-gj3su-aaaaaaa/tokensecret"); + + await store.dispatch(initAuth(config)); + expect(store.getState().auth.apiToken).toBeDefined(); + expect(localClusterTokenExpiration).not.toBe(loginClusterTokenExpiration); + expect(store.getState().auth.apiTokenExpiration).toEqual(new Date(loginClusterTokenExpiration)); + }); + + it('should initialise state with user and api token from local storage', (done) => { + axiosMock + .onGet("/users/current") + .reply(200, { email: "test@test.com", - firstName: "John", - lastName: "Doe", + first_name: "John", + last_name: "Doe", uuid: "zzzzz-tpzed-abcefg", - ownerUuid: "ownerUuid", - username: "username", - prefs: {}, - isAdmin: false, - isActive: true + owner_uuid: "ownerUuid", + is_admin: false, + is_active: true, + username: "jdoe", + prefs: {} + }) + .onGet("/api_client_authorizations/current") + .reply(200, { + expires_at: "2140-01-01T00:00:00.000Z" + }) + .onGet("https://xc59z.example.com/discovery/v1/apis/arvados/v1/rest") + .reply(200, { + baseUrl: "https://xc59z.example.com/arvados/v1", + keepWebServiceUrl: "", + keepWebInlineServiceUrl: "", + remoteHosts: {}, + rootUrl: "https://xc59z.example.com", + uuidPrefix: "xc59z", + websocketUrl: "", + workbenchUrl: "", + workbench2Url: "", + revision: 12345678 + }); + + importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services)); + + // Only test the case when a link account operation is not being cancelled + sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0"); + localStorage.setItem(API_TOKEN_KEY, "token"); + + const config: any = { + rootUrl: "https://zzzzz.example.com", + uuidPrefix: "zzzzz", + remoteHosts: { xc59z: "xc59z.example.com" }, + apiRevision: 12345678, + clusterConfig: { + Login: { LoginCluster: "" }, + }, + }; + + store.dispatch(initAuth(config)); + + store.subscribe(() => { + const auth = store.getState().auth; + if (auth.apiToken === "token" && + auth.sessions.length === 2 && + auth.sessions[0].status === SessionStatus.VALIDATED && + auth.sessions[1].status === SessionStatus.VALIDATED + ) { + try { + expect(auth).toEqual({ + apiToken: "token", + apiTokenExpiration: new Date("2140-01-01T00:00:00.000Z"), + apiTokenLocation: "localStorage", + config: { + apiRevision: 12345678, + clusterConfig: { + Login: { + LoginCluster: "", + }, + }, + remoteHosts: { + "xc59z": "xc59z.example.com", + }, + rootUrl: "https://zzzzz.example.com", + uuidPrefix: "zzzzz", + }, + sshKeys: [], + extraApiToken: undefined, + extraApiTokenExpiration: undefined, + homeCluster: "zzzzz", + localCluster: "zzzzz", + loginCluster: undefined, + remoteHostsConfig: { + "zzzzz": { + "apiRevision": 12345678, + "clusterConfig": { + "Login": { + "LoginCluster": "", + }, + }, + "remoteHosts": { + "xc59z": "xc59z.example.com", + }, + "rootUrl": "https://zzzzz.example.com", + "uuidPrefix": "zzzzz", + }, + "xc59z": mockConfig({ + apiRevision: 12345678, + baseUrl: "https://xc59z.example.com/arvados/v1", + rootUrl: "https://xc59z.example.com", + uuidPrefix: "xc59z" + }) + }, + remoteHosts: { + zzzzz: "zzzzz.example.com", + xc59z: "xc59z.example.com" + }, + sessions: [{ + "active": true, + "baseUrl": undefined, + "clusterId": "zzzzz", + "email": "test@test.com", + "loggedIn": true, + "remoteHost": "https://zzzzz.example.com", + "status": 2, + "token": "token", + "name": "John Doe", + "apiRevision": 12345678, + "uuid": "zzzzz-tpzed-abcefg", + "userIsActive": true + }, { + "active": false, + "baseUrl": "", + "clusterId": "xc59z", + "email": "", + "loggedIn": false, + "remoteHost": "xc59z.example.com", + "status": 2, + "token": "", + "name": "", + "uuid": "", + "apiRevision": 0, + }], + user: { + email: "test@test.com", + firstName: "John", + lastName: "Doe", + uuid: "zzzzz-tpzed-abcefg", + ownerUuid: "ownerUuid", + username: "jdoe", + prefs: { profile: {} }, + isAdmin: false, + isActive: true + } + }); + done(); + } catch (e) { + fail(e); + } } }); }); + // TODO: Add remaining action tests /* - it('should fire external url to login', () => { - const initialState = undefined; - window.location.assign = jest.fn(); - reducer(initialState, authActions.LOGIN()); - expect(window.location.assign).toBeCalledWith( - `/login?return_to=${window.location.protocol}//${window.location.host}/token` - ); - }); + it('should fire external url to login', () => { + const initialState = undefined; + window.location.assign = jest.fn(); + reducer(initialState, authActions.LOGIN()); + expect(window.location.assign).toBeCalledWith( + `/login?return_to=${window.location.protocol}//${window.location.host}/token` + ); + }); - it('should fire external url to logout', () => { - const initialState = undefined; - window.location.assign = jest.fn(); - reducer(initialState, authActions.LOGOUT()); - expect(window.location.assign).toBeCalledWith( - `/logout?return_to=${location.protocol}//${location.host}` - ); - }); - */ + it('should fire external url to logout', () => { + const initialState = undefined; + window.location.assign = jest.fn(); + reducer(initialState, authActions.LOGOUT()); + expect(window.location.assign).toBeCalledWith( + `/logout?return_to=${location.protocol}//${location.host}` + ); + }); + */ });