mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-04 02:21:02 +08:00
Do not keep assumed requests in app_state.json (#1254)
This commit is contained in:
@@ -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 => {
|
||||
|
||||
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-69
@@ -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,
|
||||
|
||||
+1
-1
Submodule web/packages/webapps.e updated: 3a075722db...05cd820da1
Reference in New Issue
Block a user