Browse Source

Merge cc648ece2c into 13f012fb81

pull/1624/merge
Rui Yang 4 days ago committed by GitHub
parent
commit
bf9dc83d93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      README.md
  2. 412
      connector/cloudfoundry/cloudfoundry.go
  3. 538
      connector/cloudfoundry/cloudfoundry_test.go
  4. 2
      server/server.go

1
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:

412
connector/cloudfoundry/cloudfoundry.go

@ -0,0 +1,412 @@
package cloudfoundry
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"os"
"sort"
"strings"
"time"
"golang.org/x/oauth2"
"github.com/dexidp/dex/connector"
)
type cloudfoundryConnector struct {
clientID string
clientSecret string
redirectURI string
apiURL string
tokenURL string
authorizationURL string
userInfoURL string
httpClient *http.Client
logger *slog.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 {
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 {
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"`
}
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
OrgGUID string
Role string
}
type org struct {
Name string
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 *slog.Logger) (connector.Connector, error) {
var err error
cloudfoundryConn := &cloudfoundryConnector{
clientID: c.ClientID,
clientSecret: c.ClientSecret,
apiURL: c.APIURL,
redirectURI: c.RedirectURI,
logger: logger,
}
cloudfoundryConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify)
if err != nil {
return nil, err
}
apiURL := strings.TrimRight(c.APIURL, "/")
apiResp, err := cloudfoundryConn.httpClient.Get(apiURL)
if err != nil {
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)
return nil, fmt.Errorf("failed-get-info-response-from-api: %w", err)
}
var apiResult infoResp
json.NewDecoder(apiResp.Body).Decode(&apiResult)
uaaURL := strings.TrimRight(apiResult.Links.Login.Href, "/")
uaaResp, err := cloudfoundryConn.httpClient.Get(fmt.Sprintf("%s/.well-known/openid-configuration", uaaURL))
if err != nil {
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)
return nil, fmt.Errorf("failed-to-get-well-known-config-response-from-api: %w", err)
}
defer uaaResp.Body.Close()
var uaaResult map[string]interface{}
err = json.NewDecoder(uaaResp.Body).Decode(&uaaResult)
if err != nil {
return nil, fmt.Errorf("failed-to-decode-response-from-uaa-api: %w", err)
}
cloudfoundryConn.tokenURL, _ = uaaResult["token_endpoint"].(string)
cloudfoundryConn.authorizationURL, _ = uaaResult["authorization_endpoint"].(string)
cloudfoundryConn.userInfoURL, _ = uaaResult["userinfo_endpoint"].(string)
return cloudfoundryConn, 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 := os.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 *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)
}
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 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)
for _, org_resource := range orgs {
orgMap[org_resource.GUID] = org{
Name: org_resource.Name,
GUID: org_resource.GUID,
}
}
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,
}
}
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 filteredOrgs, filteredSpaces
}
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)
}
defer resp.Body.Close()
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 response: %v", err)
}
resources = append(resources, response.Resources...)
path = strings.TrimPrefix(response.Pagination.Next.Href, baseURL)
if path == "" {
break
}
}
return resources, nil
}
func getGroupsClaims(orgs []org, spaces []space) []string {
var (
orgMap = map[string]string{}
orgSpaces = map[string][]space{}
groupsClaims = map[string]bool{}
)
for _, org := range orgs {
orgMap[org.GUID] = org.Name
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)
groupsClaims[space.GUID] = true
groupsClaims[fmt.Sprintf("%s:%s", space.GUID, space.Role)] = true
}
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
}
}
groups := make([]string, 0, len(groupsClaims))
for group := range groupsClaims {
groups = append(groups, group)
}
sort.Strings(groups)
return groups
}
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"))
}
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)
var (
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 {
userOrgsSpaces, err := fetchResources(c.apiURL, userOrgsSpacesPath, client)
if err != nil {
return identity, fmt.Errorf("failed to fetch user organizations: %v", err)
}
orgs, err := fetchResources(c.apiURL, orgsPath, client)
if err != nil {
return identity, fmt.Errorf("failed to fetch organizaitons: %v", err)
}
spaces, err := fetchResources(c.apiURL, spacesPath, client)
if err != nil {
return identity, fmt.Errorf("failed to fetch spaces: %v", err)
}
developerOrgs, developerSpaces := filterUserOrgsSpaces(userOrgsSpaces, orgs, spaces)
identity.Groups = getGroupsClaims(developerOrgs, developerSpaces)
}
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
}

538
connector/cloudfoundry/cloudfoundry_test.go

@ -0,0 +1,538 @@
package cloudfoundry
import (
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"github.com/dexidp/dex/connector"
)
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()
cloudfoundryConn := &cloudfoundryConnector{
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 := cloudfoundryConn.HandleCallback(connector.Scopes{Groups: true}, req)
expectEqual(t, err, nil)
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-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) {
identity, err := cloudfoundryConn.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 := cloudfoundryConn.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 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{}{
{
"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("%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-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,
},
},
},
}
}
return result
}
func testOrgHandler(reqURL string) (result map[string]interface{}) {
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{}{
{
"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",
},
},
},
},
{
"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{}{
"pagination": map[string]interface{}{
"next": map[string]interface{}{
"href": nextURL,
},
},
"resources": []map[string]interface{}{
{
"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",
},
},
},
},
{
"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",
},
},
},
},
},
}
}
return result
}
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("/", func(w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf("http://%s", r.Host)
json.NewEncoder(w).Encode(map[string]interface{}{
"links": map[string]interface{}{
"login": map[string]string{
"href": 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("/v3/organizations", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(testOrgHandler(r.URL.String()))
})
mux.HandleFunc("/v3/spaces", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(testSpaceHandler(r.URL.String()))
})
mux.HandleFunc("/v3/roles", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(testUserOrgsSpacesHandler(r.URL.String()))
})
return httptest.NewServer(mux)
}
func newConnector(t *testing.T, serverURL string) *cloudfoundryConnector {
callBackURL := fmt.Sprintf("%s/callback", serverURL)
testConfig := Config{
APIURL: serverURL,
ClientID: "test-client",
ClientSecret: "secret",
RedirectURI: callBackURL,
InsecureSkipVerify: true,
}
log := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
conn, err := testConfig.Open("id", log)
if err != nil {
t.Fatal(err)
}
cloudfoundryConn, ok := conn.(*cloudfoundryConnector)
if !ok {
t.Fatal(errors.New("it is not a cloudfoundry conn"))
}
return cloudfoundryConn
}
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)
}
}

2
server/server.go

@ -32,6 +32,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/cloudfoundry"
"github.com/dexidp/dex/connector/gitea"
"github.com/dexidp/dex/connector/github"
"github.com/dexidp/dex/connector/gitlab"
@ -701,6 +702,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) },
"cloudfoundry": func() ConnectorConfig { return new(cloudfoundry.Config) },
// Keep around for backwards compatibility.
"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
}

Loading…
Cancel
Save