Do not keep assumed requests in app_state.json (#1254)

This commit is contained in:
Grzegorz Zdunek
2022-10-13 12:25:21 +02:00
committed by GitHub
parent fe004fc158
commit c290483bfd
11 changed files with 75 additions and 167 deletions
@@ -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<apiCluster.Cluster.AsObject, 'loggedInUser'> & {
loggedInUser?: LoggedInUser;
};
export type LoggedInUser = apiCluster.LoggedInUser.AsObject & {
assumedRequests?: Record<string, AssumedRequest>;
};
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[];
};
@@ -37,7 +37,6 @@ export const Story = () => {
location: '',
localClusterUri: '/clusters/localhost',
accessRequests: {
assumed: {},
pending: getEmptyPendingAccessRequest(),
isBarCollapsed: true,
},
@@ -15,6 +15,7 @@ const clusterMock: tsh.Cluster = {
proxyHost: 'localhost:3080',
loggedInUser: {
activeRequestsList: [],
assumedRequests: {},
name: 'admin',
acl: {},
sshLoginsList: [],
@@ -404,6 +404,15 @@ export class ClustersService extends ImmutableStore<ClustersServiceState> {
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<ClustersServiceState> {
// 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 => {
@@ -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,
},
@@ -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<WorkspacesState, 'workspaces'> & {
workspaces: Record<string, Omit<Workspace, 'accessRequests'>>;
};
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;
}
@@ -30,25 +30,6 @@ function getMockAssumed(assumed = {}): Record<string, AccessRequest> {
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<string, AccessRequest>
@@ -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(),
@@ -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<string, AccessRequest>;
},
private setState: (
draftState: (draft: {
isBarCollapsed: boolean;
pending: PendingAccessRequest;
assumed: Record<string, AssumedAccessRequest>;
}) => 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'
>;
@@ -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: {},
@@ -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<string, AccessRequest>;
};
previous?: {
assumed: Record<string, AccessRequest>;
documents: Document[];
location: string;
};
@@ -260,13 +257,6 @@ export class WorkspacesService extends ImmutableStore<WorkspacesState> {
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<WorkspacesState> {
}
}
private removeExpiredAssumedRoles(
assumed: Record<string, AccessRequest>,
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<WorkspacesState> {
).createClusterDocument({ clusterUri: localClusterUri });
return {
accessRequests: {
assumed: {},
pending: getEmptyPendingAccessRequest(),
isBarCollapsed: false,
},
@@ -373,19 +346,13 @@ export class WorkspacesService extends ImmutableStore<WorkspacesState> {
}
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,
Submodule web/packages/webapps.e updated: 3a075722db...05cd820da1