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

Commit

Permalink
Merge pull request #2916 from cloudfoundry-incubator/token-linking
Browse files Browse the repository at this point in the history
SSO: Link tokens rather than copying them
  • Loading branch information
richard-cox committed Sep 10, 2018
2 parents 142c7c4 + 06d0143 commit f18bc8f
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 81 deletions.
21 changes: 1 addition & 20 deletions src/jetstream/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ func (p *portalProxy) DoLoginToCNSIwithConsoleUAAtoken(c echo.Context, theCNSIre
}

if uaaUrl.String() == p.GetConfig().ConsoleConfig.UAAEndpoint.String() { // CNSI UAA server matches Console UAA server
uaaToken.LinkedGUID = uaaToken.TokenGUID
err = p.setCNSITokenRecord(theCNSIrecord.GUID, u.UserGUID, uaaToken)
return err
} else {
Expand Down Expand Up @@ -688,26 +689,6 @@ func (p *portalProxy) InitEndpointTokenRecord(expiry int64, authTok string, refr
return tokenRecord
}

func (p *portalProxy) removed_saveCNSIToken(cnsiID string, u interfaces.JWTUserTokenInfo, authTok string, refreshTok string, disconnect bool) (interfaces.TokenRecord, error) {
log.Debug("saveCNSIToken")

tokenRecord := interfaces.TokenRecord{
AuthToken: authTok,
RefreshToken: refreshTok,
TokenExpiry: u.TokenExpiry,
Disconnected: disconnect,
AuthType: interfaces.AuthTypeOAuth2,
}

err := p.setCNSITokenRecord(cnsiID, u.UserGUID, tokenRecord)
if err != nil {
log.Errorf("%v", err)
return interfaces.TokenRecord{}, err
}

return tokenRecord, nil
}

func (p *portalProxy) deleteCNSIToken(cnsiID string, userGUID string) error {
log.Debug("deleteCNSIToken")

Expand Down
11 changes: 6 additions & 5 deletions src/jetstream/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

const (
findUAATokenSql = `SELECT auth_token, refresh_token, token_expiry, auth_type, meta_data FROM tokens .*`
findUAATokenSql = `SELECT token_guid, auth_token, refresh_token, token_expiry, auth_type, meta_data FROM tokens .*`
)

func TestLoginToUAA(t *testing.T) {
Expand Down Expand Up @@ -549,9 +549,10 @@ func TestVerifySession(t *testing.T) {
t.Error(errors.New("Unable to mock/stub user in session object."))
}

mockTokenGUID := "mock-token-guid"
encryptedUAAToken, _ := crypto.EncryptToken(pp.Config.EncryptionKeyInBytes, mockUAAToken)
expectedTokensRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "auth_type", "meta_data"}).
AddRow(encryptedUAAToken, encryptedUAAToken, mockTokenExpiry, "oauth", "")
expectedTokensRow := sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "auth_type", "meta_data"}).
AddRow(mockTokenGUID, encryptedUAAToken, encryptedUAAToken, mockTokenExpiry, "oauth", "")

mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockUserGUID).
Expand All @@ -561,8 +562,8 @@ func TestVerifySession(t *testing.T) {
AddRow(mockProxyVersion)
mock.ExpectQuery(getDbVersion).WillReturnRows(expectVersionRow)

rs := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "auth_type", "meta_data"}).
AddRow(encryptedUAAToken, encryptedUAAToken, mockTokenExpiry, "oauth", "")
rs := sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "auth_type", "meta_data"}).
AddRow(mockTokenGUID, encryptedUAAToken, encryptedUAAToken, mockTokenExpiry, "oauth", "")
mock.ExpectQuery(findUAATokenSql).
WillReturnRows(rs)

Expand Down
21 changes: 20 additions & 1 deletion src/jetstream/cnsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ func (p *portalProxy) GetCNSITokenRecord(cnsiGUID string, userGUID string) (inte
}

