Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Fix several space developer permission bugs (#4362)
Browse files Browse the repository at this point in the history
* Hide deployment info card if not space developer
- fixes #4322

* Only space developers can create/unmap/delete routes in app routes table
- fixes #4324

* Only Space Developers should be able to change count, terminate or ssh to instances
- fixes #4330

* Permissions: Only Space Developers should be able to create/edit/delete an Autoscaler policy
- fixes #4323

* Users with no developer roles could click on add app button
- fixes #4361

* Fix tests

* Fix autoscaler tab

* Changes following review
  • Loading branch information
richard-cox committed Jun 17, 2020
1 parent ead3453 commit 56d9129
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {
import {
RunningInstancesComponent,
} from '../../../../cloud-foundry/src/shared/components/running-instances/running-instances.component';
import {
cfCurrentUserPermissionsService,
} from '../../../../cloud-foundry/src/user-permissions/cf-user-permissions-checkers';
import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper';
import { CoreModule } from '../../../../core/src/core/core.module';
import { SharedModule } from '../../../../core/src/shared/shared.module';
Expand Down Expand Up @@ -48,7 +51,8 @@ describe('AutoscalerTabExtensionComponent', () => {
providers: [
DatePipe,
{ provide: ApplicationService, useClass: ApplicationServiceMock },
TabNavService
TabNavService,
...cfCurrentUserPermissionsService
]
})
.compileComponents();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { combineLatest, Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, first, map, pairwise, publishReplay, refCount } from 'rxjs/operators';
import { combineLatest, Observable, of, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, first, map, pairwise, publishReplay, refCount, switchMap } from 'rxjs/operators';

import { applicationEntityType } from '../../../../cloud-foundry/src/cf-entity-types';
import { createEntityRelationPaginationKey } from '../../../../cloud-foundry/src/entity-relations/entity-relations.types';
import { cfEntityCatalog } from '../../../../cloud-foundry/src/cf-entity-catalog';
import {
applicationEntityType,
organizationEntityType,
spaceEntityType,
} from '../../../../cloud-foundry/src/cf-entity-types';
import {
createEntityRelationKey,
createEntityRelationPaginationKey,
} from '../../../../cloud-foundry/src/entity-relations/entity-relations.types';
import { ApplicationMonitorService } from '../../../../cloud-foundry/src/features/applications/application-monitor.service';
import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service';
import { getGuids } from '../../../../cloud-foundry/src/features/applications/application/application-base.component';
import { CfCurrentUserPermissions } from '../../../../cloud-foundry/src/user-permissions/cf-user-permissions-checkers';
import { StratosTab, StratosTabType } from '../../../../core/src/core/extension/extension-service';
import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service';
import { safeUnsubscribe } from '../../../../core/src/core/utils.service';
import { ConfirmationDialogConfig } from '../../../../core/src/shared/components/confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../../core/src/shared/components/confirmation-dialog.service';
Expand Down Expand Up @@ -52,9 +62,32 @@ import { appAutoscalerAppMetricEntityType, autoscalerEntityFactory } from '../..
link: 'autoscale',
icon: 'meter',
iconFont: 'stratos-icons',
hidden: (store: Store<AppState>, esf: EntityServiceFactory, activatedRoute: ActivatedRoute) => {
hidden: (store: Store<AppState>, esf: EntityServiceFactory, activatedRoute: ActivatedRoute, cups: CurrentUserPermissionsService) => {
const endpointGuid = getGuids('cf')(activatedRoute) || window.location.pathname.split('/')[2];
return isAutoscalerEnabled(endpointGuid, esf).pipe(map(enabled => !enabled));
const appGuid = getGuids()(activatedRoute) || window.location.pathname.split('/')[3];
const appEntService = cfEntityCatalog.application.store.getEntityService(appGuid, endpointGuid, {
includeRelations: [
createEntityRelationKey(applicationEntityType, spaceEntityType),
createEntityRelationKey(spaceEntityType, organizationEntityType),
],
populateMissing: true
})

const canEditApp$ = appEntService.waitForEntity$.pipe(
switchMap(app => cups.can(
CfCurrentUserPermissions.APPLICATION_EDIT,
endpointGuid,
app.entity.entity.space.entity.organization_guid,
app.entity.entity.space.metadata.guid
)),
)

const autoscalerEnabled = isAutoscalerEnabled(endpointGuid, esf);

return canEditApp$.pipe(
switchMap(canEditSpace => canEditSpace ? autoscalerEnabled : of(false)),
map(can => !can)
)
}
})
@Component({
Expand Down Expand Up @@ -130,7 +163,7 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy {
private paginationMonitorFactory: PaginationMonitorFactory,
private appAutoscalerPolicySnackBar: MatSnackBar,
private appAutoscalerScalingHistorySnackBar: MatSnackBar,
private confirmDialog: ConfirmationDialogService,
private confirmDialog: ConfirmationDialogService
) { }

ngOnInit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@
</mat-card-content>
</mat-card>
</app-tile>
<app-tile id="app-build-tab-deployment-info">
<app-tile id="app-build-tab-deployment-info" *ngIf="(deploySource$ | async) as deploySource">
<mat-card>
<mat-card-header>
<mat-card-title>Deployment Info</mat-card-title>
</mat-card-header>
<mat-card-content *ngIf="(deploySource$ | async) as deploySource; else notDeployedFromStratos">
<mat-card-content>
<span [ngSwitch]="deploySource.type">
<app-metadata-item *ngSwitchCase="'giturl'" icon="code" label="Git Url">
<div matTooltip="{{ deploySource.branch + ' ' + (deploySource.commit | slice:0:8)}}"
Expand Down Expand Up @@ -183,9 +183,6 @@
</app-metadata-item>
</span>
</mat-card-content>
<ng-template #notDeployedFromStratos>
<mat-card-content>None</mat-card-content>
</ng-template>
</mat-card>
</app-tile>
</app-tile-group>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Component, Inject, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { combineLatest, delay, distinct, filter, first, map, mergeMap, startWith, tap } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, of } from 'rxjs';
import { combineLatest, delay, distinct, filter, first, map, mergeMap, startWith, switchMap, tap } from 'rxjs/operators';

import { AppMetadataTypes } from '../../../../../../../../cloud-foundry/src/actions/app-metadata.actions';
import { UpdateExistingApplication } from '../../../../../../../../cloud-foundry/src/actions/application.actions';
import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state';
import {
CurrentUserPermissionsService,
} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service';
import { getFullEndpointApiUrl } from '../../../../../../../../core/src/features/endpoints/endpoint-helpers';
import { ConfirmationDialogConfig } from '../../../../../../../../core/src/shared/components/confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service';
Expand Down Expand Up @@ -58,6 +61,7 @@ const appRestageConfirmation = new ConfirmationDialogConfig(
export class BuildTabComponent implements OnInit {
public isBusyUpdating$: Observable<{ updating: boolean }>;
public manageAppPermission = CfCurrentUserPermissions.APPLICATION_MANAGE;

constructor(
public applicationService: ApplicationService,
private scmService: GitSCMService,
Expand All @@ -66,7 +70,7 @@ export class BuildTabComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private confirmDialog: ConfirmationDialogService,

private cups: CurrentUserPermissionsService
) { }

cardTwoFetching$: Observable<boolean>;
Expand Down Expand Up @@ -108,8 +112,17 @@ export class BuildTabComponent implements OnInit {
})
);

this.deploySource$ = this.applicationService.applicationStratProject$.pipe(
combineLatest(this.applicationService.application$)
const canSeeEnvVars$ = this.applicationService.appSpace$.pipe(
switchMap(space => this.cups.can(
CfCurrentUserPermissions.APPLICATION_VIEW_ENV_VARS,
this.applicationService.cfGuid,
space.metadata.guid)
)
)

const deploySource$ = observableCombineLatest(
this.applicationService.applicationStratProject$,
this.applicationService.application$
).pipe(
map(([project, app]) => {
if (!!project) {
Expand Down Expand Up @@ -149,6 +162,10 @@ export class BuildTabComponent implements OnInit {
}
}),
startWith({ type: 'loading' })
)

this.deploySource$ = canSeeEnvVars$.pipe(
switchMap(canSeeEnvVars => canSeeEnvVars ? deploySource$ : of(null)),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
</mat-card-header>
<mat-card-content class="card-app-instances__compact">
<div *ngIf="!isEditing" class="card-app-instances__large">
<app-running-instances [instances]="(appService.application$ | async)?.app.entity?.instances" [cfGuid]="appService.cfGuid" [appGuid]="this.appService.appGuid">
<app-running-instances [instances]="(appService.application$ | async)?.app.entity?.instances"
[cfGuid]="appService.cfGuid" [appGuid]="this.appService.appGuid">
</app-running-instances>
</div>
<form [hidden]="!isEditing" class="card-app-instances__form">
Expand All @@ -14,23 +15,27 @@
</mat-form-field>
</form>
</mat-card-content>
<mat-card-actions *ngIf="showActions && (appService.applicationRunning$ | async) && !isEditing" class="card-app-instances__actions">
<button (click)="edit()" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>edit</mat-icon>
<ng-container *ngIf="canEditApp$ | async">
<mat-card-actions *ngIf="showActions && (appService.applicationRunning$ | async) && !isEditing"
class="card-app-instances__actions">
<button (click)="edit()" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>edit</mat-icon>
</button>
<button (click)="scaleDown()" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>remove_circle_outline</mat-icon>
<button (click)="scaleDown()" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>remove_circle_outline</mat-icon>
</button>
<button (click)="scaleUp()" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>add_circle_outline</mat-icon>
<button (click)="scaleUp()" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>add_circle_outline</mat-icon>
</button>
</mat-card-actions>
<mat-card-actions *ngIf="showActions && isEditing" class="card-app-instances__actions">
<button (click)="finishEdit(false)" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>clear</mat-icon>
</mat-card-actions>
<mat-card-actions *ngIf="showActions && isEditing" class="card-app-instances__actions">
<button (click)="finishEdit(false)" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>clear</mat-icon>
</button>
<button (click)="finishEdit(true)" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>done</mat-icon>
</button>
</mat-card-actions>
</mat-card>
<button (click)="finishEdit(true)" mat-icon-button [disabled]="appService.isUpdatingApp$ | async">
<mat-icon>done</mat-icon>
</button>
</mat-card-actions>
</ng-container>

</mat-card>
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild, Renderer2 } from '@angular/core';
import { Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
import { Observable, Subscription } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { combineLatest, Observable, Subscription } from 'rxjs';
import { first, map, switchMap } from 'rxjs/operators';

import { AppMetadataTypes } from '../../../../../../cloud-foundry/src/actions/app-metadata.actions';
import { ApplicationService } from '../../../../../../cloud-foundry/src/features/applications/application.service';
import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service';
import { ConfirmationDialogConfig } from '../../../../../../core/src/shared/components/confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service';
import { StratosStatus } from '../../../../../../core/src/shared/shared.types';
import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers';

const appInstanceScaleToZeroConfirmation = new ConfirmationDialogConfig('Set Instance count to 0',
'Are you sure you want to set the instance count to 0?', 'Confirm', true);
Expand All @@ -28,14 +30,26 @@ export class CardAppInstancesComponent implements OnInit, OnDestroy {

status$: Observable<StratosStatus>;

public canEditApp$: Observable<boolean>;

constructor(
public appService: ApplicationService,
private renderer: Renderer2,
private confirmDialog: ConfirmationDialogService,
private snackBar: MatSnackBar) {
private snackBar: MatSnackBar,
cups: CurrentUserPermissionsService
) {
this.status$ = this.appService.applicationState$.pipe(
map(state => state.indicator)
);
this.canEditApp$ = combineLatest(
appService.appOrg$,
appService.appSpace$
).pipe(
switchMap(([org, space]) =>
cups.can(CfCurrentUserPermissions.APPLICATION_EDIT, appService.cfGuid, org.metadata.guid, space.metadata.guid)
))

}

private currentCount = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { combineLatest as combineLatestObs, Observable } from 'rxjs';
import { combineLatest, map, switchMap } from 'rxjs/operators';

import { DeleteApplicationInstance } from '../../../../../../../cloud-foundry/src/actions/application.actions';
import { FetchApplicationMetricsAction } from '../../../../../../../cloud-foundry/src/actions/cf-metrics.actions';
import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state';
import {
CurrentUserPermissionsService,
} from '../../../../../../../core/src/core/permissions/current-user-permissions.service';
import { UtilsService } from '../../../../../../../core/src/core/utils.service';
import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service';
Expand All @@ -27,6 +30,7 @@ import { IMetricMatrixResult, IMetrics } from '../../../../../../../store/src/ty
import { IMetricApplication } from '../../../../../../../store/src/types/metric.types';
import { ApplicationService } from '../../../../../features/applications/application.service';
import { CfCellHelper } from '../../../../../features/cloud-foundry/cf-cell.helpers';
import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers';
import { ListAppInstance } from './app-instance-types';
import { CfAppInstancesDataSource } from './cf-app-instances-data-source';
import { TableCellCfCellComponent } from './table-cell-cf-cell/table-cell-cf-cell.component';
Expand Down Expand Up @@ -158,7 +162,7 @@ export class CfAppInstancesConfigService implements IListConfig<ListAppInstance>
},
label: 'Terminate',
description: ``, // Description depends on console user permission

createVisible: () => this.canEditApp$
};

private listActionSsh: IListAction<any> = {
Expand All @@ -182,22 +186,26 @@ export class CfAppInstancesConfigService implements IListConfig<ListAppInstance>
space.entity.allow_ssh;
})
);
}))
})),
createVisible: () => this.canEditApp$
};

private singleActions = [
this.listActionTerminate,
this.listActionSsh,
];

private canEditApp$: Observable<boolean>;

constructor(
private store: Store<CFAppState>,
private appService: ApplicationService,
private utilsService: UtilsService,
private router: Router,
private confirmDialog: ConfirmationDialogService,
entityServiceFactory: EntityServiceFactory,
paginationMonitorFactory: PaginationMonitorFactory
paginationMonitorFactory: PaginationMonitorFactory,
cups: CurrentUserPermissionsService
) {
const cellHelper = new CfCellHelper(store, paginationMonitorFactory);

Expand All @@ -220,6 +228,15 @@ export class CfAppInstancesConfigService implements IListConfig<ListAppInstance>
this.appService.appGuid,
this,
);

this.canEditApp$ = combineLatestObs(
appService.appOrg$,
appService.appSpace$
).pipe(
switchMap(([org, space]) =>
cups.can(CfCurrentUserPermissions.APPLICATION_EDIT, appService.cfGuid, org.metadata.guid, space.metadata.guid)
)
)
}

getGlobalActions = () => null;
Expand Down
Loading

0 comments on commit 56d9129

Please sign in to comment.