mirror of https://github.com/dexidp/dex.git
10 changed files with 474 additions and 470 deletions
@ -1,275 +1,358 @@
|
||||
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" |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"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)) |
||||
"net/http" |
||||
"os" |
||||
"reflect" |
||||
"strings" |
||||
"testing" |
||||
|
||||
if err != nil { |
||||
fmt.Printf("Error %v", err) |
||||
return "" |
||||
} |
||||
"github.com/dexidp/dex/connector" |
||||
) |
||||
|
||||
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") |
||||
const ( |
||||
adminUser = "demo" |
||||
adminPass = "DEMO_PASS" |
||||
invalidPass = "WRONG_PASS" |
||||
|
||||
if err != nil { |
||||
fmt.Printf("Error %v", err) |
||||
return "" |
||||
} |
||||
testUser = "test_user" |
||||
testPass = "test_pass" |
||||
testEmail = "test@example.com" |
||||
testGroup = "test_group" |
||||
testDomain = "default" |
||||
) |
||||
|
||||
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { |
||||
panic(err) |
||||
} |
||||
var ( |
||||
keystoneURL = "" |
||||
keystoneAdminURL = "" |
||||
authTokenURL = "" |
||||
usersURL = "" |
||||
groupsURL = "" |
||||
) |
||||
|
||||
fmt.Println(resp.ID) |
||||
return resp.ID |
||||
type userResponse struct { |
||||
User struct { |
||||
ID string `json:"id"` |
||||
} `json:"user"` |
||||
} |
||||
|
||||
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) |
||||
} |
||||
type groupResponse struct { |
||||
Group struct { |
||||
ID string `json:"id"` |
||||
} `json:"group"` |
||||
} |
||||
|
||||
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{} |
||||
|
||||
jsonData := LoginRequestData{ |
||||
Auth: Auth{ |
||||
Identity: Identity{ |
||||
Methods:[]string{"password"}, |
||||
Password: Password{ |
||||
User: User{ |
||||
Name: admin_name, |
||||
Domain: Domain{ID: "default"}, |
||||
Password: admin_pass, |
||||
jsonData := loginRequestData{ |
||||
auth: auth{ |
||||
Identity: identity{ |
||||
Methods: []string{"password"}, |
||||
Password: password{ |
||||
User: user{ |
||||
Name: adminName, |
||||
Domain: domain{ID: testDomain}, |
||||
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") |
||||
resp, _ := client.Do(req) |
||||
resp, err := client.Do(req) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
token = resp.Header["X-Subject-Token"][0] |
||||
return token |
||||
token = resp.Header.Get("X-Subject-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{} |
||||
|
||||
createUserData := CreateUserRequest{ |
||||
CreateUser: CreateUserForm{ |
||||
Name: user_name, |
||||
Email: user_email, |
||||
Enabled: true, |
||||
Password: user_pass, |
||||
Roles: []string{"admin"}, |
||||
createUserData := map[string]interface{}{ |
||||
"user": map[string]interface{}{ |
||||
"name": userName, |
||||
"email": userEmail, |
||||
"enabled": true, |
||||
"password": userPass, |
||||
"roles": []string{"admin"}, |
||||
}, |
||||
} |
||||
|
||||
body, _ := json.Marshal(createUserData) |
||||
body, err := json.Marshal(createUserData) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
req, _ := http.NewRequest("POST", userURL, bytes.NewBuffer(body)) |
||||
req, err := http.NewRequest("POST", usersURL, bytes.NewBuffer(body)) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
req.Header.Set("X-Auth-Token", token) |
||||
req.Header.Add("Content-Type", "application/json") |
||||
resp, _ := client.Do(req) |
||||
resp, err := client.Do(req) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
data, _ := ioutil.ReadAll(resp.Body) |
||||
var userResponse = new(UserResponse) |
||||
err := json.Unmarshal(data, &userResponse) |
||||
data, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.Fatal(err) |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
fmt.Println(userResponse.User.ID) |
||||
return userResponse.User.ID |
||||
var userResp = new(userResponse) |
||||
err = json.Unmarshal(data, &userResp) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
return userResp.User.ID |
||||
} |
||||
|
||||
func deleteUser(token, id string) { |
||||
// delete group or user
|
||||
func delete(t *testing.T, token, id, uri string) { |
||||
t.Helper() |
||||
client := &http.Client{} |
||||
|
||||
deleteUserURI := userURL + id |
||||
fmt.Println(deleteUserURI) |
||||
req, _ := http.NewRequest("DELETE", deleteUserURI, nil) |
||||
deleteURI := uri + id |
||||
req, err := http.NewRequest("DELETE", deleteURI, nil) |
||||
if err != nil { |
||||
t.Fatalf("error: %v", err) |
||||
} |
||||
req.Header.Set("X-Auth-Token", token) |
||||
resp, _ := client.Do(req) |
||||
fmt.Println(resp) |
||||
client.Do(req) |
||||
} |
||||
|
||||
func createGroup(token, description, name string) string{ |
||||
func createGroup(t *testing.T, token, description, name string) string { |
||||
t.Helper() |
||||
client := &http.Client{} |
||||
|
||||
createGroupData := CreateGroup{ |
||||
CreateGroupForm{ |
||||
Description: description, |
||||
Name: name, |
||||
createGroupData := map[string]interface{}{ |
||||
"group": map[string]interface{}{ |
||||
"name": name, |
||||
"description": description, |
||||
}, |
||||
} |
||||
|
||||
body, _ := json.Marshal(createGroupData) |
||||
body, err := json.Marshal(createGroupData) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
req, _ := http.NewRequest("POST", groupURL, bytes.NewBuffer(body)) |
||||
req, err := http.NewRequest("POST", groupsURL, bytes.NewBuffer(body)) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
req.Header.Set("X-Auth-Token", token) |
||||
req.Header.Add("Content-Type", "application/json") |
||||
resp, _ := client.Do(req) |
||||
data, _ := ioutil.ReadAll(resp.Body) |
||||
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 groupResponse = new(GroupID) |
||||
err := json.Unmarshal(data, &groupResponse) |
||||
var groupResp = new(groupResponse) |
||||
err = json.Unmarshal(data, &groupResp) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
return groupResponse.Group.ID |
||||
return groupResp.Group.ID |
||||
} |
||||
|
||||
func addUserToGroup(token, groupId, userId string) { |
||||
uri := groupURL + groupId + "/users/" + userId |
||||
func addUserToGroup(t *testing.T, token, groupID, userID string) error { |
||||
t.Helper() |
||||
uri := groupsURL + groupID + "/users/" + userID |
||||
client := &http.Client{} |
||||
req, _ := http.NewRequest("PUT", uri, nil) |
||||
req, err := http.NewRequest("PUT", uri, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
req.Header.Set("X-Auth-Token", token) |
||||
resp, _ := client.Do(req) |
||||
fmt.Println(resp) |
||||
client.Do(req) |
||||
return nil |
||||
} |
||||
|
||||
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() |
||||
} |
||||
c := keystoneConnector{KeystoneHost: keystoneURL, Domain: testDomain, |
||||
KeystoneUsername: adminUser, KeystonePassword: adminPass} |
||||
s := connector.Scopes{OfflineAccess: true, Groups: true} |
||||
_, validPW, err := c.Login(context.Background(), s, adminUser, invalidPass) |
||||
if err != nil { |
||||
t.Fatal(err.Error()) |
||||
} |
||||
|
||||
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) |
||||
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} |
||||
identity, validPW, err := c.Login(context.Background(), s, testUser, testPass) |
||||
if err != nil { |
||||
t.Fatal(err.Error()) |
||||
} |
||||
t.Log(identity) |
||||
|
||||
if !validPW { |
||||
t.Fail() |
||||
} |
||||
delete(t, token, userID, usersURL) |
||||
} |
||||
|
||||
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){ |
||||
t.Fatal("Not implemented") |
||||
func TestUseRefreshTokenUserDeleted(t *testing.T) { |
||||
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){ |
||||
t.Fatal("Not implemented") |
||||
func TestUseRefreshTokenGroupsChanged(t *testing.T) { |
||||
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) { |
||||
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
|
||||
keystoneURLEnv := "DEX_KEYSTONE_URL" |
||||
keystoneAdminURLEnv := "DEX_KEYSTONE_ADMIN_URL" |
||||
keystoneURL = os.Getenv(keystoneURLEnv) |
||||
if keystoneURL == "" { |
||||
fmt.Printf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv) |
||||
return |
||||
} |
||||
keystoneAdminURL := os.Getenv(keystoneAdminURLEnv) |
||||
if keystoneAdminURL == "" { |
||||
fmt.Printf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv) |
||||
return |
||||
} |
||||
authTokenURL = keystoneURL + "/v3/auth/tokens/" |
||||
fmt.Printf("Auth token url %q\n", authTokenURL) |
||||
fmt.Printf("Keystone URL %q\n", keystoneURL) |
||||
usersURL = keystoneAdminURL + "/v3/users/" |
||||
groupsURL = keystoneAdminURL + "/v3/groups/" |
||||
// run all tests
|
||||
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) |
||||
} |
||||
} |
||||
|
||||
@ -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 |
||||
|
||||
Loading…
Reference in new issue