Skip to content
This repository has been archived by the owner on Apr 11, 2023. It is now read-only.

Commit

Permalink
feat: gnap interact integration
Browse files Browse the repository at this point in the history
Signed-off-by: Filip Burlacu <[email protected]>
  • Loading branch information
Filip Burlacu committed May 12, 2022
1 parent 8b599c2 commit c43081f
Show file tree
Hide file tree
Showing 15 changed files with 1,023 additions and 113 deletions.
5 changes: 4 additions & 1 deletion cmd/auth-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,10 @@ func startAuthService(parameters *authRestParameters, srv server) error {
}

// TODO: support creating multiple GNAP user interaction handlers
interact, err := redirect.New(parameters.externalURL + gnap.InteractPath)
interact, err := redirect.New(&redirect.Config{
StoreProvider: provider,
InteractBasePath: parameters.externalURL + gnap.InteractPath,
})
if err != nil {
return fmt.Errorf("initializing GNAP interaction handler: %w", err)
}
Expand Down
12 changes: 11 additions & 1 deletion pkg/gnap/accesspolicy/access_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type AccessPolicy struct {
accessDescriptors map[string]tokenAccessMap
// basePermissions holds the base permission of given TokenAccess.Type values, if no other permission is granted.
basePermissions map[string]permissionLevel
// lifetime number of seconds that a given token access should be valid for.
lifetime map[string]int
}

// New initializes an AccessPolicy.
Expand All @@ -33,6 +35,7 @@ func New(config *Config) (*AccessPolicy, error) {
refToType: map[string]string{},
accessDescriptors: map[string]tokenAccessMap{},
basePermissions: map[string]permissionLevel{},
lifetime: map[string]int{},
}

