Skip to content

Commit

Permalink
Merge pull request #2212 from cloudfoundry-incubator/apply-user-permi…
Browse files Browse the repository at this point in the history
…ssions-2

Apply user permissions to CF pages - 2
  • Loading branch information
KlapTrap authored May 24, 2018
2 parents d245dd3 + 7c9824e commit 8c89f82
Show file tree
Hide file tree
Showing 22 changed files with 249 additions and 81 deletions.
46 changes: 34 additions & 12 deletions src/frontend/app/core/current-user-permissions.checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from '../store/selectors/current-user-roles-permissions-selectors/role.selectors';
import { endpointsRegisteredEntitiesSelector } from '../store/selectors/endpoint.selectors';
import { APIResource } from '../store/types/api.types';
import { IOrgRoleState, ISpaceRoleState } from '../store/types/current-user-roles.types';
import { IOrgRoleState, ISpaceRoleState, ISpacesRoleState } from '../store/types/current-user-roles.types';
import { IFeatureFlag } from './cf-api.types';
import {
PermissionConfig,
Expand All @@ -38,31 +38,45 @@ export enum CHECKER_GROUPS {

export type IConfigGroup = PermissionConfig[];
export class CurrentUserPermissionsChecker {
static readonly ALL_SPACES = 'PERMISSIONS__ALL_SPACES_PLEASE';
constructor(private store: Store<AppState>) { }
public check(type: PermissionTypes, permission: PermissionValues, endpointGuid?: string, orgOrSpaceGuid?: string, ) {
public check(
type: PermissionTypes,
permission: PermissionValues,
endpointGuid?: string,
orgOrSpaceGuid?: string,
allSpacesWithinOrg = false
) {
if (type === PermissionTypes.STRATOS) {
return this.store.select(getCurrentUserStratosRole(permission));
}

if (type === PermissionTypes.STRATOS_SCOPE) {
return this.store.select(getCurrentUserStratosHasScope(permission));
return this.store.select(getCurrentUserStratosHasScope(permission as ScopeStrings));
}

if (type === PermissionTypes.ENDPOINT_SCOPE) {
if (!endpointGuid) {
return Observable.of(false);
}
return this.store.select(getCurrentUserCFEndpointHasScope(endpointGuid, permission));
return this.store.select(getCurrentUserCFEndpointHasScope(endpointGuid, permission as ScopeStrings));
}

if (type === PermissionTypes.ENDPOINT) {
return this.store.select(getCurrentUserCFGlobalState(endpointGuid, permission));
}
return this.getEndpointState(endpointGuid).pipe(
filter(state => !!state),
map(state => state[type][orgOrSpaceGuid]),
filter(state => !!state),
map(state => this.selectPermission(state, permission as PermissionStrings)),
map(state => {
const permissionString = permission as PermissionStrings;
if (allSpacesWithinOrg) {
const orgOrSpaceState = state[PermissionTypes.ORGANIZATION][orgOrSpaceGuid];
const spaceState = state[PermissionTypes.SPACE];
return this.checkAllSpacesInOrg(orgOrSpaceState, spaceState, permissionString);
}
const orgOrSpaceState = state[type][orgOrSpaceGuid];
return this.selectPermission(orgOrSpaceState, permissionString);
}),
distinctUntilChanged(),
);
}
Expand Down Expand Up @@ -90,6 +104,13 @@ export class CurrentUserPermissionsChecker {
}
}

private checkAllSpacesInOrg(orgState: IOrgRoleState, endpointSpaces: ISpacesRoleState, permission: PermissionStrings) {
return orgState.spaceGuids.map(spaceGuid => {
const space = endpointSpaces[spaceGuid];
return space ? space[permission] || false : false;
}).some(check => check);
}

public getInternalChecks(
configs: PermissionConfig[]
) {
Expand Down Expand Up @@ -154,10 +175,11 @@ export class CurrentUserPermissionsChecker {

public getCfCheck(config: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string): Observable<boolean> {
const { type, permission } = config;
const actualGuid = type === PermissionTypes.SPACE && spaceGuid ? spaceGuid : orgOrSpaceGuid;
const checkAllSpaces = spaceGuid === CurrentUserPermissionsChecker.ALL_SPACES;
const actualGuid = type === PermissionTypes.SPACE && spaceGuid && !checkAllSpaces ? spaceGuid : orgOrSpaceGuid;
const cfPermissions = permission as PermissionStrings;
if (type === PermissionTypes.ENDPOINT || (endpointGuid && actualGuid)) {
return this.check(type, cfPermissions, endpointGuid, actualGuid);
return this.check(type, cfPermissions, endpointGuid, actualGuid, checkAllSpaces);
} else if (!actualGuid) {
const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid);
return endpointGuids$.pipe(
Expand Down Expand Up @@ -281,7 +303,7 @@ export class CurrentUserPermissionsChecker {
return config.type;
}

private checkAllOfType(endpointGuid: string, type: PermissionTypes, permission: PermissionStrings) {
private checkAllOfType(endpointGuid: string, type: PermissionTypes, permission: PermissionStrings, orgGuid?: string) {
return this.getEndpointState(endpointGuid).pipe(
map(state => {
if (!state || !state[type]) {
Expand All @@ -304,8 +326,8 @@ export class CurrentUserPermissionsChecker {
return !endpointGuid ? this.getAllEndpointGuids() : Observable.of([endpointGuid]);
}

private selectPermission(state: IOrgRoleState | ISpaceRoleState, permission: PermissionStrings) {
return state[permission] || false;
private selectPermission(state: IOrgRoleState | ISpaceRoleState, permission: PermissionStrings): boolean {
return state ? state[permission] || false : false;
}

private getEndpointState(endpointGuid: string) {
Expand Down
9 changes: 8 additions & 1 deletion src/frontend/app/core/current-user-permissions.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export enum CurrentUserPermissions {
ORGANIZATION_DELETE = 'delete.org',
ORGANIZATION_EDIT = 'edit.org',
ORGANIZATION_SUSPEND = 'suspend.org',
SERVICE_INSTANCE_DELETE = 'delete.service-instance',
SERVICE_BINDING_EDIT = 'edit.service-binding',
FIREHOSE_VIEW = 'view-firehose',
ENDPOINT_REGISTER = 'register.endpoint',
PASSWORD_CHANGE = 'change-password'
}
Expand Down Expand Up @@ -99,7 +102,6 @@ export const permissionConfigs: IPermissionConfigs = {
new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.route_creation),
new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER)
],
// [CurrentUserPermissions.ROUTE_BINDING_CREATE]: new PermissionConfigLink(CurrentUserPermissions.ROUTE_CREATE),
[CurrentUserPermissions.ORGANIZATION_CREATE]: [
new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.user_org_creation),
new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER),
Expand All @@ -113,6 +115,11 @@ export const permissionConfigs: IPermissionConfigs = {
[CurrentUserPermissions.ORGANIZATION_DELETE]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP),
[CurrentUserPermissions.ORGANIZATION_EDIT]: new PermissionConfigLink(CurrentUserPermissions.ORGANIZATION_DELETE),
[CurrentUserPermissions.ORGANIZATION_SUSPEND]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP),
[CurrentUserPermissions.SERVICE_INSTANCE_DELETE]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER),
[CurrentUserPermissions.SERVICE_BINDING_EDIT]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER),
[CurrentUserPermissions.FIREHOSE_VIEW]: [
new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_READ_ONLY_ADMIN_GROUP)
],
[CurrentUserPermissions.ENDPOINT_REGISTER]: new PermissionConfig(PermissionTypes.STRATOS, PermissionStrings.STRATOS_ADMIN),
[CurrentUserPermissions.PASSWORD_CHANGE]: new PermissionConfig(PermissionTypes.STRATOS_SCOPE, ScopeStrings.STRATOS_CHANGE_PASSWORD),
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h4>Organization Commands</h4>
<app-cli-command name="List" msg="To list all orgs:" syntax="cf orgs">
</app-cli-command>

<app-cli-command name="Rename" msg="To rename an org:" syntax="cf rename-org {{context.orgName || '[organization name]'}} [name]">
<app-cli-command *appUserPermission="permsOrgEdit;endpointGuid:activeRouteCfOrgSpace.cfGuid;organizationGuid:activeRouteCfOrgSpace.orgGuid" name="Rename" msg="To rename an org:" syntax="cf rename-org {{context.orgName || '[organization name]'}} [name]">
</app-cli-command>

<app-cli-command name="Information" msg="To fetch org information:" syntax="cf org {{context.orgName || '[organization name]'}}">
Expand All @@ -32,7 +32,7 @@ <h4>Space Commands</h4>
<app-cli-command name="List" msg="To list the spaces in the targeted org:" syntax="cf spaces">
</app-cli-command>

<app-cli-command name="Rename" msg="To rename a space:" syntax="cf rename-space {{context.spacegName || '[space name]'}} [name]">
<app-cli-command *appUserPermission="permsSpaceEdit;endpointGuid:activeRouteCfOrgSpace.cfGuid;organizationGuid:orgGuid;spaceGuid:spaceGuid" name="Rename" msg="To rename a space:" syntax="cf rename-space {{context.spaceName || '[space name]'}} [name]">
</app-cli-command>

<app-cli-command name="Information" msg="To fetch space information:" syntax="cf space {{context.spaceName || '[space name]'}}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { combineLatest } from 'rxjs/observable/combineLatest';
import { first, map } from 'rxjs/operators';

import { IOrganization, ISpace } from '../../../core/cf-api.types';
import { CurrentUserPermissionsChecker } from '../../../core/current-user-permissions.checker';
import { CurrentUserPermissions } from '../../../core/current-user-permissions.config';
import { CFAppCLIInfoContext } from '../../../shared/components/cli-info/cli-info.component';
import { IHeaderBreadcrumb } from '../../../shared/components/page-header/page-header.types';
import { RouterNav } from '../../../store/actions/router.actions';
Expand Down Expand Up @@ -33,6 +35,12 @@ import { CloudFoundrySpaceService } from '../services/cloud-foundry-space.servic
})
export class CliInfoCloudFoundryComponent implements OnInit {

permsOrgEdit = CurrentUserPermissions.ORGANIZATION_EDIT;
permsSpaceEdit = CurrentUserPermissions.SPACE_EDIT;

orgGuid: string;
spaceGuid: string;

cfEndpointEntityService: any;
public previousUrl: string;
public previousQueryParams: {
Expand All @@ -51,12 +59,16 @@ export class CliInfoCloudFoundryComponent implements OnInit {

constructor(
private store: Store<AppState>,
private activeRouteCfOrgSpace: ActiveRouteCfOrgSpace,
public activeRouteCfOrgSpace: ActiveRouteCfOrgSpace,
private cfEndpointService: CloudFoundryEndpointService,
@Optional() private cfOrgService: CloudFoundryOrganizationService,
@Optional() private cfSpaceService: CloudFoundrySpaceService
) {
this.breadcrumbs$ = new BehaviorSubject<IHeaderBreadcrumb[]>([]);
if (activeRouteCfOrgSpace.orgGuid) {
this.orgGuid = activeRouteCfOrgSpace.orgGuid;
this.spaceGuid = activeRouteCfOrgSpace.spaceGuid || CurrentUserPermissionsChecker.ALL_SPACES;
}
}

ngOnInit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import { Observable } from 'rxjs/Observable';
import { environment } from '../../../../environments/environment';
import { CurrentUserPermissions } from '../../../core/current-user-permissions.config';
import { CurrentUserPermissionsService } from '../../../core/current-user-permissions.service';
import { ISubHeaderTabs } from '../../../shared/components/page-subheader/page-subheader.types';
import { CloudFoundryEndpointService } from '../services/cloud-foundry-endpoint.service';
import { AppState } from './../../../store/app-state';
import { map } from 'rxjs/operators';

@Component({
selector: 'app-cloud-foundry-tabs-base',
templateUrl: './cloud-foundry-tabs-base.component.html',
styleUrls: ['./cloud-foundry-tabs-base.component.scss']
})

export class CloudFoundryTabsBaseComponent implements OnInit {
tabLinks = [
static firehose = 'firehose';

tabLinks: ISubHeaderTabs[] = [
{ link: 'summary', label: 'Summary' },
{ link: 'organizations', label: 'Organizations' },
{ link: 'users', label: 'Users' },
{ link: 'firehose', label: 'Firehose' },
{ link: CloudFoundryTabsBaseComponent.firehose, label: 'Firehose' },
{ link: 'feature-flags', label: 'Feature Flags' },
{ link: 'build-packs', label: 'Build Packs' },
{ link: 'stacks', label: 'Stacks' },
Expand All @@ -38,11 +41,15 @@ export class CloudFoundryTabsBaseComponent implements OnInit {
private store: Store<AppState>,
public currentUserPermissionsService: CurrentUserPermissionsService
) {

this.tabLinks.find(tabLink => tabLink.link === CloudFoundryTabsBaseComponent.firehose).hidden =
this.currentUserPermissionsService.can(CurrentUserPermissions.FIREHOSE_VIEW, this.cfEndpointService.cfGuid).pipe(
map(visible => !visible)
);
}

ngOnInit() {
this.isFetching$ = Observable.of(false);
this.canAddOrg$ = this.currentUserPermissionsService.can(CurrentUserPermissions.ORGANIZATION_CREATE, this.cfEndpointService.cfGuid);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export class CloudFoundryOrganizationBaseComponent {
},
{
link: 'users',
label: 'Users'
label: 'Users',
// Hide the users tab unless we are in development
hidden: Observable.of(environment.production)
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export class CloudFoundrySpaceBaseComponent implements OnInit {
},
{
link: 'users',
label: 'Users'
label: 'Users',
// Hide the users tab unless we are in development
hidden: Observable.of(environment.production)
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import {
TableCellServicePlanComponent,
} from '../cf-spaces-service-instances/table-cell-service-plan/table-cell-service-plan.component';
import { Observable } from 'rxjs/Observable';
import { CurrentUserPermissionsService } from '../../../../../core/current-user-permissions.service';
import { CurrentUserPermissions } from '../../../../../core/current-user-permissions.config';

interface CanCache {
[spaceGuid: string]: Observable<boolean>;
}

@Injectable()
export class CfServiceInstancesListConfigBase extends ListConfig<APIResource<IServiceInstance>>
Expand All @@ -36,6 +42,9 @@ export class CfServiceInstancesListConfigBase extends ListConfig<APIResource<ISe
noEntries: 'There are no service instances'
};

private canDetachCache: CanCache = {};
private canDeleteCache: CanCache = {};

protected serviceInstanceColumns: ITableColumn<APIResource<IServiceInstance>>[] = [
{
columnId: 'name',
Expand Down Expand Up @@ -87,21 +96,33 @@ export class CfServiceInstancesListConfigBase extends ListConfig<APIResource<ISe
action: (item: APIResource) => this.deleteServiceInstance(item),
label: 'Delete',
description: 'Delete Service Instance',
createVisible: (row) => Observable.of(true),
createVisible: (row: APIResource<IServiceInstance>) =>
this.can(this.canDeleteCache, CurrentUserPermissions.SERVICE_INSTANCE_DELETE, row.entity.cfGuid, row.entity.space_guid),
createEnabled: (row) => Observable.of(true)
};

private listActionDetach: IListAction<APIResource> = {
action: (item: APIResource) => this.deleteServiceBinding(item),
label: 'Detach',
description: 'Detach Service Instance',
createVisible: (row: APIResource) => Observable.of(true),
createVisible: (row: APIResource<IServiceInstance>) =>
this.can(this.canDetachCache, CurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid),
createEnabled: (row: APIResource) => Observable.of(row.entity.service_bindings.length === 1)
};

private can(cache: CanCache, perm: CurrentUserPermissions, cfGuid: string, spaceGuid: string): Observable<boolean> {
let can = cache[spaceGuid];
if (!can) {
can = this.currentUserPermissionsService.can(perm, cfGuid, spaceGuid);
cache[spaceGuid] = can;
}
return can;
}

constructor(
protected store: Store<AppState>,
protected datePipe: DatePipe,
protected currentUserPermissionsService: CurrentUserPermissionsService,
private serviceActionHelperService: ServiceActionHelperService
) {
super();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ServiceActionHelperService } from '../../../../data-services/service-ac
import { IListConfig } from '../../list.component.types';
import { CfServiceInstancesListConfigBase } from '../cf-services/cf-service-instances-list-config.base';
import { CfSpacesServiceInstancesDataSource } from './cf-spaces-service-instances-data-source';
import { CurrentUserPermissionsService } from '../../../../../core/current-user-permissions.service';

/**
* Service instance list shown for `cf / org / space / service instances` tab
Expand All @@ -27,9 +28,9 @@ export class CfSpacesServiceInstancesListConfigService extends CfServiceInstance
store: Store<AppState>,
cfSpaceService: CloudFoundrySpaceService,
datePipe: DatePipe,
serviceActionHelperService: ServiceActionHelperService
) {
super(store, datePipe, serviceActionHelperService);
currentUserPermissionsService: CurrentUserPermissionsService,
serviceActionHelperService: ServiceActionHelperService) {
super(store, datePipe, currentUserPermissionsService, serviceActionHelperService);
this.dataSource = new CfSpacesServiceInstancesDataSource(cfSpaceService.cfGuid, cfSpaceService.spaceGuid, this.store, this);
this.serviceInstanceColumns.find(column => column.columnId === 'attachedApps').cellConfig = {
breadcrumbs: 'space-services'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AppState } from '../../../../../store/app-state';
import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service';
import { CfServiceInstancesListConfigBase } from '../cf-services/cf-service-instances-list-config.base';
import { ServiceInstancesDataSource } from './service-instances-data-source';
import { CurrentUserPermissionsService } from '../../../../../core/current-user-permissions.service';

/**
* Service instance list shown for `service / service instances` component
Expand All @@ -22,9 +23,9 @@ export class ServiceInstancesListConfigService extends CfServiceInstancesListCon
store: Store<AppState>,
servicesService: ServicesService,
datePipe: DatePipe,
serviceActionHelperService: ServiceActionHelperService
) {
super(store, datePipe, serviceActionHelperService);
currentUserPermissionsService: CurrentUserPermissionsService,
serviceActionHelperService: ServiceActionHelperService) {
super(store, datePipe, currentUserPermissionsService, serviceActionHelperService);
// Remove 'Service' column
this.serviceInstanceColumns.splice(1, 1);
this.dataSource = new ServiceInstancesDataSource(servicesService.cfGuid, servicesService.serviceGuid, store, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DatePipe } from '@angular/common';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';

import { CurrentUserPermissionsService } from '../../../../../core/current-user-permissions.service';
import { ListView } from '../../../../../store/actions/list.actions';
import { AppState } from '../../../../../store/app-state';
import { CfOrgSpaceDataService } from '../../../../data-services/cf-org-space-service.service';
Expand Down Expand Up @@ -36,9 +37,10 @@ export class ServiceInstancesWallListConfigService extends CfServiceInstancesLis
constructor(store: Store<AppState>,
datePipe: DatePipe,
private cfOrgSpaceService: CfOrgSpaceDataService,
currentUserPermissionsService: CurrentUserPermissionsService,
serviceActionHelperService: ServiceActionHelperService
) {
super(store, datePipe, serviceActionHelperService);
super(store, datePipe, currentUserPermissionsService, serviceActionHelperService);
const multiFilterConfigs = [
createListFilterConfig('cf', 'Cloud Foundry', this.cfOrgSpaceService.cf),
createListFilterConfig('org', 'Organization', this.cfOrgSpaceService.org),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</button>
<div class="tab-nav__inner" #navScroller [ngStyle]="{'margin-bottom.px': isOverflowing ? -(scrollBarWidth) : 0}">
<nav *ngIf="tabs" #nav backgroundColor="primary" mat-tab-nav-bar class="tab-nav">
<a mat-tab-link [ngClass]="{'page-subheader__tab-hidden': tab.hidden}" class="mat-tab-fit" *ngFor="let tab of tabs" [routerLink]="[tab.link]" routerLinkActive="active-link" #rla="routerLinkActive" [active]="rla.isActive">
<a mat-tab-link [ngClass]="{'page-subheader__tab-hidden': (tab.hidden | async)}" class="mat-tab-fit" *ngFor="let tab of tabs" [routerLink]="[tab.link]" routerLinkActive="active-link" #rla="routerLinkActive" [active]="rla.isActive">
{{tab.label}}
</a>
</nav>
Expand Down
Loading

0 comments on commit 8c89f82

Please sign in to comment.