From e3c9b4929969658b93d2cc979df218479803b542 Mon Sep 17 00:00:00 2001 From: Damian Pacierpnik Date: Thu, 28 Sep 2017 18:30:15 +0200 Subject: [PATCH] Cross clients improvement - requesting client ID always added to the audience claim --- server/oauth2.go | 18 ++++++- server/server_test.go | 121 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/server/oauth2.go b/server/oauth2.go index 528b25a6..6a0d5eee 100644 --- a/server/oauth2.go +++ b/server/oauth2.go @@ -222,6 +222,15 @@ func accessTokenHash(alg jose.SignatureAlgorithm, accessToken string) (string, e type audience []string +func (a audience) contains(aud string) bool { + for _, e := range a { + if aud == e { + return true + } + } + return false +} + func (a audience) MarshalJSON() ([]byte, error) { if len(a) == 1 { return json.Marshal(a[0]) @@ -328,8 +337,13 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str // client as the audience. tok.Audience = audience{clientID} } else { - // Client asked for cross client audience. The current client - // becomes the authorizing party. + // Client asked for cross client audience: + // if the current client was not requested explicitly + if !tok.Audience.contains(clientID) { + // by default it becomes one of entries in Audience + tok.Audience = append(tok.Audience, clientID) + } + // The current client becomes the authorizing party. tok.AuthorizingParty = clientID } diff --git a/server/server_test.go b/server/server_test.go index 88110087..a67cfbf4 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -812,6 +812,127 @@ func TestCrossClientScopes(t *testing.T) { } } +func TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + httpServer, s := newTestServer(ctx, 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.Verifier(&oidc.Config{ClientID: testClientID}).Verify(ctx, 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:" + 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(logger) conn := newPasswordDB(s)