for _, accessType := range config.AccessTypes {
Expand All @@ -51,6 +54,8 @@ func New(config *Config) (*AccessPolicy, error) {
ap.refToType[accessType.Ref] = typeStr
}

ap.lifetime[typeStr] = accessType.Expiry

switch accessType.Permission {
case PermissionAlwaysAllowed:
ap.basePermissions[typeStr] = permissionAllowed
Expand Down Expand Up @@ -225,14 +230,19 @@ func (ap *AccessPolicy) defaultTokenAccessPermission(access gnap.TokenAccess, ac
// if the given TokenAccess wasn't a subset of a granted token's access,
// we use the AccessPolicy's default permissions
defaultPermission := permissionDenied
defaultLifetime := 0

for defaultType, defaultAccess := range ap.accessDescriptors {
ok := isTokenAccessSubset(defaultAccess, accessMap)
if ok {
perm := ap.basePermissions[defaultType]
lifetime := ap.lifetime[defaultType]

if perm > defaultPermission {
defaultPermission = perm
defaultLifetime = lifetime
} else if perm == defaultPermission && defaultLifetime < lifetime {
defaultLifetime = lifetime
}
}
}
Expand All @@ -243,7 +253,7 @@ func (ap *AccessPolicy) defaultTokenAccessPermission(access gnap.TokenAccess, ac

return defaultPermission, &expirableTokenAccess{
TokenAccess: access,
expiry: time.Time{},
expiry: time.Now().Add(time.Second * time.Duration(defaultLifetime)),
}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/gnap/accesspolicy/access_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
"access-types": [{
"reference": "foo",
"permission": "NeedsConsent",
"expires-in": 360,
"access": {
"type": "trustbloc.xyz/auth/type/foo",
"subject-keys": ["client-id", "preferred-name"],
Expand All @@ -43,6 +44,7 @@ const (
}, {
"reference": "audit-writer",
"permission": "NeedsConsent",
"expires-in": 600,
"access": {
"type": "trustbloc.xyz/auth/type/audit-write",
"subject-keys": ["client-id"],
Expand Down
1 change: 1 addition & 0 deletions pkg/gnap/accesspolicy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ type TokenAccessConfig struct {
Access gnap.TokenAccess `json:"access"`
Ref string `json:"reference"`
Permission string `json:"permission"`
Expiry int `json:"expires-in"`
}
10 changes: 6 additions & 4 deletions pkg/gnap/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ type InteractionHandler interface {
// PrepareLoginConsentFlow takes a set of requested access tokens and subject
// data, prepares a login & consent flow, and returns parameters for the user
// client to initiate the login & consent flow.
PrepareInteraction(clientInteract *gnap.RequestInteract) (*gnap.ResponseInteract, error)
PrepareInteraction(clientInteract *gnap.RequestInteract, requestedTokens []*ExpiringTokenRequest,
) (*gnap.ResponseInteract, error)

// CompleteLoginConsentFlow takes a set of access requests that the user
// consented to, and the ID of the flow where this was performed, creates an
// interact_ref, saves the consent set under the interact_ref, and returns the
// interact_ref.
CompleteInteraction(flowID string, consentSet *ConsentResult) (string, error)
CompleteInteraction(flowID string, consentSet *ConsentResult) (string, *gnap.RequestInteract, error)
// QueryInteraction returns the consent metadata and subject info saved under the interaction.
QueryInteraction(interactRef string) (*ConsentResult, error)
// DeleteInteraction deletes the interaction under interactRef if it exists.
Expand Down Expand Up @@ -70,6 +72,6 @@ type ExpiringToken struct {

// ConsentResult holds access token descriptors and subject data that were granted by a user consent interaction.
type ConsentResult struct {
Tokens []*gnap.TokenRequest
SubjectData map[string]string
Tokens []*ExpiringTokenRequest `json:"tok,omitempty"`
SubjectData map[string]string `json:"sub,omitempty"`
}
28 changes: 20 additions & 8 deletions pkg/gnap/authhandler/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,8 @@ func (h *AuthHandler) HandleAccessRequest( // nolint:funlen

s.Requested = permissions.NeedsConsent

// TODO: figure out what parameters to pass into api.InteractionHandler.PrepareInteraction()
// TODO: figure out where we save the client's finish redirect uri
// TODO: support selecting one of multiple interaction handlers
interact, err := h.loginConsent.PrepareInteraction(req.Interact)
interact, err := h.loginConsent.PrepareInteraction(req.Interact, permissions.NeedsConsent.Tokens)
if err != nil {
return nil, fmt.Errorf("creating response interaction parameters: %w", err)
}
Expand All @@ -154,7 +152,7 @@ func (h *AuthHandler) HandleAccessRequest( // nolint:funlen
}

// HandleContinueRequest handles GNAP continue requests.
func (h *AuthHandler) HandleContinueRequest(
func (h *AuthHandler) HandleContinueRequest( // nolint: funlen
req *gnap.ContinueRequest,
continueToken string,
reqVerifier api.Verifier,
Expand All @@ -181,14 +179,23 @@ func (h *AuthHandler) HandleContinueRequest(
now := time.Now()

for _, tokenRequest := range consent.Tokens {
tok := CreateToken(tokenRequest)
tok := gnap.AccessToken{
Value: uuid.New().String(),
Label: tokenRequest.Label,
Access: tokenRequest.Access,
Flags: tokenRequest.Flags,
}

tokenExpires := tokenRequest.Expires

newTokens = append(newTokens, *tok)
lifetime := tokenExpires.Sub(now)

tokenExpires := now.Add(time.Duration(tok.Expires) * time.Second)
tok.Expires = int64(lifetime / time.Second)

newTokens = append(newTokens, tok)

s.Tokens = append(s.Tokens, &api.ExpiringToken{
AccessToken: *tok,
AccessToken: tok,
Expires: tokenExpires,
})

Expand All @@ -202,6 +209,11 @@ func (h *AuthHandler) HandleContinueRequest(
return nil, err
}

err = h.loginConsent.DeleteInteraction(req.InteractRef)
if err != nil {
return nil, err
}

resp := &gnap.AuthResponse{
AccessToken: newTokens,
}
Expand Down
73 changes: 51 additions & 22 deletions pkg/gnap/authhandler/auth_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,29 @@ func TestAuthHandler_HandleAccessRequest(t *testing.T) {
require.Nil(t, resp)
})

t.Run("fail to prepare interaction", func(t *testing.T) {
h, err := New(config(t))
require.NoError(t, err)

expectErr := errors.New("expected error")

h.loginConsent = &mockinteract.InteractHandler{
PrepareErr: expectErr,
}

req := &gnap.AuthRequest{
Client: &gnap.RequestClient{
IsReference: false,
Key: clientKey(t),
},
}
v := &mockverifier.MockVerifier{}

resp, err := h.HandleAccessRequest(req, v)
require.ErrorIs(t, err, expectErr)
require.Nil(t, resp)
})

t.Run("success", func(t *testing.T) {
h, err := New(config(t))
require.NoError(t, err)
Expand Down Expand Up @@ -258,15 +281,17 @@ func TestAuthHandler_HandleContinueRequest(t *testing.T) {

h.loginConsent = &mockinteract.InteractHandler{
QueryVal: &api.ConsentResult{
Tokens: []*gnap.TokenRequest{
Tokens: []*api.ExpiringTokenRequest{
{
Access: []gnap.TokenAccess{
{
IsReference: true,
Ref: "foo",
TokenRequest: gnap.TokenRequest{
Access: []gnap.TokenAccess{
{
IsReference: true,
Ref: "foo",
},
},
Label: "foo",
},
Label: "foo",
},
},
},
Expand Down Expand Up @@ -381,12 +406,14 @@ func TestAuthHandler_HandleIntrospection(t *testing.T) {
clientSession, err := h.sessionStore.GetOrCreateByKey(clientKey(t))
require.NoError(t, err)

token := CreateToken(&gnap.TokenRequest{
Label: "foo",
Access: []gnap.TokenAccess{
{
IsReference: true,
Ref: "foo",
token := CreateToken(&api.ExpiringTokenRequest{
TokenRequest: gnap.TokenRequest{
Label: "foo",
Access: []gnap.TokenAccess{
{
IsReference: true,
Ref: "foo",
},
},
},
})
Expand Down Expand Up @@ -422,16 +449,18 @@ func TestAuthHandler_HandleIntrospection(t *testing.T) {
clientIDKey := "client-id"
clientIDVal := "123abc123"

token := CreateToken(&gnap.TokenRequest{
Label: "foo",
Access: []gnap.TokenAccess{
{
IsReference: true,
Ref: clientIDKey,
},
{
IsReference: true,
Ref: "other-access",
token := CreateToken(&api.ExpiringTokenRequest{
TokenRequest: gnap.TokenRequest{
Label: "foo",
Access: []gnap.TokenAccess{
{
IsReference: true,
Ref: clientIDKey,
},
{
IsReference: true,
Ref: "other-access",
},
},
},
})
Expand Down
3 changes: 2 additions & 1 deletion pkg/gnap/authhandler/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ package authhandler
import (
"github.com/google/uuid"

"github.com/trustbloc/auth/pkg/gnap/api"
"github.com/trustbloc/auth/spi/gnap"
)

// CreateToken creates a token object matching the given token request.
func CreateToken(req *gnap.TokenRequest) *gnap.AccessToken {
func CreateToken(req *api.ExpiringTokenRequest) *gnap.AccessToken {
return &gnap.AccessToken{
Value: uuid.New().String(),
Label: req.Label,
Expand Down
Loading

0 comments on commit c43081f

Please sign in to comment.