Repository: coreos/dex
Pull request #592 (2016-10-07 18:41:55)

server: fix cross client scope prefix


Patch:
diff --git a/server/oauth2.go b/server/oauth2.go
index e236e0c4..003d8947 100644
--- a/server/oauth2.go
+++ b/server/oauth2.go
@@ -74,7 +74,7 @@ const (
 	scopeGroups            = "groups"
 	scopeEmail             = "email"
 	scopeProfile           = "profile"
-	scopeCrossClientPrefix = "oauth2:server:client_id:"
+	scopeCrossClientPrefix = "audience:server:client_id:"
 )
 
 const (
@@ -98,7 +98,7 @@ func (a audience) MarshalJSON() ([]byte, error) {
 	if len(a) == 1 {
 		return json.Marshal(a[0])
 	}
-	return json.Marshal(a)
+	return json.Marshal([]string(a))
 }
 
 type idTokenClaims struct {


Test patch:
diff --git a/server/server_test.go b/server/server_test.go
index 46fcc710..ea0793aa 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -10,6 +10,8 @@ import (
 	"net/http/httptest"
 	"net/http/httputil"
 	"net/url"
+	"reflect"
+	"sort"
 	"strings"
 	"sync"
 	"testing"
@@ -384,6 +386,128 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
 	}
 }
 
+func TestCrossClientScopes(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	httpServer, s := newTestServer(t, func(c *Config) {
+		c.Issuer = c.Issuer + "/non-root-path"
+	})
+	defer httpServer.Close()
+
+	p, err := oidc.NewProvider(ctx, httpServer.URL)
+	if err != nil {
+		t.Fatalf("failed to get provider: %v", err)
+	}
+
+	var (
+		reqDump, respDump []byte
+		gotCode           bool
+		state             = "a_state"
+	)
+	defer func() {
+		if !gotCode {
+			t.Errorf("never got a code in callback\n%s\n%s", reqDump, respDump)
+		}
+	}()
+
+	testClientID := "testclient"
+	peerID := "peer"
+
+	var oauth2Config *oauth2.Config
+	oauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.URL.Path == "/callback" {
+			q := r.URL.Query()
+			if errType := q.Get("error"); errType != "" {
+				if desc := q.Get("error_description"); desc != "" {
+					t.Errorf("got error from server %s: %s", errType, desc)
+				} else {
+					t.Errorf("got error from server %s", errType)
+				}
+				w.WriteHeader(http.StatusInternalServerError)
+				return
+			}
+
+			if code := q.Get("code"); code != "" {
+				gotCode = true
+				token, err := oauth2Config.Exchange(ctx, code)
+				if err != nil {
+					t.Errorf("failed to exchange code for token: %v", err)
+					return
+				}
+				rawIDToken, ok := token.Extra("id_token").(string)
+				if !ok {
+					t.Errorf("no id token found: %v", err)
+					return
+				}
+				idToken, err := p.NewVerifier(ctx).Verify(rawIDToken)
+				if err != nil {
+					t.Errorf("failed to parse ID Token: %v", err)
+					return
+				}
+
+				sort.Strings(idToken.Audience)
+				expAudience := []string{peerID, testClientID}
+				if !reflect.DeepEqual(idToken.Audience, expAudience) {
+					t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience)
+				}
+
+			}
+			if gotState := q.Get("state"); gotState != state {
+				t.Errorf("state did not match, want=%q got=%q", state, gotState)
+			}
+			w.WriteHeader(http.StatusOK)
+			return
+		}
+		http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusSeeOther)
+	}))
+
+	defer oauth2Server.Close()
+
+	redirectURL := oauth2Server.URL + "/callback"
+	client := storage.Client{
+		ID:           testClientID,
+		Secret:       "testclientsecret",
+		RedirectURIs: []string{redirectURL},
+	}
+	if err := s.storage.CreateClient(client); err != nil {
+		t.Fatalf("failed to create client: %v", err)
+	}
+
+	peer := storage.Client{
+		ID:           peerID,
+		Secret:       "foobar",
+		TrustedPeers: []string{"testclient"},
+	}
+
+	if err := s.storage.CreateClient(peer); err != nil {
+		t.Fatalf("failed to create client: %v", err)
+	}
+
+	oauth2Config = &oauth2.Config{
+		ClientID:     client.ID,
+		ClientSecret: client.Secret,
+		Endpoint:     p.Endpoint(),
+		Scopes: []string{
+			oidc.ScopeOpenID, "profile", "email",
+			"audience:server:client_id:" + client.ID,
+			"audience:server:client_id:" + peer.ID,
+		},
+		RedirectURL: redirectURL,
+	}
+
+	resp, err := http.Get(oauth2Server.URL + "/login")
+	if err != nil {
+		t.Fatalf("get failed: %v", err)
+	}
+	if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
+		t.Fatal(err)
+	}
+	if respDump, err = httputil.DumpResponse(resp, true); err != nil {
+		t.Fatal(err)
+	}
+}
+
 func TestPasswordDB(t *testing.T) {
 	s := memory.New()
 	conn := newPasswordDB(s)