Skip to content

Commit

Permalink
Merge pull request #4876 from cloudfoundry/userendpoints
Browse files Browse the repository at this point in the history
Userendpoints
  • Loading branch information
richard-cox authored Apr 16, 2021
2 parents e8b0989 + 043140c commit f34f428
Show file tree
Hide file tree
Showing 64 changed files with 2,441 additions and 186 deletions.
1 change: 1 addition & 0 deletions deploy/kubernetes/console/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ The following table lists the configurable parameters of the Stratos Helm chart
|console.userInviteSubject|Email subject of the user invitation message||
|console.techPreview|Enable/disable Tech Preview features|false|
|console.apiKeysEnabled|Enable/disable API key-based access to Stratos API (disabled, admin_only, all_users)|admin_only|
|console.userEndpointsEnabled|Enable/disable user endpoints or let only admins view and manage user endpoints (disabled, admin_only, enabled)|disabled|
|console.ui.listMaxSize|Override the default maximum number of entities that a configured list can fetch. When a list meets this amount additional pages are not fetched||
|console.ui.listAllowLoadMaxed|If the maximum list size is met give the user the option to fetch all results|false|
|console.localAdminPassword|Use local admin user instead of UAA - set to a password to enable||
Expand Down
2 changes: 2 additions & 0 deletions deploy/kubernetes/console/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ spec:
value: {{ default "false" .Values.console.techPreview | quote }}
- name: API_KEYS_ENABLED
value: {{ default "admin_only" .Values.console.apiKeysEnabled | quote }}
- name: USER_ENDPOINTS_ENABLED
value: {{ default "disabled" .Values.console.userEndpointsEnabled | quote }}
- name: HELM_CACHE_FOLDER
value: /helm-cache
{{- if .Values.console.ui }}
Expand Down
5 changes: 5 additions & 0 deletions deploy/kubernetes/console/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"enum": ["disabled", "admin_only", "all_users"],
"description": "Enable API keys for admins, all users or nobody"
},
"userEndpointsEnabled": {
"type": "string",
"enum": ["disabled", "admin_only", "enabled"],
"description": "Enable, disable or let only admins view and create user endpoints"
},
"autoRegisterCF": {
"type": ["string", "null"]
},
Expand Down
3 changes: 3 additions & 0 deletions deploy/kubernetes/console/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ console:
# Enable/disable API key-based access to Stratos API
apiKeysEnabled: admin_only

# Enable/disable user endpoints
userEndpointsEnabled: disabled

ui:
# Override the default maximum number of entities that a configured list can fetch. When a list meets this amount additional pages are not fetched
listMaxSize:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ describe('CurrentUserPermissionsService with CF checker', () => {
CfScopeStrings.CF_READ_SCOPE,
]
},
creator: {
name: 'admin',
admin: true,
system: false
},
metricsAvailable: false,
connectionStatus: 'connected',
system_shared_token: false,
Expand Down Expand Up @@ -102,6 +107,11 @@ describe('CurrentUserPermissionsService with CF checker', () => {
StratosScopeStrings.SCIM_READ
]
},
creator: {
name: 'admin',
admin: true,
system: false
},
metricsAvailable: false,
connectionStatus: 'connected',
system_shared_token: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createBasicStoreModule } from '@stratosui/store/testing';