func (p *portalProxy) GetCNSITokenRecordWithDisconnected(cnsiGUID string, userGUID string) (interfaces.TokenRecord, bool) {
log.Debug("GetCNSITokenRecord")
log.Debug("GetCNSITokenRecordWithDisconnected")
tokenRepo, err := tokens.NewPgsqlTokenRepository(p.DatabaseConnectionPool)
if err != nil {
return interfaces.TokenRecord{}, false
Expand Down Expand Up @@ -380,6 +380,25 @@ func (p *portalProxy) ListEndpointsByUser(userGUID string) ([]*interfaces.Connec
return cnsiList, nil
}

// Uopdate the Access Token, Refresh Token and Token Expiry for a token
func (p *portalProxy) updateTokenAuth(userGUID string, t interfaces.TokenRecord) error {
log.Debug("updateTokenAuth")
tokenRepo, err := tokens.NewPgsqlTokenRepository(p.DatabaseConnectionPool)
if err != nil {
log.Errorf(dbReferenceError, err)
return fmt.Errorf(dbReferenceError, err)
}

err = tokenRepo.UpdateTokenAuth(userGUID, t, p.Config.EncryptionKeyInBytes)
if err != nil {
msg := "Unable to update Token: %v"
log.Errorf(msg, err)
return fmt.Errorf(msg, err)
}

return nil
}

func (p *portalProxy) setCNSITokenRecord(cnsiGUID string, userGUID string, t interfaces.TokenRecord) error {
log.Debug("setCNSITokenRecord")
tokenRepo, err := tokens.NewPgsqlTokenRepository(p.DatabaseConnectionPool)
Expand Down
42 changes: 42 additions & 0 deletions src/jetstream/datastore/20180824092600_LinkedTokens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package datastore

import (
"database/sql"

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

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

addTokenID := "ALTER TABLE tokens ADD token_guid VARCHAR(36) DEFAULT 'default-token'"
_, err := txn.Exec(addTokenID)
if err != nil {
return err
}

addLinkedTokens := "ALTER TABLE tokens ADD linked_token VARCHAR(36)"
_, err = txn.Exec(addLinkedTokens)
if err != nil {
return err
}

// Ensure any existing tokens have an ID

// For UAA tokens, use the user id
ensureUAATokenID := "UPDATE tokens SET token_guid=user_guid WHERE token_guid IS NULL AND token_type='uaa'"
_, err = txn.Exec(ensureUAATokenID)
if err != nil {
return err
}

// For CNSI tokens, use the cnsi guid
ensureCNSITokenID := "UPDATE tokens SET token_guid=cnsi_guid WHERE token_guid IS NULL"
_, err = txn.Exec(ensureCNSITokenID)
if err != nil {
return err
}

return nil
})
}
9 changes: 5 additions & 4 deletions src/jetstream/mock_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const mockCFGUID = "some-cf-guid-1234"
const mockCEGUID = "some-hce-guid-1234"
const mockUserGUID = "asd-gjfg-bob"
const mockAdminGUID = tokens.SystemSharedUserGuid
const mockTokenGUID = "mock-token-guid"

const mockURLString = "http://localhost:9999/some/fake/url/"

Expand Down Expand Up @@ -170,15 +171,15 @@ func expectCFAndCERows() sqlmock.Rows {
}

func expectTokenRow() sqlmock.Rows {
return sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(mockUAAToken, mockUAAToken, mockTokenExpiry, false, "OAuth2", "", mockUserGUID)
return sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid", "linked_token"}).
AddRow(mockTokenGUID, mockUAAToken, mockUAAToken, mockTokenExpiry, false, "OAuth2", "", mockUserGUID, nil)
}

func expectEncryptedTokenRow(mockEncryptionKey []byte) sqlmock.Rows {

encryptedUaaToken, _ := crypto.EncryptToken(mockEncryptionKey, mockUAAToken)
return sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUaaToken, encryptedUaaToken, mockTokenExpiry, false, "OAuth2", "", mockUserGUID)
return sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid", "linked_token"}).
AddRow(mockTokenGUID, encryptedUaaToken, encryptedUaaToken, mockTokenExpiry, false, "OAuth2", "", mockUserGUID, nil)
}

