keystone: refresh token and groups
This commit is contained in:
		| @@ -10,111 +10,155 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"log" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type KeystoneConnector struct { |  | ||||||
| 	domain string |  | ||||||
| 	keystoneURI string |  | ||||||
| 	Logger   logrus.FieldLogger |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	_ connector.PasswordConnector = &KeystoneConnector{} | 	_ connector.PasswordConnector = &Connector{} | ||||||
|  |   	_ connector.RefreshConnector = &Connector{} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Config holds the configuration parameters for Keystone connector. |  | ||||||
| // An example config: |  | ||||||
| //	connectors: |  | ||||||
| //		type: ksconfig |  | ||||||
| //		id: keystone |  | ||||||
| //		name: Keystone |  | ||||||
| //		config: |  | ||||||
| //			keystoneURI: http://example:5000/v3/auth/tokens |  | ||||||
| //			domain: default |  | ||||||
|  |  | ||||||
| type Config struct { |  | ||||||
| 	Domain string `json:"domain"` |  | ||||||
| 	KeystoneURI string `json:"keystoneURI"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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.KeystoneURI,logger}, nil | 	return &Connector{c.Domain, c.KeystoneHost, | ||||||
|  | 	c.KeystoneUsername, c.KeystonePassword, logger}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p KeystoneConnector) Close() error { return nil } | func (p Connector) Close() error { return nil } | ||||||
|  |  | ||||||
| // Declare KeystoneJson struct to get a token | func (p Connector) Login(ctx context.Context, s connector.Scopes, username, password string) ( | ||||||
| type KeystoneJson struct { | 		identity connector.Identity, validPassword bool, err error) { | ||||||
| 	Auth `json:"auth"` | 	response, err := p.getTokenResponse(username, password) | ||||||
|  |  | ||||||
|  | 	// Providing wrong password or wrong keystone URI throws error | ||||||
|  | 	if err == nil && response.StatusCode == 201 { | ||||||
|  |     	token := response.Header["X-Subject-Token"][0] | ||||||
|  | 		data, _ := ioutil.ReadAll(response.Body) | ||||||
|  |  | ||||||
|  |     	var tokenResponse = new(TokenResponse) | ||||||
|  |     	err := json.Unmarshal(data, &tokenResponse) | ||||||
|  |  | ||||||
|  |     	if err != nil { | ||||||
|  |       		fmt.Printf("keystone: invalid token response: %v", err) | ||||||
|  |       		return identity, false, err | ||||||
|  |     	} | ||||||
|  |     	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 | ||||||
|  |  | ||||||
|  | 	} 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 | ||||||
| } | } | ||||||
|  |  | ||||||
| type Auth struct { | func (p Connector) Prompt() string { return "username" } | ||||||
| 	Identity `json:"identity"` |  | ||||||
|  | func (p Connector) Refresh( | ||||||
|  | 	ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { | ||||||
|  |  | ||||||
|  |   	if len(identity.ConnectorData) == 0 { | ||||||
|  |   		return identity, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	token, err := p.getAdminToken() | ||||||
|  |  | ||||||
|  |   	if err != nil { | ||||||
|  |     	fmt.Printf("keystone: failed to obtain admin token") | ||||||
|  |     	return identity, err | ||||||
|  |   	} | ||||||
|  |  | ||||||
|  |   	ok := p.checkIfUserExists(identity.UserID, token) | ||||||
|  |   	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 | ||||||
| } | } | ||||||
|  |  | ||||||
| type Identity struct { |  | ||||||
| 	Methods  []string `json:"methods"` |  | ||||||
| 	Password `json:"password"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Password struct { | func (p Connector) getTokenResponse(username, password string) (response *http.Response, err error) { | ||||||
| 	User `json:"user"` | 	jsonData := LoginRequestData{ | ||||||
| } |  | ||||||
|  |  | ||||||
| type User struct { |  | ||||||
| 	Name   string `json:"name"` |  | ||||||
| 	Domain `json:"domain"` |  | ||||||
| 	Password string `json:"password"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Domain struct { |  | ||||||
| 	ID string `json:"id"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p KeystoneConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) { |  | ||||||
| 	// Instantiate KeystoneJson struct type to get a token |  | ||||||
| 	jsonData := KeystoneJson{ |  | ||||||
| 		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: password, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Marshal jsonData |  | ||||||
| 	jsonValue, _ := json.Marshal(jsonData) | 	jsonValue, _ := json.Marshal(jsonData) | ||||||
|  |   	loginURI := p.KeystoneHost + "/v3/auth/tokens" | ||||||
| 	// Make an http post request to Keystone URI | 	return http.Post(loginURI, "application/json", bytes.NewBuffer(jsonValue)) | ||||||
| 	response, err := http.Post(p.keystoneURI, "application/json", bytes.NewBuffer(jsonValue)) |  | ||||||
|  |  | ||||||
| 	// Providing wrong password or wrong keystone URI throws error |  | ||||||
| 	if err == nil && response.StatusCode == 201 { |  | ||||||
| 		data, _ := ioutil.ReadAll(response.Body) |  | ||||||
| 		fmt.Println(string(data)) |  | ||||||
| 		identity.Username =	username |  | ||||||
| 		return identity, true, nil |  | ||||||
|  |  | ||||||
| 	} else if err != nil { |  | ||||||
| 		log.Fatal(err) |  | ||||||
| 		return identity, false, err |  | ||||||
|  |  | ||||||
| 	} else { |  | ||||||
| 		fmt.Printf("The HTTP request failed with error %v\n", response.StatusCode) |  | ||||||
| 		data, _ := ioutil.ReadAll(response.Body) |  | ||||||
| 		fmt.Println(string(data)) |  | ||||||
| 		return identity, false, err |  | ||||||
|  |  | ||||||
| 	} |  | ||||||
| 	return identity, false, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p KeystoneConnector) Prompt() string { return "username" } | func (p Connector) getAdminToken()(string, error) { | ||||||
|  |   	response, err := p.getTokenResponse(p.KeystoneUsername, p.KeystonePassword) | ||||||
|  |   	if err!= nil { | ||||||
|  |     	return "", err | ||||||
|  |   	} | ||||||
|  |   	token := response.Header["X-Subject-Token"][0] | ||||||
|  |   	return token, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p Connector) checkIfUserExists(userID string, token string) (bool) { | ||||||
|  |   	groupsURI := p.KeystoneHost + "/v3/users/" + userID | ||||||
|  |   	client := &http.Client{} | ||||||
|  |   	req, _ := http.NewRequest("GET", groupsURI, nil) | ||||||
|  |   	req.Header.Set("X-Auth-Token", token) | ||||||
|  |   	response, err :=  client.Do(req) | ||||||
|  |   	if err == nil && response.StatusCode == 200 { | ||||||
|  |     	return true | ||||||
|  |   	} | ||||||
|  |   	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p Connector) getUserGroups(userID string, token string) ([]string, error) { | ||||||
|  |   	groupsURI := p.KeystoneHost + "/v3/users/" + userID + "/groups" | ||||||
|  |   	client := &http.Client{} | ||||||
|  |   	req, _ := http.NewRequest("GET", groupsURI, nil) | ||||||
|  |   	req.Header.Set("X-Auth-Token", token) | ||||||
|  |   	response, err :=  client.Do(req) | ||||||
|  |  | ||||||
|  |   	if err != nil { | ||||||
|  |     	fmt.Printf("keystone: error while fetching user %q groups\n", userID) | ||||||
|  |     	return nil, err | ||||||
|  |   	} | ||||||
|  |   	data, _ := ioutil.ReadAll(response.Body) | ||||||
|  |   	var groupsResponse = new(GroupsResponse) | ||||||
|  |   	err = json.Unmarshal(data, &groupsResponse) | ||||||
|  |   	if err != nil { | ||||||
|  |     	return nil, err | ||||||
|  |   	} | ||||||
|  |   	groups := []string{} | ||||||
|  |   	for _, group := range groupsResponse.Groups { | ||||||
|  |   		groups = append(groups, group.Name) | ||||||
|  |   	} | ||||||
|  |   	return groups, nil | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										275
									
								
								connector/keystone/keystone_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								connector/keystone/keystone_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,275 @@ | |||||||
|  | package keystone | ||||||
|  |  | ||||||
|  | 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" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io/ioutil" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const dockerCliVersion = "1.37" | ||||||
|  |  | ||||||
|  | const exposedKeystonePort = "5000" | ||||||
|  | const exposedKeystonePortAdmin = "35357" | ||||||
|  |  | ||||||
|  | const keystoneHost = "http://localhost" | ||||||
|  | const keystoneURL = keystoneHost + ":" + exposedKeystonePort | ||||||
|  | const keystoneAdminURL = keystoneHost + ":" + exposedKeystonePortAdmin | ||||||
|  | const authTokenURL = keystoneURL + "/v3/auth/tokens/" | ||||||
|  | const userURL = keystoneAdminURL + "/v3/users/" | ||||||
|  | const groupURL = keystoneAdminURL + "/v3/groups/" | ||||||
|  |  | ||||||
|  | func startKeystoneContainer() string { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	cli, err := client.NewClientWithOpts(client.WithVersion(dockerCliVersion)) | ||||||
|  |  | ||||||
|  | 	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) { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	cli, err := client.NewClientWithOpts(client.WithVersion(dockerCliVersion)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		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) { | ||||||
|  | 	client := &http.Client{} | ||||||
|  |  | ||||||
|  | 	jsonData := LoginRequestData{ | ||||||
|  | 		Auth: Auth{ | ||||||
|  | 			Identity: Identity{ | ||||||
|  | 				Methods:[]string{"password"}, | ||||||
|  | 				Password: Password{ | ||||||
|  | 					User: User{ | ||||||
|  | 						Name: admin_name, | ||||||
|  | 						Domain: Domain{ID: "default"}, | ||||||
|  | 						Password: admin_pass, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body, _ := json.Marshal(jsonData) | ||||||
|  |  | ||||||
|  | 	req, _ := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(body)) | ||||||
|  |  | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  | 	resp, _ := client.Do(req) | ||||||
|  |  | ||||||
|  | 	token = resp.Header["X-Subject-Token"][0] | ||||||
|  | 	return token | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createUser(token, user_name, user_email, user_pass string) (string){ | ||||||
|  | 	client := &http.Client{} | ||||||
|  |  | ||||||
|  | 	createUserData := CreateUserRequest{ | ||||||
|  | 		CreateUser: CreateUserForm{ | ||||||
|  | 			Name: user_name, | ||||||
|  | 			Email: user_email, | ||||||
|  | 			Enabled: true, | ||||||
|  | 			Password: user_pass, | ||||||
|  | 			Roles: []string{"admin"}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body, _ := 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 { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Println(userResponse.User.ID) | ||||||
|  | 	return userResponse.User.ID | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 	resp, _ := client.Do(req) | ||||||
|  | 	fmt.Println(resp) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createGroup(token, description, name string) string{ | ||||||
|  | 	client := &http.Client{} | ||||||
|  |  | ||||||
|  | 	createGroupData := CreateGroup{ | ||||||
|  | 		CreateGroupForm{ | ||||||
|  | 			Description: description, | ||||||
|  | 			Name: name, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body, _ := 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 { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return groupResponse.Group.ID | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 	resp, _ := client.Do(req) | ||||||
|  | 	fmt.Println(resp) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const adminUser = "demo" | ||||||
|  | const adminPass = "DEMO_PASS" | ||||||
|  | const invalidPass = "WRONG_PASS" | ||||||
|  |  | ||||||
|  | const testUser = "test_user" | ||||||
|  | const testPass = "test_pass" | ||||||
|  | const testEmail = "test@example.com" | ||||||
|  |  | ||||||
|  | const domain = "default" | ||||||
|  |  | ||||||
|  | func TestIncorrectCredentialsLogin(t *testing.T) { | ||||||
|  |   	c := Connector{KeystoneHost: keystoneURL, Domain: domain, | ||||||
|  |   				   KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|  |   	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |   	_, validPW, _ := c.Login(context.Background(), s, adminUser, invalidPass) | ||||||
|  |  | ||||||
|  |   	if validPW { | ||||||
|  |   		t.Fail() | ||||||
|  |   	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestValidUserLogin(t *testing.T) { | ||||||
|  | 	token := getAdminToken(adminUser, adminPass) | ||||||
|  | 	userID := createUser(token, testUser, testEmail, testPass) | ||||||
|  |   	c := Connector{KeystoneHost: keystoneURL, Domain: domain, | ||||||
|  |   				  KeystoneUsername: adminUser, KeystonePassword: adminPass} | ||||||
|  |   	s := connector.Scopes{OfflineAccess: true, Groups: true} | ||||||
|  |   	_, validPW, _ := c.Login(context.Background(), s, testUser, testPass) | ||||||
|  |   	if !validPW { | ||||||
|  |      	t.Fail() | ||||||
|  |   	} | ||||||
|  |   	deleteUser(token, userID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUseRefreshToken(t *testing.T) { | ||||||
|  |   t.Fatal("Not implemented") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUseRefreshTokenUserDeleted(t *testing.T){ | ||||||
|  |   t.Fatal("Not implemented") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUseRefreshTokenGroupsChanged(t *testing.T){ | ||||||
|  | 	t.Fatal("Not implemented") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMain(m *testing.M) { | ||||||
|  | 	dockerID := startKeystoneContainer() | ||||||
|  |   	repeats := 10 | ||||||
|  |   	running := false | ||||||
|  |   	for i := 0; i < repeats; i++ { | ||||||
|  |    		_, err := http.Get(keystoneURL) | ||||||
|  |    		if err == nil { | ||||||
|  |      		running = true | ||||||
|  |      		break | ||||||
|  |    		} | ||||||
|  |    		time.Sleep(10 * time.Second) | ||||||
|  |   	} | ||||||
|  |   	if !running { | ||||||
|  |     	fmt.Printf("Failed to start keystone container") | ||||||
|  |     	os.Exit(1) | ||||||
|  |   	} | ||||||
|  |   	defer cleanKeystoneContainer(dockerID) | ||||||
|  |   	// run all tests | ||||||
|  | 	m.Run() | ||||||
|  | } | ||||||
							
								
								
									
										136
									
								
								connector/keystone/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								connector/keystone/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | package keystone | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Connector struct { | ||||||
|  | 	Domain 			 string | ||||||
|  | 	KeystoneHost 	 string | ||||||
|  | 	KeystoneUsername string | ||||||
|  | 	KeystonePassword string | ||||||
|  | 	Logger 			 logrus.FieldLogger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ConnectorData struct { | ||||||
|  | 	AccessToken string `json:"accessToken"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type KeystoneUser struct { | ||||||
|  | 	Domain KeystoneDomain `json:"domain"` | ||||||
|  | 	ID 	   string 		  `json:"id"` | ||||||
|  | 	Name   string 		  `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type KeystoneDomain struct { | ||||||
|  | 	ID string   `json:"id"` | ||||||
|  | 	Name string `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 `json:"identity"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Identity struct { | ||||||
|  | 	Methods  []string `json:"methods"` | ||||||
|  | 	Password 		  `json:"password"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Password struct { | ||||||
|  | 	User `json:"user"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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"` | ||||||
|  | 	Email    string `json:"email"` | ||||||
|  | 	Enabled  bool   `json:"enabled"` | ||||||
|  | 	Password string `json:"password"` | ||||||
|  | 	Roles  []string `json:"roles"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type UserResponse 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"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Links struct { | ||||||
|  | 	Self string `json:"self"` | ||||||
|  | 	Previous string `json:"previous"` | ||||||
|  | 	Next string `json:"next"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Group struct { | ||||||
|  | 	DomainID 	string `json:"domain_id` | ||||||
|  | 	Description string `json:"description"` | ||||||
|  | 	ID 			string `json:"id"` | ||||||
|  | 	Links 		Links  `json:"links"` | ||||||
|  | 	Name 		string `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GroupsResponse struct { | ||||||
|  | 	Links  Links   `json:"links"` | ||||||
|  | 	Groups []Group `json:"groups"` | ||||||
|  | } | ||||||
| @@ -14,7 +14,11 @@ storage: | |||||||
|  |  | ||||||
| # Configuration for the HTTP endpoints. | # Configuration for the HTTP endpoints. | ||||||
| web: | web: | ||||||
|   http: 0.0.0.0:5556 |   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 | # Configuration for telemetry | ||||||
| telemetry: | telemetry: | ||||||
| @@ -32,13 +36,20 @@ staticClients: | |||||||
|   secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |   secret: ZXhhbXBsZS1hcHAtc2VjcmV0 | ||||||
|  |  | ||||||
| #Provide Keystone connector and its config here | #Provide Keystone connector and its config here | ||||||
|  | # /v3/auth/tokens | ||||||
| connectors: | connectors: | ||||||
| - type: ksconfig | - type: keystone | ||||||
|   id: keystone |   id: keystone | ||||||
|   name: Keystone |   name: Keystone | ||||||
|   config: |   config: | ||||||
|     keystoneURI: http://example:5000/v3/auth/tokens |     keystoneHost: http://localhost:5000 | ||||||
|     domain: default |     domain: default | ||||||
|  |     keystoneUsername: demo | ||||||
|  |     keystonePassword: DEMO_PASS | ||||||
|  |  | ||||||
| # Let dex keep a list of passwords which can be used to login to dex. | # Let dex keep a list of passwords which can be used to login to dex. | ||||||
| enablePasswordDB: true | enablePasswordDB: true | ||||||
|  |  | ||||||
|  | oauth2: | ||||||
|  |   skipApprovalScreen: true | ||||||
|  |  | ||||||
|   | |||||||
| @@ -211,6 +211,7 @@ 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 { | ||||||
| @@ -345,7 +346,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("Failed to get auth request: %v", err) | 		s.logger.Errorf("2Failed to get auth request: %v", err) | ||||||
| 		s.renderError(w, http.StatusInternalServerError, "Database error.") | 		s.renderError(w, http.StatusInternalServerError, "Database error.") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -357,6 +358,7 @@ 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.") | ||||||
| @@ -435,7 +437,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("Failed to get auth request: %v", err) | 		s.logger.Errorf("3Failed to get auth request: %v", err) | ||||||
| 		s.renderError(w, http.StatusInternalServerError, "Database error.") | 		s.renderError(w, http.StatusInternalServerError, "Database error.") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -434,7 +434,7 @@ type ConnectorConfig interface { | |||||||
| // ConnectorsConfig variable provides an easy way to return a config struct | // ConnectorsConfig variable provides an easy way to return a config struct | ||||||
| // depending on the connector type. | // depending on the connector type. | ||||||
| var ConnectorsConfig = map[string]func() ConnectorConfig{ | var ConnectorsConfig = map[string]func() ConnectorConfig{ | ||||||
| 	"ksconfig":        func() ConnectorConfig { return new(keystone.Config) }, | 	"keystone":        func() ConnectorConfig { return new(keystone.Config) }, | ||||||
| 	"mockCallback":    func() ConnectorConfig { return new(mock.CallbackConfig) }, | 	"mockCallback":    func() ConnectorConfig { return new(mock.CallbackConfig) }, | ||||||
| 	"mockPassword":    func() ConnectorConfig { return new(mock.PasswordConfig) }, | 	"mockPassword":    func() ConnectorConfig { return new(mock.PasswordConfig) }, | ||||||
| 	"ldap":            func() ConnectorConfig { return new(ldap.Config) }, | 	"ldap":            func() ConnectorConfig { return new(ldap.Config) }, | ||||||
| @@ -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("unknown connector type %q", conn.Type) | 		return c, fmt.Errorf("xunknown connector type %q", conn.Type) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	connConfig := f() | 	connConfig := f() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user