diff --git a/web/packages/teleterm/src/services/tshd/types.ts b/web/packages/teleterm/src/services/tshd/types.ts index 43e55400301..cc517e101bc 100644 --- a/web/packages/teleterm/src/services/tshd/types.ts +++ b/web/packages/teleterm/src/services/tshd/types.ts @@ -38,8 +38,12 @@ export type GatewayProtocol = | 'redis' | 'sqlserver'; export type Database = apiDb.Database.AsObject; -export type Cluster = apiCluster.Cluster.AsObject; -export type LoggedInUser = apiCluster.LoggedInUser.AsObject; +export type Cluster = Omit & { + loggedInUser?: LoggedInUser; +}; +export type LoggedInUser = apiCluster.LoggedInUser.AsObject & { + assumedRequests?: Record; +}; export type AuthProvider = apiAuthSettings.AuthProvider.AsObject; export type AuthSettings = apiAuthSettings.AuthSettings.AsObject; @@ -193,3 +197,9 @@ export type CreateAccessRequestParams = { suggestedReviewers: string[]; resourceIds: { kind: ResourceKind; clusterName: string; id: string }[]; }; + +export type AssumedRequest = { + id: string; + expires: Date; + roles: string[]; +}; diff --git a/web/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx b/web/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx index 2f80e6fac22..923ae547b61 100644 --- a/web/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx +++ b/web/packages/teleterm/src/ui/QuickInput/QuickInput.story.tsx @@ -37,7 +37,6 @@ export const Story = () => { location: '', localClusterUri: '/clusters/localhost', accessRequests: { - assumed: {}, pending: getEmptyPendingAccessRequest(), isBarCollapsed: true, }, diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts index d50d2c67da2..1e69e5b0575 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.test.ts @@ -15,6 +15,7 @@ const clusterMock: tsh.Cluster = { proxyHost: 'localhost:3080', loggedInUser: { activeRequestsList: [], + assumedRequests: {}, name: 'admin', acl: {}, sshLoginsList: [], diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index 2bcdc3538ee..96500a47f32 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -404,6 +404,15 @@ export class ClustersService extends ImmutableStore { return this.client.getRequestableRoles(clusterUri); } + getAssumedRequests(clusterUri: string) { + const cluster = this.state.clusters.get(clusterUri); + if (!cluster?.connected) { + return {}; + } + + return cluster.loggedInUser?.assumedRequests || {}; + } + async getAccessRequests(clusterUri: string) { const cluster = this.state.clusters.get(clusterUri); if (!cluster.connected) { @@ -700,14 +709,45 @@ export class ClustersService extends ImmutableStore { // with a retryable error in case the certs have expired. private async syncClusterInfo(clusterUri: string) { const cluster = await this.client.getCluster(clusterUri); + const assumedRequests = cluster.loggedInUser + ? await this.fetchClusterAssumedRequests( + cluster.loggedInUser.activeRequestsList, + clusterUri + ) + : undefined; this.setState(draft => { - draft.clusters.set( - clusterUri, - this.removeInternalLoginsFromCluster(cluster) - ); + draft.clusters.set(clusterUri, { + ...this.removeInternalLoginsFromCluster(cluster), + loggedInUser: cluster.loggedInUser + ? { + ...cluster.loggedInUser, + assumedRequests, + } + : undefined, + }); }); } + private async fetchClusterAssumedRequests( + activeRequestsList: string[], + clusterUri: string + ) { + return ( + await Promise.all( + activeRequestsList.map(requestId => + this.getAccessRequest(clusterUri, requestId) + ) + ) + ).reduce((requestsMap, request) => { + requestsMap[request.id] = { + id: request.id, + expires: new Date(request.expires.seconds * 1000), + roles: request.rolesList, + }; + return requestsMap; + }, {}); + } + private removeResources(clusterUri: string) { this.setState(draft => { this.findDbs(clusterUri).forEach(db => { diff --git a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts index 3eb00c11a5b..afaaba51dec 100644 --- a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts +++ b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts @@ -134,7 +134,6 @@ it('updates the port of a gateway connection when the underlying doc gets update workspacesService.setState(draftState => { draftState.workspaces['/clusters/localhost'] = { accessRequests: { - assumed: {}, pending: getEmptyPendingAccessRequest(), isBarCollapsed: false, }, diff --git a/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts b/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts index 49260442d4f..6fb2075660c 100644 --- a/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts +++ b/web/packages/teleterm/src/ui/services/statePersistence/statePersistenceService.ts @@ -16,15 +16,22 @@ limitations under the License. import { FileStorage } from 'teleterm/types'; import { ConnectionTrackerState } from 'teleterm/ui/services/connectionTracker'; -import { WorkspacesState } from 'teleterm/ui/services/workspacesService'; +import { + Workspace, + WorkspacesState, +} from 'teleterm/ui/services/workspacesService'; interface ShareFeedbackState { hasBeenOpened: boolean; } +export type WorkspacesPersistedState = Omit & { + workspaces: Record>; +}; + interface StatePersistenceState { connectionTracker: ConnectionTrackerState; - workspacesState: WorkspacesState; + workspacesState: WorkspacesPersistedState; shareFeedback: ShareFeedbackState; } @@ -43,7 +50,7 @@ export class StatePersistenceService { return this.getState().connectionTracker; } - saveWorkspacesState(workspacesState: WorkspacesState): void { + saveWorkspacesState(workspacesState: WorkspacesPersistedState): void { const newState: StatePersistenceState = { ...this.getState(), workspacesState, @@ -51,7 +58,7 @@ export class StatePersistenceService { this.putState(newState); } - getWorkspacesState(): WorkspacesState { + getWorkspacesState(): WorkspacesPersistedState { return this.getState().workspacesState; } diff --git a/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.test.ts b/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.test.ts index 03cdba3dff1..db1db35873d 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.test.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.test.ts @@ -30,25 +30,6 @@ function getMockAssumed(assumed = {}): Record { return assumed; } -function getMockAccessRequest(): AccessRequest { - return { - id: '72de9b90-04fd-5621-a55d-432d9fe56ef2', - state: 'APPROVED', - user: 'Sam', - expires: undefined, - expiresDuration: '', - created: undefined, - createdDuration: '', - roles: ['dev', 'admin'], - resolveReason: 'resolve reason', - requestReason: 'request reason', - reviews: [], - reviewers: [], - thresholdNames: ['Default'], - resources: [], - }; -} - function createService( pending: PendingAccessRequest, assumed: Record @@ -63,12 +44,10 @@ function createService( pending, assumed, }; - const service = new AccessRequestsService( + return new AccessRequestsService( () => store.state, draftState => store.setState(draftState) ); - - return service; } test('getCollapsed() returns the bar collapse state', () => { @@ -83,53 +62,6 @@ test('toggleBar() changes the collapse state', () => { expect(service.getCollapsed()).toBe(true); }); -test('addToAssumed() adds request to assumed', () => { - let service = createService( - getMockPendingAccessRequest(), - getMockAssumed({}) - ); - expect(service.getAssumed()).toStrictEqual({}); - const request = getMockAccessRequest(); - service.addToAssumed(request); - expect(service.getAssumed()).toStrictEqual({ - [request.id]: { - id: request.id, - expires: request.expires, - roles: request.roles, - }, - }); -}); - -test('getAssumedRoles() returns assumed roles', () => { - const request = getMockAccessRequest(); - let service = createService( - getMockPendingAccessRequest(), - getMockAssumed({ [request.id]: request }) - ); - expect(service.getAssumedRoles()).toStrictEqual(request.roles); -}); - -test('getAssumed() returns assumed map', () => { - const request = getMockAccessRequest(); - let service = createService( - getMockPendingAccessRequest(), - getMockAssumed({ [request.id]: request }) - ); - expect(service.getAssumed()).toStrictEqual({ - [request.id]: request, - }); -}); - -test('clearAssumed() clears assumed map', () => { - const request = getMockAccessRequest(); - let service = createService( - getMockPendingAccessRequest(), - getMockAssumed({ [request.id]: request }) - ); - service.clearAssumed(); - expect(service.getAssumed()).toStrictEqual({}); -}); - test('clearPendingAccessRequest() clears pending access reuqest', () => { let service = createService( getMockPendingAccessRequest(), diff --git a/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.ts b/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.ts index df3d65367b8..d1bd04dd969 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/accessRequestsService.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import { ResourceKind } from 'e-teleterm/ui/DocumentAccessRequests/NewRequest/useNewRequest'; -import { AccessRequest } from 'e-teleport/services/workflow'; import { PendingAccessRequest } from '../workspacesService'; @@ -23,13 +22,11 @@ export class AccessRequestsService { private getState: () => { isBarCollapsed: boolean; pending: PendingAccessRequest; - assumed: Record; }, private setState: ( draftState: (draft: { isBarCollapsed: boolean; pending: PendingAccessRequest; - assumed: Record; }) => void ) => void ) {} @@ -48,42 +45,6 @@ export class AccessRequestsService { return this.getState().pending; } - getAssumed() { - return this.getState().assumed; - } - - getAssumedRoles() { - // return only unique roles from the flatMap of all roles - // assumed in each request - return [ - ...new Set( - Object.values(this.getAssumed()).flatMap(request => request.roles) - ), - ]; - } - - addToAssumed({ id, expires, roles }: AccessRequest) { - this.setState(draftState => { - draftState.assumed[id] = { - id, - expires, - roles, - }; - }); - } - - removeFromAssumed(request: AccessRequest) { - this.setState(draftState => { - delete draftState.assumed[request.id]; - }); - } - - clearAssumed() { - this.setState(draftState => { - draftState.assumed = {}; - }); - } - clearPendingAccessRequest() { this.setState(draftState => { draftState.pending = getEmptyPendingAccessRequest(); @@ -123,8 +84,3 @@ export function getEmptyPendingAccessRequest() { windows_desktop: {}, }; } - -export type AssumedAccessRequest = Pick< - AccessRequest, - 'id' | 'expires' | 'roles' ->; diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts index 826f84f0be2..695f48b0618 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.test.ts @@ -69,7 +69,6 @@ describe('restoring workspace', () => { const testClusterUri = '/clusters/test-uri'; const testWorkspace: Workspace = { accessRequests: { - assumed: {}, isBarCollapsed: true, pending: getEmptyPendingAccessRequest(), }, @@ -93,7 +92,6 @@ describe('restoring workspace', () => { expect(workspacesService.getWorkspaces()).toStrictEqual({ [testClusterUri]: { accessRequests: { - assumed: {}, pending: { app: {}, db: {}, @@ -127,7 +125,6 @@ describe('restoring workspace', () => { [testClusterUri]: { accessRequests: { isBarCollapsed: false, - assumed: {}, pending: { app: {}, db: {}, diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index 6b824fee183..bced11ce445 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -1,19 +1,18 @@ import { useStore } from 'shared/libs/stores'; import { isEqual } from 'lodash'; -import isPast from 'date-fns/isPast'; - -import { AccessRequest } from 'e-teleport/services/workflow'; import { ResourceKind } from 'e-teleport/Workflow/NewRequest/useNewRequest'; import { ModalsService } from 'teleterm/ui/services/modals'; import { ClustersService } from 'teleterm/ui/services/clusters'; -import { StatePersistenceService } from 'teleterm/ui/services/statePersistence'; +import { + StatePersistenceService, + WorkspacesPersistedState, +} from 'teleterm/ui/services/statePersistence'; import { ImmutableStore } from 'teleterm/ui/services/immutableStore'; import { NotificationsService } from 'teleterm/ui/services/notifications'; import { routing } from 'teleterm/ui/uri'; -import { LoggedInUser } from 'teleterm/services/tshd/types'; import { AccessRequestsService, @@ -34,10 +33,8 @@ export interface Workspace { accessRequests: { isBarCollapsed: boolean; pending: PendingAccessRequest; - assumed: Record; }; previous?: { - assumed: Record; documents: Document[]; location: string; }; @@ -260,13 +257,6 @@ export class WorkspacesService extends ImmutableStore { workspaces[cluster.uri] = { ...workspaceDefaultState, - accessRequests: { - ...workspaceDefaultState.accessRequests, - assumed: this.removeExpiredAssumedRoles( - persistedWorkspace?.accessRequests?.assumed || {}, - cluster.loggedInUser - ), - }, previous: this.canReopenPreviousDocuments({ previousDocuments: persistedWorkspaceDocuments, currentDocuments: workspaceDefaultState.documents, @@ -289,22 +279,6 @@ export class WorkspacesService extends ImmutableStore { } } - private removeExpiredAssumedRoles( - assumed: Record, - user: LoggedInUser - ) { - const validRequests = {}; - const requests = Object.values(assumed); - requests.forEach(request => { - const expired = isPast(new Date(request.expires)); - // only add any assumed that still exist on the loggedInUser cert - if (user.activeRequestsList.includes(request.id) && !expired) { - validRequests[request.id] = request; - } - }); - return validRequests; - } - private reopenPreviousDocuments(clusterUri: string): void { this.setState(draftState => { const workspace = draftState.workspaces[clusterUri]; @@ -362,7 +336,6 @@ export class WorkspacesService extends ImmutableStore { ).createClusterDocument({ clusterUri: localClusterUri }); return { accessRequests: { - assumed: {}, pending: getEmptyPendingAccessRequest(), isBarCollapsed: false, }, @@ -373,19 +346,13 @@ export class WorkspacesService extends ImmutableStore { } private persistState(): void { - const stateToSave: WorkspacesState = { + const stateToSave: WorkspacesPersistedState = { rootClusterUri: this.state.rootClusterUri, workspaces: {}, }; for (let w in this.state.workspaces) { const workspace = this.state.workspaces[w]; stateToSave.workspaces[w] = { - accessRequests: { - isBarCollapsed: false, - assumed: - workspace.previous?.assumed || workspace.accessRequests?.assumed, - pending: getEmptyPendingAccessRequest(), - }, localClusterUri: workspace.localClusterUri, location: workspace.previous?.location || workspace.location, documents: workspace.previous?.documents || workspace.documents, diff --git a/web/packages/webapps.e b/web/packages/webapps.e index 3a075722db6..05cd820da17 160000 --- a/web/packages/webapps.e +++ b/web/packages/webapps.e @@ -1 +1 @@ -Subproject commit 3a075722db6d5a731f8d1a01c5b338a5593f7f95 +Subproject commit 05cd820da177ed9d1a655026426105fab00537f6