diff --git a/connector/cloudfoundry/cloudfoundry.go b/connector/cloudfoundry/cloudfoundry.go index faa86bbab1..813472252d 100644 --- a/connector/cloudfoundry/cloudfoundry.go +++ b/connector/cloudfoundry/cloudfoundry.go @@ -46,23 +46,40 @@ 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 pagination struct { + Next href `json:"next"` +} + +type href struct { + Href string `json:"href"` } type resource struct { - Metadata metadata `json:"metadata"` - Entity entity `json:"entity"` + GUID string `json:"guid"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Relationships relationships `json:"relationships"` } -type metadata struct { - GUID string `json:"guid"` +type relationships struct { + Organization relOrganization `json:"organization"` + Space relSpace `json:"space"` +} + +type relOrganization struct { + Data data `json:"data"` } -type entity struct { - Name string `json:"name"` - OrganizationGUID string `json:"organization_guid"` +type relSpace struct { + Data data `json:"data"` +} + +type data struct { + GUID string `json:"guid"` } type space struct { @@ -77,6 +94,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 @@ -94,7 +123,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(apiURL) if err != nil { logger.Errorf("failed-to-send-request-to-cloud-controller-api", err) return nil, err @@ -108,10 +137,11 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) return nil, err } - var apiResult map[string]interface{} + var apiResult infoResp + 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) @@ -191,40 +221,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 - 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, + orgMap := make(map[string]org) + spaceMap := make(map[string]space) + + 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) { @@ -249,12 +278,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 } @@ -349,36 +378,30 @@ 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) + 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) ) if s.Groups { - orgs, err := fetchOrgs(c.apiURL, orgsPath, client) + userOrgsSpaces, err := fetchResources(c.apiURL, userOrgsSpacesPath, client) if err != nil { - return identity, fmt.Errorf("failed to fetch organizaitons: %v", err) + return identity, fmt.Errorf("failed to fetch user organizations: %v", err) } - developerSpaces, err := fetchRoleSpaces(c.apiURL, devPath, "developer", client) + orgs, err := fetchResources(c.apiURL, orgsPath, client) if err != nil { - return identity, fmt.Errorf("failed to fetch spaces for developer roles: %v", err) - } - - auditorSpaces, err := fetchRoleSpaces(c.apiURL, auditorPath, "auditor", 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 { diff --git a/connector/cloudfoundry/cloudfoundry_test.go b/connector/cloudfoundry/cloudfoundry_test.go index 1e6af394de..ceb1aa519e 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("/v2/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, + }, }, { - "metadata": map[string]string{"guid": "some-org-guid-4"}, - "entity": map[string]string{"name": "some-org-name-4"}, + "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", + }, + }, + }, + }, + { + "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": "/v2/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", + }, + }, + }, + }, + { + "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", + }, + }, + }, }, { - "metadata": map[string]string{"guid": "some-org-guid-2"}, - "entity": map[string]string{"name": "some-org-name-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", + }, + }, + }, }, }, } @@ -165,11 +451,15 @@ func testSetup() *httptest.Server { }) }) - mux.HandleFunc("/v2/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, + }, + }, }) }) @@ -194,27 +484,16 @@ 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") - } - - 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)