keystone: fetching groups only if requested, refactoring.
This commit is contained in:
		
							
								
								
									
										10
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -13,14 +13,18 @@ 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_KEYSTONE_URL=http://localhost:5000 DEX_KEYSTONE_ADMIN_URL=http://localhost:35357 |   - 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 DEX_KEYSTONE_ADMIN_USER=demo DEX_KEYSTONE_ADMIN_PASS=DEMO_PASS | ||||||
|  |  | ||||||
| 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 |   - docker run -d -p 0.0.0.0:5000:5000 -p 0.0.0.0:35357:35357 openio/openstack-keystone:pike | ||||||
|   - sleep 60s |   - | | ||||||
|  |     until curl --fail http://localhost:5000/v3; do | ||||||
|  |       echo 'Waiting for keystone...' | ||||||
|  |       sleep 1; | ||||||
|  |     done; | ||||||
|  |  | ||||||
| script: | script: | ||||||
|   - make testall |   - make testall | ||||||
|   | |||||||
| @@ -14,65 +14,148 @@ import ( | |||||||
| 	"github.com/dexidp/dex/connector" | 	"github.com/dexidp/dex/connector" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type conn struct { | ||||||
|  | 	Domain        string | ||||||
|  | 	Host          string | ||||||
|  | 	AdminUsername string | ||||||
|  | 	AdminPassword string | ||||||
|  | 	Logger        logrus.FieldLogger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type userKeystone struct { | ||||||
|  | 	Domain domainKeystone `json:"domain"` | ||||||
|  | 	ID     string         `json:"id"` | ||||||
|  | 	Name   string         `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type domainKeystone struct { | ||||||
|  | 	ID   string `json:"id"` | ||||||
|  | 	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 { | ||||||
|  | 	Domain        string `json:"domain"` | ||||||
|  | 	Host          string `json:"keystoneHost"` | ||||||
|  | 	AdminUsername string `json:"keystoneUsername"` | ||||||
|  | 	AdminPassword string `json:"keystonePassword"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type loginRequestData struct { | ||||||
|  | 	auth `json:"auth"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type auth struct { | ||||||
|  | 	Identity identity `json:"identity"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type identity struct { | ||||||
|  | 	Methods  []string `json:"methods"` | ||||||
|  | 	Password password `json:"password"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type password struct { | ||||||
|  | 	User user `json:"user"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type user struct { | ||||||
|  | 	Name     string `json:"name"` | ||||||
|  | 	Domain   domain `json:"domain"` | ||||||
|  | 	Password string `json:"password"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type domain struct { | ||||||
|  | 	ID string `json:"id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type token struct { | ||||||
|  | 	User userKeystone `json:"user"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type tokenResponse struct { | ||||||
|  | 	Token token `json:"token"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type group struct { | ||||||
|  | 	ID   string `json:"id"` | ||||||
|  | 	Name string `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type groupsResponse struct { | ||||||
|  | 	Groups []group `json:"groups"` | ||||||
|  | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	_ connector.PasswordConnector = &keystoneConnector{} | 	_ connector.PasswordConnector = &conn{} | ||||||
| 	_ connector.RefreshConnector  = &keystoneConnector{} | 	_ connector.RefreshConnector  = &conn{} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // 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 &keystoneConnector{c.Domain, c.KeystoneHost, | 	return &conn{ | ||||||
| 		c.KeystoneUsername, c.KeystonePassword, logger}, nil | 		c.Domain, | ||||||
|  | 		c.Host, | ||||||
|  | 		c.AdminUsername, | ||||||
|  | 		c.AdminPassword, | ||||||
|  | 		logger}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) Close() error { return nil } | func (p *conn) Close() error { return nil } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) Login(ctx context.Context, s connector.Scopes, username, password string) ( | func (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) { | ||||||
| 	identity connector.Identity, validPassword bool, err error) { |  | ||||||
| 	resp, err := p.getTokenResponse(ctx, username, password) | 	resp, err := p.getTokenResponse(ctx, username, password) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return identity, false, fmt.Errorf("keystone: error %v", err) | 		return identity, false, fmt.Errorf("keystone: error %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	if resp.StatusCode/100 != 2 { | ||||||
| 	// Providing wrong password or wrong keystone URI throws error | 		return identity, false, fmt.Errorf("keystone login: error %v", resp.StatusCode) | ||||||
| 	if resp.StatusCode == 201 { | 	} | ||||||
| 		token := resp.Header.Get("X-Subject-Token") | 	if resp.StatusCode != 201 { | ||||||
| 		data, err := ioutil.ReadAll(resp.Body) | 		return identity, false, nil | ||||||
| 		if err != nil { | 	} | ||||||
| 			return identity, false, err | 	token := resp.Header.Get("X-Subject-Token") | ||||||
| 		} | 	data, err := ioutil.ReadAll(resp.Body) | ||||||
| 		defer resp.Body.Close() | 	if err != nil { | ||||||
|  | 		return identity, false, err | ||||||
| 		var tokenResp = new(tokenResponse) | 	} | ||||||
| 		err = json.Unmarshal(data, &tokenResp) | 	defer resp.Body.Close() | ||||||
| 		if err != nil { | 	var tokenResp = new(tokenResponse) | ||||||
| 			return identity, false, fmt.Errorf("keystone: invalid token response: %v", err) | 	err = json.Unmarshal(data, &tokenResp) | ||||||
| 		} | 	if err != nil { | ||||||
|  | 		return identity, false, fmt.Errorf("keystone: invalid token response: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if scopes.Groups { | ||||||
| 		groups, err := p.getUserGroups(ctx, tokenResp.Token.User.ID, token) | 		groups, err := p.getUserGroups(ctx, tokenResp.Token.User.ID, token) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return identity, false, err | 			return identity, false, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		identity.Username = username |  | ||||||
| 		identity.UserID = tokenResp.Token.User.ID |  | ||||||
| 		identity.Groups = groups | 		identity.Groups = groups | ||||||
| 		return identity, true, nil |  | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  | 	identity.Username = username | ||||||
| 	return identity, false, nil | 	identity.UserID = tokenResp.Token.User.ID | ||||||
|  | 	return identity, true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) Prompt() string { return "username" } | func (p *conn) Prompt() string { return "username" } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) Refresh( | func (p *conn) Refresh( | ||||||
| 	ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { | 	ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) { | ||||||
|  |  | ||||||
| 	token, err := p.getAdminToken(ctx) | 	token, err := p.getAdminToken(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err) | 		return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ok, err := p.checkIfUserExists(ctx, identity.UserID, token) | 	ok, err := p.checkIfUserExists(ctx, identity.UserID, token) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return identity, err | 		return identity, err | ||||||
| @@ -80,17 +163,17 @@ func (p *keystoneConnector) Refresh( | |||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return identity, fmt.Errorf("keystone: user %q does not exist", identity.UserID) | 		return identity, fmt.Errorf("keystone: user %q does not exist", identity.UserID) | ||||||
| 	} | 	} | ||||||
|  | 	if scopes.Groups { | ||||||
| 	groups, err := p.getUserGroups(ctx, identity.UserID, token) | 		groups, err := p.getUserGroups(ctx, identity.UserID, token) | ||||||
| 	if err != nil { | 		if err != nil { | ||||||
| 		return identity, err | 			return identity, err | ||||||
|  | 		} | ||||||
|  | 		identity.Groups = groups | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	identity.Groups = groups |  | ||||||
| 	return identity, nil | 	return identity, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) getTokenResponse(ctx context.Context, username, pass string) (response *http.Response, err error) { | func (p *conn) getTokenResponse(ctx context.Context, username, pass string) (response *http.Response, err error) { | ||||||
| 	client := &http.Client{} | 	client := &http.Client{} | ||||||
| 	jsonData := loginRequestData{ | 	jsonData := loginRequestData{ | ||||||
| 		auth: auth{ | 		auth: auth{ | ||||||
| @@ -110,8 +193,8 @@ func (p *keystoneConnector) getTokenResponse(ctx context.Context, username, pass | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 	// https://developer.openstack.org/api-ref/identity/v3/#password-authentication-with-unscoped-authorization | ||||||
| 	authTokenURL := p.KeystoneHost + "/v3/auth/tokens/" | 	authTokenURL := p.Host + "/v3/auth/tokens/" | ||||||
| 	req, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(jsonValue)) | 	req, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(jsonValue)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -123,8 +206,8 @@ func (p *keystoneConnector) getTokenResponse(ctx context.Context, username, pass | |||||||
| 	return client.Do(req) | 	return client.Do(req) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) getAdminToken(ctx context.Context) (string, error) { | func (p *conn) getAdminToken(ctx context.Context) (string, error) { | ||||||
| 	resp, err := p.getTokenResponse(ctx, p.KeystoneUsername, p.KeystonePassword) | 	resp, err := p.getTokenResponse(ctx, p.AdminUsername, p.AdminPassword) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| @@ -132,8 +215,9 @@ func (p *keystoneConnector) getAdminToken(ctx context.Context) (string, error) { | |||||||
| 	return token, nil | 	return token, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) checkIfUserExists(ctx context.Context, userID string, token string) (bool, error) { | func (p *conn) checkIfUserExists(ctx context.Context, userID string, token string) (bool, error) { | ||||||
| 	userURL := p.KeystoneHost + "/v3/users/" + userID | 	// https://developer.openstack.org/api-ref/identity/v3/#show-user-details | ||||||
|  | 	userURL := p.Host + "/v3/users/" + userID | ||||||
| 	client := &http.Client{} | 	client := &http.Client{} | ||||||
| 	req, err := http.NewRequest("GET", userURL, nil) | 	req, err := http.NewRequest("GET", userURL, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -153,10 +237,10 @@ func (p *keystoneConnector) checkIfUserExists(ctx context.Context, userID string | |||||||
| 	return false, err | 	return false, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *keystoneConnector) getUserGroups(ctx context.Context, userID string, token string) ([]string, error) { | func (p *conn) getUserGroups(ctx context.Context, userID string, token string) ([]string, error) { | ||||||
| 	client := &http.Client{} | 	client := &http.Client{} | ||||||
| 	groupsURL := p.KeystoneHost + "/v3/users/" + userID + "/groups" | 	// https://developer.openstack.org/api-ref/identity/v3/#list-groups-to-which-a-user-belongs | ||||||
|  | 	groupsURL := p.Host + "/v3/users/" + userID + "/groups" | ||||||
| 	req, err := http.NewRequest("GET", groupsURL, nil) | 	req, err := http.NewRequest("GET", groupsURL, nil) | ||||||
| 	req.Header.Set("X-Auth-Token", token) | 	req.Header.Set("X-Auth-Token", token) | ||||||
| 	req = req.WithContext(ctx) | 	req = req.WithContext(ctx) | ||||||
|   | |||||||
| @@ -16,8 +16,6 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	adminUser   = "demo" |  | ||||||
| 	adminPass   = "DEMO_PASS" |  | ||||||
| 	invalidPass = "WRONG_PASS" | 	invalidPass = "WRONG_PASS" | ||||||
|  |  | ||||||
| 	testUser   = "test_user" | 	testUser   = "test_user" | ||||||
| @@ -30,6 +28,8 @@ const ( | |||||||
| var ( | var ( | ||||||
| 	keystoneURL      = "" | 	keystoneURL      = "" | ||||||
| 	keystoneAdminURL = "" | 	keystoneAdminURL = "" | ||||||
|  | 	adminUser        = "" | ||||||
|  | 	adminPass        = "" | ||||||
| 	authTokenURL     = "" | 	authTokenURL     = "" | ||||||
| 	usersURL         = "" | 	usersURL         = "" | ||||||
| 	groupsURL        = "" | 	groupsURL        = "" | ||||||
| @@ -213,24 +213,31 @@ func addUserToGroup(t *testing.T, token, groupID, userID string) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestIncorrectCredentialsLogin(t *testing.T) { | func TestIncorrectCredentialsLogin(t *testing.T) { | ||||||
| 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | 	setupVariables(t) | ||||||
| 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | 	c := conn{Host: keystoneURL, Domain: testDomain, | ||||||
|  | 		AdminUsername: adminUser, AdminPassword: adminPass} | ||||||
| 	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
| 	_, validPW, err := 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.Fatal("Incorrect password check") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal("Error should be returned when invalid password is provided") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !strings.Contains(err.Error(), "401") { | ||||||
|  | 		t.Fatal("Unrecognized error, expecting 401") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestValidUserLogin(t *testing.T) { | func TestValidUserLogin(t *testing.T) { | ||||||
|  | 	setupVariables(t) | ||||||
| 	token, _ := getAdminToken(t, adminUser, adminPass) | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
| 	userID := createUser(t, token, testUser, testEmail, testPass) | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
| 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | 	c := conn{Host: keystoneURL, Domain: testDomain, | ||||||
| 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | 		AdminUsername: adminUser, AdminPassword: adminPass} | ||||||
| 	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
| 	identity, validPW, err := c.Login(context.Background(), s, testUser, testPass) | 	identity, validPW, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -239,18 +246,19 @@ func TestValidUserLogin(t *testing.T) { | |||||||
| 	t.Log(identity) | 	t.Log(identity) | ||||||
|  |  | ||||||
| 	if !validPW { | 	if !validPW { | ||||||
| 		t.Fail() | 		t.Fatal("Valid password was not accepted") | ||||||
| 	} | 	} | ||||||
| 	delete(t, token, userID, usersURL) | 	delete(t, token, userID, usersURL) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestUseRefreshToken(t *testing.T) { | func TestUseRefreshToken(t *testing.T) { | ||||||
|  | 	setupVariables(t) | ||||||
| 	token, adminID := getAdminToken(t, adminUser, adminPass) | 	token, adminID := getAdminToken(t, adminUser, adminPass) | ||||||
| 	groupID := createGroup(t, token, "Test group description", testGroup) | 	groupID := createGroup(t, token, "Test group description", testGroup) | ||||||
| 	addUserToGroup(t, token, groupID, adminID) | 	addUserToGroup(t, token, groupID, adminID) | ||||||
|  |  | ||||||
| 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | 	c := conn{Host: keystoneURL, Domain: testDomain, | ||||||
| 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | 		AdminUsername: adminUser, AdminPassword: adminPass} | ||||||
| 	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |  | ||||||
| 	identityLogin, _, err := c.Login(context.Background(), s, adminUser, adminPass) | 	identityLogin, _, err := c.Login(context.Background(), s, adminUser, adminPass) | ||||||
| @@ -270,11 +278,12 @@ func TestUseRefreshToken(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestUseRefreshTokenUserDeleted(t *testing.T) { | func TestUseRefreshTokenUserDeleted(t *testing.T) { | ||||||
|  | 	setupVariables(t) | ||||||
| 	token, _ := getAdminToken(t, adminUser, adminPass) | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
| 	userID := createUser(t, token, testUser, testEmail, testPass) | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
|  |  | ||||||
| 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | 	c := conn{Host: keystoneURL, Domain: testDomain, | ||||||
| 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | 		AdminUsername: adminUser, AdminPassword: adminPass} | ||||||
| 	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |  | ||||||
| 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
| @@ -296,11 +305,12 @@ func TestUseRefreshTokenUserDeleted(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestUseRefreshTokenGroupsChanged(t *testing.T) { | func TestUseRefreshTokenGroupsChanged(t *testing.T) { | ||||||
|  | 	setupVariables(t) | ||||||
| 	token, _ := getAdminToken(t, adminUser, adminPass) | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
| 	userID := createUser(t, token, testUser, testEmail, testPass) | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
|  |  | ||||||
| 	c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, | 	c := conn{Host: keystoneURL, Domain: testDomain, | ||||||
| 		KeystoneUsername: adminUser, KeystonePassword: adminPass} | 		AdminUsername: adminUser, AdminPassword: adminPass} | ||||||
| 	s := connector.Scopes{OfflineAccess: true, Groups: true} | 	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |  | ||||||
| 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
| @@ -315,7 +325,7 @@ func TestUseRefreshTokenGroupsChanged(t *testing.T) { | |||||||
|  |  | ||||||
| 	expectEquals(t, 0, len(identityRefresh.Groups)) | 	expectEquals(t, 0, len(identityRefresh.Groups)) | ||||||
|  |  | ||||||
| 	groupID := createGroup(t, token, "Test group description", testGroup) | 	groupID := createGroup(t, token, "Test group", testGroup) | ||||||
| 	addUserToGroup(t, token, groupID, userID) | 	addUserToGroup(t, token, groupID, userID) | ||||||
|  |  | ||||||
| 	identityRefresh, err = c.Refresh(context.Background(), s, identityLogin) | 	identityRefresh, err = c.Refresh(context.Background(), s, identityLogin) | ||||||
| @@ -329,26 +339,62 @@ func TestUseRefreshTokenGroupsChanged(t *testing.T) { | |||||||
| 	expectEquals(t, 1, len(identityRefresh.Groups)) | 	expectEquals(t, 1, len(identityRefresh.Groups)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestMain(m *testing.M) { | func TestNoGroupsInScope(t *testing.T) { | ||||||
|  | 	setupVariables(t) | ||||||
|  | 	token, _ := getAdminToken(t, adminUser, adminPass) | ||||||
|  | 	userID := createUser(t, token, testUser, testEmail, testPass) | ||||||
|  |  | ||||||
|  | 	c := conn{Host: keystoneURL, Domain: testDomain, | ||||||
|  | 		AdminUsername: adminUser, AdminPassword: adminPass} | ||||||
|  | 	s := connector.Scopes{OfflineAccess: true, Groups: false} | ||||||
|  |  | ||||||
|  | 	groupID := createGroup(t, token, "Test group", testGroup) | ||||||
|  | 	addUserToGroup(t, token, groupID, userID) | ||||||
|  |  | ||||||
|  | 	identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  | 	expectEquals(t, 0, len(identityLogin.Groups)) | ||||||
|  |  | ||||||
|  | 	identityRefresh, err := c.Refresh(context.Background(), s, identityLogin) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err.Error()) | ||||||
|  | 	} | ||||||
|  | 	expectEquals(t, 0, len(identityRefresh.Groups)) | ||||||
|  |  | ||||||
|  | 	delete(t, token, groupID, groupsURL) | ||||||
|  | 	delete(t, token, userID, usersURL) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func setupVariables(t *testing.T) { | ||||||
| 	keystoneURLEnv := "DEX_KEYSTONE_URL" | 	keystoneURLEnv := "DEX_KEYSTONE_URL" | ||||||
| 	keystoneAdminURLEnv := "DEX_KEYSTONE_ADMIN_URL" | 	keystoneAdminURLEnv := "DEX_KEYSTONE_ADMIN_URL" | ||||||
|  | 	keystoneAdminUserEnv := "DEX_KEYSTONE_ADMIN_USER" | ||||||
|  | 	keystoneAdminPassEnv := "DEX_KEYSTONE_ADMIN_PASS" | ||||||
| 	keystoneURL = os.Getenv(keystoneURLEnv) | 	keystoneURL = os.Getenv(keystoneURLEnv) | ||||||
| 	if keystoneURL == "" { | 	if keystoneURL == "" { | ||||||
| 		fmt.Printf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv) | 		t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	keystoneAdminURL := os.Getenv(keystoneAdminURLEnv) | 	keystoneAdminURL = os.Getenv(keystoneAdminURLEnv) | ||||||
| 	if keystoneAdminURL == "" { | 	if keystoneAdminURL == "" { | ||||||
| 		fmt.Printf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv) | 		t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	adminUser = os.Getenv(keystoneAdminUserEnv) | ||||||
|  | 	if adminUser == "" { | ||||||
|  | 		t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneAdminUserEnv)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	adminPass = os.Getenv(keystoneAdminPassEnv) | ||||||
|  | 	if adminPass == "" { | ||||||
|  | 		t.Skip(fmt.Sprintf("variable %q not set, skipping keystone connector tests\n", keystoneAdminPassEnv)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	authTokenURL = keystoneURL + "/v3/auth/tokens/" | 	authTokenURL = keystoneURL + "/v3/auth/tokens/" | ||||||
| 	fmt.Printf("Auth token url %q\n", authTokenURL) |  | ||||||
| 	fmt.Printf("Keystone URL %q\n", keystoneURL) |  | ||||||
| 	usersURL = keystoneAdminURL + "/v3/users/" | 	usersURL = keystoneAdminURL + "/v3/users/" | ||||||
| 	groupsURL = keystoneAdminURL + "/v3/groups/" | 	groupsURL = keystoneAdminURL + "/v3/groups/" | ||||||
| 	// run all tests |  | ||||||
| 	m.Run() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func expectEquals(t *testing.T, a interface{}, b interface{}) { | func expectEquals(t *testing.T, a interface{}, b interface{}) { | ||||||
|   | |||||||
| @@ -1,87 +0,0 @@ | |||||||
| package keystone |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"github.com/sirupsen/logrus" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type keystoneConnector struct { |  | ||||||
| 	Domain           string |  | ||||||
| 	KeystoneHost     string |  | ||||||
| 	KeystoneUsername string |  | ||||||
| 	KeystonePassword string |  | ||||||
| 	Logger           logrus.FieldLogger |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type userKeystone struct { |  | ||||||
| 	Domain domainKeystone `json:"domain"` |  | ||||||
| 	ID     string         `json:"id"` |  | ||||||
| 	Name   string         `json:"name"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type domainKeystone struct { |  | ||||||
| 	ID   string `json:"id"` |  | ||||||
| 	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 { |  | ||||||
| 	Domain           string `json:"domain"` |  | ||||||
| 	KeystoneHost     string `json:"keystoneHost"` |  | ||||||
| 	KeystoneUsername string `json:"keystoneUsername"` |  | ||||||
| 	KeystonePassword string `json:"keystonePassword"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type loginRequestData struct { |  | ||||||
| 	auth `json:"auth"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type auth struct { |  | ||||||
| 	Identity identity `json:"identity"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type identity struct { |  | ||||||
| 	Methods  []string `json:"methods"` |  | ||||||
| 	Password password `json:"password"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type password struct { |  | ||||||
| 	User user `json:"user"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type user struct { |  | ||||||
| 	Name     string `json:"name"` |  | ||||||
| 	Domain   domain `json:"domain"` |  | ||||||
| 	Password string `json:"password"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type domain struct { |  | ||||||
| 	ID string `json:"id"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type token struct { |  | ||||||
| 	User userKeystone `json:"user"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type tokenResponse struct { |  | ||||||
| 	Token token `json:"token"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type group struct { |  | ||||||
| 	ID   string `json:"id"` |  | ||||||
| 	Name string `json:"name"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type groupsResponse struct { |  | ||||||
| 	Groups []group `json:"groups"` |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user