Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove all tokens associated with a cnsi on unregister, also fix e2e #2821

Merged
merged 23 commits into from
Aug 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aee99ee
Remove all tokens associated with a cnsi on unregister, also fix e2e
richard-cox Aug 10, 2018
9413c98
Remove sleep
richard-cox Aug 13, 2018
0c5416d
Remove stale tokens from Tokens table
richard-cox Aug 13, 2018
a9f6ae0
Make deploy step agnostic of e2e user's other roles
richard-cox Aug 13, 2018
f65fa18
Output backend log if tests fail
nwmac Aug 13, 2018
b76a6a9
Ensure SQL statement works in both SQLite and MariaDB
richard-cox Aug 13, 2018
b08dbf0
Add debugging to help solve app deploy failure
richard-cox Aug 14, 2018
8d6ffb3
Three bug fixes...
richard-cox Aug 14, 2018
e84a791
WIP
richard-cox Aug 14, 2018
b28b9dd
Removed fdescribe for full travis run
richard-cox Aug 14, 2018
6db66d1
Upfront fetch default cf, org and space to avoid timing out when cf g…
richard-cox Aug 15, 2018
e6c13f9
Fix type failure in travis + other tidy ups
richard-cox Aug 15, 2018
5d9c732
Merge branch 'cforgspace-e2e' into e2e-fix-and-clear-token-on-unregister
richard-cox Aug 15, 2018
21f1ecc
Fix skip of create service instance bind app step
richard-cox Aug 15, 2018
76f0099
Potential fix for travis e2e compile error
richard-cox Aug 15, 2018
53fad00
Merge remote-tracking branch 'origin/v2-master' into e2e-fix-and-clea…
richard-cox Aug 15, 2018
bfa1882
Fix incorrect guid when fetching space
richard-cox Aug 15, 2018
d2834be
When fetching space's, don't attempt to fetch space's app's route's apps
richard-cox Aug 15, 2018
aaa8922
Another travis typescript wonderfail
richard-cox Aug 15, 2018
1c6902c
Remove fdescribe, prodding travis
richard-cox Aug 15, 2018
8738fac
Merge remote-tracking branch 'origin/v2-master' into e2e-fix-and-clea…
richard-cox Aug 16, 2018
e8ed11f
Updates following review
richard-cox Aug 16, 2018
2bddc71
Allow a longer time for all before, after, it and space in between
richard-cox Aug 16, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions deploy/ci/travis/run-e2e-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ if [ "${TRAVIS_EVENT_TYPE}" != "pull_request" ]; then
popd
fi

# Output backend log if the tests failed
if [ "${RUN_TYPE}" == "quick" ]; then
if [ $RESULT -ne 0 ]; then
cat outputs/backend.log
fi
fi

# Check environment variable that will ignore E2E failures
if [ -n "${STRATOS_ALLOW_E2E_FAILURES}" ]; then
echo "Ignoring E2E test failures (if any) because STRATOS_ALLOW_E2E_FAILURES is set"
Expand Down
2 changes: 1 addition & 1 deletion protractor.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ try {
}

exports.config = {
allScriptsTimeout: 11000,
allScriptsTimeout: 30000,
specs: [
'./src/test-e2e/**/*-e2e.spec.ts',
],
Expand Down
33 changes: 24 additions & 9 deletions src/backend/app-core/cnsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/labstack/echo"

"crypto/sha1"
"encoding/base64"

"github.com/cloudfoundry-incubator/stratos/repository/cnsis"
"github.com/cloudfoundry-incubator/stratos/repository/interfaces"
"github.com/cloudfoundry-incubator/stratos/repository/tokens"
"crypto/sha1"
"encoding/base64"
)

const dbReferenceError = "Unable to establish a database reference: '%v'"
Expand Down Expand Up @@ -47,7 +48,7 @@ func (p *portalProxy) RegisterEndpoint(c echo.Context, fetchInfo interfaces.Info
skipSSLValidation = false
}

cnsiClientId := c.FormValue("cnsi_client_id")
cnsiClientId := c.FormValue("cnsi_client_id")
cnsiClientSecret := c.FormValue("cnsi_client_secret")

if cnsiClientId == "" {
Expand Down Expand Up @@ -143,12 +144,7 @@ func (p *portalProxy) unregisterCluster(c echo.Context) error {

p.unsetCNSIRecord(cnsiGUID)

userID, err := p.GetSessionStringValue(c, "user_id")
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Could not find correct session value")
}

p.unsetCNSITokenRecord(cnsiGUID, userID)
p.unsetCNSITokenRecords(cnsiGUID)

return nil
}
Expand Down Expand Up @@ -420,3 +416,22 @@ func (p *portalProxy) unsetCNSITokenRecord(cnsiGUID string, userGUID string) err