import { PaginationMonitorFactory } from '../../../store/src/monitors/pagination-monitor.factory';
import { CoreTestingModule } from '../../test-framework/core-test.modules';
import { SessionService } from '../shared/services/session.service';
import { CoreModule } from './core.module';
import { EndpointsService } from './endpoints.service';
import { UtilsService } from './utils.service';
Expand All @@ -13,7 +14,8 @@ describe('EndpointsService', () => {
providers: [
EndpointsService,
UtilsService,
PaginationMonitorFactory
PaginationMonitorFactory,
SessionService
],
imports: [
CoreModule,
Expand Down
12 changes: 8 additions & 4 deletions src/frontend/packages/core/src/core/endpoints.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { endpointEntitiesSelector, endpointStatusSelector } from '../../../store
import { EndpointModel, EndpointState } from '../../../store/src/types/endpoint.types';
import { IEndpointFavMetadata, UserFavorite } from '../../../store/src/types/user-favorites.types';
import { endpointHasMetricsByAvailable } from '../features/endpoints/endpoint-helpers';
import { SessionService } from '../shared/services/session.service';
import { EndpointHealthChecks } from './endpoints-health-checks';
import { UserService } from './user.service';

Expand Down Expand Up @@ -50,7 +51,8 @@ export class EndpointsService implements CanActivate {
constructor(
private store: Store<EndpointOnlyAppState>,
private userService: UserService,
private endpointHealthChecks: EndpointHealthChecks
private endpointHealthChecks: EndpointHealthChecks,
private sessionService: SessionService
) {
this.endpoints$ = store.select(endpointEntitiesSelector);
this.haveRegistered$ = this.endpoints$.pipe(map(endpoints => !!Object.keys(endpoints).length));
Expand Down Expand Up @@ -99,17 +101,19 @@ export class EndpointsService implements CanActivate {
this.haveRegistered$,
this.haveConnected$,
this.userService.isAdmin$,
this.userService.isEndpointAdmin$,
this.sessionService.userEndpointsEnabled(),
this.disablePersistenceFeatures$
),
map(([state, haveRegistered, haveConnected, isAdmin, disablePersistenceFeatures]
: [[AuthState, EndpointState], boolean, boolean, boolean, boolean]) => {
map(([state, haveRegistered, haveConnected, isAdmin, isEndpointAdmin, userEndpointsEnabled, disablePersistenceFeatures]
: [[AuthState, EndpointState], boolean, boolean, boolean, boolean, boolean, boolean]) => {
const [authState] = state;
if (authState.sessionData.valid) {
// Redirect to endpoints if there's no connected endpoints
let redirect: string;
if (!disablePersistenceFeatures) {
if (!haveRegistered) {
redirect = isAdmin ? '/endpoints' : '/noendpoints';
redirect = isAdmin || (userEndpointsEnabled && isEndpointAdmin) ? '/endpoints' : '/noendpoints';
} else if (!haveConnected) {
redirect = '/endpoints';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { UserFavoriteManager } from '../../../../store/src/user-favorite-manager
import { BaseTestModulesNoShared } from '../../../test-framework/core-test.helper';
import { ConfirmationDialogService } from '../../shared/components/confirmation-dialog.service';
import { DialogConfirmComponent } from '../../shared/components/dialog-confirm/dialog-confirm.component';
import { SessionService } from '../../shared/services/session.service';
import { EntityFavoriteStarComponent } from './entity-favorite-star.component';

describe('EntityFavoriteStarComponent', () => {
Expand All @@ -28,7 +29,8 @@ describe('EntityFavoriteStarComponent', () => {
overlayContainerElement = document.createElement('div');
return { getContainerElement: () => overlayContainerElement };
}
}
},
SessionService
],
declarations: [
DialogConfirmComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ describe('CurrentUserPermissionsService', () => {
StratosScopeStrings.STRATOS_CHANGE_PASSWORD,
]
},
creator: {
name: 'admin',
admin: true,
system: false
},
metricsAvailable: false,
connectionStatus: 'connected',
system_shared_token: false,
Expand Down Expand Up @@ -83,6 +88,11 @@ describe('CurrentUserPermissionsService', () => {
StratosScopeStrings.SCIM_READ
]
},
creator: {
name: 'admin',
admin: true,
system: false
},
metricsAvailable: false,
connectionStatus: 'connected',
system_shared_token: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {


export enum StratosCurrentUserPermissions {
ENDPOINT_REGISTER = 'register.endpoint',
EDIT_ENDPOINT = 'edit-endpoint',
EDIT_ADMIN_ENDPOINT = 'edit-admin-endpoint',
PASSWORD_CHANGE = 'change-password',
EDIT_PROFILE = 'edit-profile',
/**
Expand All @@ -35,12 +36,12 @@ export enum StratosPermissionStrings {
STRATOS_ADMIN = 'isAdmin'
}


export enum StratosScopeStrings {
STRATOS_CHANGE_PASSWORD = 'password.write',
SCIM_READ = 'scim.read',
SCIM_WRITE = 'scim.write',
STRATOS_NOAUTH = 'stratos.noauth'
STRATOS_NOAUTH = 'stratos.noauth',
STRATOS_ENDPOINTADMIN = 'stratos.endpointadmin'
}

export enum StratosPermissionTypes {
Expand All @@ -53,7 +54,11 @@ export enum StratosPermissionTypes {
// Every group result must be true in order for the permission to be true. A group result is true if all or some of it's permissions are
// true (see `getCheckFromConfig`).
export const stratosPermissionConfigs: IPermissionConfigs = {
[StratosCurrentUserPermissions.ENDPOINT_REGISTER]: new PermissionConfig(
[StratosCurrentUserPermissions.EDIT_ENDPOINT]: new PermissionConfig(
StratosPermissionTypes.STRATOS_SCOPE,
StratosScopeStrings.STRATOS_ENDPOINTADMIN
),
[StratosCurrentUserPermissions.EDIT_ADMIN_ENDPOINT]: new PermissionConfig(
StratosPermissionTypes.STRATOS,
StratosPermissionStrings.STRATOS_ADMIN
),
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/packages/core/src/core/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ import { AuthOnlyAppState } from '../../../store/src/app-state';
export class UserService {

isAdmin$: Observable<boolean>;
isEndpointAdmin$: Observable<boolean>;

constructor(store: Store<AuthOnlyAppState>) {
this.isAdmin$ = store.select(s => s.auth).pipe(
map((auth: AuthState) => auth.sessionData && auth.sessionData.user && auth.sessionData.user.admin));

this.isEndpointAdmin$ = store.select(s => s.auth).pipe(
map((auth: AuthState) => {
return (auth.sessionData
&& auth.sessionData.user
&& auth.sessionData.user.scopes.find(e => e === 'stratos.endpointadmin') !== undefined);
}));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@
<h1 class="create-endpoint__section-title">{{endpoint.definition.label}} Information</h1>
<mat-form-field>
<input matInput id="name" name="name" formControlName="nameField" placeholder="Name"
[appUnique]="(existingEndpoints | async)?.names">
[appUnique]="registerForm.value.createSystemEndpointField ? (customEndpoints | async)?.names : (existingPersonalEndpoints | async)?.names">
<mat-error *ngIf="registerForm.controls.nameField.errors?.required">Name is required</mat-error>
<mat-error *ngIf="registerForm.controls.nameField.errors?.appUnique">Name is not unique</mat-error>
</mat-form-field>
<mat-form-field novalidate>
<input matInput id="url" name="url" formControlName="urlField" type="url" required placeholder="Endpoint Address"
pattern="{{urlValidation}}" [appUnique]="(existingEndpoints | async)?.urls">
pattern="{{urlValidation}}"
[appUnique]="registerForm.value.createSystemEndpointField ? (customEndpoints | async)?.urls : (existingPersonalEndpoints | async)?.urls">
<mat-error *ngIf="registerForm.controls.urlField.errors?.required">URL is required</mat-error>
<mat-error *ngIf="registerForm.controls.urlField.errors?.pattern">Invalid API URL</mat-error>
<mat-error *ngIf="registerForm.controls.urlField.errors?.appUnique">URL is not unique</mat-error>
</mat-form-field>
<mat-checkbox matInput *ngIf="userEndpointsAndIsAdmin | async" name="createSystemEndpoint"
formControlName="createSystemEndpointField" (change)="toggleCreateSystemEndpoint()"
[ngClass]="{'hide': fixedUrl, 'show': !fixedUrl}">Create a system endpoint (visible to all users)
</mat-checkbox>
<mat-checkbox matInput name="skipSll" formControlName="skipSllField"
[ngClass]="{'hide': fixedUrl, 'show': !fixedUrl}">Skip SSL validation for the
endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CoreTestingModule } from '../../../../../test-framework/core-test.modul
import { CoreModule } from '../../../../core/core.module';
import { SharedModule } from '../../../../shared/shared.module';
import { CreateEndpointCfStep1Component } from './create-endpoint-cf-step-1.component';
import { CurrentUserPermissionsService } from '../../../../core/permissions/current-user-permissions.service';

describe('CreateEndpointCfStep1Component', () => {
let component: CreateEndpointCfStep1Component;
Expand All @@ -29,8 +30,10 @@ describe('CreateEndpointCfStep1Component', () => {
queryParams: {},
params: { type: 'metrics' }
}
}
}]
},
},
CurrentUserPermissionsService
]
})
.compileComponents();
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { AfterContentInit, Component, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { filter, map, pairwise } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, pairwise } from 'rxjs/operators';

import { getFullEndpointApiUrl } from '../../../../../../store/src/endpoint-utils';
import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog';
import {
StratosCatalogEndpointEntity,
Expand All @@ -13,16 +12,20 @@ import { ActionState } from '../../../../../../store/src/reducers/api-request-re
import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog';
import { getIdFromRoute } from '../../../../core/utils.service';
import { IStepperStep, StepOnNextFunction } from '../../../../shared/components/stepper/step/step.component';
import { SessionService } from '../../../../shared/services/session.service';
import { CurrentUserPermissionsService } from '../../../../core/permissions/current-user-permissions.service';
import { UserProfileService } from '../../../../core/user-profile.service';
import { SnackBarService } from '../../../../shared/services/snackbar.service';
import { ConnectEndpointConfig } from '../../connect.service';
import { getSSOClientRedirectURI } from '../../endpoint-helpers';
import { CreateEndpointHelperComponent } from '../create-endpoint-helper';

@Component({
selector: 'app-create-endpoint-cf-step-1',
templateUrl: './create-endpoint-cf-step-1.component.html',
styleUrls: ['./create-endpoint-cf-step-1.component.scss']
})
export class CreateEndpointCfStep1Component implements IStepperStep, AfterContentInit {
export class CreateEndpointCfStep1Component extends CreateEndpointHelperComponent implements IStepperStep, AfterContentInit {

registerForm: FormGroup;

Expand All @@ -42,11 +45,6 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten
}
}

existingEndpoints: Observable<{
names: string[],
urls: string[],
}>;

validate: Observable<boolean>;

urlValidation: string;
Expand All @@ -63,8 +61,13 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten
constructor(
private fb: FormBuilder,
activatedRoute: ActivatedRoute,
private snackBarService: SnackBarService
private snackBarService: SnackBarService,
sessionService: SessionService,
currentUserPermissionsService: CurrentUserPermissionsService,
userProfileService: UserProfileService
) {
super(sessionService, currentUserPermissionsService, userProfileService);

this.registerForm = this.fb.group({
nameField: ['', [Validators.required]],
urlField: ['', [Validators.required]],
Expand All @@ -73,15 +76,9 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten
// Optional Client ID and Client Secret
clientIDField: ['', []],
clientSecretField: ['', []],
createSystemEndpointField: [true, []],
});

this.existingEndpoints = stratosEntityCatalog.endpoint.store.getAll.getPaginationMonitor().currentPage$.pipe(
map(endpoints => ({
names: endpoints.map(ep => ep.name),
urls: endpoints.map(ep => getFullEndpointApiUrl(ep)),
}))
);

const epType = getIdFromRoute(activatedRoute, 'type');
const epSubType = getIdFromRoute(activatedRoute, 'subtype');
this.endpoint = entityCatalog.getEndpoint(epType, epSubType);
Expand All @@ -102,6 +99,7 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten
this.registerForm.value.clientIDField,
this.registerForm.value.clientSecretField,
this.registerForm.value.ssoAllowedField,
this.registerForm.value.createSystemEndpointField,
).pipe(
pairwise(),
filter(([oldVal, newVal]) => (oldVal.busy && !newVal.busy)),
Expand Down Expand Up @@ -151,4 +149,12 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten
toggleAdvancedOptions() {
this.showAdvancedOptions = !this.showAdvancedOptions;
}

toggleCreateSystemEndpoint() {
// wait a tick for validators to adjust to new data in the directive
setTimeout(() => {
this.registerForm.controls.nameField.updateValueAndValidity();
this.registerForm.controls.urlField.updateValueAndValidity();
});
}
}
Loading

0 comments on commit f34f428

Please sign in to comment.