diff --git a/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx b/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx index f0ba47d98..3c23e6325 100644 --- a/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx +++ b/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx @@ -28,10 +28,8 @@ import { AppAlerts } from '@/services/alerts/appAlerts'; import { AppState } from '@/store'; import { selectIsAllowedSourcesConfigured } from '@/store/ServerConfig/selectors'; import { workspacePreferencesActionCreators } from '@/store/Workspaces/Preferences'; -import { - selectPreferencesIsTrustedSource, - selectPreferencesTrustedSources, -} from '@/store/Workspaces/Preferences/selectors'; +import { isTrustedRepo } from '@/store/Workspaces/Preferences/helpers'; +import { selectPreferencesTrustedSources } from '@/store/Workspaces/Preferences/selectors'; export type Props = MappedProps & { location: string; @@ -58,15 +56,15 @@ class UntrustedSourceModal extends React.Component { this.state = { canContinue: true, continueButtonDisabled: false, - isTrusted: this.props.isTrustedSource(props.location), + isTrusted: isTrustedRepo(props.trustedSources, props.location), isAllowedSourcesConfigured: this.props.isAllowedSourcesConfigured, trustAllCheckbox: false, }; } public shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - const isTrusted = this.props.isTrustedSource(this.props.location); - const nextIsTrusted = nextProps.isTrustedSource(nextProps.location); + const isTrusted = isTrustedRepo(this.props.trustedSources, this.props.location); + const nextIsTrusted = isTrustedRepo(nextProps.trustedSources, nextProps.location); if (isTrusted !== nextIsTrusted) { return true; } @@ -101,7 +99,7 @@ class UntrustedSourceModal extends React.Component { } public componentDidUpdate(prevProps: Readonly): void { - const isTrusted = this.props.isTrustedSource(this.props.location); + const isTrusted = isTrustedRepo(this.props.trustedSources, this.props.location); const isAllowedSourcesConfigured = this.props.isAllowedSourcesConfigured; this.setState({ @@ -237,7 +235,6 @@ class UntrustedSourceModal extends React.Component { const mapStateToProps = (state: AppState) => ({ trustedSources: selectPreferencesTrustedSources(state), - isTrustedSource: selectPreferencesIsTrustedSource(state), isAllowedSourcesConfigured: selectIsAllowedSourcesConfigured(state), }); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx index 6b39d089e..6b648f3f3 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/__tests__/index.spec.tsx @@ -338,7 +338,7 @@ describe('Creating steps, initializing', () => { [FACTORY_URL_ATTR]: factoryUrl, }); - renderComponent(store, searchParams); + const { reRenderComponent } = renderComponent(store, searchParams); const stepTitle = screen.getByTestId('step-title'); expect(stepTitle.textContent).not.toContain('untrusted source'); @@ -349,6 +349,26 @@ describe('Creating steps, initializing', () => { expect(stepTitleNext.textContent).toContain('untrusted source'); expect(mockOnNextStep).not.toHaveBeenCalled(); + + // add factory URL to trusted sources + const nextStore = new FakeStoreBuilder() + .withInfrastructureNamespace([{ name: 'user-che', attributes: { phase: 'Active' } }]) + .withSshKeys({ + keys: [{ name: 'key1', keyPub: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD' }], + }) + .withWorkspacePreferences({ + 'trusted-sources': ['some-trusted-source', factoryUrl], + }) + .build(); + + reRenderComponent(nextStore, searchParams); + + await jest.runOnlyPendingTimersAsync(); + + const _stepTitleNext = screen.getByTestId('step-title'); + await waitFor(() => expect(_stepTitleNext.textContent).not.toContain('untrusted source')); + + await waitFor(() => expect(mockOnNextStep).toHaveBeenCalled()); }); test('source URL is not allowed', async () => { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/index.tsx index 2be1f9252..d1c1500b1 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Initialize/index.tsx @@ -10,7 +10,7 @@ * Red Hat, Inc. - initial API and implementation */ -import { helpers } from '@eclipse-che/common'; +import { api, helpers } from '@eclipse-che/common'; import { AlertVariant, pluralize } from '@patternfly/react-core'; import isEqual from 'lodash/isEqual'; import React from 'react'; @@ -39,7 +39,8 @@ import { selectInfrastructureNamespaces } from '@/store/InfrastructureNamespaces import { isSourceAllowed } from '@/store/ServerConfig/helpers'; import { selectAllowedSources } from '@/store/ServerConfig/selectors'; import { selectSshKeys } from '@/store/SshKeys/selectors'; -import { selectPreferencesIsTrustedSource } from '@/store/Workspaces/Preferences'; +import { selectPreferencesTrustedSources } from '@/store/Workspaces/Preferences'; +import { isTrustedRepo } from '@/store/Workspaces/Preferences/helpers'; import { selectAllWorkspaces } from '@/store/Workspaces/selectors'; export type Props = MappedProps & @@ -102,8 +103,13 @@ class CreatingStepInitialize extends ProgressStep { } // source URL trusted/untrusted - const { sourceUrl } = nextState.factoryParams; - if (this.state.isSourceTrusted !== this.isSourceTrusted(sourceUrl)) { + const trustedSourcesStr = Array.isArray(this.props.trustedSources) + ? this.props.trustedSources.join(',') + : this.props.trustedSources; + const nextTrustedSourcesStr = Array.isArray(nextProps.trustedSources) + ? nextProps.trustedSources.join(',') + : nextProps.trustedSources; + if (trustedSourcesStr !== nextTrustedSourcesStr) { return true; } @@ -148,7 +154,8 @@ class CreatingStepInitialize extends ProgressStep { // check if the source is trusted const isSourceTrusted = - this.isSourceTrusted(sourceUrl) || this.props.allowedSources.length > 0; + this.isSourceTrusted(this.props.trustedSources, sourceUrl) || + this.props.allowedSources.length > 0; if (isSourceTrusted === true) { this.setState({ @@ -261,8 +268,11 @@ class CreatingStepInitialize extends ProgressStep { } } - private isSourceTrusted(sourceUrl: string): boolean { - const isTrustedSource = this.props.isTrustedSource(sourceUrl); + private isSourceTrusted( + trustedSources: api.TrustedSources | undefined, + sourceUrl: string, + ): boolean { + const isTrustedSource = isTrustedRepo(trustedSources, sourceUrl); const isRegistryDevfile = this.props.isRegistryDevfile(sourceUrl); if (isRegistryDevfile || isTrustedSource) { return true; @@ -319,7 +329,7 @@ const mapStateToProps = (state: AppState) => ({ allWorkspacesLimit: selectAllWorkspacesLimit(state), infrastructureNamespaces: selectInfrastructureNamespaces(state), isRegistryDevfile: selectIsRegistryDevfile(state), - isTrustedSource: selectPreferencesIsTrustedSource(state), + trustedSources: selectPreferencesTrustedSources(state), allowedSources: selectAllowedSources(state), sshKeys: selectSshKeys(state), }); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx index 4ac93a99f..04869d7dc 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx @@ -49,7 +49,8 @@ import { Workspace } from '@/services/workspace-adapter'; import { AppState } from '@/store'; import { selectIsRegistryDevfile } from '@/store/DevfileRegistries/selectors'; import * as WorkspaceStore from '@/store/Workspaces'; -import { selectPreferencesIsTrustedSource } from '@/store/Workspaces/Preferences'; +import { selectPreferencesTrustedSources } from '@/store/Workspaces/Preferences'; +import { isTrustedRepo } from '@/store/Workspaces/Preferences/helpers'; import { selectAllWorkspaces } from '@/store/Workspaces/selectors'; export type Props = MappedProps & { @@ -157,7 +158,7 @@ class Progress extends React.Component { } else { // if workspace is not created yet, check if source is trusted const { sourceUrl } = this.state.factoryParams; - const isTrustedSource = this.props.isTrustedSource(sourceUrl); + const isTrustedSource = isTrustedRepo(props.trustedSources, sourceUrl); const isRegistryDevfile = this.props.isRegistryDevfile(sourceUrl); // trust source if it is taken from the registry or it is in the list of trusted sources if (isRegistryDevfile || isTrustedSource) { @@ -705,7 +706,7 @@ class Progress extends React.Component { const mapStateToProps = (state: AppState) => ({ allWorkspaces: selectAllWorkspaces(state), isRegistryDevfile: selectIsRegistryDevfile(state), - isTrustedSource: selectPreferencesIsTrustedSource(state), + trustedSources: selectPreferencesTrustedSources(state), }); const connector = connect(mapStateToProps, WorkspaceStore.actionCreators, null, { diff --git a/packages/dashboard-frontend/src/store/Workspaces/Preferences/__tests__/selectors.spec.ts b/packages/dashboard-frontend/src/store/Workspaces/Preferences/__tests__/selectors.spec.ts index 79d3fdef0..cbcd58e53 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/Preferences/__tests__/selectors.spec.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/Preferences/__tests__/selectors.spec.ts @@ -16,7 +16,6 @@ import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; import { selectPreferences, selectPreferencesError, - selectPreferencesIsTrustedSource, selectPreferencesSkipAuthorization, selectPreferencesTrustedSources, } from '@/store/Workspaces/Preferences/selectors'; @@ -64,52 +63,4 @@ describe('Workspace preferences, selectors', () => { expect(result).toEqual(mockState.preferences['trusted-sources']); }); - - describe('check if a location is a trusted source', () => { - it('some sources are trusted', () => { - const store = new FakeStoreBuilder() - .withWorkspacePreferences({ - 'skip-authorisation': mockState.preferences['skip-authorisation'], - 'trusted-sources': ['source1'], - error: mockState.error, - }) - .build(); - const state = store.getState(); - const result = selectPreferencesIsTrustedSource(state); - - expect(result('source1')).toBeTruthy(); - expect(result('source2')).toBeFalsy(); - }); - - it('all sources are trusted', () => { - const store = new FakeStoreBuilder() - .withWorkspacePreferences({ - 'skip-authorisation': mockState.preferences['skip-authorisation'], - 'trusted-sources': '*', - error: mockState.error, - }) - .build(); - const state = store.getState(); - - selectPreferences(state); - const result = selectPreferencesIsTrustedSource(state); - - expect(result('any-source')).toBeTruthy(); - }); - - it('no sources are trusted', () => { - const store = new FakeStoreBuilder() - .withWorkspacePreferences({ - 'skip-authorisation': mockState.preferences['skip-authorisation'], - 'trusted-sources': undefined, - error: mockState.error, - }) - .build(); - const state = store.getState(); - - const result = selectPreferencesIsTrustedSource(state); - - expect(result('any-source')).toBeFalsy(); - }); - }); }); diff --git a/packages/dashboard-frontend/src/store/Workspaces/Preferences/helpers.ts b/packages/dashboard-frontend/src/store/Workspaces/Preferences/helpers.ts index c74303fdf..67eabcd17 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/Preferences/helpers.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/Preferences/helpers.ts @@ -10,6 +10,8 @@ * Red Hat, Inc. - initial API and implementation */ +import { api } from '@eclipse-che/common'; + export const gitProviderPatterns = { github: { https: /^https:\/\/github\.com\/([^\\/]+\/[^\\/]+)(?:\/.*)?$/, @@ -100,13 +102,23 @@ function getRepoPattern(url: string): RegExp | undefined { } } -export function isTrustedRepo(trustedUrls: string[], url: string | URL): boolean { +export function isTrustedRepo( + trustedSources: api.TrustedSources | undefined, + url: string | URL, +): boolean { + if (trustedSources === undefined) { + return false; + } + if (trustedSources === '*') { + return true; + } + const urlString = url.toString(); const urlPattern = getRepoPattern(urlString); const urlRepo = extractRepo(urlString, urlPattern); // Check if the URL matches any of the trusted repositories - return trustedUrls.some(trustedUrl => { + return trustedSources.some(trustedUrl => { const trustedUrlPattern = getRepoPattern(trustedUrl); const trustedUrlRepo = extractRepo(trustedUrl, trustedUrlPattern); diff --git a/packages/dashboard-frontend/src/store/Workspaces/Preferences/selectors.ts b/packages/dashboard-frontend/src/store/Workspaces/Preferences/selectors.ts index a399a405c..d3caf831c 100644 --- a/packages/dashboard-frontend/src/store/Workspaces/Preferences/selectors.ts +++ b/packages/dashboard-frontend/src/store/Workspaces/Preferences/selectors.ts @@ -13,7 +13,6 @@ import { createSelector } from 'reselect'; import { AppState } from '@/store'; -import { isTrustedRepo } from '@/store/Workspaces/Preferences/helpers'; const selectState = (state: AppState) => state.workspacePreferences; @@ -30,20 +29,3 @@ export const selectPreferencesTrustedSources = createSelector( selectPreferences, state => state['trusted-sources'], ); - -export const selectPreferencesIsTrustedSource = createSelector( - selectPreferencesTrustedSources, - trustedSources => { - return (location: string) => { - if (!trustedSources || (Array.isArray(trustedSources) && trustedSources.length === 0)) { - // no trusted sources yet - return false; - } else if (trustedSources === '*') { - // all sources are trusted - return true; - } - - return isTrustedRepo(trustedSources, location); - }; - }, -);