return nil
}

func (p *portalProxy) unsetCNSITokenRecords(cnsiGUID string) error {
log.Debug("unsetCNSITokenRecord")
tokenRepo, err := tokens.NewPgsqlTokenRepository(p.DatabaseConnectionPool)
if err != nil {
msg := "Unable to establish a database reference: '%v'"
log.Errorf(msg, err)
return fmt.Errorf(msg, err)
}

err = tokenRepo.DeleteCNSITokens(cnsiGUID)
if err != nil {
msg := "Unable to delete a CNSI Token: %v"
log.Errorf(msg, err)
return fmt.Errorf(msg, err)
}

return nil
}
20 changes: 20 additions & 0 deletions src/backend/app-core/datastore/20180813110300_RemoveStaleTokens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package datastore

import (
"database/sql"

"bitbucket.org/liamstask/goose/lib/goose"
)

func init() {
RegisterMigration(20180813110300, "RemoveStaleTokens", func(txn *sql.Tx, conf *goose.DBConf) error {

removeStaleTokens := "DELETE FROM tokens WHERE token_type='cnsi' AND cnsi_guid NOT IN (SELECT guid FROM cnsis);"
_, err := txn.Exec(removeStaleTokens)
if err != nil {
return err
}

return nil
})
}
24 changes: 22 additions & 2 deletions src/backend/app-core/repository/tokens/pgsql_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ var insertCNSIToken = `INSERT INTO tokens (cnsi_guid, user_guid, token_type, aut
var updateCNSIToken = `UPDATE tokens
SET auth_token = $1, refresh_token = $2, token_expiry = $3, disconnected = $4, meta_data = $5
WHERE cnsi_guid = $6 AND user_guid = $7 AND token_type = $8 AND auth_type = $9`

var deleteCNSIToken = `DELETE FROM tokens
WHERE token_type = 'cnsi' AND cnsi_guid = $1 AND user_guid = $2`
WHERE token_type = 'cnsi' AND cnsi_guid = $1 AND user_guid = $2`
var deleteCNSITokens = `DELETE FROM tokens
WHERE token_type = 'cnsi' AND cnsi_guid = $1`

// TODO (wchrisjohnson) We need to adjust several calls ^ to accept a list of items (guids) as input

Expand Down Expand Up @@ -74,6 +75,7 @@ func InitRepositoryProvider(databaseProvider string) {
insertCNSIToken = datastore.ModifySQLStatement(insertCNSIToken, databaseProvider)
updateCNSIToken = datastore.ModifySQLStatement(updateCNSIToken, databaseProvider)
deleteCNSIToken = datastore.ModifySQLStatement(deleteCNSIToken, databaseProvider)
deleteCNSITokens = datastore.ModifySQLStatement(deleteCNSITokens, databaseProvider)
}

// saveAuthToken - Save the Auth token to the datastore
Expand Down Expand Up @@ -387,3 +389,21 @@ func (p *PgsqlTokenRepository) DeleteCNSIToken(cnsiGUID string, userGUID string)

return nil
}

func (p *PgsqlTokenRepository) DeleteCNSITokens(cnsiGUID string) error {
log.Debug("DeleteCNSITokens")
if cnsiGUID == "" {
msg := "Unable to delete CNSI Token without a valid CNSI GUID."
log.Debug(msg)
return errors.New(msg)
}

_, err := p.db.Exec(deleteCNSITokens, cnsiGUID)
if err != nil {
msg := "Unable to Delete CNSI token: %v"
log.Debugf(msg, err)
return fmt.Errorf(msg, err)
}

return nil
}
44 changes: 42 additions & 2 deletions src/backend/app-core/repository/tokens/pgsql_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,9 @@ func TestDeleteCNSIToken(t *testing.T) {
So(err, ShouldNotBeNil)
})

