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

Allow metrics endpoint token to be shared #2283

Merged
merged 6 commits into from
Jun 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 57 additions & 3 deletions src/backend/app-core/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,11 @@ func (p *portalProxy) loginToUAA(c echo.Context) error {
}

// Connect to the given Endpoint
// Note, an admin user can connect an endpoint as a system endpoint to share it with others
func (p *portalProxy) loginToCNSI(c echo.Context) error {
log.Debug("loginToCNSI")
cnsiGuid := c.FormValue("cnsi_guid")
var systemSharedToken = false

if len(cnsiGuid) == 0 {
return interfaces.NewHTTPShadowError(
Expand All @@ -161,7 +163,12 @@ func (p *portalProxy) loginToCNSI(c echo.Context) error {
"Need Endpoint GUID passed as form param")
}

resp, err := p.DoLoginToCNSI(c, cnsiGuid)
systemSharedValue := c.FormValue("system_shared")
if len(systemSharedValue) > 0 {
systemSharedToken = systemSharedValue == "true"
}

resp, err := p.DoLoginToCNSI(c, cnsiGuid, systemSharedToken)
if err != nil {
return err
}
Expand All @@ -176,7 +183,7 @@ func (p *portalProxy) loginToCNSI(c echo.Context) error {
return nil
}

func (p *portalProxy) DoLoginToCNSI(c echo.Context, cnsiGUID string) (*interfaces.LoginRes, error) {
func (p *portalProxy) DoLoginToCNSI(c echo.Context, cnsiGUID string, systemSharedToken bool) (*interfaces.LoginRes, error) {

cnsiRecord, err := p.GetCNSIRecord(cnsiGUID)
if err != nil {
Expand All @@ -192,6 +199,23 @@ func (p *portalProxy) DoLoginToCNSI(c echo.Context, cnsiGUID string) (*interface
return nil, echo.NewHTTPError(http.StatusUnauthorized, "Could not find correct session value")
}

// Register as a system endpoint?
if systemSharedToken {
// User needs to be an admin
user, err := p.GetUAAUser(userID)
if err != nil {
return nil, echo.NewHTTPError(http.StatusUnauthorized, "Can not connect System Shared endpoint - could not check user")
}

if !user.Admin {
return nil, echo.NewHTTPError(http.StatusUnauthorized, "Can not connect System Shared endpoint - user is not an administrator")
}

// We are all good to go - change the userID, so we record this token against the system-shared user and not this specific user
// This is how we identify system-shared endpoint tokens
userID = tokens.SystemSharedUserGuid
}

// Ask the endpoint type to connect
for _, plugin := range p.Plugins {
endpointPlugin, err := plugin.GetEndpointPlugin()
Expand Down Expand Up @@ -227,6 +251,9 @@ func (p *portalProxy) DoLoginToCNSI(c echo.Context, cnsiGUID string) (*interface

cnsiUser, ok := p.GetCNSIUserFromToken(cnsiGUID, tokenRecord)
if ok {
// If this is a system shared endpoint, then remove some metadata that should be send back to other users
santizeInfoForSystemSharedTokenUser(cnsiUser, systemSharedToken)

resp.User = cnsiUser
}

Expand All @@ -240,6 +267,14 @@ func (p *portalProxy) DoLoginToCNSI(c echo.Context, cnsiGUID string) (*interface
"Endpoint connection not supported")
}

func santizeInfoForSystemSharedTokenUser(cnsiUser *interfaces.ConnectedUser, isSysystemShared bool) {
if isSysystemShared {
cnsiUser.GUID = tokens.SystemSharedUserGuid
cnsiUser.Scopes = make([]string, 0)
cnsiUser.Name = "system_shared"
}
}

func (p *portalProxy) ConnectOAuth2(c echo.Context, cnsiRecord interfaces.CNSIRecord) (*interfaces.TokenRecord, error) {
uaaRes, u, _, err := p.FetchOAuth2Token(cnsiRecord, c)
if err != nil {
Expand Down Expand Up @@ -316,6 +351,21 @@ func (p *portalProxy) logoutOfCNSI(c echo.Context) error {
return fmt.Errorf("Unable to load CNSI record: %s", err)
}

// Get the existing token to see if it is connected as a system shared endpoint
tr, ok := p.GetCNSITokenRecord(cnsiGUID, userGUID)
if ok && tr.SystemShared {
// User needs to be an admin
user, err := p.GetUAAUser(userGUID)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Can not disconnect System Shared endpoint - could not check user")
}

if !user.Admin {
return echo.NewHTTPError(http.StatusUnauthorized, "Can not disconnect System Shared endpoint - user is not an administrator")
}
userGUID = tokens.SystemSharedUserGuid
}

// If cnsi is cf AND cf is auto-register only clear the entry
if cnsiRecord.CNSIType == "cf" && p.GetConfig().AutoRegisterCFUrl == cnsiRecord.APIEndpoint.String() {
log.Debug("Setting token record as disconnected")
Expand Down Expand Up @@ -716,7 +766,7 @@ func (p *portalProxy) handleSessionExpiryHeader(c echo.Context) error {
return nil
}

func (p *portalProxy) getUAAUser(userGUID string) (*interfaces.ConnectedUser, error) {
func (p *portalProxy) GetUAAUser(userGUID string) (*interfaces.ConnectedUser, error) {
log.Debug("getUAAUser")

// get the uaa token record
Expand Down Expand Up @@ -766,6 +816,10 @@ func (p *portalProxy) GetCNSIUserAndToken(cnsiGUID string, userGUID string) (*in
}

cnsiUser, ok := p.GetCNSIUserFromToken(cnsiGUID, &cfTokenRecord)

// If this is a system shared endpoint, then remove some metadata that should be send back to other users
santizeInfoForSystemSharedTokenUser(cnsiUser, cfTokenRecord.SystemShared)

return cnsiUser, &cfTokenRecord, ok
}

Expand Down
8 changes: 5 additions & 3 deletions src/backend/app-core/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (p *portalProxy) getInfo(c echo.Context) (*interfaces.Info, error) {
return nil, errors.New("Could not find session user_id")
}

uaaUser, err := p.getUAAUser(userGUID)
uaaUser, err := p.GetUAAUser(userGUID)
if err != nil {
return nil, errors.New("Could not load session user data")
}
Expand Down Expand Up @@ -68,14 +68,16 @@ func (p *portalProxy) getInfo(c echo.Context) (*interfaces.Info, error) {
for _, cnsi := range cnsiList {
// Extend the CNSI record
endpoint := &interfaces.EndpointDetail{
CNSIRecord: cnsi,
Metadata: make(map[string]string),
CNSIRecord: cnsi,
Metadata: make(map[string]string),
SystemSharedToken: false,
}
// try to get the user info for this cnsi for the user
cnsiUser, token, ok := p.GetCNSIUserAndToken(cnsi.GUID, userGUID)
if ok {
endpoint.User = cnsiUser
endpoint.TokenMetadata = token.Metadata
endpoint.SystemSharedToken = token.SystemShared
}
cnsiType := cnsi.CNSIType
s.Endpoints[cnsiType][cnsi.GUID] = endpoint
Expand Down
2 changes: 1 addition & 1 deletion src/backend/app-core/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (p *portalProxy) adminMiddleware(h echo.HandlerFunc) echo.HandlerFunc {
if err == nil {

// check their admin status in UAA
u, err := p.getUAAUser(userID.(string))
u, err := p.GetUAAUser(userID.(string))
if err != nil {
return c.NoContent(http.StatusUnauthorized)
}
Expand Down
10 changes: 6 additions & 4 deletions src/backend/app-core/mock_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/SUSE/stratos-ui/repository/crypto"
"github.com/SUSE/stratos-ui/repository/interfaces"
"github.com/SUSE/stratos-ui/repository/tokens"

"github.com/SUSE/stratos-ui/plugins/cloudfoundry"
)
Expand Down Expand Up @@ -63,6 +64,7 @@ const mockCNSIGUID = "some-guid-1234"
const mockCFGUID = "some-cf-guid-1234"
const mockCEGUID = "some-hce-guid-1234"
const mockUserGUID = "asd-gjfg-bob"
const mockAdminGUID = tokens.SystemSharedUserGuid

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

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

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

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"}).
AddRow(encryptedUaaToken, encryptedUaaToken, mockTokenExpiry, false, "OAuth2", "")
return sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUaaToken, encryptedUaaToken, mockTokenExpiry, false, "OAuth2", "", mockUserGUID)
}

func setupHTTPTest(req *http.Request) (*httptest.ResponseRecorder, *echo.Echo, echo.Context, *portalProxy, *sql.DB, sqlmock.Sqlmock) {
Expand Down
30 changes: 15 additions & 15 deletions src/backend/app-core/oauth_requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ 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"}).
AddRow(encryptedToken, encryptedToken, tokenExpiration, false, "OAuth2", "")
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedToken, encryptedToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)

// p.GetCNSIRecord(r.GUID) -> cnsiRepo.Find(guid)
Expand Down Expand Up @@ -227,10 +227,10 @@ 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"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "")
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)

// p.GetCNSIRecord(r.GUID) -> cnsiRepo.Find(guid)
Expand All @@ -240,10 +240,10 @@ func TestDoOauthFlowRequestWithExpiredToken(t *testing.T) {
WithArgs(mockCNSIGUID).
WillReturnRows(expectedCNSIRecordRow)

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

mock.ExpectQuery(selectAnyFromTokens).
Expand Down Expand Up @@ -370,10 +370,10 @@ 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"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "")
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data", "user_guid"}).
AddRow(encryptedUAAToken, encryptedUAAToken, tokenExpiration, false, "OAuth2", "", mockUserGUID)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)

// p.GetCNSIRecord(r.GUID) -> cnsiRepo.Find(guid)
Expand Down Expand Up @@ -617,7 +617,7 @@ func TestRefreshTokenWithDatabaseErrorOnSave(t *testing.T) {
expectedCNSITokenRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data"}).
AddRow(mockUAAToken, mockUAAToken, tokenExpiration, false, "OAuth2", "")
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRow)

// p.GetCNSIRecord(r.GUID) -> cnsiRepo.Find(guid)
Expand All @@ -630,11 +630,11 @@ func TestRefreshTokenWithDatabaseErrorOnSave(t *testing.T) {
expectedCNSITokenRecordRow := sqlmock.NewRows([]string{"auth_token", "refresh_token", "token_expiry", "disconnected", "auth_type", "meta_data"}).
AddRow(mockUAAToken, mockUAAToken, tokenExpiration, false, "OAuth2", "")
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCNSIGUID, mockUserGUID).
WithArgs(mockCNSIGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectedCNSITokenRecordRow)

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

// p.saveCNSIToken(cnsiGUID, *u, uaaRes.AccessToken, uaaRes.RefreshToken)
Expand Down
4 changes: 2 additions & 2 deletions src/backend/app-core/passthrough_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestPassthroughDoRequest(t *testing.T) {
// p.getCNSITokenRecord(r.GUID, r.UserGUID) ->
// tokenRepo.FindCNSIToken(cnsiGUID, userGUID)
mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCFGUID, mockUserGUID).
WithArgs(mockCFGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectEncryptedTokenRow(pp.Config.EncryptionKeyInBytes))

// p.GetCNSIRecord(r.GUID) -> cnsiRepo.Find(guid)
Expand All @@ -81,7 +81,7 @@ func TestPassthroughDoRequest(t *testing.T) {
WillReturnRows(expectCFRow())

mock.ExpectQuery(selectAnyFromTokens).
WithArgs(mockCFGUID, mockUserGUID).
WithArgs(mockCFGUID, mockUserGUID, mockAdminGUID).
WillReturnRows(expectEncryptedTokenRow(pp.Config.EncryptionKeyInBytes))

// p.GetCNSIRecord(r.GUID) -> cnsiRepo.Find(guid)
Expand Down
3 changes: 2 additions & 1 deletion src/backend/app-core/repository/interfaces/portal_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type PortalProxy interface {
SaveConsoleConfig(consoleConfig *ConsoleConfig, consoleRepoInterface interface{}) error

RefreshOAuthToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t TokenRecord, err error)
DoLoginToCNSI(c echo.Context, cnsiGUID string) (*LoginRes, error)
DoLoginToCNSI(c echo.Context, cnsiGUID string, systemSharedToken bool) (*LoginRes, error)
// Expose internal portal proxy records to extensions
GetCNSIRecord(guid string) (CNSIRecord, error)
GetCNSIRecordByEndpoint(endpoint string) (CNSIRecord, error)
Expand All @@ -49,6 +49,7 @@ type PortalProxy interface {
GetUsername(userid string) (string, error)
RefreshUAALogin(username, password string, store bool) error
GetUserTokenInfo(tok string) (u *JWTUserTokenInfo, err error)
GetUAAUser(userGUID string) (*ConnectedUser, error)

// Proxy API requests
ProxyRequest(c echo.Context, uri *url.URL) (map[string]*CNSIRequest, error)
Expand Down
8 changes: 5 additions & 3 deletions src/backend/app-core/repository/interfaces/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type TokenRecord struct {
Disconnected bool
AuthType string
Metadata string
SystemShared bool
}

type CFInfo struct {
Expand Down Expand Up @@ -144,9 +145,10 @@ type Info struct {
// Extends CNSI Record and adds the user
type EndpointDetail struct {
*CNSIRecord
User *ConnectedUser `json:"user"`
Metadata map[string]string `json:"metadata,omitempty"`
TokenMetadata string `json:"-"`
User *ConnectedUser `json:"user"`
Metadata map[string]string `json:"metadata,omitempty"`
TokenMetadata string `json:"-"`
SystemSharedToken bool `json:"system_shared_token"`
}

// Versions - response returned to caller from a getVersions action
Expand Down
14 changes: 8 additions & 6 deletions src/backend/app-core/repository/tokens/pgsql_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ var updateAuthToken = `UPDATE tokens
SET auth_token = $1, refresh_token = $2, token_expiry = $3
WHERE user_guid = $4 AND token_type = $5`

var findCNSIToken = `SELECT auth_token, refresh_token, token_expiry, disconnected, auth_type, meta_data
var findCNSIToken = `SELECT auth_token, refresh_token, token_expiry, disconnected, auth_type, meta_data, user_guid
FROM tokens
WHERE cnsi_guid = $1 AND user_guid = $2 AND token_type = 'cnsi'`
WHERE cnsi_guid = $1 AND (user_guid = $2 OR user_guid = $3) AND token_type = 'cnsi'`

var findCNSITokenConnected = `SELECT auth_token, refresh_token, token_expiry, disconnected, auth_type, meta_data
var findCNSITokenConnected = `SELECT auth_token, refresh_token, token_expiry, disconnected, auth_type, meta_data, user_guid
FROM tokens
WHERE cnsi_guid = $1 AND user_guid = $2 AND token_type = 'cnsi' AND disconnected = '0'`
WHERE cnsi_guid = $1 AND (user_guid = $2 OR user_guid = $3) AND token_type = 'cnsi' AND disconnected = '0'`

var countCNSITokens = `SELECT COUNT(*)
FROM tokens
Expand Down Expand Up @@ -304,13 +304,14 @@ func (p *PgsqlTokenRepository) findCNSIToken(cnsiGUID string, userGUID string, e
disconnected bool
authType string
metadata string
tokenUserGUID string
)

var err error
if includeDisconnected {
err = p.db.QueryRow(findCNSIToken, cnsiGUID, userGUID).Scan(&ciphertextAuthToken, &ciphertextRefreshToken, &tokenExpiry, &disconnected, &authType, &metadata)
err = p.db.QueryRow(findCNSIToken, cnsiGUID, userGUID, SystemSharedUserGuid).Scan(&ciphertextAuthToken, &ciphertextRefreshToken, &tokenExpiry, &disconnected, &authType, &metadata, &tokenUserGUID)
} else {
err = p.db.QueryRow(findCNSITokenConnected, cnsiGUID, userGUID).Scan(&ciphertextAuthToken, &ciphertextRefreshToken, &tokenExpiry, &disconnected, &authType, &metadata)
err = p.db.QueryRow(findCNSITokenConnected, cnsiGUID, userGUID, SystemSharedUserGuid).Scan(&ciphertextAuthToken, &ciphertextRefreshToken, &tokenExpiry, &disconnected, &authType, &metadata, &tokenUserGUID)
}

if err != nil {
Expand Down Expand Up @@ -339,6 +340,7 @@ func (p *PgsqlTokenRepository) findCNSIToken(cnsiGUID string, userGUID string, e
tr.Disconnected = disconnected
tr.AuthType = authType
tr.Metadata = metadata
tr.SystemShared = tokenUserGUID == SystemSharedUserGuid

return *tr, nil
}
Expand Down
Loading