func setupHTTPTest(req *http.Request) (*httptest.ResponseRecorder, *echo.Echo, echo.Context, *portalProxy, *sql.DB, sqlmock.Sqlmock) {
Expand Down
5 changes: 3 additions & 2 deletions src/jetstream/oauth_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ func (p *portalProxy) RefreshOAuthToken(skipSSLValidation bool, cnsiGUID, userGU
u.UserGUID = userGUID

tokenRecord := p.InitEndpointTokenRecord(u.TokenExpiry, uaaRes.AccessToken, uaaRes.RefreshToken, userToken.Disconnected)
err = p.setCNSITokenRecord(cnsiGUID, userGUID, tokenRecord)
tokenRecord.TokenGUID = userToken.TokenGUID
err = p.updateTokenAuth(userGUID, tokenRecord)
if err != nil {
return t, fmt.Errorf("Couldn't save new token: %v", err)
return t, fmt.Errorf("Couldn't update token: %v", err)
}

return tokenRecord, nil
Expand Down
29 changes: 14 additions & 15 deletions src/jetstream/oauth_requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ func TestDoOauthFlowRequestWithValidToken(t *testing.T) {
TokenExpiry: tokenExpiration,
}

mockTokenGUID := "mock-token-guid"

// set up the database expectation for pp.setCNSITokenRecord
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
Expand All @@ -97,8 +99,8 @@ func TestDoOauthFlowRequestWithValidToken(t *testing.T) {
// p.getCNSIRequestRecords(cnsiRequest) ->
// p.getCNSITokenRecord(r.GUID, r.UserGUID) ->
// tokenRepo.FindCNSIToken(cnsiGUID, userGUID)
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedToken, encryptedToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
expectedCNSITokenRow := sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid", "linked_token"}).
AddRow(mockTokenGUID, encryptedToken, encryptedToken, tokenExpiration, false, "OAuth2", "", mockUserGUID, nil)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)
Expand Down Expand Up @@ -204,6 +206,8 @@ func TestDoOauthFlowRequestWithExpiredToken(t *testing.T) {
TokenExpiry: tokenExpiration,
}

mockTokenGUID := "mock-token-guid"

_, _, _, pp, db, mock := setupHTTPTest(req)
defer db.Close()
encryptedUAAToken, _ := crypto.EncryptToken(pp.Config.EncryptionKeyInBytes, mockUAAToken)
Expand All @@ -227,8 +231,8 @@ func TestDoOauthFlowRequestWithExpiredToken(t *testing.T) {
// p.getCNSIRequestRecords(cnsiRequest) ->
// p.getCNSITokenRecord(r.GUID, r.UserGUID) ->
// tokenRepo.FindCNSIToken(cnsiGUID, userGUID)
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
expectedCNSITokenRow := sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid", "linked_token"}).
AddRow(mockTokenGUID, encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID, nil)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)
Expand All @@ -240,19 +244,14 @@ func TestDoOauthFlowRequestWithExpiredToken(t *testing.T) {
WithArgs(mockCNSIGUID).
WillReturnRows(expectedCNSIRecordRow)

expectedCNSITokenRecordRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
expectedCNSITokenRecordRow := sqlmock.NewRows([]string{"token_guid", "auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid", "linked_token"}).
AddRow(mockTokenGUID, encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID, nil)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRecordRow)

mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow("0"))

// Expect the INSERT
mock.ExpectExec(insertIntoTokens).
//WithArgs(mockCNSIGUID, mockUserGUID, "cnsi", encryptedUAAToken, encryptedUAAToken, mockTokenRecord.TokenExpiry).
// A token refresh attempt will be made - which is just an update
mock.ExpectExec(updateTokens).
WillReturnResult(sqlmock.NewResult(1, 1))

//
Expand Down Expand Up @@ -370,8 +369,8 @@ func TestDoOauthFlowRequestWithFailedRefreshMethod(t *testing.T) {
// p.getCNSIRequestRecords(cnsiRequest) ->
// p.getCNSITokenRecord(r.GUID, r.UserGUID) ->
// tokenRepo.FindCNSIToken(cnsiGUID, userGUID)
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid", "linked_token"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID, nil)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)
Expand Down
2 changes: 1 addition & 1 deletion src/jetstream/passthrough.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func (p *portalProxy) ProxyRequest(c echo.Context, uri *url.URL) (map[string]*in

if shouldPassthrough {
if len(cnsiList) > 1 {
err := errors.New("Requested passthrough to multiple CNSIs. Only single CNSI passthroughs are supported.")
err := errors.New("Requested passthrough to multiple CNSIs. Only single CNSI passthroughs are supported")
return nil, echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/jetstream/repository/interfaces/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,17 @@ type EndpointTokenRecord struct {
LoggingEndpoint string
}

//TODO this could be moved back to tokens subpackage, and extensions could import it?
// TokenRecord repsrents and endpoint or uaa token
type TokenRecord struct {
TokenGUID string
AuthToken string
RefreshToken string
TokenExpiry int64
Disconnected bool
AuthType string
Metadata string
SystemShared bool
LinkedGUID string // Indicates the GUID of the token that this token is linked to (if any)
}

type CFInfo struct {
Expand Down
Loading

0 comments on commit f18bc8f

Please sign in to comment.