diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bb18f835..01b28c2603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Change Log +## 2.5.0 + +[Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.4.0...2.5.0) + +This release contains a number of fixes and improvements: + +**Fixes:** + +- Fix connect to IBM Cloud [\#3715](https://github.com/cloudfoundry-incubator/stratos/pull/3715) +- Create route - owned domains not proposed [\#3685](https://github.com/cloudfoundry-incubator/stratos/issues/3685) +- Fixed connect endpoint issues in Firefox [\#3679](https://github.com/cloudfoundry-incubator/stratos/pull/3679) +- Fixed connect endpoint rendering issue in Firefox [\#3678](https://github.com/cloudfoundry-incubator/stratos/pull/3678) +- Fix ExpressionChangedAfterItHasBeenCheckedError exception on home page [\#3667](https://github.com/cloudfoundry-incubator/stratos/pull/3667) +- Fix bosh prometheus metrics [\#3602](https://github.com/cloudfoundry-incubator/stratos/pull/3602) +- Fix stepper height on Chrome pre v72 & Bleeding of error style in stateful-icon [\#3599](https://github.com/cloudfoundry-incubator/stratos/pull/3599) +- Remove sidenav header underline [\#3556](https://github.com/cloudfoundry-incubator/stratos/issues/3556) +- cf/spaces routes: fixed breadcrumbs path for apps [\#3572](https://github.com/cloudfoundry-incubator/stratos/pull/3572) +- No space/org shown in cell app instances page [\#3536](https://github.com/cloudfoundry-incubator/stratos/issues/3536) +- Services Marketplace: cf endpoint on card when multiple cf connected [\#3560](https://github.com/cloudfoundry-incubator/stratos/pull/3560) +- Setup screens are missing header [\#3557](https://github.com/cloudfoundry-incubator/stratos/issues/3557) +- /uaa page is still available in a configured system [\#3056](https://github.com/cloudfoundry-incubator/stratos/issues/3056) +- Endpoint login exposes user credentials via query string [\#3160](https://github.com/cloudfoundry-incubator/stratos/issues/3160) +- Don't send registration info in query string [\#3777](https://github.com/cloudfoundry-incubator/stratos/pull/3777) + +**Improvements:** + +- Add Application Autoscaler UI [\#3455](https://github.com/cloudfoundry-incubator/stratos/pull/3455) +- Quotas: org/space quotas: listing and management [\#3650](https://github.com/cloudfoundry-incubator/stratos/pull/3650) +- Quotas: allow quota to be specified on creation/edit of org/space [\#3593](https://github.com/cloudfoundry-incubator/stratos/pull/3593) +- Refactor setup config [\#3694](https://github.com/cloudfoundry-incubator/stratos/pull/3694) +- Helm Chart: Add imagelist to the helm chart [\#3638](https://github.com/cloudfoundry-incubator/stratos/pull/3638) +- Support separate auth and token endpoints [\#3635](https://github.com/cloudfoundry-incubator/stratos/pull/3635) +- Improve failed connection message for cf & metrics endpoints [\#3600](https://github.com/cloudfoundry-incubator/stratos/pull/3600) +- Handle endpoint types that do no support connect [\#3596](https://github.com/cloudfoundry-incubator/stratos/pull/3596) +- Make disconnected endpoints more obviously different [\#3554](https://github.com/cloudfoundry-incubator/stratos/issues/3554) +- Always build with go modules [\#3589](https://github.com/cloudfoundry-incubator/stratos/pull/3589) +- Don't show cell metrics when they're not available [\#3586](https://github.com/cloudfoundry-incubator/stratos/pull/3586) +- Handle 'no auth' auth type in optional connect step [\#3535](https://github.com/cloudfoundry-incubator/stratos/issues/3535) +- Notifications page: Add a back button [\#3523](https://github.com/cloudfoundry-incubator/stratos/issues/3523) + ## 2.4.0 [Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.3.0...2.4.0) diff --git a/build/tools/changelog.js b/build/tools/changelog.js new file mode 100755 index 0000000000..6783917e52 --- /dev/null +++ b/build/tools/changelog.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +console.log('Collecting Change Log'); + +let since = '>=2019-05-03'; + +const request = require('request-promise-native'); +const repo = 'cloudfoundry-incubator/stratos'; +const fs = require('fs'); + +let url = 'https://api.github.com/search/issues?q=state:closed+repo:' + repo + '+updated:' + since; +url = url + '&per_page=100'; + +const fileName = './log.md'; + +console.log(url); + +let total = -1; +let fetched = 0; +let results = []; + +function fetchPage(url, page) { + const pageUrl = url + '&page=' + page; + return request(pageUrl, { + headers: { + 'User-Agent': 'Changelog' + }, + json: true}).then(data => { + console.log('Fetched page : ' + page); + + if (page === 1) { + total = data.total_count; + console.log('Total results : ' + total); + } + fetched += data.items.length; + results = results.concat(data.items); + + if (fetched < total) { + return fetchPage(url, page + 1); + } + console.log('Got all data'); + }); +} + +fetchPage(url, 1).then(data => { + fs.writeFileSync(fileName, '# Changes\n'); + for(let i = 0; i { + map(([quota, spaceQuota]) => { if (!spaceQuota) { return [ '/cloud-foundry', @@ -170,12 +170,12 @@ export class CloudFoundrySpaceService { this.cfGuid, 'organizations', this.orgGuid, - 'space', + 'spaces', this.spaceGuid, 'space-quota' ]; } - ) + ) ); } diff --git a/src/frontend/packages/core/src/app.routing.ts b/src/frontend/packages/core/src/app.routing.ts index 9c5c67b076..9489ebebf4 100644 --- a/src/frontend/packages/core/src/app.routing.ts +++ b/src/frontend/packages/core/src/app.routing.ts @@ -14,10 +14,15 @@ import { DomainMismatchComponent } from './features/setup/domain-mismatch/domain import { ConsoleUaaWizardComponent } from './features/setup/uaa-wizard/console-uaa-wizard.component'; import { UpgradePageComponent } from './features/setup/upgrade-page/upgrade-page.component'; import { SharedModule } from './shared/shared.module'; +import { NotSetupGuardService } from './core/not-setup-guard.service'; const appRoutes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, - { path: 'uaa', component: ConsoleUaaWizardComponent }, + { + path: 'uaa', + component: ConsoleUaaWizardComponent, + canActivate: [NotSetupGuardService] + }, { path: 'upgrade', component: UpgradePageComponent }, { path: 'domainMismatch', component: DomainMismatchComponent }, { path: 'login', loadChildren: './features/login/login.module#LoginModule' }, diff --git a/src/frontend/packages/core/src/core/core.module.ts b/src/frontend/packages/core/src/core/core.module.ts index 193fef06cd..9af48e2353 100644 --- a/src/frontend/packages/core/src/core/core.module.ts +++ b/src/frontend/packages/core/src/core/core.module.ts @@ -7,6 +7,7 @@ import { MomentModule } from 'ngx-moment'; import { NoContentMessageComponent } from '../shared/components/no-content-message/no-content-message.component'; import { RecentEntitiesComponent } from '../shared/components/recent-entities/recent-entities.component'; import { AuthGuardService } from './auth-guard.service'; +import { NotSetupGuardService } from './not-setup-guard.service'; import { ButtonBlurOnClickDirective } from './button-blur-on-click.directive'; import { BytesToHumanSize, MegaBytesToHumanSize } from './byte-formatters.pipe'; import { ClickStopPropagationDirective } from './click-stop-propagation.directive'; @@ -63,6 +64,7 @@ import { WindowRef } from './window-ref/window-ref.service'; ], providers: [ AuthGuardService, + NotSetupGuardService, PageHeaderService, EventWatcherService, WindowRef, diff --git a/src/frontend/packages/core/src/core/not-setup-guard.service.ts b/src/frontend/packages/core/src/core/not-setup-guard.service.ts new file mode 100644 index 0000000000..815e80a087 --- /dev/null +++ b/src/frontend/packages/core/src/core/not-setup-guard.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { Observable, of as observableOf } from 'rxjs'; +import { map, catchError, tap } from 'rxjs/operators'; + +import { RouterNav } from '../../../store/src/actions/router.actions'; +import { AppState } from '../../../store/src/app-state'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../environments/environment'; + +const { proxyAPIVersion } = environment; + +@Injectable() +export class NotSetupGuardService implements CanActivate { + + constructor( + private http: HttpClient, + private store: Store + ) { } + + canActivate(): Observable { + + const url = `/pp/${proxyAPIVersion}/auth/session/verify`; + return this.http.get(url).pipe( + map(v => { + // If the requests succeeds, then the user has a session, so everything must be setup already + return false; + }), + catchError(err => { + const needsSetup = err.status === 503 && err.headers.has('stratos-setup-required'); + return observableOf(needsSetup); + }), + tap(result => { + // False means already setup, so should not be able to access /uaa endpoint + if (!result) { + this.store.dispatch(new RouterNav({ + path: ['/not-found'] + })); + } + }) + ); + } + +} diff --git a/src/frontend/packages/core/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts b/src/frontend/packages/core/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts index 5a6ef38140..f412a11cfa 100644 --- a/src/frontend/packages/core/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts +++ b/src/frontend/packages/core/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts @@ -40,21 +40,7 @@ export class CreateQuotaStepComponent { submit: StepOnNextFunction = () => { const formValues = this.form.formGroup.value; - const UNLIMITED = -1; - - this.store.dispatch(new CreateQuotaDefinition(this.cfGuid, { - name: formValues.name, - total_services: formValues.totalServices || UNLIMITED, - total_routes: formValues.totalRoutes || UNLIMITED, - memory_limit: formValues.memoryLimit, - instance_memory_limit: formValues.instanceMemoryLimit, - app_task_limit: formValues.appTasksLimit, - total_private_domains: formValues.totalPrivateDomains, - total_service_keys: formValues.totalServiceKeys, - non_basic_services_allowed: formValues.nonBasicServicesAllowed, - total_reserved_route_ports: formValues.totalReservedRoutePorts, - app_instance_limit: formValues.appInstanceLimit - })); + this.store.dispatch(new CreateQuotaDefinition(this.cfGuid, formValues)); return this.store.select( selectRequestInfo( diff --git a/src/frontend/packages/core/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts b/src/frontend/packages/core/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts index 64cea1a671..cbefa8136a 100644 --- a/src/frontend/packages/core/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts +++ b/src/frontend/packages/core/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts @@ -41,21 +41,7 @@ export class CreateSpaceQuotaStepComponent { submit: StepOnNextFunction = () => { const formValues = this.form.formGroup.value; - const UNLIMITED = -1; - - this.store.dispatch(new CreateSpaceQuotaDefinition(this.cfGuid, { - name: formValues.name, - organization_guid: this.orgGuid, - total_services: formValues.totalServices || UNLIMITED, - total_service_keys: formValues.totalServiceKeys, - total_routes: formValues.totalRoutes || UNLIMITED, - memory_limit: formValues.memoryLimit, - instance_memory_limit: formValues.instanceMemoryLimit, - non_basic_services_allowed: formValues.nonBasicServicesAllowed, - total_reserved_route_ports: formValues.totalReservedRoutePorts, - app_instance_limit: formValues.appInstanceLimit, - app_task_limit: formValues.appTasksLimit, - })); + this.store.dispatch(new CreateSpaceQuotaDefinition(this.cfGuid, this.orgGuid, formValues)); return this.store.select(selectRequestInfo(spaceQuotaEntityType, formValues.name)).pipe( filter(requestInfo => !!requestInfo && !requestInfo.creating), diff --git a/src/frontend/packages/core/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts b/src/frontend/packages/core/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts index f11e56a4a1..973d968511 100644 --- a/src/frontend/packages/core/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts +++ b/src/frontend/packages/core/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts @@ -11,6 +11,8 @@ import { IQuotaDefinition } from '../../../../core/cf-api.types'; import { EntityServiceFactory } from '../../../../core/entity-service-factory.service'; import { safeUnsubscribe } from '../../../../core/utils.service'; import { StepOnNextFunction } from '../../../../shared/components/stepper/step/step.component'; +import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { QuotaDefinitionFormComponent } from '../../quota-definition-form/quota-definition-form.component'; import { quotaDefinitionEntityType } from '../../../../../../cloud-foundry/src/cf-entity-factory'; import { GetQuotaDefinition, UpdateQuotaDefinition } from '../../../../../../cloud-foundry/src/actions/quota-definitions.actions'; @@ -21,7 +23,10 @@ import { CF_ENDPOINT_TYPE } from '../../../../../../cloud-foundry/cf-types'; @Component({ selector: 'app-edit-quota-step', templateUrl: './edit-quota-step.component.html', - styleUrls: ['./edit-quota-step.component.scss'] + styleUrls: ['./edit-quota-step.component.scss'], + providers: [ + getActiveRouteCfOrgSpaceProvider + ] }) export class EditQuotaStepComponent implements OnDestroy { @@ -37,9 +42,10 @@ export class EditQuotaStepComponent implements OnDestroy { constructor( private store: Store, private activatedRoute: ActivatedRoute, - private entityServiceFactory: EntityServiceFactory + private entityServiceFactory: EntityServiceFactory, + activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, ) { - this.cfGuid = this.activatedRoute.snapshot.params.endpointId; + this.cfGuid = activeRouteCfOrgSpace.cfGuid; this.quotaGuid = this.activatedRoute.snapshot.params.quotaId; this.fetchQuotaDefinition(); @@ -62,20 +68,7 @@ export class EditQuotaStepComponent implements OnDestroy { submit: StepOnNextFunction = () => { const formValues = this.form.formGroup.value; - - this.store.dispatch(new UpdateQuotaDefinition(this.quotaGuid, this.cfGuid, { - name: formValues.name, - total_services: formValues.totalServices, - total_routes: formValues.totalRoutes, - memory_limit: formValues.memoryLimit, - app_task_limit: formValues.appTasksLimit, - total_private_domains: formValues.totalPrivateDomains, - total_service_keys: formValues.totalServiceKeys, - instance_memory_limit: formValues.instanceMemoryLimit, - non_basic_services_allowed: formValues.nonBasicServicesAllowed, - total_reserved_route_ports: formValues.totalReservedRoutePorts, - app_instance_limit: formValues.appInstanceLimit - })); + this.store.dispatch(new UpdateQuotaDefinition(this.quotaGuid, this.cfGuid, formValues)); return this.store.select( selectRequestInfo( diff --git a/src/frontend/packages/core/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts b/src/frontend/packages/core/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts index 32bd09a523..0cce0743eb 100644 --- a/src/frontend/packages/core/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts +++ b/src/frontend/packages/core/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts @@ -60,19 +60,7 @@ export class EditSpaceQuotaStepComponent implements OnDestroy { submit: StepOnNextFunction = () => { const formValues = this.form.formGroup.value; - - this.store.dispatch(new UpdateSpaceQuotaDefinition(this.spaceQuotaGuid, this.cfGuid, { - name: formValues.name, - total_services: formValues.totalServices, - total_service_keys: formValues.totalServiceKeys, - total_routes: formValues.totalRoutes, - memory_limit: formValues.memoryLimit, - instance_memory_limit: formValues.instanceMemoryLimit, - non_basic_services_allowed: formValues.nonBasicServicesAllowed, - total_reserved_route_ports: formValues.totalReservedRoutePorts, - app_instance_limit: formValues.appInstanceLimit, - app_task_limit: formValues.appTasksLimit, - })); + this.store.dispatch(new UpdateSpaceQuotaDefinition(this.spaceQuotaGuid, this.cfGuid, formValues)); return this.store.select(selectRequestInfo(spaceQuotaEntityType, this.spaceQuotaGuid)).pipe( filter(o => !!o && !o.updating[UpdateSpaceQuotaDefinition.UpdateExistingSpaceQuota].busy), diff --git a/src/frontend/packages/core/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts b/src/frontend/packages/core/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts index dcaa37dda0..facc233807 100644 --- a/src/frontend/packages/core/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts +++ b/src/frontend/packages/core/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts @@ -15,6 +15,19 @@ import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-facto import { GetQuotaDefinitions } from '../../../../../cloud-foundry/src/actions/quota-definitions.actions'; import { quotaDefinitionEntityType, cfEntityFactory } from '../../../../../cloud-foundry/src/cf-entity-factory'; +export interface QuotaFormValues { + name: string; + totalServices: number; + totalRoutes: number; + memoryLimit: number; + appTasksLimit: number; + totalPrivateDomains: number; + totalServiceKeys: number; + instanceMemoryLimit: number; + nonBasicServicesAllowed: boolean; + totalReservedRoutePorts: number; + appInstanceLimit: number; +} @Component({ selector: 'app-quota-definition-form', diff --git a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts index c092b55cf1..698b6b2800 100644 --- a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts +++ b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts @@ -6,7 +6,7 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { delay, filter, map, skipWhile, take } from 'rxjs/operators'; import { VerifySession } from '../../../../../store/src/actions/auth.actions'; -import { SetUAAScope, SetupUAA } from '../../../../../store/src/actions/setup.actions'; +import { SetupUAA } from '../../../../../store/src/actions/setup.actions'; import { InternalAppState } from '../../../../../store/src/app-state'; import { AuthState } from '../../../../../store/src/reducers/auth.reducer'; import { UAASetupState } from '../../../../../store/src/types/uaa-setup.types'; @@ -43,6 +43,7 @@ export class ConsoleUaaWizardComponent implements OnInit { username: this.uaaForm.get('adminUsername').value, console_client_secret: this.uaaForm.get('clientSecret').value, use_sso: this.uaaForm.get('useSSO').value, + console_admin_scope: '' })); return this.store.select('uaaSetup').pipe( skipWhile((state: UAASetupState) => { @@ -66,7 +67,17 @@ export class ConsoleUaaWizardComponent implements OnInit { } uaaScopeNext: StepOnNextFunction = () => { - this.store.dispatch(new SetUAAScope(this.selectedScope)); + this.store.dispatch(new SetupUAASave({ + uaa_endpoint: this.uaaForm.get('apiUrl').value, + console_client: this.uaaForm.get('clientId').value, + password: this.uaaForm.get('adminPassword').value, + skip_ssl_validation: this.uaaForm.get('skipSll').value, + username: this.uaaForm.get('adminUsername').value, + console_client_secret: this.uaaForm.get('clientSecret').value, + use_sso: this.uaaForm.get('useSSO').value, + console_admin_scope: this.selectedScope + })); + this.applyingSetup$.next(true); return this.store.select(s => [s.uaaSetup, s.auth]).pipe( filter(([uaa, auth]: [UAASetupState, AuthState]) => { diff --git a/src/frontend/packages/store/src/actions/setup.actions.ts b/src/frontend/packages/store/src/actions/setup.actions.ts index daefba4730..2bd72603a3 100644 --- a/src/frontend/packages/store/src/actions/setup.actions.ts +++ b/src/frontend/packages/store/src/actions/setup.actions.ts @@ -2,7 +2,7 @@ import { Action } from '@ngrx/store'; import { UaaSetupData } from '../types/uaa-setup.types'; export const SETUP_UAA = '[Setup] Setup UAA'; -export const SETUP_UAA_SCOPE = '[Setup] Setup UAA scope'; +export const SETUP_UAA_SAVE = '[Setup] Setup UAA save'; export const SETUP_UAA_SUCCESS = '[Setup] Setup UAA success'; export const SETUP_UAA_FAILED = '[Setup] Setup UAA failed'; @@ -13,14 +13,10 @@ export class SetupUAA implements Action { type = SETUP_UAA; } -export class SetUAAScope implements Action { - constructor( - public scope: string - ) { } - type = SETUP_UAA_SCOPE; +export class SetupUAASave extends SetupUAA { + type = SETUP_UAA_SAVE; } - export class SetupUAASuccess implements Action { constructor(public payload: {}) { } type = SETUP_UAA_SUCCESS; diff --git a/src/frontend/packages/store/src/effects/endpoint.effects.ts b/src/frontend/packages/store/src/effects/endpoint.effects.ts index e1acaa64a9..c506969157 100644 --- a/src/frontend/packages/store/src/effects/endpoint.effects.ts +++ b/src/frontend/packages/store/src/effects/endpoint.effects.ts @@ -115,14 +115,34 @@ export class EndpointsEffect { } const apiAction = this.getEndpointUpdateAction(action.guid, action.type, EndpointsEffect.connectingKey); - const params: HttpParams = new HttpParams({ - fromObject: { - ...action.authValues as any, + + let fromObject: any; + let body = action.body as any; + + if (action.body) { + fromObject = { + ...action.authValues, cnsi_guid: action.guid, connect_type: action.authType, - system_shared: action.systemShared, - }, - // Fix for #angular/18261 + system_shared: action.systemShared + }; + } else { + // If no body, then we will put the auth values in the body, not in the URL + fromObject = { + cnsi_guid: action.guid, + connect_type: action.authType, + system_shared: action.systemShared + }; + + // Encode auth values in the body + body = new FormData(); + Object.keys(action.authValues).forEach(key => { + body.set(key, action.authValues[key]); + }); + } + + const params: HttpParams = new HttpParams({ + fromObject, encoder: new BrowserStandardEncoder() }); @@ -133,7 +153,7 @@ export class EndpointsEffect { null, [CONNECT_ENDPOINTS_SUCCESS, CONNECT_ENDPOINTS_FAILED], action.endpointType, - action.body, + body, response => response && response.error && response.error.error ? response.error.error : 'Could not connect, please try again' ); })); @@ -198,18 +218,20 @@ export class EndpointsEffect { /* tslint:disable-next-line:no-string-literal */ paramsObj['sub_type'] = action.endpointSubType; } - const params: HttpParams = new HttpParams({ - fromObject: paramsObj + // Encode auth values in the body, not the query string + const body: any = new FormData(); + Object.keys(paramsObj).forEach(key => { + body.set(key, paramsObj[key]); }); return this.doEndpointAction( apiAction, '/pp/v1/register/' + action.endpointType, - params, + new HttpParams({}), 'create', [REGISTER_ENDPOINTS_SUCCESS, REGISTER_ENDPOINTS_FAILED], action.endpointType, - null, + body, this.processRegisterError ); })); diff --git a/src/frontend/packages/store/src/effects/uaa-setup.effects.ts b/src/frontend/packages/store/src/effects/uaa-setup.effects.ts index 9a2eec4763..df42eaf1ca 100644 --- a/src/frontend/packages/store/src/effects/uaa-setup.effects.ts +++ b/src/frontend/packages/store/src/effects/uaa-setup.effects.ts @@ -4,12 +4,12 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { catchError, map, switchMap } from 'rxjs/operators'; import { - SetUAAScope, SETUP_UAA, - SETUP_UAA_SCOPE, SetupUAA, SetupUAAFailed, SetupUAASuccess, + SETUP_UAA_SAVE, + SetupUAASave, } from './../actions/setup.actions'; @@ -42,7 +42,7 @@ export class UAASetupEffect { } headers.append('Content-Type', 'application/x-www-form-urlencoded'); - return this.http.post(this.baseUrl, params, { + return this.http.post(`${this.baseUrl}/check`, params, { headers }).pipe( map(data => new SetupUAASuccess(data.json())), @@ -52,14 +52,21 @@ export class UAASetupEffect { @Effect() uassSetScope = this.actions$.pipe( - ofType(SETUP_UAA_SCOPE), - switchMap(({ scope }) => { + ofType(SETUP_UAA_SAVE), + switchMap(({ setupData }) => { const headers = new Headers(); const params = new URLSearchParams(); - params.set('console_admin_scope', scope); + params.set('console_client', setupData.console_client); + params.set('username', setupData.username); + params.set('password', setupData.password); + params.set('skip_ssl_validation', setupData.skip_ssl_validation.toString() || 'false'); + params.set('uaa_endpoint', setupData.uaa_endpoint); + params.set('use_sso', setupData.use_sso.toString() || 'false'); + params.set('console_admin_scope', setupData.console_admin_scope); + headers.append('Content-Type', 'application/x-www-form-urlencoded'); - return this.http.post(`${this.baseUrl}/update`, params, { + return this.http.post(this.baseUrl, params, { headers }).pipe( map(data => new SetupUAASuccess({})), diff --git a/src/frontend/packages/store/src/reducers/uaa-setup.reducers.ts b/src/frontend/packages/store/src/reducers/uaa-setup.reducers.ts index 5ce80b4bf1..6ca565f7b8 100644 --- a/src/frontend/packages/store/src/reducers/uaa-setup.reducers.ts +++ b/src/frontend/packages/store/src/reducers/uaa-setup.reducers.ts @@ -1,4 +1,4 @@ -import { SETUP_UAA, SETUP_UAA_FAILED, SETUP_UAA_SCOPE, SETUP_UAA_SUCCESS } from './../actions/setup.actions'; +import { SETUP_UAA, SETUP_UAA_FAILED, SETUP_UAA_SAVE, SETUP_UAA_SUCCESS } from './../actions/setup.actions'; import { Action } from '@ngrx/store'; import { UAASetupState } from '../types/uaa-setup.types'; @@ -12,7 +12,7 @@ const defaultState = { export function uaaSetupReducer(state: UAASetupState = defaultState, action) { switch (action.type) { - case SETUP_UAA_SCOPE: + case SETUP_UAA_SAVE: case SETUP_UAA: return { ...state, diff --git a/src/frontend/packages/store/src/types/uaa-setup.types.ts b/src/frontend/packages/store/src/types/uaa-setup.types.ts index d6696b0c38..3f5b756ef4 100644 --- a/src/frontend/packages/store/src/types/uaa-setup.types.ts +++ b/src/frontend/packages/store/src/types/uaa-setup.types.ts @@ -19,4 +19,5 @@ export interface UaaSetupData { username: string; console_client_secret?: string; use_sso: boolean; + console_admin_scope?: string; } diff --git a/src/jetstream/load_plugins.go b/src/jetstream/load_plugins.go index ad00413813..0c8b9fe639 100644 --- a/src/jetstream/load_plugins.go +++ b/src/jetstream/load_plugins.go @@ -6,7 +6,6 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/cfappssh" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/cloudfoundry" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/cloudfoundryhosting" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/demo" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/metrics" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/userfavorites" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/userinfo" @@ -35,7 +34,6 @@ func (pp *portalProxy) loadPlugins() { {"userinvite", userinvite.Init}, {"userfavorites", userfavorites.Init}, {"autoscaler", autoscaler.Init}, - {"demo", demo.Init}, } { plugin, err := p.Init(pp) pp.Plugins[p.Name] = plugin diff --git a/src/jetstream/main.go b/src/jetstream/main.go index 81ae750756..fae03f8be5 100644 --- a/src/jetstream/main.go +++ b/src/jetstream/main.go @@ -774,7 +774,7 @@ func (p *portalProxy) registerRoutes(e *echo.Echo, needSetupMiddleware bool) { if needSetupMiddleware { e.Use(p.SetupMiddleware()) pp.POST("/v1/setup", p.setupConsole) - pp.POST("/v1/setup/update", p.setupConsoleUpdate) + pp.POST("/v1/setup/check", p.setupConsoleCheck) } pp.POST("/v1/auth/login/uaa", p.loginToUAA) diff --git a/src/jetstream/plugins/demo/main.go b/src/jetstream/plugins/demo/main.go deleted file mode 100644 index 5bfa4b273c..0000000000 --- a/src/jetstream/plugins/demo/main.go +++ /dev/null @@ -1,159 +0,0 @@ -package demo - -import ( - "errors" - - "strings" - - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - "github.com/labstack/echo" - log "github.com/sirupsen/logrus" -) - -var hiddenEndpoints []string - -// DemoSpecification is a plugin to support the demo plugin -type DemoSpecification struct { - portalProxy interfaces.PortalProxy -} - -const ( - EndpointType = "demo" - CLIENT_ID_KEY = "DEMO_CLIENT" -) - -// Init creates a new DemoSpecification -func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) { - return &DemoSpecification{portalProxy: portalProxy}, nil -} - -// GetEndpointPlugin gets the endpoint plugin for this plugin -func (m *DemoSpecification) GetEndpointPlugin() (interfaces.EndpointPlugin, error) { - return m, nil -} - -// GetRoutePlugin gets the route plugin for this plugin -func (m *DemoSpecification) GetRoutePlugin() (interfaces.RoutePlugin, error) { - return m, nil -} - -// GetMiddlewarePlugin gets the middleware plugin for this plugin -func (m *DemoSpecification) GetMiddlewarePlugin() (interfaces.MiddlewarePlugin, error) { - return nil, errors.New("Not implemented!") -} - -// AddAdminGroupRoutes adds the admin routes for this plugin to the Echo server -func (m *DemoSpecification) AddAdminGroupRoutes(echoContext *echo.Group) { -} - -// AddSessionGroupRoutes adds the session routes for this plugin to the Echo server -func (m *DemoSpecification) AddSessionGroupRoutes(echoContext *echo.Group) { - echoContext.POST("/demo/endpoints/:action", m.demo) -} - -func (m *DemoSpecification) GetType() string { - return EndpointType -} - -func (m *DemoSpecification) GetClientId() string { - return m.portalProxy.Env().String(CLIENT_ID_KEY, "demo") -} - -func (m *DemoSpecification) Register(echoContext echo.Context) error { - return errors.New("Not implemented!") -} - -func (m *DemoSpecification) Validate(userGUID string, cnsiRecord interfaces.CNSIRecord, tokenRecord interfaces.TokenRecord) error { - return nil -} - -func (m *DemoSpecification) Connect(ec echo.Context, cnsiRecord interfaces.CNSIRecord, userId string) (*interfaces.TokenRecord, bool, error) { - return nil, false, errors.New("Not implemented!") -} - -// Init performs plugin initialization -func (m *DemoSpecification) Init() error { - return errors.New("Manually disabled") -} - -func (m *DemoSpecification) Info(apiEndpoint string, skipSSLValidation bool) (interfaces.CNSIRecord, interface{}, error) { - var newCNSI interfaces.CNSIRecord - return newCNSI, nil, errors.New("Not implemented!") -} - -func (m *DemoSpecification) UpdateMetadata(info *interfaces.Info, userGUID string, echoContext echo.Context) { - // Go through again, annotate this time with which have metrics - for key, values := range info.Endpoints { - shown := make(map[string]*interfaces.EndpointDetail) - for n, endpoint := range values { - if !stringInSlice(endpoint.Name, hiddenEndpoints) { - shown[n] = endpoint - } - } - - // Only return those that are shown - info.Endpoints[key] = shown - } -} - -func stringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -func (m *DemoSpecification) demo(c echo.Context) error { - - action := c.Param("action") - // podId := c.Param("podId") - - log.Infof("Demo: %s", action) - - if action == "show" { - hiddenEndpoints = make([]string, 0) - } else if action == "hide" { - hiddenEndpoints = make([]string, 0) - // Hide all endpoints (apart from one named 'cf') - - endpoints, err := m.portalProxy.ListEndpoints() - if err != nil { - return err - } - - for _, endpoint := range endpoints { - if endpoint.Name != "CF" && endpoint.Name != "cf" { - hiddenEndpoints = append(hiddenEndpoints, endpoint.Name) - } - } - } else { - m.unhide(action) - } - - log.Infof("Hidden Endpoints: %s", strings.Join(hiddenEndpoints, ",")) - return c.JSON(200, hiddenEndpoints) -} - -func (m *DemoSpecification) unhide(epType string) { - endpoints, err := m.portalProxy.ListEndpoints() - if err != nil { - return - } - - for _, endpoint := range endpoints { - if endpoint.CNSIType == epType { - hiddenEndpoints = remove(hiddenEndpoints, endpoint.Name) - } - } -} - -func remove(s []string, r string) []string { - for i, v := range s { - if v == r { - return append(s[:i], s[i+1:]...) - } - } - return s -} diff --git a/src/jetstream/setup_console.go b/src/jetstream/setup_console.go index 64078c1111..9d9783e27c 100644 --- a/src/jetstream/setup_console.go +++ b/src/jetstream/setup_console.go @@ -21,63 +21,63 @@ import ( ) const ( - setupRequestRegex = "^/pp/v1/setup$" - setupUpdateRequestRegex = "^/pp/v1/setup/update$" - versionRequestRegex = "^/pp/v1/version$" - backendRequestRegex = "^/pp/v1/" - systemGroupName = "env" + setupRequestRegex = "^/pp/v1/setup$" + setupCheckRequestRegex = "^/pp/v1/setup/check$" + versionRequestRegex = "^/pp/v1/version$" + backendRequestRegex = "^/pp/v1/" + systemGroupName = "env" ) -func (p *portalProxy) setupConsole(c echo.Context) error { - - consoleRepo, err := console_config.NewPostgresConsoleConfigRepository(p.DatabaseConnectionPool) - if err != nil { - return echo.NewHTTPError(http.StatusForbidden, "Failed to connect to Database!") - } - - // Check if alerady set up - if p.GetConfig().ConsoleConfig.IsSetupComplete() { - return c.NoContent(http.StatusServiceUnavailable) - } - +func parseConsoleConfigFromForm(c echo.Context) (*interfaces.ConsoleConfig, error) { consoleConfig := new(interfaces.ConsoleConfig) url, err := url.Parse(c.FormValue("uaa_endpoint")) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid UAA Endpoint value") + return nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid UAA Endpoint value") } consoleConfig.UAAEndpoint = url // Default auth endpoint to the same value as UAA Endpoint when setup via the UI setup (for now) consoleConfig.AuthorizationEndpoint = url - username := c.FormValue("username") - password := c.FormValue("password") consoleConfig.ConsoleClient = c.FormValue("console_client") consoleConfig.ConsoleClientSecret = c.FormValue("console_client_secret") skipSSLValidation, err := strconv.ParseBool(c.FormValue("skip_ssl_validation")) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid Skip SSL Validation value") + return nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid Skip SSL Validation value") } consoleConfig.SkipSSLValidation = skipSSLValidation ssoLogin, err := strconv.ParseBool(c.FormValue("use_sso")) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid Use SSO value") + return nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid Use SSO value") } consoleConfig.UseSSO = ssoLogin + consoleConfig.ConsoleAdminScope = c.FormValue("console_admin_scope") + return consoleConfig, nil +} + +// Check the initial parameter set and fetch the list of available scopes +// This does not persist the configuration to the database at this stage +func (p *portalProxy) setupConsoleCheck(c echo.Context) error { + + // Check if already set up + if p.GetConfig().ConsoleConfig.IsSetupComplete() { + return c.NoContent(http.StatusServiceUnavailable) + } + + consoleConfig, err := parseConsoleConfigFromForm(c) if err != nil { - return interfaces.NewHTTPShadowError( - http.StatusInternalServerError, - "Failed to store Console configuration data", - "Failed to establish DB connection due to %s", err) + return err } + username := c.FormValue("username") + password := c.FormValue("password") + // Authenticate with UAA - authEndpoint := fmt.Sprintf("%s/oauth/token", url) - uaaRes, err := p.getUAATokenWithCreds(skipSSLValidation, username, password, consoleConfig.ConsoleClient, consoleConfig.ConsoleClientSecret, authEndpoint) + authEndpoint := fmt.Sprintf("%s/oauth/token", consoleConfig.UAAEndpoint) + uaaRes, err := p.getUAATokenWithCreds(consoleConfig.SkipSSLValidation, username, password, consoleConfig.ConsoleClient, consoleConfig.ConsoleClientSecret, authEndpoint) if err != nil { - errInfo, ok := err.(interfaces.ErrHTTPRequest) if ok { if errInfo.Status == 0 { @@ -107,15 +107,6 @@ func (p *portalProxy) setupConsole(c echo.Context) error { "Failed to authenticate with UAA due to %s", err) } - // Persist to database - err = saveConsoleConfig(consoleRepo, consoleConfig) - if err != nil { - return interfaces.NewHTTPShadowError( - http.StatusInternalServerError, - "Failed to store Console configuration data", - "Console configuration data storage failed due to %s", err) - } - c.JSON(http.StatusOK, userTokenInfo) return nil } @@ -147,16 +138,15 @@ func saveConsoleConfig(consoleRepo console_config.Repository, consoleConfig *int return err } - return nil -} - -func updateConsoleConfig(consoleRepo console_config.Repository, consoleConfig *interfaces.ConsoleConfig) error { - log.Debugf("Update ConsoleConfig: %+v", consoleConfig) + if err := consoleRepo.SetValue(systemGroupName, "CONSOLE_ADMIN_SCOPE", consoleConfig.ConsoleAdminScope); err != nil { + return err + } - return consoleRepo.SetValue(systemGroupName, "CONSOLE_ADMIN_SCOPE", consoleConfig.ConsoleAdminScope) + return nil } -func (p *portalProxy) setupConsoleUpdate(c echo.Context) error { +// Save the console setup +func (p *portalProxy) setupConsole(c echo.Context) error { consoleRepo, err := console_config.NewPostgresConsoleConfigRepository(p.DatabaseConnectionPool) if err != nil { @@ -168,24 +158,19 @@ func (p *portalProxy) setupConsoleUpdate(c echo.Context) error { return c.NoContent(http.StatusServiceUnavailable) } - consoleConfig := new(interfaces.ConsoleConfig) - consoleConfig.ConsoleAdminScope = c.FormValue("console_admin_scope") - + consoleConfig, err := parseConsoleConfigFromForm(c) if err != nil { - return fmt.Errorf("Unable to initialise console backend config due to: %+v", err) - return interfaces.NewHTTPShadowError( - http.StatusInternalServerError, - "Failed to store Console configuration data", - "Failed to establish DB connection due to %s", err) + return err } - err = updateConsoleConfig(consoleRepo, consoleConfig) + err = saveConsoleConfig(consoleRepo, consoleConfig) if err != nil { return interfaces.NewHTTPShadowError( http.StatusInternalServerError, "Failed to store Console configuration data", "Console configuration data storage failed due to %s", err) } + c.NoContent(http.StatusOK) log.Infof("Updated Stratos setup") return nil @@ -305,7 +290,7 @@ func (p *portalProxy) SetupMiddleware() echo.MiddlewareFunc { isSetupRequest, _ := regexp.MatchString(setupRequestRegex, requestURLPath) if !isSetupRequest { - isSetupRequest, _ = regexp.MatchString(setupUpdateRequestRegex, requestURLPath) + isSetupRequest, _ = regexp.MatchString(setupCheckRequestRegex, requestURLPath) } if isSetupRequest { return h(c) diff --git a/src/test-e2e/login/sso-login.po.ts b/src/test-e2e/login/sso-login.po.ts index ce76c9b911..11edf840b5 100644 --- a/src/test-e2e/login/sso-login.po.ts +++ b/src/test-e2e/login/sso-login.po.ts @@ -24,7 +24,7 @@ export class SSOLoginPage { return welcome.getText().then(text => text.indexOf('Welcome') === 0); } - getTitle() { + getTitle() { return element(by.css('app-root h1')).getText(); } @@ -78,9 +78,17 @@ export class SSOLoginPage { browser.waitForAngularEnabled(false); that.enterLogin(username, password); that.submit(); - browser.waitForAngularEnabled(true); + SSOLoginPage.ssoLastUsername = username; + // UAA might ask us to confirm which scopes we are happy to share + browser.driver.sleep(3000); + element(by.id('authorize')).isPresent().then(exists => { + if (exists) { + element(by.id('authorize')).click(); + } + browser.waitForAngularEnabled(true); + }); } else { browser.waitForAngularEnabled(true); browser.wait(() => {