Skip to content

Commit

Permalink
sdk: add consent helper - closes #397
Browse files Browse the repository at this point in the history
  • Loading branch information
Aeneas Rekkas (arekkas) committed Mar 22, 2017
1 parent 109af2f commit 9ad3bed
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
7 changes: 7 additions & 0 deletions sdk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type Client struct {
// Groups offers warden group management capabilities.
Groups *group.HTTPManager

// Consent offers
Consent *Consent

http *http.Client
clusterURL *url.URL
clientID string
Expand Down Expand Up @@ -147,6 +150,10 @@ func Connect(opts ...option) (*Client, error) {
Client: c.http,
}

c.Consent = &Consent{
KeyManager: c.JSONWebKeys,
}

return c, nil
}

Expand Down
107 changes: 107 additions & 0 deletions sdk/consent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package sdk

import (
"crypto/rsa"
"fmt"
"github.com/dgrijalva/jwt-go"
ejwt "github.com/ory-am/fosite/token/jwt"
"github.com/ory-am/hydra/jwk"
"github.com/ory-am/hydra/oauth2"
"github.com/pkg/errors"
"time"
)

type Consent struct {
KeyManager jwk.Manager
}

type ResponseRequest struct {
Challenge string
Subject string
Scopes []string
AccessTokenExtra interface{}
IDTokenExtra interface{}
}

type ChallengeClaims struct {
RequestedScopes []string `json:"scp"`
Audience string `json:"aud"`
RedirectURL string `json:"redir"`
ExpiresAt float64 `json:"exp"`
ID string `json:"jti"`
}

func (c *ChallengeClaims) Valid() error {
if time.Now().After(ejwt.ToTime(c.ExpiresAt)) {
return errors.Errorf("Consent challenge expired")
}
return nil
}

func (c *Consent) VerifyChallenge(challenge string) (*ChallengeClaims, error) {
var claims ChallengeClaims
t, err := jwt.ParseWithClaims(challenge, &claims, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
return nil, errors.Errorf("Unexpected signing method: %v", t.Header["alg"])
}

pk, err := c.KeyManager.GetKey(oauth2.ConsentChallengeKey, "public")
if err != nil {
return nil, err
}

rsaKey, ok := jwk.First(pk.Keys).Key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("Could not convert to RSA Private Key")
}
return rsaKey, nil
})
if err != nil {
return nil, errors.Wrap(err, "The consent chalnge is not a valid JSON Web Token")
}

if !t.Valid {
return nil, errors.Errorf("Consent challenge is invalid")
} else if err := claims.Valid(); err != nil {
return nil, errors.Wrap(err, "The consent challenge claims could not be verified")
}

return &claims, err
}

func (c *Consent) GenerateResponse(r *ResponseRequest) (string, error) {
challenge, err := c.VerifyChallenge(r.Challenge)
if err != nil {
return "", err
}

token := jwt.New(jwt.SigningMethodRS256)
token.Claims = jwt.MapClaims{
"jti": challenge.ID,
"scp": r.Scopes,
"aud": challenge.Audience,
"exp": challenge.ExpiresAt,
"sub": r.Subject,
"at_ext": r.AccessTokenExtra,
"id_ext": r.IDTokenExtra,
}

ks, err := c.KeyManager.GetKey(oauth2.ConsentEndpointKey, "private")
if err != nil {
return "", errors.WithStack(err)
}

rsaKey, ok := jwk.First(ks.Keys).Key.(*rsa.PrivateKey)
if !ok {
return "", errors.New("Could not convert to RSA Private Key")
}

var signature, encoded string
if encoded, err = token.SigningString(); err != nil {
return "", errors.WithStack(err)
} else if signature, err = token.Method.Sign(encoded, rsaKey); err != nil {
return "", errors.WithStack(err)
}

return fmt.Sprintf("%s.%s", encoded, signature), nil
}
64 changes: 64 additions & 0 deletions sdk/consent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package sdk

import (
"encoding/base64"
"encoding/json"
"github.com/gorilla/sessions"
"github.com/ory-am/fosite"
"github.com/ory-am/hydra/jwk"
"github.com/ory-am/hydra/oauth2"
"github.com/square/go-jose"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"strings"
"testing"
"time"
)

func genKey() *jose.JsonWebKeySet {
g := &jwk.RS256Generator{}
k, _ := g.Generate("")
return k
}

func TestConsentHelper(t *testing.T) {
km := &jwk.MemoryManager{Keys: map[string]*jose.JsonWebKeySet{}}
km.AddKeySet(oauth2.ConsentChallengeKey, genKey())
km.AddKeySet(oauth2.ConsentEndpointKey, genKey())

_, err := km.GetKey(oauth2.ConsentChallengeKey, "private")
require.Nil(t, err)
c := Consent{KeyManager: km}
s := oauth2.DefaultConsentStrategy{
KeyManager: km,
DefaultChallengeLifespan: time.Hour,
}

ar := fosite.NewAuthorizeRequest()
ar.Client = &fosite.DefaultClient{ID: "foobarclient"}
challenge, err := s.IssueChallenge(ar, "/lightyear", &sessions.Session{Values: map[interface{}]interface{}{}})
require.Nil(t, err)

claims, err := c.VerifyChallenge(challenge)
require.Nil(t, err)
assert.Equal(t, claims.Audience, "foobarclient")
assert.Equal(t, claims.RedirectURL, "/lightyear")
assert.NotEmpty(t, claims.ID)

resp, err := c.GenerateResponse(&ResponseRequest{
Challenge: challenge,
Subject: "buzz",
Scopes: []string{"offline", "openid"},
})
require.Nil(t, err)

var dec map[string]interface{}
result, err := base64.RawURLEncoding.DecodeString(strings.Split(resp, ".")[1])
require.Nil(t, err)

require.Nil(t, json.Unmarshal(result, &dec))
assert.Equal(t, dec["jti"], claims.ID)
t.Logf("%v", dec["jti"])
assert.Equal(t, dec["scp"].([]interface{}), []interface{}{"offline", "openid"})
assert.Equal(t, dec["sub"], "buzz")
}

0 comments on commit 9ad3bed

Please sign in to comment.