keystone: test cases, refactoring and cleanup
This commit is contained in:
		
				
					committed by
					
						 Krzysztof Balka
						Krzysztof Balka
					
				
			
			
				
	
			
			
			
						parent
						
							a965365a2b
						
					
				
				
					commit
					88d1e2b041
				
			| @@ -13,13 +13,14 @@ services: | |||||||
|   - docker |   - docker | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   - DEX_POSTGRES_DATABASE=postgres DEX_POSTGRES_USER=postgres DEX_POSTGRES_HOST="localhost" DEX_ETCD_ENDPOINTS=http://localhost:2379 DEX_LDAP_TESTS=1 DEBIAN_FRONTEND=noninteractive |   - DEX_POSTGRES_DATABASE=postgres DEX_POSTGRES_USER=postgres DEX_POSTGRES_HOST="localhost" DEX_ETCD_ENDPOINTS=http://localhost:2379 DEX_LDAP_TESTS=1 DEBIAN_FRONTEND=noninteractive DEX_KEYSTONE_URL=http://localhost:5000 DEX_KEYSTONE_ADMIN_URL=http://localhost:35357 | ||||||
|  |  | ||||||
| install: | install: | ||||||
|   - sudo -E apt-get install -y --force-yes slapd time ldap-utils |   - sudo -E apt-get install -y --force-yes slapd time ldap-utils | ||||||
|   - sudo /etc/init.d/slapd stop |   - sudo /etc/init.d/slapd stop | ||||||
|   - docker run -d --net=host gcr.io/etcd-development/etcd:v3.2.9 |   - docker run -d --net=host gcr.io/etcd-development/etcd:v3.2.9 | ||||||
|  |   - docker run -d -p 0.0.0.0:5000:5000 -p 0.0.0.0:35357:35357 openio/openstack-keystone | ||||||
|  |   - sleep 60s | ||||||
|  |  | ||||||
| script: | script: | ||||||
|   - make testall |   - make testall | ||||||
|   | |||||||
| @@ -11,12 +11,15 @@ FROM alpine:3.8 | |||||||
| # experience when this doesn't work out of the box. | # experience when this doesn't work out of the box. | ||||||
| # | # | ||||||
| # OpenSSL is required so wget can query HTTPS endpoints for health checking. | # OpenSSL is required so wget can query HTTPS endpoints for health checking. | ||||||
| RUN apk add --update ca-certificates openssl bash | RUN apk add --update ca-certificates openssl | ||||||
|  |  | ||||||
|  | COPY --from=0 /go/bin/dex /usr/local/bin/dex | ||||||
|  |  | ||||||
| # Import frontend assets and set the correct CWD directory so the assets | # Import frontend assets and set the correct CWD directory so the assets | ||||||
| # are in the default path. | # are in the default path. | ||||||
| COPY web /web | COPY web /web | ||||||
| WORKDIR / | WORKDIR / | ||||||
|  |  | ||||||
| EXPOSE 5500-5600 | ENTRYPOINT ["dex"] | ||||||
| CMD ["bash"] |  | ||||||
|  | CMD ["version"] | ||||||
|   | |||||||
| @@ -35,7 +35,6 @@ type Identity struct { | |||||||
| 	// | 	// | ||||||
| 	// This data is never shared with end users, OAuth clients, or through the API. | 	// This data is never shared with end users, OAuth clients, or through the API. | ||||||
| 	ConnectorData []byte | 	ConnectorData []byte | ||||||
| 	Password string |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // PasswordConnector is an interface implemented by connectors which take a | // PasswordConnector is an interface implemented by connectors which take a | ||||||
|   | |||||||
| @@ -2,163 +2,186 @@ | |||||||
| package keystone | package keystone | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
| 	"github.com/dexidp/dex/connector" |  | ||||||
| 	"github.com/sirupsen/logrus" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"net/http" |  | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  |  | ||||||
|  | 	"github.com/dexidp/dex/connector" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	_ connector.PasswordConnector = &Connector{} | 	_ connector.PasswordConnector = &keystoneConnector{} | ||||||
|   	_ connector.RefreshConnector = &Connector{} | 	_ connector.RefreshConnector  = &keystoneConnector{} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Open returns an authentication strategy using Keystone. | // Open returns an authentication strategy using Keystone. | ||||||
| func (c *Config) Open(id string, logger logrus.FieldLogger) (connector.Connector, error) { | func (c *Config) Open(id string, logger logrus.FieldLogger) (connector.Connector, error) { | ||||||
| 	return &Connector{c.Domain, c.KeystoneHost, | 	return &keystoneConnector{c.Domain, c.KeystoneHost, | ||||||
| 	c.KeystoneUsername, c.KeystonePassword, logger}, nil | 		c.KeystoneUsername, c.KeystonePassword, logger}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p Connector) Close() error { return nil } | func (p *keystoneConnector) Close() error { return nil } | ||||||
|  |  | ||||||
| func (p Connector) Login(ctx context.Context, s connector.Scopes, username, password string) ( | func (p *keystoneConnector) Login(ctx context.Context, s connector.Scopes, username, password string) ( | ||||||
| 		identity connector.Identity, validPassword bool, err error) { | 	identity connector.Identity, validPassword bool, err error) { | ||||||
| 	response, err := p.getTokenResponse(username, password) | 	resp, err := p.getTokenResponse(ctx, username, password) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return identity, false, fmt.Errorf("keystone: error %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Providing wrong password or wrong keystone URI throws error | 	// Providing wrong password or wrong keystone URI throws error | ||||||
| 	if err == nil && response.StatusCode == 201 { | 	if resp.StatusCode == 201 { | ||||||
|     	token := response.Header["X-Subject-Token"][0] | 		token := resp.Header.Get("X-Subject-Token") | ||||||
| 		data, _ := ioutil.ReadAll(response.Body) | 		data, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return identity, false, err | ||||||
|  | 		} | ||||||
|  | 		defer resp.Body.Close() | ||||||
|  |  | ||||||
|     	var tokenResponse = new(TokenResponse) | 		var tokenResp = new(tokenResponse) | ||||||
|     	err := json.Unmarshal(data, &tokenResponse) | 		err = json.Unmarshal(data, &tokenResp) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return identity, false, fmt.Errorf("keystone: invalid token response: %v", err) | ||||||
|  | 		} | ||||||
|  | 		groups, err := p.getUserGroups(ctx, tokenResp.Token.User.ID, token) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return identity, false, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|     	if err != nil { | 		identity.Username = username | ||||||
|       		fmt.Printf("keystone: invalid token response: %v", err) | 		identity.UserID = tokenResp.Token.User.ID | ||||||
|       		return identity, false, err | 		identity.Groups = groups | ||||||
|     	} |  | ||||||
|     	groups, err := p.getUserGroups(tokenResponse.Token.User.ID, token) |  | ||||||
|  |  | ||||||
|     	if err != nil { |  | ||||||
|       		return identity, false, err |  | ||||||
|     	} |  | ||||||
|  |  | ||||||
| 		identity.Username =	username |  | ||||||
|     	identity.UserID = tokenResponse.Token.User.ID |  | ||||||
|    	 	identity.Groups = groups |  | ||||||
| 		return identity, true, nil | 		return identity, true, nil | ||||||
|  |  | ||||||
| 	} else if err != nil { |  | ||||||
|     	fmt.Printf("keystone: error %v", err) |  | ||||||
| 		return identity, false, err |  | ||||||
|  |  | ||||||
| 	} else { |  | ||||||
| 		data, _ := ioutil.ReadAll(response.Body) |  | ||||||
| 		fmt.Println(string(data)) |  | ||||||
| 		return identity, false, err |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return identity, false, nil | 	return identity, false, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p Connector) Prompt() string { return "username" } | func (p *keystoneConnector) Prompt() string { return "username" } | ||||||
|  |  | ||||||
| func (p Connector) Refresh( | func (p *keystoneConnector) Refresh( | ||||||
| 	ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { | 	ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { | ||||||
|  |  | ||||||
|   	if len(identity.ConnectorData) == 0 { | 	token, err := p.getAdminToken(ctx) | ||||||
|   		return identity, nil | 	if err != nil { | ||||||
|  | 		return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	token, err := p.getAdminToken() | 	ok, err := p.checkIfUserExists(ctx, identity.UserID, token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return identity, err | ||||||
|  | 	} | ||||||
|  | 	if !ok { | ||||||
|  | 		return identity, fmt.Errorf("keystone: user %q does not exist", identity.UserID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|   	if err != nil { | 	groups, err := p.getUserGroups(ctx, identity.UserID, token) | ||||||
|     	fmt.Printf("keystone: failed to obtain admin token") | 	if err != nil { | ||||||
|     	return identity, err | 		return identity, err | ||||||
|   	} | 	} | ||||||
|  |  | ||||||
|   	ok := p.checkIfUserExists(identity.UserID, token) | 	identity.Groups = groups | ||||||
|   	if !ok { |  | ||||||
|   		fmt.Printf("keystone: user %q does not exist\n", identity.UserID) |  | ||||||
|      	return identity, fmt.Errorf("keystone: user %q does not exist", identity.UserID) |  | ||||||
|   	} |  | ||||||
|  |  | ||||||
|   	groups, err := p.getUserGroups(identity.UserID, token) |  | ||||||
|   	if err != nil { |  | ||||||
|     	fmt.Printf("keystone: Failed to fetch user %q groups", identity.UserID) |  | ||||||
|     	return identity, fmt.Errorf("keystone: failed to fetch user %q groups", identity.UserID) |  | ||||||
|   	} |  | ||||||
|  |  | ||||||
|   	identity.Groups = groups |  | ||||||
|   	fmt.Printf("Identity data after use of refresh token: %v", identity) |  | ||||||
| 	return identity, nil | 	return identity, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (p *keystoneConnector) getTokenResponse(ctx context.Context, username, pass string) (response *http.Response, err error) { | ||||||
| func (p Connector) getTokenResponse(username, password string) (response *http.Response, err error) { | 	client := &http.Client{} | ||||||
| 	jsonData := LoginRequestData{ | 	jsonData := loginRequestData{ | ||||||
| 		Auth: Auth{ | 		auth: auth{ | ||||||
| 			Identity: Identity{ | 			Identity: identity{ | ||||||
| 				Methods:[]string{"password"}, | 				Methods: []string{"password"}, | ||||||
| 				Password: Password{ | 				Password: password{ | ||||||
| 					User: User{ | 					User: user{ | ||||||
| 						Name: username, | 						Name:     username, | ||||||
| 						Domain: Domain{ID:p.Domain}, | 						Domain:   domain{ID: p.Domain}, | ||||||
| 						Password: password, | 						Password: pass, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	jsonValue, _ := json.Marshal(jsonData) | 	jsonValue, err := json.Marshal(jsonData) | ||||||
|   	loginURI := p.KeystoneHost + "/v3/auth/tokens" | 	if err != nil { | ||||||
| 	return http.Post(loginURI, "application/json", bytes.NewBuffer(jsonValue)) | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	authTokenURL := p.KeystoneHost + "/v3/auth/tokens/" | ||||||
|  | 	req, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(jsonValue)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  | 	req = req.WithContext(ctx) | ||||||
|  |  | ||||||
|  | 	return client.Do(req) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p Connector) getAdminToken()(string, error) { | func (p *keystoneConnector) getAdminToken(ctx context.Context) (string, error) { | ||||||
|   	response, err := p.getTokenResponse(p.KeystoneUsername, p.KeystonePassword) | 	resp, err := p.getTokenResponse(ctx, p.KeystoneUsername, p.KeystonePassword) | ||||||
|   	if err!= nil { | 	if err != nil { | ||||||
|     	return "", err | 		return "", err | ||||||
|   	} | 	} | ||||||
|   	token := response.Header["X-Subject-Token"][0] | 	token := resp.Header.Get("X-Subject-Token") | ||||||
|   	return token, nil | 	return token, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p Connector) checkIfUserExists(userID string, token string) (bool) { | func (p *keystoneConnector) checkIfUserExists(ctx context.Context, userID string, token string) (bool, error) { | ||||||
|   	groupsURI := p.KeystoneHost + "/v3/users/" + userID | 	userURL := p.KeystoneHost + "/v3/users/" + userID | ||||||
|   	client := &http.Client{} | 	client := &http.Client{} | ||||||
|   	req, _ := http.NewRequest("GET", groupsURI, nil) | 	req, err := http.NewRequest("GET", userURL, nil) | ||||||
|   	req.Header.Set("X-Auth-Token", token) | 	if err != nil { | ||||||
|   	response, err :=  client.Do(req) | 		return false, err | ||||||
|   	if err == nil && response.StatusCode == 200 { | 	} | ||||||
|     	return true |  | ||||||
|   	} | 	req.Header.Set("X-Auth-Token", token) | ||||||
|   	return false | 	req = req.WithContext(ctx) | ||||||
|  | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode == 200 { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 	return false, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p Connector) getUserGroups(userID string, token string) ([]string, error) { | func (p *keystoneConnector) getUserGroups(ctx context.Context, userID string, token string) ([]string, error) { | ||||||
|   	groupsURI := p.KeystoneHost + "/v3/users/" + userID + "/groups" | 	client := &http.Client{} | ||||||
|   	client := &http.Client{} | 	groupsURL := p.KeystoneHost + "/v3/users/" + userID + "/groups" | ||||||
|   	req, _ := http.NewRequest("GET", groupsURI, nil) |  | ||||||
|   	req.Header.Set("X-Auth-Token", token) |  | ||||||
|   	response, err :=  client.Do(req) |  | ||||||
|  |  | ||||||
|   	if err != nil { | 	req, err := http.NewRequest("GET", groupsURL, nil) | ||||||
|     	fmt.Printf("keystone: error while fetching user %q groups\n", userID) | 	req.Header.Set("X-Auth-Token", token) | ||||||
|     	return nil, err | 	req = req.WithContext(ctx) | ||||||
|   	} | 	resp, err := client.Do(req) | ||||||
|   	data, _ := ioutil.ReadAll(response.Body) | 	if err != nil { | ||||||
|   	var groupsResponse = new(GroupsResponse) | 		p.Logger.Errorf("keystone: error while fetching user %q groups\n", userID) | ||||||
|   	err = json.Unmarshal(data, &groupsResponse) | 		return nil, err | ||||||
|   	if err != nil { | 	} | ||||||
|     	return nil, err |  | ||||||
|   	} | 	data, err := ioutil.ReadAll(resp.Body) | ||||||
|   	groups := []string{} | 	if err != nil { | ||||||
|   	for _, group := range groupsResponse.Groups { | 		return nil, err | ||||||
|   		groups = append(groups, group.Name) | 	} | ||||||
|   	} | 	defer resp.Body.Close() | ||||||
|   	return groups, nil |  | ||||||
|  | 	var groupsResp = new(groupsResponse) | ||||||
|  |  | ||||||
|  | 	err = json.Unmarshal(data, &groupsResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	groups := make([]string, len(groupsResp.Groups)) | ||||||
|  | 	for i, group := range groupsResp.Groups { | ||||||
|  | 		groups[i] = group.Name | ||||||
|  | 	} | ||||||
|  | 	return groups, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,275 +1,358 @@ | |||||||
| package keystone | package keystone | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" |  | ||||||
| 	"github.com/dexidp/dex/connector" |  | ||||||
|  |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
|   	"time" |  | ||||||
|   	"net/http" |  | ||||||
|  |  | ||||||
| 	"github.com/docker/docker/api/types" |  | ||||||
| 	"github.com/docker/docker/api/types/container" |  | ||||||
| 	"github.com/docker/docker/client" |  | ||||||
|    	networktypes "github.com/docker/docker/api/types/network" |  | ||||||
|   	"github.com/docker/go-connections/nat" |  | ||||||
| 	"golang.org/x/net/context" |  | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/dexidp/dex/connector" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const dockerCliVersion = "1.37" | const ( | ||||||
|  | 	adminUser   = "demo" | ||||||
|  | 	adminPass   = "DEMO_PASS" | ||||||
|  | 	invalidPass = "WRONG_PASS" | ||||||
|  |  | ||||||
| const exposedKeystonePort = "5000" | 	testUser   = "test_user" | ||||||
| const exposedKeystonePortAdmin = "35357" | 	testPass   = "test_pass" | ||||||
|  | 	testEmail  = "test@example.com" | ||||||
|  | 	testGroup  = "test_group" | ||||||
|  | 	testDomain = "default" | ||||||
|  | ) | ||||||
|  |  | ||||||
| const keystoneHost = "http://localhost" | var ( | ||||||
| const keystoneURL = keystoneHost + ":" + exposedKeystonePort | 	keystoneURL      = "" | ||||||
| const keystoneAdminURL = keystoneHost + ":" + exposedKeystonePortAdmin | 	keystoneAdminURL = "" | ||||||
| const authTokenURL = keystoneURL + "/v3/auth/tokens/" | 	authTokenURL     = "" | ||||||
| const userURL = keystoneAdminURL + "/v3/users/" | 	usersURL         = "" | ||||||
| const groupURL = keystoneAdminURL + "/v3/groups/" | 	groupsURL        = "" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func startKeystoneContainer() string { | type userResponse struct { | ||||||
| 	ctx := context.Background() | 	User struct { | ||||||
| 	cli, err := client.NewClientWithOpts(client.WithVersion(dockerCliVersion)) | 		ID string `json:"id"` | ||||||
|  | 	} `json:"user"` | ||||||
| 	if err != nil { |  | ||||||
|     	fmt.Printf("Error %v", err) |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	imageName := "openio/openstack-keystone" |  | ||||||
| 	out, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{}) |  | ||||||
| 	if err != nil { |  | ||||||
|     	fmt.Printf("Error %v", err) |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 	io.Copy(os.Stdout, out) |  | ||||||
|  |  | ||||||
| 	resp, err := cli.ContainerCreate(ctx, &container.Config{ |  | ||||||
| 		Image: imageName, |  | ||||||
|     }, &container.HostConfig{ |  | ||||||
|     		PortBindings: nat.PortMap{ |  | ||||||
|         		"5000/tcp": []nat.PortBinding{ |  | ||||||
|             		{ |  | ||||||
|                 		HostIP:   "0.0.0.0", |  | ||||||
|                 		HostPort: exposedKeystonePort, |  | ||||||
|             		}, |  | ||||||
|         		}, |  | ||||||
| 				"35357/tcp": []nat.PortBinding{ |  | ||||||
| 					{ |  | ||||||
| 					HostIP:   "0.0.0.0", |  | ||||||
| 					HostPort: exposedKeystonePortAdmin, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
|     		}, |  | ||||||
| 		}, &networktypes.NetworkingConfig{}, "dex_keystone_test") |  | ||||||
|  |  | ||||||
| 	if err != nil { |  | ||||||
|     	fmt.Printf("Error %v", err) |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fmt.Println(resp.ID) |  | ||||||
|   	return resp.ID |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func cleanKeystoneContainer(ID string) { | type groupResponse struct { | ||||||
| 	ctx := context.Background() | 	Group struct { | ||||||
| 	cli, err := client.NewClientWithOpts(client.WithVersion(dockerCliVersion)) | 		ID string `json:"id"` | ||||||
| 	if err != nil { | 	} `json:"group"` | ||||||
| 		fmt.Printf("Error %v", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	duration := time.Duration(1) |  | ||||||
| 	if err:= cli.ContainerStop(ctx, ID, &duration); err != nil { |  | ||||||
| 		fmt.Printf("Error %v", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if err:= cli.ContainerRemove(ctx, ID, types.ContainerRemoveOptions{}); err != nil { |  | ||||||
| 		fmt.Printf("Error %v", err) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAdminToken(admin_name, admin_pass string) (token string) { | func getAdminToken(t *testing.T, adminName, adminPass string) (token, id string) { | ||||||
|  | 	t.Helper() | ||||||
| 	client := &http.Client{} | 	client := &http.Client{} | ||||||
|  |  | ||||||
| 	jsonData := LoginRequestData{ | 	jsonData := loginRequestData{ | ||||||
| 		Auth: Auth{ | 		auth: auth{ | ||||||
| 			Identity: Identity{ | 			Identity: identity{ | ||||||
| 				Methods:[]string{"password"}, | 				Methods: []string{"password"}, | ||||||
| 				Password: Password{ | 				Password: password{ | ||||||
| 					User: User{ | 					User: user{ | ||||||
| 						Name: admin_name, | 						Name:     adminName, | ||||||
| 						Domain: Domain{ID: "default"}, | 						Domain:   domain{ID: testDomain}, | ||||||
| 						Password: admin_pass, | 						Password: adminPass, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	body, _ := json.Marshal(jsonData) | 	body, err := json.Marshal(jsonData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	req, _ := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(body)) | 	req, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(body)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("keystone: failed to obtain admin token: %v\n", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	req.Header.Set("Content-Type", "application/json") | 	req.Header.Set("Content-Type", "application/json") | ||||||
| 	resp, _ := client.Do(req) | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	token = resp.Header["X-Subject-Token"][0] | 	token = resp.Header.Get("X-Subject-Token") | ||||||
| 	return token |  | ||||||
|  | 	data, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	var tokenResp = new(tokenResponse) | ||||||
|  | 	err = json.Unmarshal(data, &tokenResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	return token, tokenResp.Token.User.ID | ||||||
| } | } | ||||||
|  |  | ||||||
| func createUser(token, user_name, user_email, user_pass string) (string){ | func createUser(t *testing.T, token, userName, userEmail, userPass string) string { | ||||||
|  | 	t.Helper() | ||||||
| 	client := &http.Client{} | 	client := &http.Client{} | ||||||
|  |  | ||||||
| 	createUserData := CreateUserRequest{ | 	createUserData := map[string]interface{}{ | ||||||
| 		CreateUser: CreateUserForm{ | 		"user": map[string]interface{}{ | ||||||
| 			Name: user_name, | 			"name":     userName, | ||||||
| 			Email: user_email, | 			"email":    userEmail, | ||||||
| 			Enabled: true, | 			"enabled":  true, | ||||||
| 			Password: user_pass, | 			"password": userPass, | ||||||
| 			Roles: []string{"admin"}, | 			"roles":    []string{"admin"}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	body, _ := json.Marshal(createUserData) | 	body, err := json.Marshal(createUserData) | ||||||
|  |  | ||||||
| 	req, _ := http.NewRequest("POST", userURL, bytes.NewBuffer(body)) |  | ||||||
| 	req.Header.Set("X-Auth-Token", token) |  | ||||||
| 	req.Header.Add("Content-Type", "application/json") |  | ||||||
| 	resp, _ := client.Do(req) |  | ||||||
|  |  | ||||||
| 	data, _ := ioutil.ReadAll(resp.Body) |  | ||||||
| 	var userResponse = new(UserResponse) |  | ||||||
| 	err := json.Unmarshal(data, &userResponse) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fmt.Println(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fmt.Println(userResponse.User.ID) | 	req, err := http.NewRequest("POST", usersURL, bytes.NewBuffer(body)) | ||||||
| 	return userResponse.User.ID | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
| } | 	} | ||||||
|  |  | ||||||
| func deleteUser(token, id string) { |  | ||||||
| 	client := &http.Client{} |  | ||||||
|  |  | ||||||
| 	deleteUserURI := userURL + id |  | ||||||
| 	fmt.Println(deleteUserURI) |  | ||||||
| 	req, _ := http.NewRequest("DELETE", deleteUserURI, nil) |  | ||||||
| 	req.Header.Set("X-Auth-Token", token) | 	req.Header.Set("X-Auth-Token", token) | ||||||
| 	resp, _ := client.Do(req) | 	req.Header.Add("Content-Type", "application/json") | ||||||
| 	fmt.Println(resp) | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	var userResp = new(userResponse) | ||||||
|  | 	err = json.Unmarshal(data, &userResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return userResp.User.ID | ||||||
| } | } | ||||||
|  |  | ||||||
| func createGroup(token, description, name string) string{ | // delete group or user | ||||||
|  | func delete(t *testing.T, token, id, uri string) { | ||||||
|  | 	t.Helper() | ||||||
| 	client := &http.Client{} | 	client := &http.Client{} | ||||||
|  |  | ||||||
| 	createGroupData := CreateGroup{ | 	deleteURI := uri + id | ||||||
| 		CreateGroupForm{ | 	req, err := http.NewRequest("DELETE", deleteURI, nil) | ||||||
| 			Description: description, | 	if err != nil { | ||||||
| 			Name: name, | 		t.Fatalf("error: %v", err) | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("X-Auth-Token", token) | ||||||
|  | 	client.Do(req) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createGroup(t *testing.T, token, description, name string) string { | ||||||
|  | 	t.Helper() | ||||||
|  | 	client := &http.Client{} | ||||||
|  |  | ||||||
|  | 	createGroupData := map[string]interface{}{ | ||||||
|  | 		"group": map[string]interface{}{ | ||||||
|  | 			"name":        name, | ||||||
|  | 			"description": description, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	body, _ := json.Marshal(createGroupData) | 	body, err := json.Marshal(createGroupData) | ||||||
|  |  | ||||||
| 	req, _ := http.NewRequest("POST", groupURL, bytes.NewBuffer(body)) |  | ||||||
| 	req.Header.Set("X-Auth-Token", token) |  | ||||||
| 	req.Header.Add("Content-Type", "application/json") |  | ||||||
| 	resp, _ := client.Do(req) |  | ||||||
| 	data, _ := ioutil.ReadAll(resp.Body) |  | ||||||
|  |  | ||||||
| 	var groupResponse = new(GroupID) |  | ||||||
| 	err := json.Unmarshal(data, &groupResponse) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fmt.Println(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return groupResponse.Group.ID | 	req, err := http.NewRequest("POST", groupsURL, bytes.NewBuffer(body)) | ||||||
| } | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
| func addUserToGroup(token, groupId, userId string) { | 	} | ||||||
| 	uri := groupURL + groupId + "/users/" + userId |  | ||||||
| 	client := &http.Client{} |  | ||||||
| 	req, _ := http.NewRequest("PUT", uri, nil) |  | ||||||
| 	req.Header.Set("X-Auth-Token", token) | 	req.Header.Set("X-Auth-Token", token) | ||||||
| 	resp, _ := client.Do(req) | 	req.Header.Add("Content-Type", "application/json") | ||||||
| 	fmt.Println(resp) | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	var groupResp = new(groupResponse) | ||||||
|  | 	err = json.Unmarshal(data, &groupResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return groupResp.Group.ID | ||||||
| } | } | ||||||
|  |  | ||||||
| const adminUser = "demo" | func addUserToGroup(t *testing.T, token, groupID, userID string) error { | ||||||
| const adminPass = "DEMO_PASS" | 	t.Helper() | ||||||
| const invalidPass = "WRONG_PASS" | 	uri := groupsURL + groupID + "/users/" + userID | ||||||
|  | 	client := &http.Client{} | ||||||
| const testUser = "test_user" | 	req, err := http.NewRequest("PUT", uri, nil) | ||||||
| const testPass = "test_pass" | 	if err != nil { | ||||||
| const testEmail = "test@example.com" | 		return err | ||||||
|  | 	} | ||||||
| const domain = "default" | 	req.Header.Set("X-Auth-Token", token) | ||||||
|  | 	client.Do(req) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestIncorrectCredentialsLogin(t *testing.T) { | func TestIncorrectCredentialsLogin(t *testing.T) { | ||||||
|   	c := Connector{KeystoneHost: keystoneURL, Domain: domain, | 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | ||||||
|   				   KeystoneUsername: adminUser, KeystonePassword: adminPass} | 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|   	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|   	_, validPW, _ := c.Login(context.Background(), s, adminUser, invalidPass) | 	_, validPW, err := c.Login(context.Background(), s, adminUser, invalidPass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|   	if validPW { | 	if validPW { | ||||||
|   		t.Fail() | 		t.Fail() | ||||||
|   	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestValidUserLogin(t *testing.T) { | func TestValidUserLogin(t *testing.T) { | ||||||
| 	token := getAdminToken(adminUser, adminPass) | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
| 	userID := createUser(token, testUser, testEmail, testPass) | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
|   	c := Connector{KeystoneHost: keystoneURL, Domain: domain, | 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | ||||||
|   				  KeystoneUsername: adminUser, KeystonePassword: adminPass} | 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|   	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|   	_, validPW, _ := c.Login(context.Background(), s, testUser, testPass) | 	identity, validPW, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
|   	if !validPW { | 	if err != nil { | ||||||
|      	t.Fail() | 		t.Fatal(err.Error()) | ||||||
|   	} | 	} | ||||||
|   	deleteUser(token, userID) | 	t.Log(identity) | ||||||
|  |  | ||||||
|  | 	if !validPW { | ||||||
|  | 		t.Fail() | ||||||
|  | 	} | ||||||
|  | 	delete(t, token, userID, usersURL) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestUseRefreshToken(t *testing.T) { | func TestUseRefreshToken(t *testing.T) { | ||||||
|   t.Fatal("Not implemented") | 	token, adminID := getAdminToken(t, adminUser, adminPass) | ||||||
|  | 	groupID := createGroup(t, token, "Test group description", testGroup) | ||||||
|  | 	addUserToGroup(t, token, groupID, adminID) | ||||||
|  |  | ||||||
|  | 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | ||||||
|  | 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|  | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |  | ||||||
|  | 	identityLogin, _, err := c.Login(context.Background(), s, adminUser, adminPass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	identityRefresh, err := c.Refresh(context.Background(), s, identityLogin) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	delete(t, token, groupID, groupsURL) | ||||||
|  |  | ||||||
|  | 	expectEquals(t, 1, len(identityRefresh.Groups)) | ||||||
|  | 	expectEquals(t, testGroup, string(identityRefresh.Groups[0])) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestUseRefreshTokenUserDeleted(t *testing.T){ | func TestUseRefreshTokenUserDeleted(t *testing.T) { | ||||||
|   t.Fatal("Not implemented") | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
|  | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
|  |  | ||||||
|  | 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | ||||||
|  | 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|  | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |  | ||||||
|  | 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = c.Refresh(context.Background(), s, identityLogin) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	delete(t, token, userID, usersURL) | ||||||
|  | 	_, err = c.Refresh(context.Background(), s, identityLogin) | ||||||
|  |  | ||||||
|  | 	if !strings.Contains(err.Error(), "does not exist") { | ||||||
|  | 		t.Errorf("unexpected error: %s", err.Error()) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestUseRefreshTokenGroupsChanged(t *testing.T){ | func TestUseRefreshTokenGroupsChanged(t *testing.T) { | ||||||
| 	t.Fatal("Not implemented") | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
|  | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
|  |  | ||||||
|  | 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | ||||||
|  | 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|  | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |  | ||||||
|  | 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	identityRefresh, err := c.Refresh(context.Background(), s, identityLogin) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expectEquals(t, 0, len(identityRefresh.Groups)) | ||||||
|  |  | ||||||
|  | 	groupID := createGroup(t, token, "Test group description", testGroup) | ||||||
|  | 	addUserToGroup(t, token, groupID, userID) | ||||||
|  |  | ||||||
|  | 	identityRefresh, err = c.Refresh(context.Background(), s, identityLogin) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	delete(t, token, groupID, groupsURL) | ||||||
|  | 	delete(t, token, userID, usersURL) | ||||||
|  |  | ||||||
|  | 	expectEquals(t, 1, len(identityRefresh.Groups)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestMain(m *testing.M) { | func TestMain(m *testing.M) { | ||||||
| 	dockerID := startKeystoneContainer() | 	keystoneURLEnv := "DEX_KEYSTONE_URL" | ||||||
|   	repeats := 10 | 	keystoneAdminURLEnv := "DEX_KEYSTONE_ADMIN_URL" | ||||||
|   	running := false | 	keystoneURL = os.Getenv(keystoneURLEnv) | ||||||
|   	for i := 0; i < repeats; i++ { | 	if keystoneURL == "" { | ||||||
|    		_, err := http.Get(keystoneURL) | 		fmt.Printf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv) | ||||||
|    		if err == nil { | 		return | ||||||
|      		running = true | 	} | ||||||
|      		break | 	keystoneAdminURL := os.Getenv(keystoneAdminURLEnv) | ||||||
|    		} | 	if keystoneAdminURL == "" { | ||||||
|    		time.Sleep(10 * time.Second) | 		fmt.Printf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv) | ||||||
|   	} | 		return | ||||||
|   	if !running { | 	} | ||||||
|     	fmt.Printf("Failed to start keystone container") | 	authTokenURL = keystoneURL + "/v3/auth/tokens/" | ||||||
|     	os.Exit(1) | 	fmt.Printf("Auth token url %q\n", authTokenURL) | ||||||
|   	} | 	fmt.Printf("Keystone URL %q\n", keystoneURL) | ||||||
|   	defer cleanKeystoneContainer(dockerID) | 	usersURL = keystoneAdminURL + "/v3/users/" | ||||||
|   	// run all tests | 	groupsURL = keystoneAdminURL + "/v3/groups/" | ||||||
|  | 	// run all tests | ||||||
| 	m.Run() | 	m.Run() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func expectEquals(t *testing.T, a interface{}, b interface{}) { | ||||||
|  | 	if !reflect.DeepEqual(a, b) { | ||||||
|  | 		t.Errorf("Expected %v to be equal %v", a, b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,133 +4,84 @@ import ( | |||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Connector struct { | type keystoneConnector struct { | ||||||
| 	Domain 			 string | 	Domain           string | ||||||
| 	KeystoneHost 	 string | 	KeystoneHost     string | ||||||
| 	KeystoneUsername string | 	KeystoneUsername string | ||||||
| 	KeystonePassword string | 	KeystonePassword string | ||||||
| 	Logger 			 logrus.FieldLogger | 	Logger           logrus.FieldLogger | ||||||
| } | } | ||||||
|  |  | ||||||
| type ConnectorData struct { | type userKeystone struct { | ||||||
| 	AccessToken string `json:"accessToken"` | 	Domain domainKeystone `json:"domain"` | ||||||
|  | 	ID     string         `json:"id"` | ||||||
|  | 	Name   string         `json:"name"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type KeystoneUser struct { | type domainKeystone struct { | ||||||
| 	Domain KeystoneDomain `json:"domain"` | 	ID   string `json:"id"` | ||||||
| 	ID 	   string 		  `json:"id"` |  | ||||||
| 	Name   string 		  `json:"name"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type KeystoneDomain struct { |  | ||||||
| 	ID string   `json:"id"` |  | ||||||
| 	Name string `json:"name"` | 	Name string `json:"name"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Config holds the configuration parameters for Keystone connector. | ||||||
|  | // Keystone should expose API v3 | ||||||
|  | // An example config: | ||||||
|  | //	connectors: | ||||||
|  | //		type: keystone | ||||||
|  | //		id: keystone | ||||||
|  | //		name: Keystone | ||||||
|  | //		config: | ||||||
|  | //			keystoneHost: http://example:5000 | ||||||
|  | //			domain: default | ||||||
|  | //      keystoneUsername: demo | ||||||
|  | //      keystonePassword: DEMO_PASS | ||||||
| type Config struct { | type Config struct { | ||||||
| 	Domain 			 string `json:"domain"` | 	Domain           string `json:"domain"` | ||||||
| 	KeystoneHost 	 string `json:"keystoneHost"` | 	KeystoneHost     string `json:"keystoneHost"` | ||||||
| 	KeystoneUsername string `json:"keystoneUsername"` | 	KeystoneUsername string `json:"keystoneUsername"` | ||||||
| 	KeystonePassword string `json:"keystonePassword"` | 	KeystonePassword string `json:"keystonePassword"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type LoginRequestData struct { | type loginRequestData struct { | ||||||
| 	Auth `json:"auth"` | 	auth `json:"auth"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Auth struct { | type auth struct { | ||||||
| 	Identity `json:"identity"` | 	Identity identity `json:"identity"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Identity struct { | type identity struct { | ||||||
| 	Methods  []string `json:"methods"` | 	Methods  []string `json:"methods"` | ||||||
| 	Password 		  `json:"password"` | 	Password password `json:"password"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Password struct { | type password struct { | ||||||
| 	User `json:"user"` | 	User user `json:"user"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type User struct { | type user struct { | ||||||
| 	Name   string 	`json:"name"` |  | ||||||
| 	Domain 			`json:"domain"` |  | ||||||
| 	Password string `json:"password"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Domain struct { |  | ||||||
| 	ID string `json:"id"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Token struct { |  | ||||||
| 	IssuedAt  string 	   			 `json:"issued_at"` |  | ||||||
| 	Extras 	  map[string]interface{} `json:"extras"` |  | ||||||
| 	Methods   []string 	   			 `json:"methods"` |  | ||||||
| 	ExpiresAt string 	   			 `json:"expires_at"` |  | ||||||
| 	User 	  KeystoneUser 			 `json:"user"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type TokenResponse struct { |  | ||||||
| 	Token Token `json:"token"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type CreateUserRequest struct { |  | ||||||
| 	CreateUser CreateUserForm  `json:"user"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type CreateUserForm struct { |  | ||||||
| 	Name     string `json:"name"` | 	Name     string `json:"name"` | ||||||
| 	Email    string `json:"email"` | 	Domain   domain `json:"domain"` | ||||||
| 	Enabled  bool   `json:"enabled"` |  | ||||||
| 	Password string `json:"password"` | 	Password string `json:"password"` | ||||||
| 	Roles  []string `json:"roles"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type UserResponse struct { | type domain struct { | ||||||
| 	User CreateUserResponse `json:"user"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type CreateUserResponse struct { |  | ||||||
| 	Username string   `json:"username"` |  | ||||||
| 	Name 	 string   `json:"name"` |  | ||||||
| 	Roles 	 []string `json:"roles"` |  | ||||||
| 	Enabled  bool     `json:"enabled"` |  | ||||||
| 	Options  string   `json:"options"` |  | ||||||
| 	ID 		 string   `json:"id"` |  | ||||||
| 	Email 	 string   `json:"email"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type CreateGroup struct { |  | ||||||
| 	Group CreateGroupForm `json:"group"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type CreateGroupForm struct { |  | ||||||
| 	Description string `json:"description"` |  | ||||||
| 	Name 		string `json:"name"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type GroupID struct { |  | ||||||
| 	Group GroupIDForm `json:"group"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type GroupIDForm struct { |  | ||||||
| 	ID string `json:"id"` | 	ID string `json:"id"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Links struct { | type token struct { | ||||||
| 	Self string `json:"self"` | 	User userKeystone `json:"user"` | ||||||
| 	Previous string `json:"previous"` |  | ||||||
| 	Next string `json:"next"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type Group struct { | type tokenResponse struct { | ||||||
| 	DomainID 	string `json:"domain_id` | 	Token token `json:"token"` | ||||||
| 	Description string `json:"description"` |  | ||||||
| 	ID 			string `json:"id"` |  | ||||||
| 	Links 		Links  `json:"links"` |  | ||||||
| 	Name 		string `json:"name"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type GroupsResponse struct { | type group struct { | ||||||
| 	Links  Links   `json:"links"` | 	ID   string `json:"id"` | ||||||
| 	Groups []Group `json:"groups"` | 	Name string `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type groupsResponse struct { | ||||||
|  | 	Groups []group `json:"groups"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,55 +0,0 @@ | |||||||
| # The base path of dex and the external name of the OpenID Connect service. |  | ||||||
| # This is the canonical URL that all clients MUST use to refer to dex. If a |  | ||||||
| # path is provided, dex's HTTP service will listen at a non-root URL. |  | ||||||
| issuer: http://0.0.0.0:5556/dex |  | ||||||
|  |  | ||||||
| # The storage configuration determines where dex stores its state. Supported |  | ||||||
| # options include SQL flavors and Kubernetes third party resources. |  | ||||||
| # |  | ||||||
| # See the storage document at Documentation/storage.md for further information. |  | ||||||
| storage: |  | ||||||
|   type: sqlite3 |  | ||||||
|   config: |  | ||||||
|     file: examples/dex.db   #be in the dex directory, else change path here |  | ||||||
|  |  | ||||||
| # Configuration for the HTTP endpoints. |  | ||||||
| web: |  | ||||||
|   https: 0.0.0.0:5556 |  | ||||||
|   # Uncomment for HTTPS options. |  | ||||||
|   # https: 127.0.0.1:5554 |  | ||||||
|   tlsCert: ./ssl/dex.crt |  | ||||||
|   tlsKey: ./ssl/dex.key |  | ||||||
|  |  | ||||||
| # Configuration for telemetry |  | ||||||
| telemetry: |  | ||||||
|   http: 0.0.0.0:5558 |  | ||||||
|  |  | ||||||
| oauth2: |  | ||||||
|   responseTypes: ["id_token"] |  | ||||||
|  |  | ||||||
| # Instead of reading from an external storage, use this list of clients. |  | ||||||
| staticClients: |  | ||||||
| - id: example-app |  | ||||||
|   redirectURIs: |  | ||||||
|   - 'http://127.0.0.1:5555/callback' |  | ||||||
|   name: 'Example App' |  | ||||||
|   secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |  | ||||||
|  |  | ||||||
| #Provide Keystone connector and its config here |  | ||||||
| # /v3/auth/tokens |  | ||||||
| connectors: |  | ||||||
| - type: keystone |  | ||||||
|   id: keystone |  | ||||||
|   name: Keystone |  | ||||||
|   config: |  | ||||||
|     keystoneHost: http://localhost:5000 |  | ||||||
|     domain: default |  | ||||||
|     keystoneUsername: demo |  | ||||||
|     keystonePassword: DEMO_PASS |  | ||||||
|  |  | ||||||
| # Let dex keep a list of passwords which can be used to login to dex. |  | ||||||
| enablePasswordDB: true |  | ||||||
|  |  | ||||||
| oauth2: |  | ||||||
|   skipApprovalScreen: true |  | ||||||
|  |  | ||||||
| @@ -211,7 +211,6 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	authReqID := r.FormValue("req") | 	authReqID := r.FormValue("req") | ||||||
|   s.logger.Errorf("Auth req id %v", authReqID) |  | ||||||
|  |  | ||||||
| 	authReq, err := s.storage.GetAuthRequest(authReqID) | 	authReq, err := s.storage.GetAuthRequest(authReqID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -346,7 +345,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) | |||||||
| 			s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") | 			s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		s.logger.Errorf("2Failed to get auth request: %v", err) | 		s.logger.Errorf("Failed to get auth request: %v", err) | ||||||
| 		s.renderError(w, http.StatusInternalServerError, "Database error.") | 		s.renderError(w, http.StatusInternalServerError, "Database error.") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -358,7 +357,6 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	conn, err := s.getConnector(authReq.ConnectorID) | 	conn, err := s.getConnector(authReq.ConnectorID) | ||||||
|   s.logger.Errorf("X Connector %v", conn) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err) | 		s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err) | ||||||
| 		s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") | 		s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") | ||||||
| @@ -437,7 +435,7 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth | |||||||
| func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { | func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { | ||||||
| 	authReq, err := s.storage.GetAuthRequest(r.FormValue("req")) | 	authReq, err := s.storage.GetAuthRequest(r.FormValue("req")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		s.logger.Errorf("3Failed to get auth request: %v", err) | 		s.logger.Errorf("Failed to get auth request: %v", err) | ||||||
| 		s.renderError(w, http.StatusInternalServerError, "Database error.") | 		s.renderError(w, http.StatusInternalServerError, "Database error.") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ import ( | |||||||
| 	"github.com/dexidp/dex/connector/bitbucketcloud" | 	"github.com/dexidp/dex/connector/bitbucketcloud" | ||||||
| 	"github.com/dexidp/dex/connector/github" | 	"github.com/dexidp/dex/connector/github" | ||||||
| 	"github.com/dexidp/dex/connector/gitlab" | 	"github.com/dexidp/dex/connector/gitlab" | ||||||
|  | 	"github.com/dexidp/dex/connector/keystone" | ||||||
| 	"github.com/dexidp/dex/connector/ldap" | 	"github.com/dexidp/dex/connector/ldap" | ||||||
| 	"github.com/dexidp/dex/connector/linkedin" | 	"github.com/dexidp/dex/connector/linkedin" | ||||||
| 	"github.com/dexidp/dex/connector/microsoft" | 	"github.com/dexidp/dex/connector/microsoft" | ||||||
| @@ -34,7 +35,6 @@ import ( | |||||||
| 	"github.com/dexidp/dex/connector/oidc" | 	"github.com/dexidp/dex/connector/oidc" | ||||||
| 	"github.com/dexidp/dex/connector/saml" | 	"github.com/dexidp/dex/connector/saml" | ||||||
| 	"github.com/dexidp/dex/storage" | 	"github.com/dexidp/dex/storage" | ||||||
|   "github.com/dexidp/dex/connector/keystone" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // LocalConnector is the local passwordDB connector which is an internal | // LocalConnector is the local passwordDB connector which is an internal | ||||||
| @@ -456,7 +456,7 @@ func openConnector(logger logrus.FieldLogger, conn storage.Connector) (connector | |||||||
|  |  | ||||||
| 	f, ok := ConnectorsConfig[conn.Type] | 	f, ok := ConnectorsConfig[conn.Type] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return c, fmt.Errorf("xunknown connector type %q", conn.Type) | 		return c, fmt.Errorf("unknown connector type %q", conn.Type) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	connConfig := f() | 	connConfig := f() | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package storage | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user