forked from mystiq/dex
ca18efb1fe
The Client object on its own doesn't fully express everything about a single client, and so when loading clients from a static configuration it's not enough to just (de)serialize clients. To that end, LoadableClient contains the full representation of a client and associated entities.
284 lines
8.6 KiB
Go
284 lines
8.6 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/coreos/dex/client"
|
|
"github.com/coreos/dex/client/manager"
|
|
"github.com/coreos/dex/db"
|
|
schema "github.com/coreos/dex/schema/workerschema"
|
|
"github.com/coreos/go-oidc/oidc"
|
|
"github.com/kylelemons/godebug/pretty"
|
|
)
|
|
|
|
func makeBody(s string) io.ReadCloser {
|
|
return ioutil.NopCloser(strings.NewReader(s))
|
|
}
|
|
|
|
func TestCreateInvalidRequest(t *testing.T) {
|
|
u := &url.URL{Scheme: "http", Host: "example.com", Path: "clients"}
|
|
h := http.Header{"Content-Type": []string{"application/json"}}
|
|
dbm := db.NewMemDB()
|
|
repo := db.NewClientRepo(dbm)
|
|
manager := manager.NewClientManager(repo, db.TransactionFactory(dbm), manager.ManagerOptions{})
|
|
res := &clientResource{manager: manager}
|
|
tests := []struct {
|
|
req *http.Request
|
|
wantCode int
|
|
wantBody string
|
|
}{
|
|
// invalid content-type
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: http.Header{"Content-Type": []string{"application/xml"}}},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_request","error_description":"unsupported content-type"}`,
|
|
},
|
|
// invalid method
|
|
{
|
|
req: &http.Request{Method: "DELETE", URL: u, Header: h},
|
|
wantCode: http.StatusMethodNotAllowed,
|
|
wantBody: `{"error":"invalid_request","error_description":"HTTP DELETE method not supported for this resource"}`,
|
|
},
|
|
// invalid method
|
|
{
|
|
req: &http.Request{Method: "PUT", URL: u, Header: h},
|
|
wantCode: http.StatusMethodNotAllowed,
|
|
wantBody: `{"error":"invalid_request","error_description":"HTTP PUT method not supported for this resource"}`,
|
|
},
|
|
// invalid method
|
|
{
|
|
req: &http.Request{Method: "HEAD", URL: u, Header: h},
|
|
wantCode: http.StatusMethodNotAllowed,
|
|
wantBody: `{"error":"invalid_request","error_description":"HTTP HEAD method not supported for this resource"}`,
|
|
},
|
|
// unserializable body
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody("asdf")},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_request","error_description":"unable to decode request body"}`,
|
|
},
|
|
// empty body
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody("")},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_request","error_description":"unable to decode request body"}`,
|
|
},
|
|
// missing url field
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"id":"foo"}`)},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_client_metadata","error_description":"zero redirect URLs"}`,
|
|
},
|
|
// empty url array
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":[]}`)},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_client_metadata","error_description":"zero redirect URLs"}`,
|
|
},
|
|
// array with empty string
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":[""]}`)},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_client_metadata","error_description":"missing or invalid field: redirectURIs"}`,
|
|
},
|
|
// uri with unusable scheme
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["asdf.com"]}`)},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_client_metadata","error_description":"no host for uri field redirect_uris"}`,
|
|
},
|
|
// uri missing host
|
|
{
|
|
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["http://"]}`)},
|
|
wantCode: http.StatusBadRequest,
|
|
wantBody: `{"error":"invalid_client_metadata","error_description":"no host for uri field redirect_uris"}`,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
w := httptest.NewRecorder()
|
|
res.ServeHTTP(w, tt.req)
|
|
|
|
if w.Code != tt.wantCode {
|
|
t.Errorf("case %d: invalid response code, want=%d, got=%d", i, tt.wantCode, w.Code)
|
|
}
|
|
|
|
gotBody := w.Body.String()
|
|
if gotBody != tt.wantBody {
|
|
t.Errorf("case %d: invalid response body, want=%s, got=%s", i, tt.wantBody, gotBody)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCreate(t *testing.T) {
|
|
dbm := db.NewMemDB()
|
|
repo := db.NewClientRepo(dbm)
|
|
manager := manager.NewClientManager(repo, db.TransactionFactory(dbm), manager.ManagerOptions{})
|
|
res := &clientResource{manager: manager}
|
|
tests := [][]string{
|
|
[]string{"http://example.com"},
|
|
[]string{"https://example.com"},
|
|
[]string{"http://example.com/foo"},
|
|
[]string{"http://example.com/bar", "http://example.com/foo"},
|
|
}
|
|
endpoint := "http://example.com/clients"
|
|
|
|
for i, tt := range tests {
|
|
body := strings.NewReader(fmt.Sprintf(`{"redirectURIs":["%s"]}`, strings.Join(tt, `","`)))
|
|
r, err := http.NewRequest("POST", endpoint, body)
|
|
if err != nil {
|
|
t.Fatalf("Failed creating http.Request: %v", err)
|
|
}
|
|
r.Header.Set("content-type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
res.ServeHTTP(w, r)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Errorf("case %d: invalid response code, want=%d, got=%d", i, http.StatusCreated, w.Code)
|
|
}
|
|
|
|
var client schema.ClientWithSecret
|
|
if err := json.Unmarshal(w.Body.Bytes(), &client); err != nil {
|
|
t.Errorf("case %d: unexpected error=%v", i, err)
|
|
}
|
|
if len(client.RedirectURIs) != len(tt) {
|
|
t.Errorf("case %d: unexpected number of redirect URIs, want=%d, got=%d", i, len(tt), len(client.RedirectURIs))
|
|
}
|
|
|
|
if !reflect.DeepEqual(tt, client.RedirectURIs) {
|
|
t.Errorf("case %d: unexpected client redirect URIs: want=%v got=%v", i, tt, client.RedirectURIs)
|
|
}
|
|
|
|
if client.Id == "" {
|
|
t.Errorf("case %d: empty client ID in response", i)
|
|
}
|
|
|
|
if client.Secret == "" {
|
|
t.Errorf("case %d: empty client secret in response", i)
|
|
}
|
|
|
|
wantLoc := fmt.Sprintf("%s/%s", endpoint, client.Id)
|
|
gotLoc := w.Header().Get("Location")
|
|
if gotLoc != wantLoc {
|
|
t.Errorf("case %d: invalid location header, want=%v, got=%v", i, wantLoc, gotLoc)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
|
|
b64Encode := func(s string) string {
|
|
return base64.URLEncoding.EncodeToString([]byte(s))
|
|
}
|
|
|
|
tests := []struct {
|
|
cs []client.Client
|
|
want []*schema.Client
|
|
}{
|
|
// empty repo
|
|
{
|
|
cs: []client.Client{},
|
|
want: nil,
|
|
},
|
|
// single client
|
|
{
|
|
cs: []client.Client{
|
|
client.Client{
|
|
Credentials: oidc.ClientCredentials{ID: "example.com", Secret: b64Encode("secret")},
|
|
Metadata: oidc.ClientMetadata{
|
|
RedirectURIs: []url.URL{
|
|
url.URL{Scheme: "http", Host: "example.com"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: []*schema.Client{
|
|
&schema.Client{
|
|
Id: "example.com",
|
|
RedirectURIs: []string{"http://example.com"},
|
|
},
|
|
},
|
|
},
|
|
// multi client
|
|
{
|
|
cs: []client.Client{
|
|
client.Client{
|
|
Credentials: oidc.ClientCredentials{ID: "example.com", Secret: b64Encode("secret")},
|
|
Metadata: oidc.ClientMetadata{
|
|
RedirectURIs: []url.URL{
|
|
url.URL{Scheme: "http", Host: "example.com"},
|
|
},
|
|
},
|
|
},
|
|
client.Client{
|
|
Credentials: oidc.ClientCredentials{ID: "example2.com", Secret: b64Encode("secret")},
|
|
Metadata: oidc.ClientMetadata{
|
|
RedirectURIs: []url.URL{
|
|
url.URL{Scheme: "https", Host: "example2.com", Path: "one/two/three"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: []*schema.Client{
|
|
&schema.Client{
|
|
Id: "example2.com",
|
|
RedirectURIs: []string{"https://example2.com/one/two/three"},
|
|
},
|
|
&schema.Client{
|
|
Id: "example.com",
|
|
RedirectURIs: []string{"http://example.com"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
f, err := makeTestFixturesWithOptions(testFixtureOptions{
|
|
clients: clientsToLoadableClients(tt.cs),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error making test fixtures: %v", err)
|
|
}
|
|
|
|
res := &clientResource{manager: f.clientManager}
|
|
|
|
r, err := http.NewRequest("GET", "http://example.com/clients", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed creating http.Request: %v", err)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
res.ServeHTTP(w, r)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("case %d: invalid response code, want=%d, got=%d", i, http.StatusOK, w.Code)
|
|
}
|
|
|
|
var resp schema.ClientPage
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Errorf("case %d: unexpected error=%v", i, err)
|
|
}
|
|
sort.Sort(byClientId(tt.want))
|
|
sort.Sort(byClientId(resp.Clients))
|
|
|
|
if diff := pretty.Compare(tt.want, resp.Clients); diff != "" {
|
|
t.Errorf("case %d: invalid response body: %s", i, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
type byClientId []*schema.Client
|
|
|
|
func (b byClientId) Len() int { return len(b) }
|
|
func (b byClientId) Less(i, j int) bool { return b[i].Id < b[j].Id }
|
|
func (b byClientId) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|