Convey("should through exception when encoutring DB error", func() {
Convey("should throw exception when encountering DB error", func() {
mock.ExpectExec(deleteFromTokensSql).
WillReturnError(errors.New("doesnt exist"))
WillReturnError(errors.New("doesn't exist"))
err := repository.DeleteCNSIToken(mockCNSIToken, mockUserGuid)

So(err, ShouldNotBeNil)
Expand All @@ -419,3 +419,43 @@ func TestDeleteCNSIToken(t *testing.T) {
})

}

func TestDeleteCNSITokens(t *testing.T) {

Convey("DeleteCNSITokens Tests", t, func() {

db, mock, repository := initialiseRepo(t)

Convey("should fail to delete token with an invalid CNSI GUID", func() {
var cnsiGuid string = ""
err := repository.DeleteCNSITokens(cnsiGuid)
So(err, ShouldNotBeNil)
})

Convey("should throw exception when encountering DB error", func() {
mock.ExpectExec(deleteFromTokensSql).
WillReturnError(errors.New("doesn't exist"))
err := repository.DeleteCNSITokens(mockCNSIToken)

So(err, ShouldNotBeNil)
So(mock.ExpectationsWereMet(), ShouldBeNil)

})

Convey("Test successful path", func() {
mock.ExpectExec(deleteFromTokensSql).
WillReturnResult(sqlmock.NewResult(1, 1))
err := repository.DeleteCNSITokens(mockCNSIToken)

So(err, ShouldBeNil)
So(mock.ExpectationsWereMet(), ShouldBeNil)

})

Reset(func() {
db.Close()
})

})

}
1 change: 1 addition & 0 deletions src/backend/app-core/repository/tokens/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ type Repository interface {
FindCNSIToken(cnsiGUID string, userGUID string, encryptionKey []byte) (interfaces.TokenRecord, error)
FindCNSITokenIncludeDisconnected(cnsiGUID string, userGUID string, encryptionKey []byte) (interfaces.TokenRecord, error)
DeleteCNSIToken(cnsiGUID string, userGUID string) error
DeleteCNSITokens(cnsiGUID string) error
SaveCNSIToken(cnsiGUID string, userGUID string, tokenRecord interfaces.TokenRecord, encryptionKey []byte) error
}
19 changes: 15 additions & 4 deletions src/frontend/app/features/applications/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
GetAppSummaryAction,
} from '../../store/actions/app-metadata.actions';
import { GetApplication, UpdateApplication, UpdateExistingApplication } from '../../store/actions/application.actions';
import { GetSpace } from '../../store/actions/space.actions';
import { AppState } from '../../store/app-state';
import {
appEnvVarsSchemaKey,
Expand All @@ -32,6 +33,7 @@ import {
routeSchemaKey,
serviceBindingSchemaKey,
spaceSchemaKey,
spaceWithOrgKey,
stackSchemaKey,
} from '../../store/helpers/entity-factory';
import { createEntityRelationKey } from '../../store/helpers/entity-relations/entity-relations.types';
Expand All @@ -53,8 +55,6 @@ import {
import { getRoute, isTCPRoute } from './routes/routes.helper';




export function createGetApplicationAction(guid: string, endpointGuid: string) {
return new GetApplication(
guid,
Expand Down Expand Up @@ -184,7 +184,17 @@ export class ApplicationService {
map(entityInfo => entityInfo.entity.entity), );
this.appSpace$ = moreWaiting$.pipe(
first(),
switchMap(app => this.store.select(selectEntity(spaceSchemaKey, app.space_guid))), );
switchMap(app => {
return this.entityServiceFactory.create<APIResource<ISpace>>(
spaceSchemaKey,
entityFactory(spaceWithOrgKey),
app.space_guid,
new GetSpace(app.space_guid, app.cfGuid, [createEntityRelationKey(spaceSchemaKey, organizationSchemaKey)], true)
).waitForEntity$.pipe(
map(entityInfo => entityInfo.entity)
);
})
);
this.appOrg$ = moreWaiting$.pipe(
first(),
switchMap(app => this.appSpace$.pipe(
Expand All @@ -193,7 +203,8 @@ export class ApplicationService {
return this.store.select(selectEntity(organizationSchemaKey, orgGuid));
}),
filter(org => !!org)
)), );
))
);

