From 204dbb2e3ff7692af3b7ca4362b1ee46fb43c227 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Thu, 26 Feb 2026 16:09:07 +0100 Subject: [PATCH 01/22] fix(connector): update authproxy and oauth to match CallbackConnector interface (#4589) The PKCE support added in v2.45.0 changed the CallbackConnector interface signatures but missed updating the authproxy and oauth connectors. This caused a type assertion failure in handleConnectorLogin(), resulting in "Requested resource does not exist" errors when using these connectors. Update LoginURL to return (string, []byte, error) and HandleCallback to accept a []byte connData parameter for both connectors and their tests. Signed-off-by: Mathias Gebbe --- connector/authproxy/authproxy.go | 8 ++++---- connector/authproxy/authproxy_test.go | 12 ++++++------ connector/oauth/oauth.go | 8 ++++---- connector/oauth/oauth_test.go | 10 +++++----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/connector/authproxy/authproxy.go b/connector/authproxy/authproxy.go index f3d87fcb..3fe451b2 100644 --- a/connector/authproxy/authproxy.go +++ b/connector/authproxy/authproxy.go @@ -83,20 +83,20 @@ type callback struct { } // LoginURL returns the URL to redirect the user to login with. -func (m *callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) { +func (m *callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) { u, err := url.Parse(callbackURL) if err != nil { - return "", fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err) + return "", nil, fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err) } u.Path += m.pathSuffix v := u.Query() v.Set("state", state) u.RawQuery = v.Encode() - return u.String(), nil + return u.String(), nil, nil } // HandleCallback parses the request and returns the user's identity -func (m *callback) HandleCallback(s connector.Scopes, r *http.Request) (connector.Identity, error) { +func (m *callback) HandleCallback(s connector.Scopes, _ []byte, r *http.Request) (connector.Identity, error) { remoteUser := r.Header.Get(m.userHeader) if remoteUser == "" { return connector.Identity{}, fmt.Errorf("required HTTP header %s is not set", m.userHeader) diff --git a/connector/authproxy/authproxy_test.go b/connector/authproxy/authproxy_test.go index fbdd2a5d..bd8b4f36 100644 --- a/connector/authproxy/authproxy_test.go +++ b/connector/authproxy/authproxy_test.go @@ -36,7 +36,7 @@ func TestUser(t *testing.T) { "X-Remote-User": {testUsername}, } - ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, req) + ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) // If not specified, the userID and email should fall back to the remote user @@ -62,7 +62,7 @@ func TestExtraHeaders(t *testing.T) { "X-Remote-User-Email": {testEmail}, } - ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, req) + ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testUserID) @@ -85,7 +85,7 @@ func TestSingleGroup(t *testing.T) { "X-Remote-Group": {testGroup1}, } - ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, req) + ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) @@ -106,7 +106,7 @@ func TestMultipleGroup(t *testing.T) { "X-Remote-Group": {testGroup1 + ", " + testGroup2 + ", " + testGroup3 + ", " + testGroup4}, } - ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, req) + ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) @@ -132,7 +132,7 @@ func TestMultipleGroupWithCustomSeparator(t *testing.T) { "X-Remote-Group": {testGroup1 + ";" + testGroup2 + ";" + testGroup3 + ";" + testGroup4}, } - ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, req) + ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) @@ -158,7 +158,7 @@ func TestStaticGroup(t *testing.T) { "X-Remote-Group": {testGroup1 + ", " + testGroup2 + ", " + testGroup3 + ", " + testGroup4}, } - ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, req) + ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) diff --git a/connector/oauth/oauth.go b/connector/oauth/oauth.go index 413a813a..7661a9f8 100644 --- a/connector/oauth/oauth.go +++ b/connector/oauth/oauth.go @@ -116,9 +116,9 @@ func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, erro return oauthConn, err } -func (c *oauthConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) { +func (c *oauthConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { - return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) + return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } oauth2Config := &oauth2.Config{ @@ -129,10 +129,10 @@ func (c *oauthConnector) LoginURL(scopes connector.Scopes, callbackURL, state st Scopes: c.scopes, } - return oauth2Config.AuthCodeURL(state), nil + return oauth2Config.AuthCodeURL(state), nil, nil } -func (c *oauthConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { +func (c *oauthConnector) HandleCallback(s connector.Scopes, _ []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, errors.New(q.Get("error_description")) diff --git a/connector/oauth/oauth_test.go b/connector/oauth/oauth_test.go index 2f6b0b95..cdd2d3c6 100644 --- a/connector/oauth/oauth_test.go +++ b/connector/oauth/oauth_test.go @@ -50,7 +50,7 @@ func TestLoginURL(t *testing.T) { conn := newConnector(t, testServer.URL) - loginURL, err := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state") + loginURL, _, err := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state") assert.Equal(t, err, nil) expectedURL, err := url.Parse(testServer.URL + "/authorize") @@ -86,7 +86,7 @@ func TestHandleCallBackForGroupsInUserInfo(t *testing.T) { conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupsInUserInfo") - identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req) + identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) sort.Strings(identity.Groups) @@ -122,7 +122,7 @@ func TestHandleCallBackForGroupMapsInUserInfo(t *testing.T) { conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupMapsInUserInfo") - identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req) + identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) sort.Strings(identity.Groups) @@ -156,7 +156,7 @@ func TestHandleCallBackForGroupsInToken(t *testing.T) { conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupsInToken") - identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req) + identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) assert.Equal(t, len(identity.Groups), 1) @@ -186,7 +186,7 @@ func TestHandleCallbackForNumericUserID(t *testing.T) { conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallbackForNumericUserID") - identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req) + identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) assert.Equal(t, identity.UserID, "1000") From eaa45e2ca59df692d2b9d7ffcd0830d8907f5e60 Mon Sep 17 00:00:00 2001 From: Michiel De Backker Date: Tue, 24 Feb 2026 13:56:32 +0100 Subject: [PATCH 02/22] fix(mysql): quote `groups` reserved word in query replacer (#4580) `groups` is a reserved word in MySQL >= 8.0.2, causing migration 13 to fail with a syntax error on `ALTER TABLE password ADD COLUMN groups`. Fixes #4579 Signed-off-by: Michiel De Backker --- storage/sql/sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/sql/sql.go b/storage/sql/sql.go index d671021f..1965f5ff 100644 --- a/storage/sql/sql.go +++ b/storage/sql/sql.go @@ -95,7 +95,7 @@ var ( // For compound indexes (with two keys) even less. {matchLiteral("text"), "varchar(384)"}, // Quote keywords and reserved words used as identifiers. - {regexp.MustCompile(`\b(keys)\b`), "`$1`"}, + {regexp.MustCompile(`\b(keys|groups)\b`), "`$1`"}, // Change default timestamp to fit datetime. {regexp.MustCompile(`0001-01-01 00:00:00 UTC`), "1000-01-01 00:00:00"}, }, From 806e1d1854f81e461a2912100ef994a4853a86ee Mon Sep 17 00:00:00 2001 From: Joshua Winters Date: Tue, 10 Apr 2018 09:47:59 -0400 Subject: [PATCH 03/22] Add new connector for Cloudfoundry - Verifies user is part of orgs and spaces for group claims Signed-off-by: Joshua Winters Co-authored-by: Shash Reddy --- connector/cf/cf.go | 303 ++++++++++++++++++++++++++++++++++++++++ connector/cf/cf_test.go | 191 +++++++++++++++++++++++++ server/server.go | 2 + 3 files changed, 496 insertions(+) create mode 100644 connector/cf/cf.go create mode 100644 connector/cf/cf_test.go diff --git a/connector/cf/cf.go b/connector/cf/cf.go new file mode 100644 index 00000000..2e4d2243 --- /dev/null +++ b/connector/cf/cf.go @@ -0,0 +1,303 @@ +package cf + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "strings" + "time" + + "github.com/dexidp/dex/connector" + "github.com/dexidp/dex/pkg/log" + "golang.org/x/oauth2" +) + +type cfConnector struct { + clientID string + clientSecret string + redirectURI string + apiURL string + tokenURL string + authorizationURL string + userInfoURL string + httpClient *http.Client + logger log.Logger +} + +type connectorData struct { + AccessToken string +} + +type Config struct { + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + RedirectURI string `json:"redirectURI"` + APIURL string `json:"apiURL"` + RootCAs []string `json:"rootCAs"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` +} + +type CCResponse struct { + Resources []Resource `json:"resources"` + TotalResults int `json:"total_results"` +} + +type Resource struct { + Metadata Metadata `json:"metadata"` + Entity Entity `json:"entity"` +} + +type Metadata struct { + Guid string `json:"guid"` +} + +type Entity struct { + Name string `json:"name"` + OrganizationGuid string `json:"organization_guid"` +} + +func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { + var err error + + cfConn := &cfConnector{ + clientID: c.ClientID, + clientSecret: c.ClientSecret, + apiURL: c.APIURL, + redirectURI: c.RedirectURI, + logger: logger, + } + + cfConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify) + if err != nil { + return nil, err + } + + apiURL := strings.TrimRight(c.APIURL, "/") + apiResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/v2/info", apiURL)) + + if err != nil { + logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) + return nil, err + } + + defer apiResp.Body.Close() + + if apiResp.StatusCode != http.StatusOK { + err = errors.New(fmt.Sprintf("request failed with status %d", apiResp.StatusCode)) + logger.Errorf("failed-get-info-response-from-api", err) + return nil, err + } + + var apiResult map[string]interface{} + json.NewDecoder(apiResp.Body).Decode(&apiResult) + + uaaURL := strings.TrimRight(apiResult["token_endpoint"].(string), "/") + uaaResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) + + if err != nil { + logger.Errorf("failed-to-send-request-to-uaa-api", err) + return nil, err + } + + if apiResp.StatusCode != http.StatusOK { + err = errors.New(fmt.Sprintf("request failed with status %d", apiResp.StatusCode)) + logger.Errorf("failed-to-get-well-known-config-repsonse-from-api", err) + return nil, err + } + + defer uaaResp.Body.Close() + + var uaaResult map[string]interface{} + err = json.NewDecoder(uaaResp.Body).Decode(&uaaResult) + + if err != nil { + logger.Errorf("failed-to-decode-response-from-uaa-api", err) + return nil, err + } + + cfConn.tokenURL, _ = uaaResult["token_endpoint"].(string) + cfConn.authorizationURL, _ = uaaResult["authorization_endpoint"].(string) + cfConn.userInfoURL, _ = uaaResult["userinfo_endpoint"].(string) + + return cfConn, err +} + +func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) { + pool, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + + tlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify} + for _, rootCA := range rootCAs { + rootCABytes, err := ioutil.ReadFile(rootCA) + if err != nil { + return nil, fmt.Errorf("failed to read root-ca: %v", err) + } + if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) { + return nil, fmt.Errorf("no certs found in root CA file %q", rootCA) + } + } + + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tlsConfig, + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + }, nil +} + +func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) { + + if c.redirectURI != callbackURL { + return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) + } + + oauth2Config := &oauth2.Config{ + ClientID: c.clientID, + ClientSecret: c.clientSecret, + Endpoint: oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL}, + RedirectURL: c.redirectURI, + Scopes: []string{"openid", "cloud_controller.read"}, + } + + return oauth2Config.AuthCodeURL(state), nil +} + +func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { + + q := r.URL.Query() + if errType := q.Get("error"); errType != "" { + return identity, errors.New(q.Get("error_description")) + } + + oauth2Config := &oauth2.Config{ + ClientID: c.clientID, + ClientSecret: c.clientSecret, + Endpoint: oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL}, + RedirectURL: c.redirectURI, + Scopes: []string{"openid", "cloud_controller.read"}, + } + + ctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) + + token, err := oauth2Config.Exchange(ctx, q.Get("code")) + if err != nil { + return identity, fmt.Errorf("CF connector: failed to get token: %v", err) + } + + client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)) + + userInfoResp, err := client.Get(c.userInfoURL) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to execute request to userinfo: %v", err) + } + + if userInfoResp.StatusCode != http.StatusOK { + return identity, fmt.Errorf("CF Connector: failed to execute request to userinfo: status %d", userInfoResp.StatusCode) + } + + defer userInfoResp.Body.Close() + + var userInfoResult map[string]interface{} + err = json.NewDecoder(userInfoResp.Body).Decode(&userInfoResult) + + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to parse userinfo: %v", err) + } + + identity.UserID, _ = userInfoResult["user_id"].(string) + identity.Username, _ = userInfoResult["user_name"].(string) + identity.PreferredUsername, _ = userInfoResult["user_name"].(string) + identity.Email, _ = userInfoResult["email"].(string) + identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) + + if s.Groups { + // fetch orgs + orgsResp, err := client.Get(fmt.Sprintf("%s/v2/users/%s/organizations", c.apiURL, identity.UserID)) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: %v", err) + } + if orgsResp.StatusCode != http.StatusOK { + return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: status %d", orgsResp.StatusCode) + } + + var orgs CCResponse + + err = json.NewDecoder(orgsResp.Body).Decode(&orgs) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to parse orgs: %v", err) + } + + var orgMap = make(map[string]string) + var orgSpaces = make(map[string][]string) + + for _, resource := range orgs.Resources { + orgMap[resource.Metadata.Guid] = resource.Entity.Name + orgSpaces[resource.Entity.Name] = []string{} + } + + // fetch spaces + spacesResp, err := client.Get(fmt.Sprintf("%s/v2/users/%s/spaces", c.apiURL, identity.UserID)) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: %v", err) + } + if spacesResp.StatusCode != http.StatusOK { + return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: status %d", spacesResp.StatusCode) + } + + var spaces CCResponse + + err = json.NewDecoder(spacesResp.Body).Decode(&spaces) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to parse spaces: %v", err) + } + + var groupsClaims []string + + for _, resource := range spaces.Resources { + orgName := orgMap[resource.Entity.OrganizationGuid] + orgSpaces[orgName] = append(orgSpaces[orgName], resource.Entity.Name) + + groupsClaims = append(groupsClaims, resource.Metadata.Guid) + } + + for orgName, spaceNames := range orgSpaces { + if len(spaceNames) > 0 { + for _, spaceName := range spaceNames { + groupsClaims = append(groupsClaims, fmt.Sprintf("%s:%s", orgName, spaceName)) + } + } else { + groupsClaims = append(groupsClaims, fmt.Sprintf("%s", orgName)) + } + } + + identity.Groups = groupsClaims + } + + if s.OfflineAccess { + data := connectorData{AccessToken: token.AccessToken} + connData, err := json.Marshal(data) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to parse connector data for offline access: %v", err) + } + identity.ConnectorData = connData + } + + return identity, nil +} diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go new file mode 100644 index 00000000..6680da0b --- /dev/null +++ b/connector/cf/cf_test.go @@ -0,0 +1,191 @@ +package cf + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "sort" + "strings" + "testing" + + "github.com/dexidp/dex/connector" + "github.com/sirupsen/logrus" +) + +func TestOpen(t *testing.T) { + testServer := testSetup() + defer testServer.Close() + + conn := newConnector(t, testServer.URL) + + expectEqual(t, conn.clientID, "test-client") + expectEqual(t, conn.clientSecret, "secret") + expectEqual(t, conn.redirectURI, testServer.URL+"/callback") +} + +func TestHandleCallback(t *testing.T) { + + testServer := testSetup() + defer testServer.Close() + + cfConn := &cfConnector{ + tokenURL: fmt.Sprintf("%s/token", testServer.URL), + authorizationURL: fmt.Sprintf("%s/authorize", testServer.URL), + userInfoURL: fmt.Sprintf("%s/userinfo", testServer.URL), + apiURL: testServer.URL, + clientSecret: "secret", + clientID: "test-client", + redirectURI: "localhost:8080/sky/dex/callback", + httpClient: http.DefaultClient, + } + + req, err := http.NewRequest("GET", testServer.URL, nil) + expectEqual(t, err, nil) + + t.Run("CallbackWithGroupsScope", func(t *testing.T) { + identity, err := cfConn.HandleCallback(connector.Scopes{Groups: true}, req) + expectEqual(t, err, nil) + + sort.Strings(identity.Groups) + expectEqual(t, len(identity.Groups), 3) + expectEqual(t, identity.Groups[0], "some-org-name-1:some-space-name") + expectEqual(t, identity.Groups[1], "some-org-name-2") + expectEqual(t, identity.Groups[2], "some-space-guid") + }) + + t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { + identity, err := cfConn.HandleCallback(connector.Scopes{}, req) + + expectEqual(t, err, nil) + expectEqual(t, identity.UserID, "12345") + expectEqual(t, identity.Username, "test-user") + }) + + t.Run("CallbackWithOfflineAccessScope", func(t *testing.T) { + identity, err := cfConn.HandleCallback(connector.Scopes{OfflineAccess: true}, req) + + expectEqual(t, err, nil) + expectNotEqual(t, len(identity.ConnectorData), 0) + + cData := connectorData{} + err = json.Unmarshal(identity.ConnectorData, &cData) + + expectEqual(t, err, nil) + expectNotEqual(t, cData.AccessToken, "") + }) +} + +func testSetup() *httptest.Server { + mux := http.NewServeMux() + mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { + token := "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiIxMjk4MTNhZjJiNGM0ZDNhYmYyNjljMzM4OTFkZjNiZCIsInN1YiI6ImNmMWFlODk4LWQ1ODctNDBhYS1hNWRiLTE5ZTY3MjI0N2I1NyIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJjb25jb3Vyc2UiLCJjaWQiOiJjb25jb3Vyc2UiLCJhenAiOiJjb25jb3Vyc2UiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6ImNmMWFlODk4LWQ1ODctNDBhYS1hNWRiLTE5ZTY3MjI0N2I1NyIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbiIsImF1dGhfdGltZSI6MTUyMzM3NDIwNCwicmV2X3NpZyI6IjYxNWJjMTk0IiwiaWF0IjoxNTIzMzc3MTUyLCJleHAiOjE1MjM0MjAzNTIsImlzcyI6Imh0dHBzOi8vdWFhLnN0eXgucHVzaC5nY3AuY2YtYXBwLmNvbS9vYXV0aC90b2tlbiIsInppZCI6InVhYSIsImF1ZCI6WyJjbG91ZF9jb250cm9sbGVyIiwiY29uY291cnNlIiwib3BlbmlkIl19.FslbnwvW0WScVRNK8IWghRX0buXfl6qaI1K7z_dzjPUVrdEyMtaYa3kJI8srA-2G1PjSSEWa_3Vzs_BEnTc3iG0JQWU0XlcjdCdAFTvnmKiHSzffy1O_oGYyH47KXtnZOxHf3rdV_Xgw4XTqPrfKXQxnPemUAJyKf2tjgs3XToGaqqBw-D_2BQVY79kF0_GgksQsViqq1GW0Dur6m2CgBhtc2h1AQGO16izXl3uNbpW6ClhaW43NQXlE4wqtr7kfmxyOigHJb2MSQ3wwPc6pqYdUT6ka_TMqavqbxEJ4QcS6SoEcVsDTmEQ4c8dmWUgXM0AZjd0CaEGTB6FDHxH5sw" + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "access_token": token, + }) + }) + + mux.HandleFunc("/v2/info", func(w http.ResponseWriter, r *http.Request) { + url := fmt.Sprintf("http://%s", r.Host) + + json.NewEncoder(w).Encode(map[string]string{ + "token_endpoint": url, + }) + }) + + mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { + url := fmt.Sprintf("http://%s", r.Host) + + json.NewEncoder(w).Encode(map[string]string{ + "token_endpoint": url, + "authorization_endpoint": url, + "userinfo_endpoint": url, + }) + }) + + mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { + }) + + mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]string{ + "user_id": "12345", + "user_name": "test-user", + "email": "blah-email", + }) + }) + + mux.HandleFunc("/v2/users/", func(w http.ResponseWriter, r *http.Request) { + var result map[string]interface{} + + if strings.Contains(r.URL.String(), "spaces") { + result = map[string]interface{}{ + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-space-guid"}, + "entity": map[string]string{"name": "some-space-name", "organization_guid": "some-org-guid-1"}, + }, + }, + } + } + + if strings.Contains(r.URL.String(), "organizations") { + result = map[string]interface{}{ + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-org-guid-1"}, + "entity": map[string]string{"name": "some-org-name-1"}, + }, + { + "metadata": map[string]string{"guid": "some-org-guid-2"}, + "entity": map[string]string{"name": "some-org-name-2"}, + }, + }, + } + } + json.NewEncoder(w).Encode(result) + }) + + return httptest.NewServer(mux) +} + +func newConnector(t *testing.T, serverURL string) *cfConnector { + + callBackURL := fmt.Sprintf("%s/callback", serverURL) + + testConfig := Config{ + APIURL: serverURL, + ClientID: "test-client", + ClientSecret: "secret", + RedirectURI: callBackURL, + InsecureSkipVerify: true, + } + + log := logrus.New() + + conn, err := testConfig.Open("id", log) + if err != nil { + t.Fatal(err) + } + + cfConn, ok := conn.(*cfConnector) + if !ok { + t.Fatal(errors.New("it is not a cf conn")) + } + + return cfConn +} + +func expectEqual(t *testing.T, a interface{}, b interface{}) { + if !reflect.DeepEqual(a, b) { + t.Fatalf("Expected %+v to equal %+v", a, b) + } +} + +func expectNotEqual(t *testing.T, a interface{}, b interface{}) { + if reflect.DeepEqual(a, b) { + t.Fatalf("Expected %+v to NOT equal %+v", a, b) + } +} diff --git a/server/server.go b/server/server.go index e923e3e0..6caad21d 100644 --- a/server/server.go +++ b/server/server.go @@ -31,6 +31,7 @@ import ( "github.com/dexidp/dex/connector/atlassiancrowd" "github.com/dexidp/dex/connector/authproxy" "github.com/dexidp/dex/connector/bitbucketcloud" + "github.com/dexidp/dex/connector/cf" "github.com/dexidp/dex/connector/gitea" "github.com/dexidp/dex/connector/github" "github.com/dexidp/dex/connector/gitlab" @@ -691,6 +692,7 @@ var ConnectorsConfig = map[string]func() ConnectorConfig{ "bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) }, "openshift": func() ConnectorConfig { return new(openshift.Config) }, "atlassian-crowd": func() ConnectorConfig { return new(atlassiancrowd.Config) }, + "cf": func() ConnectorConfig { return new(cf.Config) }, // Keep around for backwards compatibility. "samlExperimental": func() ConnectorConfig { return new(saml.Config) }, } From de4a36e19e4aa91bce0824520afcf1a049ffbba1 Mon Sep 17 00:00:00 2001 From: Josh Winters Date: Thu, 4 Oct 2018 15:07:26 -0400 Subject: [PATCH 04/22] update cf connector to use 'authorization_endpoint' from /v2/info Co-authored-by: Topher Bullock Signed-off-by: Josh Winters --- connector/cf/cf.go | 2 +- connector/cf/cf_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 2e4d2243..4452e2f9 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -97,7 +97,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) var apiResult map[string]interface{} json.NewDecoder(apiResp.Body).Decode(&apiResult) - uaaURL := strings.TrimRight(apiResult["token_endpoint"].(string), "/") + uaaURL := strings.TrimRight(apiResult["authorization_endpoint"].(string), "/") uaaResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) if err != nil { diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index 6680da0b..bd1026bd 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -92,7 +92,7 @@ func testSetup() *httptest.Server { url := fmt.Sprintf("http://%s", r.Host) json.NewEncoder(w).Encode(map[string]string{ - "token_endpoint": url, + "authorization_endpoint": url, }) }) From a0570a2ae22e4736b80e391af85ad2537412b8ce Mon Sep 17 00:00:00 2001 From: Daniel Lavoie Date: Thu, 4 Apr 2019 18:26:59 -0400 Subject: [PATCH 05/22] Added support for CF resources pagination Signed-off-by: Daniel Lavoie --- connector/cf/cf.go | 81 ++++++++++++++++++++++++----------------- connector/cf/cf_test.go | 70 +++++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 52 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 4452e2f9..db185307 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -44,6 +44,7 @@ type Config struct { } type CCResponse struct { + NextUrl string `json:"next_url"` Resources []Resource `json:"resources"` TotalResults int `json:"total_results"` } @@ -227,54 +228,68 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident identity.Email, _ = userInfoResult["email"].(string) identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) + var orgMap = make(map[string]string) + var orgSpaces = make(map[string][]string) + var groupsClaims []string + if s.Groups { // fetch orgs - orgsResp, err := client.Get(fmt.Sprintf("%s/v2/users/%s/organizations", c.apiURL, identity.UserID)) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: %v", err) - } - if orgsResp.StatusCode != http.StatusOK { - return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: status %d", orgsResp.StatusCode) - } var orgs CCResponse + var nextUrl = fmt.Sprintf("%s/v2/users/%s/organizations", c.apiURL, identity.UserID) + for moreResults := true; moreResults; moreResults = orgs.NextUrl != "" { + orgsResp, err := client.Get(nextUrl) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: %v", err) + } + if orgsResp.StatusCode != http.StatusOK { + return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: status %d", orgsResp.StatusCode) + } - err = json.NewDecoder(orgsResp.Body).Decode(&orgs) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to parse orgs: %v", err) - } + orgs = CCResponse{} + err = json.NewDecoder(orgsResp.Body).Decode(&orgs) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to parse orgs: %v", err) + } - var orgMap = make(map[string]string) - var orgSpaces = make(map[string][]string) + for _, resource := range orgs.Resources { + orgMap[resource.Metadata.Guid] = resource.Entity.Name + orgSpaces[resource.Entity.Name] = []string{} + } - for _, resource := range orgs.Resources { - orgMap[resource.Metadata.Guid] = resource.Entity.Name - orgSpaces[resource.Entity.Name] = []string{} + if orgs.NextUrl != "" { + nextUrl = fmt.Sprintf("%s%s", c.apiURL, orgs.NextUrl) + } } // fetch spaces - spacesResp, err := client.Get(fmt.Sprintf("%s/v2/users/%s/spaces", c.apiURL, identity.UserID)) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: %v", err) - } - if spacesResp.StatusCode != http.StatusOK { - return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: status %d", spacesResp.StatusCode) - } - var spaces CCResponse + nextUrl = fmt.Sprintf("%s/v2/users/%s/spaces", c.apiURL, identity.UserID) + for moreResults := true; moreResults; moreResults = spaces.NextUrl != "" { + spacesResp, err := client.Get(nextUrl) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: %v", err) + } + if spacesResp.StatusCode != http.StatusOK { + return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: status %d", spacesResp.StatusCode) + } - err = json.NewDecoder(spacesResp.Body).Decode(&spaces) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to parse spaces: %v", err) - } + spaces = CCResponse{} + err = json.NewDecoder(spacesResp.Body).Decode(&spaces) + if err != nil { + return identity, fmt.Errorf("CF Connector: failed to parse spaces: %v", err) + } - var groupsClaims []string + for _, resource := range spaces.Resources { + orgName := orgMap[resource.Entity.OrganizationGuid] + orgSpaces[orgName] = append(orgSpaces[orgName], resource.Entity.Name) - for _, resource := range spaces.Resources { - orgName := orgMap[resource.Entity.OrganizationGuid] - orgSpaces[orgName] = append(orgSpaces[orgName], resource.Entity.Name) + groupsClaims = append(groupsClaims, resource.Metadata.Guid) + } - groupsClaims = append(groupsClaims, resource.Metadata.Guid) + if spaces.NextUrl != "" { + nextUrl = fmt.Sprintf("%s%s", c.apiURL, spaces.NextUrl) + } } for orgName, spaceNames := range orgSpaces { diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index bd1026bd..138dff22 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -50,10 +50,13 @@ func TestHandleCallback(t *testing.T) { expectEqual(t, err, nil) sort.Strings(identity.Groups) - expectEqual(t, len(identity.Groups), 3) - expectEqual(t, identity.Groups[0], "some-org-name-1:some-space-name") - expectEqual(t, identity.Groups[1], "some-org-name-2") - expectEqual(t, identity.Groups[2], "some-space-guid") + expectEqual(t, len(identity.Groups), 6) + expectEqual(t, identity.Groups[0], "some-org-name-1:some-space-name-1") + expectEqual(t, identity.Groups[1], "some-org-name-2:some-space-name-2") + expectEqual(t, identity.Groups[2], "some-org-name-3") + expectEqual(t, identity.Groups[3], "some-org-name-4") + expectEqual(t, identity.Groups[4], "some-space-guid-1") + expectEqual(t, identity.Groups[5], "some-space-guid-2") }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { @@ -121,30 +124,59 @@ func testSetup() *httptest.Server { var result map[string]interface{} if strings.Contains(r.URL.String(), "spaces") { - result = map[string]interface{}{ - "resources": []map[string]interface{}{ - { - "metadata": map[string]string{"guid": "some-space-guid"}, - "entity": map[string]string{"name": "some-space-name", "organization_guid": "some-org-guid-1"}, + if strings.Contains(r.URL.String(), "spaces?order-direction=asc&page=2&results-per-page=50") { + result = map[string]interface{}{ + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-space-guid-2"}, + "entity": map[string]string{"name": "some-space-name-2", "organization_guid": "some-org-guid-2"}, + }, }, - }, + } + } else { + result = map[string]interface{}{ + "next_url": "/v2/users/12345/spaces?order-direction=asc&page=2&results-per-page=50", + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-space-guid-1"}, + "entity": map[string]string{"name": "some-space-name-1", "organization_guid": "some-org-guid-1"}, + }, + }, + } } } if strings.Contains(r.URL.String(), "organizations") { - result = map[string]interface{}{ - "resources": []map[string]interface{}{ - { - "metadata": map[string]string{"guid": "some-org-guid-1"}, - "entity": map[string]string{"name": "some-org-name-1"}, + if strings.Contains(r.URL.String(), "organizations?order-direction=asc&page=2&results-per-page=50") { + result = map[string]interface{}{ + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-org-guid-3"}, + "entity": map[string]string{"name": "some-org-name-3"}, + }, + { + "metadata": map[string]string{"guid": "some-org-guid-4"}, + "entity": map[string]string{"name": "some-org-name-4"}, + }, }, - { - "metadata": map[string]string{"guid": "some-org-guid-2"}, - "entity": map[string]string{"name": "some-org-name-2"}, + } + } else { + result = map[string]interface{}{ + "next_url": "/v2/users/12345/organizations?order-direction=asc&page=2&results-per-page=50", + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-org-guid-1"}, + "entity": map[string]string{"name": "some-org-name-1"}, + }, + { + "metadata": map[string]string{"guid": "some-org-guid-2"}, + "entity": map[string]string{"name": "some-org-name-2"}, + }, }, - }, + } } } + json.NewEncoder(w).Encode(result) }) From dd69f963a94e5dfc640b49d64757bd1268081679 Mon Sep 17 00:00:00 2001 From: Joshua Winters Date: Thu, 7 Nov 2019 12:36:10 -0500 Subject: [PATCH 06/22] cf: add org to groups claims Signed-off-by: Joshua Winters Co-authored-by: Rui Yang --- connector/cf/cf.go | 9 +++------ connector/cf/cf_test.go | 16 +++++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index db185307..0dd76fb8 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -293,12 +293,9 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident } for orgName, spaceNames := range orgSpaces { - if len(spaceNames) > 0 { - for _, spaceName := range spaceNames { - groupsClaims = append(groupsClaims, fmt.Sprintf("%s:%s", orgName, spaceName)) - } - } else { - groupsClaims = append(groupsClaims, fmt.Sprintf("%s", orgName)) + groupsClaims = append(groupsClaims, fmt.Sprintf("%s", orgName)) + for _, spaceName := range spaceNames { + groupsClaims = append(groupsClaims, fmt.Sprintf("%s:%s", orgName, spaceName)) } } diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index 138dff22..b5b58195 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -50,13 +50,15 @@ func TestHandleCallback(t *testing.T) { expectEqual(t, err, nil) sort.Strings(identity.Groups) - expectEqual(t, len(identity.Groups), 6) - expectEqual(t, identity.Groups[0], "some-org-name-1:some-space-name-1") - expectEqual(t, identity.Groups[1], "some-org-name-2:some-space-name-2") - expectEqual(t, identity.Groups[2], "some-org-name-3") - expectEqual(t, identity.Groups[3], "some-org-name-4") - expectEqual(t, identity.Groups[4], "some-space-guid-1") - expectEqual(t, identity.Groups[5], "some-space-guid-2") + expectEqual(t, len(identity.Groups), 8) + expectEqual(t, identity.Groups[0], "some-org-name-1") + expectEqual(t, identity.Groups[1], "some-org-name-1:some-space-name-1") + expectEqual(t, identity.Groups[2], "some-org-name-2") + expectEqual(t, identity.Groups[3], "some-org-name-2:some-space-name-2") + expectEqual(t, identity.Groups[4], "some-org-name-3") + expectEqual(t, identity.Groups[5], "some-org-name-4") + expectEqual(t, identity.Groups[6], "some-space-guid-1") + expectEqual(t, identity.Groups[7], "some-space-guid-2") }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { From e926929f3e339a2a07c7983656f2246afe9b801f Mon Sep 17 00:00:00 2001 From: Joshua Winters Date: Mon, 18 Nov 2019 16:38:33 -0500 Subject: [PATCH 07/22] cf: add org guid to groups claims Co-authored-by: Rui Yang Signed-off-by: Joshua Winters --- connector/cf/cf.go | 12 +++++++++--- connector/cf/cf_test.go | 24 +++++++++++++----------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 0dd76fb8..d677a33f 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "net" "net/http" + "sort" "strings" "time" @@ -255,6 +256,9 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident for _, resource := range orgs.Resources { orgMap[resource.Metadata.Guid] = resource.Entity.Name orgSpaces[resource.Entity.Name] = []string{} + + groupsClaims = append(groupsClaims, resource.Metadata.Guid) + groupsClaims = append(groupsClaims, resource.Entity.Name) } if orgs.NextUrl != "" { @@ -292,14 +296,16 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident } } + var orgSpaceClaims []string for orgName, spaceNames := range orgSpaces { - groupsClaims = append(groupsClaims, fmt.Sprintf("%s", orgName)) for _, spaceName := range spaceNames { - groupsClaims = append(groupsClaims, fmt.Sprintf("%s:%s", orgName, spaceName)) + orgSpaceClaims = append(orgSpaceClaims, fmt.Sprintf("%s:%s", orgName, spaceName)) } } - identity.Groups = groupsClaims + sort.Strings(orgSpaceClaims) + + identity.Groups = append(groupsClaims, orgSpaceClaims...) } if s.OfflineAccess { diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index b5b58195..67850d2e 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -7,7 +7,6 @@ import ( "net/http" "net/http/httptest" "reflect" - "sort" "strings" "testing" @@ -49,16 +48,19 @@ func TestHandleCallback(t *testing.T) { identity, err := cfConn.HandleCallback(connector.Scopes{Groups: true}, req) expectEqual(t, err, nil) - sort.Strings(identity.Groups) - expectEqual(t, len(identity.Groups), 8) - expectEqual(t, identity.Groups[0], "some-org-name-1") - expectEqual(t, identity.Groups[1], "some-org-name-1:some-space-name-1") - expectEqual(t, identity.Groups[2], "some-org-name-2") - expectEqual(t, identity.Groups[3], "some-org-name-2:some-space-name-2") - expectEqual(t, identity.Groups[4], "some-org-name-3") - expectEqual(t, identity.Groups[5], "some-org-name-4") - expectEqual(t, identity.Groups[6], "some-space-guid-1") - expectEqual(t, identity.Groups[7], "some-space-guid-2") + expectEqual(t, len(identity.Groups), 12) + expectEqual(t, identity.Groups[0], "some-org-guid-1") + expectEqual(t, identity.Groups[1], "some-org-name-1") + expectEqual(t, identity.Groups[2], "some-org-guid-2") + expectEqual(t, identity.Groups[3], "some-org-name-2") + expectEqual(t, identity.Groups[4], "some-org-guid-3") + expectEqual(t, identity.Groups[5], "some-org-name-3") + expectEqual(t, identity.Groups[6], "some-org-guid-4") + expectEqual(t, identity.Groups[7], "some-org-name-4") + expectEqual(t, identity.Groups[8], "some-space-guid-1") + expectEqual(t, identity.Groups[9], "some-space-guid-2") + expectEqual(t, identity.Groups[10], "some-org-name-1:some-space-name-1") + expectEqual(t, identity.Groups[11], "some-org-name-2:some-space-name-2") }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { From 0cdd860e2e999750481a74aab0dfa1c3b6e83d36 Mon Sep 17 00:00:00 2001 From: Zoe Tian Date: Mon, 7 Oct 2019 17:16:00 -0400 Subject: [PATCH 08/22] add unit test and api call to `audited_spaces` and `managed_spaces` Signed-off-by: Zoe Tian Co-authored-by: Ciro S. Costa Signed-off-by: w3tian --- connector/cf/cf.go | 210 +++++++++++++++++++++++++++------------- connector/cf/cf_test.go | 143 +++++++++++++++------------ 2 files changed, 224 insertions(+), 129 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index d677a33f..6b33ebe1 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -64,6 +64,17 @@ type Entity struct { OrganizationGuid string `json:"organization_guid"` } +type Space struct { + Name string + Guid string + OrgGuid string +} + +type Org struct { + Name string + Guid string +} + func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { var err error @@ -181,6 +192,115 @@ func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state strin return oauth2Config.AuthCodeURL(state), nil } +func fetchRoleSpaces(baseUrl, path string, client *http.Client) ([]Space, error) { + var spaces []Space + + resources, err := fetchResources(baseUrl, path, client) + if err != nil { + return nil, fmt.Errorf("failed to fetch resources: %v", err) + } + + for _, resource := range resources { + spaces = append(spaces, Space{ + Name: resource.Entity.Name, + Guid: resource.Metadata.Guid, + OrgGuid: resource.Entity.OrganizationGuid, + }) + } + + return spaces, nil +} + +func fetchOrgs(baseUrl, path string, client *http.Client) ([]Org, error) { + var orgs []Org + + resources, err := fetchResources(baseUrl, path, client) + if err != nil { + return nil, fmt.Errorf("failed to fetch resources: %v", err) + } + + for _, resource := range resources { + orgs = append(orgs, Org{ + Name: resource.Entity.Name, + Guid: resource.Metadata.Guid, + }) + } + + return orgs, nil +} + +func fetchResources(baseUrl, path string, client *http.Client) ([]Resource, error) { + var ( + resources []Resource + url string + ) + + for { + url = fmt.Sprintf("%s%s", baseUrl, path) + + resp, err := client.Get(url) + if err != nil { + return nil, fmt.Errorf("failed to execute request: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unsuccessful status code %d", resp.StatusCode) + } + + response := CCResponse{} + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to parse spaces: %v", err) + } + + resources = append(resources, response.Resources...) + + path = response.NextUrl + if path == "" { + break + } + } + + return resources, nil +} + +func getGroupsClaims(orgs []Org, spaces []Space) []string { + + var ( + orgMap = map[string]string{} + orgSpaces = map[string][]string{} + groupsClaims = map[string]bool{} + ) + + for _, org := range orgs { + orgMap[org.Guid] = org.Name + orgSpaces[org.Name] = []string{} + groupsClaims[org.Guid] = true + groupsClaims[org.Name] = true + } + + for _, space := range spaces { + orgName := orgMap[space.OrgGuid] + orgSpaces[orgName] = append(orgSpaces[orgName], space.Name) + groupsClaims[space.Guid] = true + } + + for orgName, spaceNames := range orgSpaces { + for _, spaceName := range spaceNames { + groupsClaims[fmt.Sprintf("%s:%s", orgName, spaceName)] = true + } + } + + var groups []string + for k, _ := range groupsClaims { + groups = append(groups, k) + } + + sort.Strings(groups) + + return groups +} + func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() @@ -229,83 +349,37 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident identity.Email, _ = userInfoResult["email"].(string) identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) - var orgMap = make(map[string]string) - var orgSpaces = make(map[string][]string) - var groupsClaims []string + var ( + devPath = fmt.Sprintf("/v2/users/%s/spaces", identity.UserID) + auditorPath = fmt.Sprintf("/v2/users/%s/audited_spaces", identity.UserID) + managerPath = fmt.Sprintf("/v2/users/%s/managed_spaces", identity.UserID) + orgsPath = fmt.Sprintf("/v2/users/%s/organizations", identity.UserID) + ) if s.Groups { - // fetch orgs - - var orgs CCResponse - var nextUrl = fmt.Sprintf("%s/v2/users/%s/organizations", c.apiURL, identity.UserID) - for moreResults := true; moreResults; moreResults = orgs.NextUrl != "" { - orgsResp, err := client.Get(nextUrl) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: %v", err) - } - if orgsResp.StatusCode != http.StatusOK { - return identity, fmt.Errorf("CF Connector: failed to execute request for orgs: status %d", orgsResp.StatusCode) - } - - orgs = CCResponse{} - err = json.NewDecoder(orgsResp.Body).Decode(&orgs) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to parse orgs: %v", err) - } - - for _, resource := range orgs.Resources { - orgMap[resource.Metadata.Guid] = resource.Entity.Name - orgSpaces[resource.Entity.Name] = []string{} - - groupsClaims = append(groupsClaims, resource.Metadata.Guid) - groupsClaims = append(groupsClaims, resource.Entity.Name) - } - - if orgs.NextUrl != "" { - nextUrl = fmt.Sprintf("%s%s", c.apiURL, orgs.NextUrl) - } + orgs, err := fetchOrgs(c.apiURL, orgsPath, client) + if err != nil { + return identity, fmt.Errorf("failed to fetch organizaitons: %v", err) } - // fetch spaces - var spaces CCResponse - nextUrl = fmt.Sprintf("%s/v2/users/%s/spaces", c.apiURL, identity.UserID) - for moreResults := true; moreResults; moreResults = spaces.NextUrl != "" { - spacesResp, err := client.Get(nextUrl) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: %v", err) - } - if spacesResp.StatusCode != http.StatusOK { - return identity, fmt.Errorf("CF Connector: failed to execute request for spaces: status %d", spacesResp.StatusCode) - } - - spaces = CCResponse{} - err = json.NewDecoder(spacesResp.Body).Decode(&spaces) - if err != nil { - return identity, fmt.Errorf("CF Connector: failed to parse spaces: %v", err) - } - - for _, resource := range spaces.Resources { - orgName := orgMap[resource.Entity.OrganizationGuid] - orgSpaces[orgName] = append(orgSpaces[orgName], resource.Entity.Name) - - groupsClaims = append(groupsClaims, resource.Metadata.Guid) - } - - if spaces.NextUrl != "" { - nextUrl = fmt.Sprintf("%s%s", c.apiURL, spaces.NextUrl) - } + developerSpaces, err := fetchRoleSpaces(c.apiURL, devPath, client) + if err != nil { + return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) } - var orgSpaceClaims []string - for orgName, spaceNames := range orgSpaces { - for _, spaceName := range spaceNames { - orgSpaceClaims = append(orgSpaceClaims, fmt.Sprintf("%s:%s", orgName, spaceName)) - } + auditorSpaces, err := fetchRoleSpaces(c.apiURL, auditorPath, client) + if err != nil { + return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) + } + + managerSpaces, err := fetchRoleSpaces(c.apiURL, managerPath, client) + if err != nil { + return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) } - sort.Strings(orgSpaceClaims) + spaces := append(developerSpaces, append(auditorSpaces, managerSpaces...)...) - identity.Groups = append(groupsClaims, orgSpaceClaims...) + identity.Groups = getGroupsClaims(orgs, spaces) } if s.OfflineAccess { diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index 67850d2e..40daa7c7 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -50,17 +50,17 @@ func TestHandleCallback(t *testing.T) { expectEqual(t, len(identity.Groups), 12) expectEqual(t, identity.Groups[0], "some-org-guid-1") - expectEqual(t, identity.Groups[1], "some-org-name-1") - expectEqual(t, identity.Groups[2], "some-org-guid-2") - expectEqual(t, identity.Groups[3], "some-org-name-2") - expectEqual(t, identity.Groups[4], "some-org-guid-3") - expectEqual(t, identity.Groups[5], "some-org-name-3") - expectEqual(t, identity.Groups[6], "some-org-guid-4") - expectEqual(t, identity.Groups[7], "some-org-name-4") - expectEqual(t, identity.Groups[8], "some-space-guid-1") - expectEqual(t, identity.Groups[9], "some-space-guid-2") - expectEqual(t, identity.Groups[10], "some-org-name-1:some-space-name-1") - expectEqual(t, identity.Groups[11], "some-org-name-2:some-space-name-2") + expectEqual(t, identity.Groups[1], "some-org-guid-2") + expectEqual(t, identity.Groups[2], "some-org-guid-3") + expectEqual(t, identity.Groups[3], "some-org-guid-4") + expectEqual(t, identity.Groups[4], "some-org-name-1") + expectEqual(t, identity.Groups[5], "some-org-name-1:some-space-name-1") + expectEqual(t, identity.Groups[6], "some-org-name-2") + expectEqual(t, identity.Groups[7], "some-org-name-2:some-space-name-2") + expectEqual(t, identity.Groups[8], "some-org-name-3") + expectEqual(t, identity.Groups[9], "some-org-name-4") + expectEqual(t, identity.Groups[10], "some-space-guid-1") + expectEqual(t, identity.Groups[11], "some-space-guid-2") }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { @@ -85,6 +85,64 @@ func TestHandleCallback(t *testing.T) { }) } +func testSpaceHandler(reqUrl, spaceApiEndpoint string) (result map[string]interface{}) { + fullUrl := fmt.Sprintf("%s?order-direction=asc&page=2&results-per-page=50", spaceApiEndpoint) + if strings.Contains(reqUrl, fullUrl) { + result = map[string]interface{}{ + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-space-guid-2"}, + "entity": map[string]string{"name": "some-space-name-2", "organization_guid": "some-org-guid-2"}, + }, + }, + } + } else { + nextUrl := fmt.Sprintf("/v2/users/12345/%s?order-direction=asc&page=2&results-per-page=50", spaceApiEndpoint) + result = map[string]interface{}{ + "next_url": nextUrl, + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-space-guid-1"}, + "entity": map[string]string{"name": "some-space-name-1", "organization_guid": "some-org-guid-1"}, + }, + }, + } + } + return result +} + +func testOrgHandler(reqUrl string) (result map[string]interface{}) { + if strings.Contains(reqUrl, "organizations?order-direction=asc&page=2&results-per-page=50") { + result = map[string]interface{}{ + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-org-guid-3"}, + "entity": map[string]string{"name": "some-org-name-3"}, + }, + { + "metadata": map[string]string{"guid": "some-org-guid-4"}, + "entity": map[string]string{"name": "some-org-name-4"}, + }, + }, + } + } else { + result = map[string]interface{}{ + "next_url": "/v2/users/12345/organizations?order-direction=asc&page=2&results-per-page=50", + "resources": []map[string]interface{}{ + { + "metadata": map[string]string{"guid": "some-org-guid-1"}, + "entity": map[string]string{"name": "some-org-name-1"}, + }, + { + "metadata": map[string]string{"guid": "some-org-guid-2"}, + "entity": map[string]string{"name": "some-org-name-2"}, + }, + }, + } + } + return result +} + func testSetup() *httptest.Server { mux := http.NewServeMux() mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { @@ -127,58 +185,21 @@ func testSetup() *httptest.Server { mux.HandleFunc("/v2/users/", func(w http.ResponseWriter, r *http.Request) { var result map[string]interface{} - if strings.Contains(r.URL.String(), "spaces") { - if strings.Contains(r.URL.String(), "spaces?order-direction=asc&page=2&results-per-page=50") { - result = map[string]interface{}{ - "resources": []map[string]interface{}{ - { - "metadata": map[string]string{"guid": "some-space-guid-2"}, - "entity": map[string]string{"name": "some-space-name-2", "organization_guid": "some-org-guid-2"}, - }, - }, - } - } else { - result = map[string]interface{}{ - "next_url": "/v2/users/12345/spaces?order-direction=asc&page=2&results-per-page=50", - "resources": []map[string]interface{}{ - { - "metadata": map[string]string{"guid": "some-space-guid-1"}, - "entity": map[string]string{"name": "some-space-name-1", "organization_guid": "some-org-guid-1"}, - }, - }, - } - } + reqUrl := r.URL.String() + if strings.Contains(reqUrl, "/spaces") { + result = testSpaceHandler(reqUrl, "spaces") + } + + if strings.Contains(reqUrl, "/audited_spaces") { + result = testSpaceHandler(reqUrl, "audited_spaces") + } + + if strings.Contains(reqUrl, "/managed_spaces") { + result = testSpaceHandler(reqUrl, "managed_spaces") } - if strings.Contains(r.URL.String(), "organizations") { - if strings.Contains(r.URL.String(), "organizations?order-direction=asc&page=2&results-per-page=50") { - result = map[string]interface{}{ - "resources": []map[string]interface{}{ - { - "metadata": map[string]string{"guid": "some-org-guid-3"}, - "entity": map[string]string{"name": "some-org-name-3"}, - }, - { - "metadata": map[string]string{"guid": "some-org-guid-4"}, - "entity": map[string]string{"name": "some-org-name-4"}, - }, - }, - } - } else { - result = map[string]interface{}{ - "next_url": "/v2/users/12345/organizations?order-direction=asc&page=2&results-per-page=50", - "resources": []map[string]interface{}{ - { - "metadata": map[string]string{"guid": "some-org-guid-1"}, - "entity": map[string]string{"name": "some-org-name-1"}, - }, - { - "metadata": map[string]string{"guid": "some-org-guid-2"}, - "entity": map[string]string{"name": "some-org-name-2"}, - }, - }, - } - } + if strings.Contains(reqUrl, "organizations") { + result = testOrgHandler(reqUrl) } json.NewEncoder(w).Encode(result) From 13e3c24f4bb27376af084a4e8fa422270cdb85cd Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Mon, 4 Nov 2019 17:06:23 -0500 Subject: [PATCH 09/22] append role to space guids Signed-off-by: Rui Yang Co-authored-by: Joshua Winters --- connector/cf/cf.go | 27 +++++++++++++++------------ connector/cf/cf_test.go | 10 ++++++++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 6b33ebe1..67c3d567 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -68,6 +68,7 @@ type Space struct { Name string Guid string OrgGuid string + Role string } type Org struct { @@ -192,7 +193,7 @@ func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state strin return oauth2Config.AuthCodeURL(state), nil } -func fetchRoleSpaces(baseUrl, path string, client *http.Client) ([]Space, error) { +func fetchRoleSpaces(baseUrl, path, role string, client *http.Client) ([]Space, error) { var spaces []Space resources, err := fetchResources(baseUrl, path, client) @@ -205,6 +206,7 @@ func fetchRoleSpaces(baseUrl, path string, client *http.Client) ([]Space, error) Name: resource.Entity.Name, Guid: resource.Metadata.Guid, OrgGuid: resource.Entity.OrganizationGuid, + Role: role, }) } @@ -268,32 +270,33 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { var ( orgMap = map[string]string{} - orgSpaces = map[string][]string{} + orgSpaces = map[string][]Space{} groupsClaims = map[string]bool{} ) for _, org := range orgs { orgMap[org.Guid] = org.Name - orgSpaces[org.Name] = []string{} + orgSpaces[org.Name] = []Space{} groupsClaims[org.Guid] = true groupsClaims[org.Name] = true } for _, space := range spaces { orgName := orgMap[space.OrgGuid] - orgSpaces[orgName] = append(orgSpaces[orgName], space.Name) + orgSpaces[orgName] = append(orgSpaces[orgName], space) groupsClaims[space.Guid] = true + groupsClaims[fmt.Sprintf("%s:%s", space.Guid, space.Role)] = true } - for orgName, spaceNames := range orgSpaces { - for _, spaceName := range spaceNames { - groupsClaims[fmt.Sprintf("%s:%s", orgName, spaceName)] = true + for orgName, spaces := range orgSpaces { + for _, space := range spaces { + groupsClaims[fmt.Sprintf("%s:%s", orgName, space.Name)] = true } } var groups []string - for k, _ := range groupsClaims { - groups = append(groups, k) + for group, _ := range groupsClaims { + groups = append(groups, group) } sort.Strings(groups) @@ -362,17 +365,17 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident return identity, fmt.Errorf("failed to fetch organizaitons: %v", err) } - developerSpaces, err := fetchRoleSpaces(c.apiURL, devPath, client) + developerSpaces, err := fetchRoleSpaces(c.apiURL, devPath, "developer", client) if err != nil { return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) } - auditorSpaces, err := fetchRoleSpaces(c.apiURL, auditorPath, client) + auditorSpaces, err := fetchRoleSpaces(c.apiURL, auditorPath, "auditor", client) if err != nil { return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) } - managerSpaces, err := fetchRoleSpaces(c.apiURL, managerPath, client) + managerSpaces, err := fetchRoleSpaces(c.apiURL, managerPath, "manager", client) if err != nil { return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) } diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index 40daa7c7..f6014230 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -48,7 +48,7 @@ func TestHandleCallback(t *testing.T) { identity, err := cfConn.HandleCallback(connector.Scopes{Groups: true}, req) expectEqual(t, err, nil) - expectEqual(t, len(identity.Groups), 12) + expectEqual(t, len(identity.Groups), 18) expectEqual(t, identity.Groups[0], "some-org-guid-1") expectEqual(t, identity.Groups[1], "some-org-guid-2") expectEqual(t, identity.Groups[2], "some-org-guid-3") @@ -60,7 +60,13 @@ func TestHandleCallback(t *testing.T) { expectEqual(t, identity.Groups[8], "some-org-name-3") expectEqual(t, identity.Groups[9], "some-org-name-4") expectEqual(t, identity.Groups[10], "some-space-guid-1") - expectEqual(t, identity.Groups[11], "some-space-guid-2") + expectEqual(t, identity.Groups[11], "some-space-guid-1:auditor") + expectEqual(t, identity.Groups[12], "some-space-guid-1:developer") + expectEqual(t, identity.Groups[13], "some-space-guid-1:manager") + expectEqual(t, identity.Groups[14], "some-space-guid-2") + expectEqual(t, identity.Groups[15], "some-space-guid-2:auditor") + expectEqual(t, identity.Groups[16], "some-space-guid-2:developer") + expectEqual(t, identity.Groups[17], "some-space-guid-2:manager") }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { From 397b26b8578f18a7c00ffeba3afb9bc9c4290447 Mon Sep 17 00:00:00 2001 From: Joshua Winters Date: Mon, 25 Nov 2019 15:15:30 -0500 Subject: [PATCH 10/22] add cf org:space:role group claim to token Signed-off-by: Joshua Winters Co-authored-by: Rui Yang --- connector/cf/cf.go | 1 + connector/cf/cf_test.go | 32 +++++++++++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 67c3d567..0dcbb3a8 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -291,6 +291,7 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { for orgName, spaces := range orgSpaces { for _, space := range spaces { groupsClaims[fmt.Sprintf("%s:%s", orgName, space.Name)] = true + groupsClaims[fmt.Sprintf("%s:%s:%s", orgName, space.Name, space.Role)] = true } } diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index f6014230..afc273da 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -48,25 +48,31 @@ func TestHandleCallback(t *testing.T) { identity, err := cfConn.HandleCallback(connector.Scopes{Groups: true}, req) expectEqual(t, err, nil) - expectEqual(t, len(identity.Groups), 18) + expectEqual(t, len(identity.Groups), 24) expectEqual(t, identity.Groups[0], "some-org-guid-1") expectEqual(t, identity.Groups[1], "some-org-guid-2") expectEqual(t, identity.Groups[2], "some-org-guid-3") expectEqual(t, identity.Groups[3], "some-org-guid-4") expectEqual(t, identity.Groups[4], "some-org-name-1") expectEqual(t, identity.Groups[5], "some-org-name-1:some-space-name-1") - expectEqual(t, identity.Groups[6], "some-org-name-2") - expectEqual(t, identity.Groups[7], "some-org-name-2:some-space-name-2") - expectEqual(t, identity.Groups[8], "some-org-name-3") - expectEqual(t, identity.Groups[9], "some-org-name-4") - expectEqual(t, identity.Groups[10], "some-space-guid-1") - expectEqual(t, identity.Groups[11], "some-space-guid-1:auditor") - expectEqual(t, identity.Groups[12], "some-space-guid-1:developer") - expectEqual(t, identity.Groups[13], "some-space-guid-1:manager") - expectEqual(t, identity.Groups[14], "some-space-guid-2") - expectEqual(t, identity.Groups[15], "some-space-guid-2:auditor") - expectEqual(t, identity.Groups[16], "some-space-guid-2:developer") - expectEqual(t, identity.Groups[17], "some-space-guid-2:manager") + expectEqual(t, identity.Groups[6], "some-org-name-1:some-space-name-1:auditor") + expectEqual(t, identity.Groups[7], "some-org-name-1:some-space-name-1:developer") + expectEqual(t, identity.Groups[8], "some-org-name-1:some-space-name-1:manager") + expectEqual(t, identity.Groups[9], "some-org-name-2") + expectEqual(t, identity.Groups[10], "some-org-name-2:some-space-name-2") + expectEqual(t, identity.Groups[11], "some-org-name-2:some-space-name-2:auditor") + expectEqual(t, identity.Groups[12], "some-org-name-2:some-space-name-2:developer") + expectEqual(t, identity.Groups[13], "some-org-name-2:some-space-name-2:manager") + expectEqual(t, identity.Groups[14], "some-org-name-3") + expectEqual(t, identity.Groups[15], "some-org-name-4") + expectEqual(t, identity.Groups[16], "some-space-guid-1") + expectEqual(t, identity.Groups[17], "some-space-guid-1:auditor") + expectEqual(t, identity.Groups[18], "some-space-guid-1:developer") + expectEqual(t, identity.Groups[19], "some-space-guid-1:manager") + expectEqual(t, identity.Groups[20], "some-space-guid-2") + expectEqual(t, identity.Groups[21], "some-space-guid-2:auditor") + expectEqual(t, identity.Groups[22], "some-space-guid-2:developer") + expectEqual(t, identity.Groups[23], "some-space-guid-2:manager") }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { From e27765170523d9e163896ab9863083bb941bf582 Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Mon, 13 Jan 2020 13:19:53 -0500 Subject: [PATCH 11/22] fix lint errors gofumpt-ed Signed-off-by: Rui Yang --- connector/cf/cf.go | 59 +++++++++++++++++++---------------------- connector/cf/cf_test.go | 37 +++++++++++++------------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 0dcbb3a8..ba0b09d1 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -14,9 +14,10 @@ import ( "strings" "time" + "golang.org/x/oauth2" + "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/log" - "golang.org/x/oauth2" ) type cfConnector struct { @@ -45,7 +46,7 @@ type Config struct { } type CCResponse struct { - NextUrl string `json:"next_url"` + NextURL string `json:"next_url"` Resources []Resource `json:"resources"` TotalResults int `json:"total_results"` } @@ -56,24 +57,24 @@ type Resource struct { } type Metadata struct { - Guid string `json:"guid"` + GUID string `json:"guid"` } type Entity struct { Name string `json:"name"` - OrganizationGuid string `json:"organization_guid"` + OrganizationGUID string `json:"organization_guid"` } type Space struct { Name string - Guid string - OrgGuid string + GUID string + OrgGUID string Role string } type Org struct { Name string - Guid string + GUID string } func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { @@ -94,7 +95,6 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) apiURL := strings.TrimRight(c.APIURL, "/") apiResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/v2/info", apiURL)) - if err != nil { logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) return nil, err @@ -103,7 +103,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) defer apiResp.Body.Close() if apiResp.StatusCode != http.StatusOK { - err = errors.New(fmt.Sprintf("request failed with status %d", apiResp.StatusCode)) + err = fmt.Errorf("request failed with status %d", apiResp.StatusCode) logger.Errorf("failed-get-info-response-from-api", err) return nil, err } @@ -113,15 +113,14 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) uaaURL := strings.TrimRight(apiResult["authorization_endpoint"].(string), "/") uaaResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) - if err != nil { logger.Errorf("failed-to-send-request-to-uaa-api", err) return nil, err } if apiResp.StatusCode != http.StatusOK { - err = errors.New(fmt.Sprintf("request failed with status %d", apiResp.StatusCode)) - logger.Errorf("failed-to-get-well-known-config-repsonse-from-api", err) + err = fmt.Errorf("request failed with status %d", apiResp.StatusCode) + logger.Errorf("failed-to-get-well-known-config-response-from-api", err) return nil, err } @@ -177,7 +176,6 @@ func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, err } func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) { - if c.redirectURI != callbackURL { return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } @@ -193,10 +191,10 @@ func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state strin return oauth2Config.AuthCodeURL(state), nil } -func fetchRoleSpaces(baseUrl, path, role string, client *http.Client) ([]Space, error) { +func fetchRoleSpaces(baseURL, path, role string, client *http.Client) ([]Space, error) { var spaces []Space - resources, err := fetchResources(baseUrl, path, client) + resources, err := fetchResources(baseURL, path, client) if err != nil { return nil, fmt.Errorf("failed to fetch resources: %v", err) } @@ -204,8 +202,8 @@ func fetchRoleSpaces(baseUrl, path, role string, client *http.Client) ([]Space, for _, resource := range resources { spaces = append(spaces, Space{ Name: resource.Entity.Name, - Guid: resource.Metadata.Guid, - OrgGuid: resource.Entity.OrganizationGuid, + GUID: resource.Metadata.GUID, + OrgGUID: resource.Entity.OrganizationGUID, Role: role, }) } @@ -213,10 +211,10 @@ func fetchRoleSpaces(baseUrl, path, role string, client *http.Client) ([]Space, return spaces, nil } -func fetchOrgs(baseUrl, path string, client *http.Client) ([]Org, error) { +func fetchOrgs(baseURL, path string, client *http.Client) ([]Org, error) { var orgs []Org - resources, err := fetchResources(baseUrl, path, client) + resources, err := fetchResources(baseURL, path, client) if err != nil { return nil, fmt.Errorf("failed to fetch resources: %v", err) } @@ -224,26 +222,27 @@ func fetchOrgs(baseUrl, path string, client *http.Client) ([]Org, error) { for _, resource := range resources { orgs = append(orgs, Org{ Name: resource.Entity.Name, - Guid: resource.Metadata.Guid, + GUID: resource.Metadata.GUID, }) } return orgs, nil } -func fetchResources(baseUrl, path string, client *http.Client) ([]Resource, error) { +func fetchResources(baseURL, path string, client *http.Client) ([]Resource, error) { var ( resources []Resource url string ) for { - url = fmt.Sprintf("%s%s", baseUrl, path) + url = fmt.Sprintf("%s%s", baseURL, path) resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("failed to execute request: %v", err) } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unsuccessful status code %d", resp.StatusCode) @@ -257,7 +256,7 @@ func fetchResources(baseUrl, path string, client *http.Client) ([]Resource, erro resources = append(resources, response.Resources...) - path = response.NextUrl + path = response.NextURL if path == "" { break } @@ -267,7 +266,6 @@ func fetchResources(baseUrl, path string, client *http.Client) ([]Resource, erro } func getGroupsClaims(orgs []Org, spaces []Space) []string { - var ( orgMap = map[string]string{} orgSpaces = map[string][]Space{} @@ -275,17 +273,17 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { ) for _, org := range orgs { - orgMap[org.Guid] = org.Name + orgMap[org.GUID] = org.Name orgSpaces[org.Name] = []Space{} - groupsClaims[org.Guid] = true + groupsClaims[org.GUID] = true groupsClaims[org.Name] = true } for _, space := range spaces { - orgName := orgMap[space.OrgGuid] + orgName := orgMap[space.OrgGUID] orgSpaces[orgName] = append(orgSpaces[orgName], space) - groupsClaims[space.Guid] = true - groupsClaims[fmt.Sprintf("%s:%s", space.Guid, space.Role)] = true + groupsClaims[space.GUID] = true + groupsClaims[fmt.Sprintf("%s:%s", space.GUID, space.Role)] = true } for orgName, spaces := range orgSpaces { @@ -296,7 +294,7 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { } var groups []string - for group, _ := range groupsClaims { + for group := range groupsClaims { groups = append(groups, group) } @@ -306,7 +304,6 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { } func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { - q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, errors.New(q.Get("error_description")) diff --git a/connector/cf/cf_test.go b/connector/cf/cf_test.go index afc273da..b9bf68db 100644 --- a/connector/cf/cf_test.go +++ b/connector/cf/cf_test.go @@ -10,8 +10,9 @@ import ( "strings" "testing" - "github.com/dexidp/dex/connector" "github.com/sirupsen/logrus" + + "github.com/dexidp/dex/connector" ) func TestOpen(t *testing.T) { @@ -26,7 +27,6 @@ func TestOpen(t *testing.T) { } func TestHandleCallback(t *testing.T) { - testServer := testSetup() defer testServer.Close() @@ -97,9 +97,9 @@ func TestHandleCallback(t *testing.T) { }) } -func testSpaceHandler(reqUrl, spaceApiEndpoint string) (result map[string]interface{}) { - fullUrl := fmt.Sprintf("%s?order-direction=asc&page=2&results-per-page=50", spaceApiEndpoint) - if strings.Contains(reqUrl, fullUrl) { +func testSpaceHandler(reqURL, spaceAPIEndpoint string) (result map[string]interface{}) { + fullURL := fmt.Sprintf("%s?order-direction=asc&page=2&results-per-page=50", spaceAPIEndpoint) + if strings.Contains(reqURL, fullURL) { result = map[string]interface{}{ "resources": []map[string]interface{}{ { @@ -109,9 +109,9 @@ func testSpaceHandler(reqUrl, spaceApiEndpoint string) (result map[string]interf }, } } else { - nextUrl := fmt.Sprintf("/v2/users/12345/%s?order-direction=asc&page=2&results-per-page=50", spaceApiEndpoint) + nextURL := fmt.Sprintf("/v2/users/12345/%s?order-direction=asc&page=2&results-per-page=50", spaceAPIEndpoint) result = map[string]interface{}{ - "next_url": nextUrl, + "next_url": nextURL, "resources": []map[string]interface{}{ { "metadata": map[string]string{"guid": "some-space-guid-1"}, @@ -123,8 +123,8 @@ func testSpaceHandler(reqUrl, spaceApiEndpoint string) (result map[string]interf return result } -func testOrgHandler(reqUrl string) (result map[string]interface{}) { - if strings.Contains(reqUrl, "organizations?order-direction=asc&page=2&results-per-page=50") { +func testOrgHandler(reqURL string) (result map[string]interface{}) { + if strings.Contains(reqURL, "organizations?order-direction=asc&page=2&results-per-page=50") { result = map[string]interface{}{ "resources": []map[string]interface{}{ { @@ -197,21 +197,21 @@ func testSetup() *httptest.Server { mux.HandleFunc("/v2/users/", func(w http.ResponseWriter, r *http.Request) { var result map[string]interface{} - reqUrl := r.URL.String() - if strings.Contains(reqUrl, "/spaces") { - result = testSpaceHandler(reqUrl, "spaces") + reqURL := r.URL.String() + if strings.Contains(reqURL, "/spaces") { + result = testSpaceHandler(reqURL, "spaces") } - if strings.Contains(reqUrl, "/audited_spaces") { - result = testSpaceHandler(reqUrl, "audited_spaces") + if strings.Contains(reqURL, "/audited_spaces") { + result = testSpaceHandler(reqURL, "audited_spaces") } - if strings.Contains(reqUrl, "/managed_spaces") { - result = testSpaceHandler(reqUrl, "managed_spaces") + if strings.Contains(reqURL, "/managed_spaces") { + result = testSpaceHandler(reqURL, "managed_spaces") } - if strings.Contains(reqUrl, "organizations") { - result = testOrgHandler(reqUrl) + if strings.Contains(reqURL, "organizations") { + result = testOrgHandler(reqURL) } json.NewEncoder(w).Encode(result) @@ -221,7 +221,6 @@ func testSetup() *httptest.Server { } func newConnector(t *testing.T, serverURL string) *cfConnector { - callBackURL := fmt.Sprintf("%s/callback", serverURL) testConfig := Config{ From de399a5a5e6d83f71bead79d1c2016ccd0896c2c Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Fri, 5 Mar 2021 12:40:56 -0500 Subject: [PATCH 12/22] run golangcli-lint Signed-off-by: Rui Yang --- connector/cf/cf.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index ba0b09d1..4d839ff6 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -192,38 +192,36 @@ func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state strin } func fetchRoleSpaces(baseURL, path, role string, client *http.Client) ([]Space, error) { - var spaces []Space - resources, err := fetchResources(baseURL, path, client) if err != nil { return nil, fmt.Errorf("failed to fetch resources: %v", err) } - for _, resource := range resources { - spaces = append(spaces, Space{ + spaces := make([]Space, len(resources)) + for i, resource := range resources { + spaces[i] = Space{ Name: resource.Entity.Name, GUID: resource.Metadata.GUID, OrgGUID: resource.Entity.OrganizationGUID, Role: role, - }) + } } return spaces, nil } func fetchOrgs(baseURL, path string, client *http.Client) ([]Org, error) { - var orgs []Org - resources, err := fetchResources(baseURL, path, client) if err != nil { return nil, fmt.Errorf("failed to fetch resources: %v", err) } - for _, resource := range resources { - orgs = append(orgs, Org{ + orgs := make([]Org, len(resources)) + for i, resource := range resources { + orgs[i] = Org{ Name: resource.Entity.Name, GUID: resource.Metadata.GUID, - }) + } } return orgs, nil @@ -293,7 +291,7 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { } } - var groups []string + groups := make([]string, 0, len(groupsClaims)) for group := range groupsClaims { groups = append(groups, group) } From c4a6920c5ee06d93f288de018fde8b09d015c9c4 Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Wed, 1 Dec 2021 10:37:56 -0500 Subject: [PATCH 13/22] fix sanity check errors Signed-off-by: Rui Yang --- connector/cf/cf.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/cf/cf.go b/connector/cf/cf.go index 4d839ff6..4cd04275 100644 --- a/connector/cf/cf.go +++ b/connector/cf/cf.go @@ -7,9 +7,9 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net" "net/http" + "os" "sort" "strings" "time" @@ -149,7 +149,7 @@ func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, err tlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify} for _, rootCA := range rootCAs { - rootCABytes, err := ioutil.ReadFile(rootCA) + rootCABytes, err := os.ReadFile(rootCA) if err != nil { return nil, fmt.Errorf("failed to read root-ca: %v", err) } @@ -376,9 +376,9 @@ func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (ident return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) } - spaces := append(developerSpaces, append(auditorSpaces, managerSpaces...)...) + developerSpaces = append(developerSpaces, append(auditorSpaces, managerSpaces...)...) - identity.Groups = getGroupsClaims(orgs, spaces) + identity.Groups = getGroupsClaims(orgs, developerSpaces) } if s.OfflineAccess { From c1eb2f389b3325c0244288a760182485876814e0 Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Tue, 4 Oct 2022 22:53:06 -0400 Subject: [PATCH 14/22] rename connector;make types private; Signed-off-by: Rui Yang --- .../cf.go => cloudfoundry/cloudfoundry.go} | 66 +++++++++---------- .../cloudfoundry_test.go} | 18 ++--- server/server.go | 4 +- 3 files changed, 44 insertions(+), 44 deletions(-) rename connector/{cf/cf.go => cloudfoundry/cloudfoundry.go} (85%) rename connector/{cf/cf_test.go => cloudfoundry/cloudfoundry_test.go} (94%) diff --git a/connector/cf/cf.go b/connector/cloudfoundry/cloudfoundry.go similarity index 85% rename from connector/cf/cf.go rename to connector/cloudfoundry/cloudfoundry.go index 4cd04275..8d3ab7f0 100644 --- a/connector/cf/cf.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -1,4 +1,4 @@ -package cf +package cloudfoundry import ( "context" @@ -20,7 +20,7 @@ import ( "github.com/dexidp/dex/pkg/log" ) -type cfConnector struct { +type cloudfoundryConnector struct { clientID string clientSecret string redirectURI string @@ -45,34 +45,34 @@ type Config struct { InsecureSkipVerify bool `json:"insecureSkipVerify"` } -type CCResponse struct { +type ccResponse struct { NextURL string `json:"next_url"` - Resources []Resource `json:"resources"` + Resources []resource `json:"resources"` TotalResults int `json:"total_results"` } -type Resource struct { - Metadata Metadata `json:"metadata"` - Entity Entity `json:"entity"` +type resource struct { + Metadata metadata `json:"metadata"` + Entity entity `json:"entity"` } -type Metadata struct { +type metadata struct { GUID string `json:"guid"` } -type Entity struct { +type entity struct { Name string `json:"name"` OrganizationGUID string `json:"organization_guid"` } -type Space struct { +type space struct { Name string GUID string OrgGUID string Role string } -type Org struct { +type org struct { Name string GUID string } @@ -80,7 +80,7 @@ type Org struct { func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { var err error - cfConn := &cfConnector{ + cloudfoundryConn := &cloudfoundryConnector{ clientID: c.ClientID, clientSecret: c.ClientSecret, apiURL: c.APIURL, @@ -88,13 +88,13 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) logger: logger, } - cfConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify) + cloudfoundryConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify) if err != nil { return nil, err } apiURL := strings.TrimRight(c.APIURL, "/") - apiResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/v2/info", apiURL)) + apiResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/v2/info", apiURL)) if err != nil { logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) return nil, err @@ -112,7 +112,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) json.NewDecoder(apiResp.Body).Decode(&apiResult) uaaURL := strings.TrimRight(apiResult["authorization_endpoint"].(string), "/") - uaaResp, err := cfConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) + uaaResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) if err != nil { logger.Errorf("failed-to-send-request-to-uaa-api", err) return nil, err @@ -134,11 +134,11 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) return nil, err } - cfConn.tokenURL, _ = uaaResult["token_endpoint"].(string) - cfConn.authorizationURL, _ = uaaResult["authorization_endpoint"].(string) - cfConn.userInfoURL, _ = uaaResult["userinfo_endpoint"].(string) + cloudfoundryConn.tokenURL, _ = uaaResult["token_endpoint"].(string) + cloudfoundryConn.authorizationURL, _ = uaaResult["authorization_endpoint"].(string) + cloudfoundryConn.userInfoURL, _ = uaaResult["userinfo_endpoint"].(string) - return cfConn, err + return cloudfoundryConn, err } func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) { @@ -175,7 +175,7 @@ func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, err }, nil } -func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) { +func (c *cloudfoundryConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) { if c.redirectURI != callbackURL { return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } @@ -191,15 +191,15 @@ func (c *cfConnector) LoginURL(scopes connector.Scopes, callbackURL, state strin return oauth2Config.AuthCodeURL(state), nil } -func fetchRoleSpaces(baseURL, path, role string, client *http.Client) ([]Space, error) { +func fetchRoleSpaces(baseURL, path, role string, client *http.Client) ([]space, error) { resources, err := fetchResources(baseURL, path, client) if err != nil { return nil, fmt.Errorf("failed to fetch resources: %v", err) } - spaces := make([]Space, len(resources)) + spaces := make([]space, len(resources)) for i, resource := range resources { - spaces[i] = Space{ + spaces[i] = space{ Name: resource.Entity.Name, GUID: resource.Metadata.GUID, OrgGUID: resource.Entity.OrganizationGUID, @@ -210,15 +210,15 @@ func fetchRoleSpaces(baseURL, path, role string, client *http.Client) ([]Space, return spaces, nil } -func fetchOrgs(baseURL, path string, client *http.Client) ([]Org, error) { +func fetchOrgs(baseURL, path string, client *http.Client) ([]org, error) { resources, err := fetchResources(baseURL, path, client) if err != nil { return nil, fmt.Errorf("failed to fetch resources: %v", err) } - orgs := make([]Org, len(resources)) + orgs := make([]org, len(resources)) for i, resource := range resources { - orgs[i] = Org{ + orgs[i] = org{ Name: resource.Entity.Name, GUID: resource.Metadata.GUID, } @@ -227,9 +227,9 @@ func fetchOrgs(baseURL, path string, client *http.Client) ([]Org, error) { return orgs, nil } -func fetchResources(baseURL, path string, client *http.Client) ([]Resource, error) { +func fetchResources(baseURL, path string, client *http.Client) ([]resource, error) { var ( - resources []Resource + resources []resource url string ) @@ -246,7 +246,7 @@ func fetchResources(baseURL, path string, client *http.Client) ([]Resource, erro return nil, fmt.Errorf("unsuccessful status code %d", resp.StatusCode) } - response := CCResponse{} + response := ccResponse{} err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return nil, fmt.Errorf("failed to parse spaces: %v", err) @@ -263,16 +263,16 @@ func fetchResources(baseURL, path string, client *http.Client) ([]Resource, erro return resources, nil } -func getGroupsClaims(orgs []Org, spaces []Space) []string { +func getGroupsClaims(orgs []org, spaces []space) []string { var ( orgMap = map[string]string{} - orgSpaces = map[string][]Space{} + orgSpaces = map[string][]space{} groupsClaims = map[string]bool{} ) for _, org := range orgs { orgMap[org.GUID] = org.Name - orgSpaces[org.Name] = []Space{} + orgSpaces[org.Name] = []space{} groupsClaims[org.GUID] = true groupsClaims[org.Name] = true } @@ -301,7 +301,7 @@ func getGroupsClaims(orgs []Org, spaces []Space) []string { return groups } -func (c *cfConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { +func (c *cloudfoundryConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, errors.New(q.Get("error_description")) diff --git a/connector/cf/cf_test.go b/connector/cloudfoundry/cloudfoundry_test.go similarity index 94% rename from connector/cf/cf_test.go rename to connector/cloudfoundry/cloudfoundry_test.go index b9bf68db..73b521a9 100644 --- a/connector/cf/cf_test.go +++ b/connector/cloudfoundry/cloudfoundry_test.go @@ -1,4 +1,4 @@ -package cf +package cloudfoundry import ( "encoding/json" @@ -30,7 +30,7 @@ func TestHandleCallback(t *testing.T) { testServer := testSetup() defer testServer.Close() - cfConn := &cfConnector{ + cloudfoundryConn := &cloudfoundryConnector{ tokenURL: fmt.Sprintf("%s/token", testServer.URL), authorizationURL: fmt.Sprintf("%s/authorize", testServer.URL), userInfoURL: fmt.Sprintf("%s/userinfo", testServer.URL), @@ -45,7 +45,7 @@ func TestHandleCallback(t *testing.T) { expectEqual(t, err, nil) t.Run("CallbackWithGroupsScope", func(t *testing.T) { - identity, err := cfConn.HandleCallback(connector.Scopes{Groups: true}, req) + identity, err := cloudfoundryConn.HandleCallback(connector.Scopes{Groups: true}, req) expectEqual(t, err, nil) expectEqual(t, len(identity.Groups), 24) @@ -76,7 +76,7 @@ func TestHandleCallback(t *testing.T) { }) t.Run("CallbackWithoutGroupsScope", func(t *testing.T) { - identity, err := cfConn.HandleCallback(connector.Scopes{}, req) + identity, err := cloudfoundryConn.HandleCallback(connector.Scopes{}, req) expectEqual(t, err, nil) expectEqual(t, identity.UserID, "12345") @@ -84,7 +84,7 @@ func TestHandleCallback(t *testing.T) { }) t.Run("CallbackWithOfflineAccessScope", func(t *testing.T) { - identity, err := cfConn.HandleCallback(connector.Scopes{OfflineAccess: true}, req) + identity, err := cloudfoundryConn.HandleCallback(connector.Scopes{OfflineAccess: true}, req) expectEqual(t, err, nil) expectNotEqual(t, len(identity.ConnectorData), 0) @@ -220,7 +220,7 @@ func testSetup() *httptest.Server { return httptest.NewServer(mux) } -func newConnector(t *testing.T, serverURL string) *cfConnector { +func newConnector(t *testing.T, serverURL string) *cloudfoundryConnector { callBackURL := fmt.Sprintf("%s/callback", serverURL) testConfig := Config{ @@ -238,12 +238,12 @@ func newConnector(t *testing.T, serverURL string) *cfConnector { t.Fatal(err) } - cfConn, ok := conn.(*cfConnector) + cloudfoundryConn, ok := conn.(*cloudfoundryConnector) if !ok { - t.Fatal(errors.New("it is not a cf conn")) + t.Fatal(errors.New("it is not a cloudfoundry conn")) } - return cfConn + return cloudfoundryConn } func expectEqual(t *testing.T, a interface{}, b interface{}) { diff --git a/server/server.go b/server/server.go index 6caad21d..c3310369 100644 --- a/server/server.go +++ b/server/server.go @@ -31,7 +31,7 @@ import ( "github.com/dexidp/dex/connector/atlassiancrowd" "github.com/dexidp/dex/connector/authproxy" "github.com/dexidp/dex/connector/bitbucketcloud" - "github.com/dexidp/dex/connector/cf" + "github.com/dexidp/dex/connector/cloudfoundry" "github.com/dexidp/dex/connector/gitea" "github.com/dexidp/dex/connector/github" "github.com/dexidp/dex/connector/gitlab" @@ -692,7 +692,7 @@ var ConnectorsConfig = map[string]func() ConnectorConfig{ "bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) }, "openshift": func() ConnectorConfig { return new(openshift.Config) }, "atlassian-crowd": func() ConnectorConfig { return new(atlassiancrowd.Config) }, - "cf": func() ConnectorConfig { return new(cf.Config) }, + "cloudfoundry": func() ConnectorConfig { return new(cloudfoundry.Config) }, // Keep around for backwards compatibility. "samlExperimental": func() ConnectorConfig { return new(saml.Config) }, } From 46f7b89893af579e011e18928f464340774b56ea Mon Sep 17 00:00:00 2001 From: Rui Yang Date: Tue, 4 Oct 2022 23:28:34 -0400 Subject: [PATCH 15/22] add cloudfoundry to connector list in readme Signed-off-by: Rui Yang --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dac886ee..8a6ee01f 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Dex implements the following connectors: | [Atlassian Crowd](https://dexidp.io/docs/connectors/atlassian-crowd/) | yes | yes | yes * | beta | preferred_username claim must be configured through config | | [Gitea](https://dexidp.io/docs/connectors/gitea/) | yes | no | yes | beta | | | [OpenStack Keystone](https://dexidp.io/docs/connectors/keystone/) | yes | yes | no | alpha | | +| [Cloud Foundry](https://dexidp.io/docs/connectors/cloudfoundry/) | no | yes | no | alpha | This connector is community maintained by [Concourse](https://github.com/concourse) | Stable, beta, and alpha are defined as: From 17c0d4f3835e49925e8f0fe5997b8ba9dcebca71 Mon Sep 17 00:00:00 2001 From: Kump3r Date: Wed, 2 Oct 2024 15:40:33 +0300 Subject: [PATCH 16/22] Switching to CloudFoundry v3 API Signed-off-by: Kump3r --- connector/cloudfoundry/cloudfoundry.go | 8 ++++---- connector/cloudfoundry/cloudfoundry_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index 8d3ab7f0..3eeaae98 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -349,10 +349,10 @@ func (c *cloudfoundryConnector) HandleCallback(s connector.Scopes, r *http.Reque identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) var ( - devPath = fmt.Sprintf("/v2/users/%s/spaces", identity.UserID) - auditorPath = fmt.Sprintf("/v2/users/%s/audited_spaces", identity.UserID) - managerPath = fmt.Sprintf("/v2/users/%s/managed_spaces", identity.UserID) - orgsPath = fmt.Sprintf("/v2/users/%s/organizations", identity.UserID) + devPath = fmt.Sprintf("/v3/users/%s/spaces", identity.UserID) + auditorPath = fmt.Sprintf("/v3/users/%s/audited_spaces", identity.UserID) + managerPath = fmt.Sprintf("/v3/users/%s/managed_spaces", identity.UserID) + orgsPath = fmt.Sprintf("/v3/users/%s/organizations", identity.UserID) ) if s.Groups { diff --git a/connector/cloudfoundry/cloudfoundry_test.go b/connector/cloudfoundry/cloudfoundry_test.go index 73b521a9..b7c2e0ba 100644 --- a/connector/cloudfoundry/cloudfoundry_test.go +++ b/connector/cloudfoundry/cloudfoundry_test.go @@ -109,7 +109,7 @@ func testSpaceHandler(reqURL, spaceAPIEndpoint string) (result map[string]interf }, } } else { - nextURL := fmt.Sprintf("/v2/users/12345/%s?order-direction=asc&page=2&results-per-page=50", spaceAPIEndpoint) + nextURL := fmt.Sprintf("/v3/users/12345/%s?order-direction=asc&page=2&results-per-page=50", spaceAPIEndpoint) result = map[string]interface{}{ "next_url": nextURL, "resources": []map[string]interface{}{ @@ -139,7 +139,7 @@ func testOrgHandler(reqURL string) (result map[string]interface{}) { } } else { result = map[string]interface{}{ - "next_url": "/v2/users/12345/organizations?order-direction=asc&page=2&results-per-page=50", + "next_url": "/v3/users/12345/organizations?order-direction=asc&page=2&results-per-page=50", "resources": []map[string]interface{}{ { "metadata": map[string]string{"guid": "some-org-guid-1"}, @@ -165,7 +165,7 @@ func testSetup() *httptest.Server { }) }) - mux.HandleFunc("/v2/info", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/v3/info", func(w http.ResponseWriter, r *http.Request) { url := fmt.Sprintf("http://%s", r.Host) json.NewEncoder(w).Encode(map[string]string{ @@ -194,7 +194,7 @@ func testSetup() *httptest.Server { }) }) - mux.HandleFunc("/v2/users/", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/v3/users/", func(w http.ResponseWriter, r *http.Request) { var result map[string]interface{} reqURL := r.URL.String() From 9c9fdd9266004bda26c0c1f2495eb5d32ee54ba1 Mon Sep 17 00:00:00 2001 From: Kump3r Date: Fri, 3 Jan 2025 11:25:51 +0200 Subject: [PATCH 17/22] Add missing endpoint Signed-off-by: Kump3r --- connector/cloudfoundry/cloudfoundry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index 3eeaae98..acfb77fd 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -94,7 +94,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) } apiURL := strings.TrimRight(c.APIURL, "/") - apiResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/v2/info", apiURL)) + apiResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/v3/info", apiURL)) if err != nil { logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) return nil, err From ff451646ab91859e9ea25d15f52530ddbedae187 Mon Sep 17 00:00:00 2001 From: Kump3r Date: Tue, 7 Jan 2025 12:28:51 +0200 Subject: [PATCH 18/22] Refactor CloudFoundry API request handling to use updated response structure Signed-off-by: Kump3r --- connector/cloudfoundry/cloudfoundry.go | 13 ++++++++++--- connector/cloudfoundry/cloudfoundry_test.go | 10 +++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index acfb77fd..32ec6a91 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -94,7 +94,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) } apiURL := strings.TrimRight(c.APIURL, "/") - apiResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/v3/info", apiURL)) + apiResp, err := cloudfoundryConn.httpClient.Get(apiURL) if err != nil { logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) return nil, err @@ -108,10 +108,17 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) return nil, err } - var apiResult map[string]interface{} + var apiResult struct { + Links struct { + Login struct { + Href string `json:"href"` + } `json:"login"` + } `json:"links"` + } + json.NewDecoder(apiResp.Body).Decode(&apiResult) - uaaURL := strings.TrimRight(apiResult["authorization_endpoint"].(string), "/") + uaaURL := strings.TrimRight(apiResult.Links.Login.Href, "/") uaaResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) if err != nil { logger.Errorf("failed-to-send-request-to-uaa-api", err) diff --git a/connector/cloudfoundry/cloudfoundry_test.go b/connector/cloudfoundry/cloudfoundry_test.go index b7c2e0ba..e90f66a7 100644 --- a/connector/cloudfoundry/cloudfoundry_test.go +++ b/connector/cloudfoundry/cloudfoundry_test.go @@ -165,11 +165,15 @@ func testSetup() *httptest.Server { }) }) - mux.HandleFunc("/v3/info", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { url := fmt.Sprintf("http://%s", r.Host) - json.NewEncoder(w).Encode(map[string]string{ - "authorization_endpoint": url, + json.NewEncoder(w).Encode(map[string]interface{}{ + "links": map[string]interface{}{ + "login": map[string]string{ + "href": url, + }, + }, }) }) From 0d5987ae471fff0a8813467783d513ae8d6cb16a Mon Sep 17 00:00:00 2001 From: Kump3r Date: Thu, 9 Jan 2025 15:35:04 +0200 Subject: [PATCH 19/22] Use simple structures for the apiResult Signed-off-by: Kump3r --- connector/cloudfoundry/cloudfoundry.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index 32ec6a91..1aad39ce 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -77,6 +77,18 @@ type org struct { GUID string } +type infoResp struct { + Links links `json:"links"` +} + +type links struct { + Login login `json:"login"` +} + +type login struct { + Href string `json:"href"` +} + func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { var err error @@ -108,13 +120,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) return nil, err } - var apiResult struct { - Links struct { - Login struct { - Href string `json:"href"` - } `json:"login"` - } `json:"links"` - } + var apiResult infoResp json.NewDecoder(apiResp.Body).Decode(&apiResult) From 1d65ae4e2f59b755e8343c7ad1a339173be3a45b Mon Sep 17 00:00:00 2001 From: IvanChalukov Date: Thu, 9 Jan 2025 16:58:54 +0200 Subject: [PATCH 20/22] Switching to CF API V3 endpoints Signed-off-by: IvanChalukov --- connector/cloudfoundry/cloudfoundry.go | 119 ++++++++++++++----------- 1 file changed, 65 insertions(+), 54 deletions(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index 1aad39ce..3ba88df5 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -46,25 +46,43 @@ type Config struct { } type ccResponse struct { - NextURL string `json:"next_url"` - Resources []resource `json:"resources"` - TotalResults int `json:"total_results"` + Pagination pagination `json:"pagination"` + Resources []resource `json:"resources"` } -type resource struct { - Metadata metadata `json:"metadata"` - Entity entity `json:"entity"` +type pagination struct { + Next href `json:"next"` +} + +type href struct { + Href string `json:"href"` } -type metadata struct { +type resource struct { GUID string `json:"guid"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Relationships relationships `json:"relationships"` } -type entity struct { - Name string `json:"name"` - OrganizationGUID string `json:"organization_guid"` +type relationships struct { + Organization relOrganization `json:"organization"` + Space relSpace `json:"space"` } +type relOrganization struct { + Data data `json:"data"` +} + +type relSpace struct { + Data data `json:"data"` +} + +type data struct { + GUID string `json:"guid"` +} + + type space struct { Name string GUID string @@ -204,40 +222,39 @@ func (c *cloudfoundryConnector) LoginURL(scopes connector.Scopes, callbackURL, s return oauth2Config.AuthCodeURL(state), nil } -func fetchRoleSpaces(baseURL, path, role string, client *http.Client) ([]space, error) { - resources, err := fetchResources(baseURL, path, client) - if err != nil { - return nil, fmt.Errorf("failed to fetch resources: %v", err) - } +func filterUserOrgsSpaces(userOrgsSpaces []resource, orgs []resource, spaces []resource) ([]org, []space) { + var filteredOrgs []org + var filteredSpaces []space + + orgMap := make(map[string]org) + spaceMap := make(map[string]space) - spaces := make([]space, len(resources)) - for i, resource := range resources { - spaces[i] = space{ - Name: resource.Entity.Name, - GUID: resource.Metadata.GUID, - OrgGUID: resource.Entity.OrganizationGUID, - Role: role, + for _, org_resource := range orgs { + orgMap[org_resource.GUID] = org{ + Name: org_resource.Name, + GUID: org_resource.GUID, } } - return spaces, nil -} - -func fetchOrgs(baseURL, path string, client *http.Client) ([]org, error) { - resources, err := fetchResources(baseURL, path, client) - if err != nil { - return nil, fmt.Errorf("failed to fetch resources: %v", err) + for _, space_resource := range spaces { + spaceMap[space_resource.GUID] = space{ + Name: space_resource.Name, + GUID: space_resource.GUID, + OrgGUID: space_resource.Relationships.Organization.Data.GUID, + } } - orgs := make([]org, len(resources)) - for i, resource := range resources { - orgs[i] = org{ - Name: resource.Entity.Name, - GUID: resource.Metadata.GUID, + for _, userOrgSpace := range userOrgsSpaces { + if space, ok := spaceMap[userOrgSpace.Relationships.Space.Data.GUID]; ok { + space.Role = strings.TrimPrefix(userOrgSpace.Type, "space_") + filteredSpaces = append(filteredSpaces, space) + } + if org, ok := orgMap[userOrgSpace.Relationships.Organization.Data.GUID]; ok { + filteredOrgs = append(filteredOrgs, org) } } - return orgs, nil + return filteredOrgs, filteredSpaces } func fetchResources(baseURL, path string, client *http.Client) ([]resource, error) { @@ -262,12 +279,12 @@ func fetchResources(baseURL, path string, client *http.Client) ([]resource, erro response := ccResponse{} err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { - return nil, fmt.Errorf("failed to parse spaces: %v", err) + return nil, fmt.Errorf("failed to parse response: %v", err) } resources = append(resources, response.Resources...) - path = response.NextURL + path = strings.TrimPrefix(response.Pagination.Next.Href, baseURL) if path == "" { break } @@ -362,36 +379,30 @@ func (c *cloudfoundryConnector) HandleCallback(s connector.Scopes, r *http.Reque identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) var ( - devPath = fmt.Sprintf("/v3/users/%s/spaces", identity.UserID) - auditorPath = fmt.Sprintf("/v3/users/%s/audited_spaces", identity.UserID) - managerPath = fmt.Sprintf("/v3/users/%s/managed_spaces", identity.UserID) - orgsPath = fmt.Sprintf("/v3/users/%s/organizations", identity.UserID) + orgsPath = fmt.Sprintf("/v3/organizations") + spacesPath = fmt.Sprintf("/v3/spaces") + userOrgsSpacesPath = fmt.Sprintf("/v3/roles?user_guids=%s&types=space_developer,space_manager,space_auditor,organization_user", identity.UserID) ) if s.Groups { - orgs, err := fetchOrgs(c.apiURL, orgsPath, client) - if err != nil { - return identity, fmt.Errorf("failed to fetch organizaitons: %v", err) - } - - developerSpaces, err := fetchRoleSpaces(c.apiURL, devPath, "developer", client) + userOrgsSpaces, err := fetchResources(c.apiURL, userOrgsSpacesPath, client) if err != nil { - return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) + return identity, fmt.Errorf("failed to fetch user organizations: %v", err) } - auditorSpaces, err := fetchRoleSpaces(c.apiURL, auditorPath, "auditor", client) + orgs, err := fetchResources(c.apiURL, orgsPath, client) if err != nil { - return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) + return identity, fmt.Errorf("failed to fetch organizaitons: %v", err) } - managerSpaces, err := fetchRoleSpaces(c.apiURL, managerPath, "manager", client) + spaces, err := fetchResources(c.apiURL, spacesPath, client) if err != nil { - return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) + return identity, fmt.Errorf("failed to fetch spaces: %v", err) } - developerSpaces = append(developerSpaces, append(auditorSpaces, managerSpaces...)...) + developerOrgs, developerSpaces := filterUserOrgsSpaces(userOrgsSpaces, orgs, spaces) - identity.Groups = getGroupsClaims(orgs, developerSpaces) + identity.Groups = getGroupsClaims(developerOrgs, developerSpaces) } if s.OfflineAccess { From d03f9004103b1cd2b9ccb0e545f281124265bd3e Mon Sep 17 00:00:00 2001 From: Kump3r Date: Fri, 10 Jan 2025 16:20:58 +0200 Subject: [PATCH 21/22] Addapt cloudfoundry_test.go acceptance tests for CF API v3 Signed-off-by: Kump3r --- connector/cloudfoundry/cloudfoundry.go | 19 +- connector/cloudfoundry/cloudfoundry_test.go | 351 +++++++++++++++++--- 2 files changed, 322 insertions(+), 48 deletions(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index 3ba88df5..472c14e9 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -47,27 +47,27 @@ type Config struct { type ccResponse struct { Pagination pagination `json:"pagination"` - Resources []resource `json:"resources"` + Resources []resource `json:"resources"` } type pagination struct { - Next href `json:"next"` + Next href `json:"next"` } type href struct { - Href string `json:"href"` + Href string `json:"href"` } type resource struct { - GUID string `json:"guid"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` + GUID string `json:"guid"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` Relationships relationships `json:"relationships"` } type relationships struct { Organization relOrganization `json:"organization"` - Space relSpace `json:"space"` + Space relSpace `json:"space"` } type relOrganization struct { @@ -82,7 +82,6 @@ type data struct { GUID string `json:"guid"` } - type space struct { Name string GUID string @@ -379,8 +378,8 @@ func (c *cloudfoundryConnector) HandleCallback(s connector.Scopes, r *http.Reque identity.EmailVerified, _ = userInfoResult["email_verified"].(bool) var ( - orgsPath = fmt.Sprintf("/v3/organizations") - spacesPath = fmt.Sprintf("/v3/spaces") + orgsPath = "/v3/organizations" + spacesPath = "/v3/spaces" userOrgsSpacesPath = fmt.Sprintf("/v3/roles?user_guids=%s&types=space_developer,space_manager,space_auditor,organization_user", identity.UserID) ) diff --git a/connector/cloudfoundry/cloudfoundry_test.go b/connector/cloudfoundry/cloudfoundry_test.go index e90f66a7..383ade22 100644 --- a/connector/cloudfoundry/cloudfoundry_test.go +++ b/connector/cloudfoundry/cloudfoundry_test.go @@ -97,25 +97,51 @@ func TestHandleCallback(t *testing.T) { }) } -func testSpaceHandler(reqURL, spaceAPIEndpoint string) (result map[string]interface{}) { - fullURL := fmt.Sprintf("%s?order-direction=asc&page=2&results-per-page=50", spaceAPIEndpoint) - if strings.Contains(reqURL, fullURL) { +func testSpaceHandler(reqURL string) (result map[string]interface{}) { + if strings.Contains(reqURL, "spaces?page=2&per_page=50") { result = map[string]interface{}{ + "pagination": map[string]interface{}{ + "next": map[string]interface{}{ + "href": nil, + }, + }, "resources": []map[string]interface{}{ { - "metadata": map[string]string{"guid": "some-space-guid-2"}, - "entity": map[string]string{"name": "some-space-name-2", "organization_guid": "some-org-guid-2"}, + "guid": "some-space-guid-2", + "name": "some-space-name-2", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": nil, + }, }, }, } } else { - nextURL := fmt.Sprintf("/v3/users/12345/%s?order-direction=asc&page=2&results-per-page=50", spaceAPIEndpoint) + nextURL := fmt.Sprintf("%s?page=2&per_page=50", reqURL) result = map[string]interface{}{ - "next_url": nextURL, + "pagination": map[string]interface{}{ + "next": map[string]interface{}{ + "href": nextURL, + }, + }, "resources": []map[string]interface{}{ { - "metadata": map[string]string{"guid": "some-space-guid-1"}, - "entity": map[string]string{"name": "some-space-name-1", "organization_guid": "some-org-guid-1"}, + "guid": "some-space-guid-1", + "name": "some-space-name-1", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-1", + }, + }, + "space": nil, + }, }, }, } @@ -124,30 +150,290 @@ func testSpaceHandler(reqURL, spaceAPIEndpoint string) (result map[string]interf } func testOrgHandler(reqURL string) (result map[string]interface{}) { - if strings.Contains(reqURL, "organizations?order-direction=asc&page=2&results-per-page=50") { + if strings.Contains(reqURL, "organizations?page=2&per_page=50") { + result = map[string]interface{}{ + "pagination": map[string]interface{}{ + "next": map[string]interface{}{ + "href": nil, + }, + }, + "resources": []map[string]interface{}{ + { + "guid": "some-org-guid-3", + "name": "some-org-name-3", + "relationships": map[string]interface{}{ + "user": nil, + "organization": nil, + "space": nil, + }, + }, + { + "guid": "some-org-guid-4", + "name": "some-org-name-4", + "relationships": map[string]interface{}{ + "user": nil, + "organization": nil, + "space": nil, + }, + }, + }, + } + } else { + nextURL := fmt.Sprintf("%s?page=2&per_page=50", reqURL) + result = map[string]interface{}{ + "pagination": map[string]interface{}{ + "next": map[string]interface{}{ + "href": nextURL, + }, + }, + "resources": []map[string]interface{}{ + { + "guid": "some-org-guid-1", + "name": "some-org-name-1", + "relationships": map[string]interface{}{ + "user": nil, + "organization": nil, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-1", + }, + }, + }, + }, + { + "guid": "some-org-guid-2", + "name": "some-org-name-2", + "relationships": map[string]interface{}{ + "user": nil, + "organization": nil, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, + }, + }, + } + } + return result +} + +func testUserOrgsSpacesHandler(reqURL string) (result map[string]interface{}) { + if strings.Contains(reqURL, "page=2&per_page=50") { result = map[string]interface{}{ + "pagination": map[string]interface{}{ + "next": map[string]interface{}{ + "href": nil, + }, + }, "resources": []map[string]interface{}{ { - "metadata": map[string]string{"guid": "some-org-guid-3"}, - "entity": map[string]string{"name": "some-org-name-3"}, + "guid": "some-type-guid-3", + "type": "organization_user", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-3", + }, + }, + "space": nil, + }, + }, + { + "guid": "some-type-guid-4", + "type": "organization_user", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-4", + }, + }, + "space": nil, + }, + }, + { + "guid": "some-type-guid-1", + "type": "space_manager", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-1", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-1", + }, + }, + }, + }, + { + "guid": "some-type-guid-2", + "type": "space_developer", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, }, { - "metadata": map[string]string{"guid": "some-org-guid-4"}, - "entity": map[string]string{"name": "some-org-name-4"}, + "guid": "some-type-guid-2", + "type": "space_auditor", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, + }, + { + "guid": "some-type-guid-2", + "type": "space_manager", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, }, }, } } else { + nextURL := fmt.Sprintf("%s?page=2&per_page=50", reqURL) result = map[string]interface{}{ - "next_url": "/v3/users/12345/organizations?order-direction=asc&page=2&results-per-page=50", + "pagination": map[string]interface{}{ + "next": map[string]interface{}{ + "href": nextURL, + }, + }, "resources": []map[string]interface{}{ { - "metadata": map[string]string{"guid": "some-org-guid-1"}, - "entity": map[string]string{"name": "some-org-name-1"}, + "guid": "some-type-guid-1", + "type": "space_developer", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-1", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-1", + }, + }, + }, }, { - "metadata": map[string]string{"guid": "some-org-guid-2"}, - "entity": map[string]string{"name": "some-org-name-2"}, + "guid": "some-type-guid-1", + "type": "space_auditor", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-1", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-1", + }, + }, + }, + }, + { + "guid": "some-type-guid-1", + "type": "space_manager", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-1", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-1", + }, + }, + }, + }, + { + "guid": "some-type-guid-2", + "type": "space_developer", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, + }, + { + "guid": "some-type-guid-2", + "type": "space_auditor", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, + }, + { + "guid": "some-type-guid-2", + "type": "space_manager", + "relationships": map[string]interface{}{ + "user": nil, + "organization": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-org-guid-2", + }, + }, + "space": map[string]interface{}{ + "data": map[string]interface{}{ + "guid": "some-space-guid-2", + }, + }, + }, }, }, } @@ -198,27 +484,16 @@ func testSetup() *httptest.Server { }) }) - mux.HandleFunc("/v3/users/", func(w http.ResponseWriter, r *http.Request) { - var result map[string]interface{} - - reqURL := r.URL.String() - if strings.Contains(reqURL, "/spaces") { - result = testSpaceHandler(reqURL, "spaces") - } - - if strings.Contains(reqURL, "/audited_spaces") { - result = testSpaceHandler(reqURL, "audited_spaces") - } - - if strings.Contains(reqURL, "/managed_spaces") { - result = testSpaceHandler(reqURL, "managed_spaces") - } + mux.HandleFunc("/v3/organizations", func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(testOrgHandler(r.URL.String())) + }) - if strings.Contains(reqURL, "organizations") { - result = testOrgHandler(reqURL) - } + mux.HandleFunc("/v3/spaces", func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(testSpaceHandler(r.URL.String())) + }) - json.NewEncoder(w).Encode(result) + mux.HandleFunc("/v3/roles", func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(testUserOrgsSpacesHandler(r.URL.String())) }) return httptest.NewServer(mux) From cc648ece2cc0cf0b1e610cdab85ad4dd55ff98e6 Mon Sep 17 00:00:00 2001 From: Taylor Silva Date: Thu, 16 Jan 2025 18:47:18 +0000 Subject: [PATCH 22/22] update cf connector to use slog instead Signed-off-by: Taylor Silva --- connector/cloudfoundry/cloudfoundry.go | 21 ++++++++------------- connector/cloudfoundry/cloudfoundry_test.go | 6 +++--- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index 472c14e9..ff10bccd 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net" "net/http" "os" @@ -17,7 +18,6 @@ import ( "golang.org/x/oauth2" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) type cloudfoundryConnector struct { @@ -29,7 +29,7 @@ type cloudfoundryConnector struct { authorizationURL string userInfoURL string httpClient *http.Client - logger log.Logger + logger *slog.Logger } type connectorData struct { @@ -106,7 +106,7 @@ type login struct { Href string `json:"href"` } -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { var err error cloudfoundryConn := &cloudfoundryConnector{ @@ -125,16 +125,14 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) apiURL := strings.TrimRight(c.APIURL, "/") apiResp, err := cloudfoundryConn.httpClient.Get(apiURL) if err != nil { - logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) - return nil, err + return nil, fmt.Errorf("failed-to-send-request-to-cloud-controller-api: %w", err) } defer apiResp.Body.Close() if apiResp.StatusCode != http.StatusOK { err = fmt.Errorf("request failed with status %d", apiResp.StatusCode) - logger.Errorf("failed-get-info-response-from-api", err) - return nil, err + return nil, fmt.Errorf("failed-get-info-response-from-api: %w", err) } var apiResult infoResp @@ -144,14 +142,12 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) uaaURL := strings.TrimRight(apiResult.Links.Login.Href, "/") uaaResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL)) if err != nil { - logger.Errorf("failed-to-send-request-to-uaa-api", err) - return nil, err + return nil, fmt.Errorf("failed-to-send-request-to-uaa-api: %w", err) } if apiResp.StatusCode != http.StatusOK { err = fmt.Errorf("request failed with status %d", apiResp.StatusCode) - logger.Errorf("failed-to-get-well-known-config-response-from-api", err) - return nil, err + return nil, fmt.Errorf("failed-to-get-well-known-config-response-from-api: %w", err) } defer uaaResp.Body.Close() @@ -160,8 +156,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) err = json.NewDecoder(uaaResp.Body).Decode(&uaaResult) if err != nil { - logger.Errorf("failed-to-decode-response-from-uaa-api", err) - return nil, err + return nil, fmt.Errorf("failed-to-decode-response-from-uaa-api: %w", err) } cloudfoundryConn.tokenURL, _ = uaaResult["token_endpoint"].(string) diff --git a/connector/cloudfoundry/cloudfoundry_test.go b/connector/cloudfoundry/cloudfoundry_test.go index 383ade22..d15b3261 100644 --- a/connector/cloudfoundry/cloudfoundry_test.go +++ b/connector/cloudfoundry/cloudfoundry_test.go @@ -4,14 +4,14 @@ import ( "encoding/json" "errors" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "reflect" "strings" "testing" - "github.com/sirupsen/logrus" - "github.com/dexidp/dex/connector" ) @@ -510,7 +510,7 @@ func newConnector(t *testing.T, serverURL string) *cloudfoundryConnector { InsecureSkipVerify: true, } - log := logrus.New() + log := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) conn, err := testConfig.Open("id", log) if err != nil {