this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount(), );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export class CloudFoundrySpaceService {
createEntityRelationKey(serviceInstancesSchemaKey, serviceBindingSchemaKey),
createEntityRelationKey(serviceBindingSchemaKey, applicationSchemaKey),
createEntityRelationKey(spaceSchemaKey, routeSchemaKey),
createEntityRelationKey(routeSchemaKey, applicationSchemaKey),
];
if (!isAdmin) {
// We're only interested in fetching space roles via the space request for non-admins. This is the only way to guarantee the roles
Expand Down
38 changes: 24 additions & 14 deletions src/test-e2e/application/application-create-e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { browser } from 'protractor';

import { IApp } from '../../frontend/app/core/cf-api.types';
import { APIResource } from '../../frontend/app/store/types/api.types';
import { ApplicationsPage } from '../applications/applications.po';
import { e2e } from '../e2e';
import { ConsoleUserType } from '../helpers/e2e-helpers';
Expand All @@ -6,13 +10,12 @@ import { ApplicationE2eHelper } from './application-e2e-helpers';
import { ApplicationSummary } from './application-summary.po';
import { CreateApplicationStepper } from './create-application-stepper.po';


describe('Application Create', function () {

let nav: SideNavigation;
let appWall: ApplicationsPage;
let applicationE2eHelper: ApplicationE2eHelper;
let cfGuid, app;
let cfGuid, app: APIResource<IApp>;

beforeAll(() => {
nav = new SideNavigation();
Expand All @@ -21,10 +24,14 @@ describe('Application Create', function () {
.clearAllEndpoints()
.registerDefaultCloudFoundry()
.connectAllEndpoints(ConsoleUserType.user)
.connectAllEndpoints(ConsoleUserType.admin);
.connectAllEndpoints(ConsoleUserType.admin)
.getInfo();
applicationE2eHelper = new ApplicationE2eHelper(setup);
});

// Fetch the default cf, org and space up front. This saves time later
beforeAll(() => applicationE2eHelper.cfHelper.updateDefaultCfOrgSpace());

beforeEach(() => nav.goto(SideNavMenuItem.Applications));

it('Should create app', () => {
Expand Down Expand Up @@ -69,19 +76,22 @@ describe('Application Create', function () {
createAppStepper.waitUntilNotShown();

// Determine the app guid and confirm we're on the app summary page
const getCfCnsi = applicationE2eHelper.cfRequestHelper.getCfInfo();
const fetchApp = getCfCnsi.then(endpointModel => {
cfGuid = endpointModel.guid;
return applicationE2eHelper.fetchApp(cfGuid, testAppName);
});
const appFetched = fetchApp.then(response => {
expect(response.total_results).toBe(1);
app = response.resources[0];
const appSummaryPage = new ApplicationSummary(cfGuid, app.metadata.guid, app.entity.name);
browser.wait(applicationE2eHelper.fetchAppInDefaultOrgSpace(testAppName).then((res) => {
expect(res.app).not.toBe(null);
app = res.app;
cfGuid = res.cfGuid;
const appSummaryPage = new ApplicationSummary(res.cfGuid, app.metadata.guid, app.entity.name);
appSummaryPage.waitForPage();
});
}));

});

afterEach(() => applicationE2eHelper.deleteApplication(cfGuid, app));
afterAll(() => {
expect(cfGuid).toBeDefined();
expect(cfGuid).not.toBeNull();
expect(app).toBeDefined();
expect(app).not.toBeNull();
return app ? applicationE2eHelper.deleteApplication({ cfGuid, app }) : null;
});

});
20 changes: 12 additions & 8 deletions src/test-e2e/application/application-delete-e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { ApplicationsPage } from '../applications/applications.po';
import { e2e } from '../e2e';
import { CFHelpers } from '../helpers/cf-helpers';
import { ConsoleUserType } from '../helpers/e2e-helpers';
import { SideNavigation, SideNavMenuItem } from '../po/side-nav.po';
import { ApplicationE2eHelper } from './application-e2e-helpers';
import { ApplicationSummary } from './application-summary.po';
import { CreateApplicationStepper } from './create-application-stepper.po';
import { CFHelpers } from '../helpers/cf-helpers';
import { ExpectedConditions } from 'protractor';


describe('Application Delete', function () {
Expand Down Expand Up @@ -40,12 +38,19 @@ describe('Application Delete', function () {
cfGuid = e2e.helper.getEndpointGuid(e2e.info, endpointName);
const testTime = (new Date()).toISOString();
testAppName = ApplicationE2eHelper.createApplicationName(testTime);
return applicationE2eHelper.createApp(cfGuid, e2e.secrets.getDefaultCFEndpoint().testSpace, testAppName).then(appl => {
app = appl;
});
return applicationE2eHelper.createApp(
cfGuid,
e2e.secrets.getDefaultCFEndpoint().testOrg,
e2e.secrets.getDefaultCFEndpoint().testSpace,
testAppName
).then(appl => app = appl);
});

afterAll(() => applicationE2eHelper.deleteApplication(cfGuid, app));
afterAll(() => {
if (app) {
applicationE2eHelper.deleteApplication({ cfGuid, app });
}
});

it('Should return to summary page after cancel', () => {
const appSummaryPage = new ApplicationSummary(cfGuid, app.metadata.guid, app.entity.name);
Expand Down Expand Up @@ -120,5 +125,4 @@ describe('Application Delete', function () {
});
});


});
Loading