Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization Response and Server ID additions #186

Merged
merged 1 commit into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 106 additions & 5 deletions v2/authorization_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ import (

// ServerID is basic static info for a NATS server.
type ServerID struct {
Name string `json:"name"`
Host string `json:"host"`
ID string `json:"id"`
XKey string `json:"xkey,omitempty"`
Name string `json:"name"`
Host string `json:"host"`
ID string `json:"id"`
Version string `json:"version,omitempty"`
Cluster string `json:"cluster,omitempty"`
Tags TagList `json:"tags,omitempty"`
XKey string `json:"xkey,omitempty"`
}

// ClientInformation is information about a client that is trying to authorize.
Expand Down Expand Up @@ -135,7 +138,7 @@ func (ac *AuthorizationRequestClaims) ClaimType() ClaimType {
return ac.Type
}

// Claims returns the accounts claims data.
// Claims returns the request claims data.
func (ac *AuthorizationRequestClaims) Claims() *ClaimsData {
return &ac.ClaimsData
}
Expand All @@ -152,3 +155,101 @@ func (ac *AuthorizationRequestClaims) String() string {
func (ac *AuthorizationRequestClaims) updateVersion() {
ac.GenericFields.Version = libVersion
}

// Represents an authorization response error.
type AuthorizationError struct {
Description string `json:"description"`
}

// AuthorizationResponse represents a response to an authorization callout.
// Will be a valid user or an error.
type AuthorizationResponse struct {
User *UserClaims `json:"user_claims,omitempty"`
Error *AuthorizationError `json:"error,omitempty"`
GenericFields
}

// AuthorizationResponseClaims defines an external auth response.
// This will be signed by the trusted account issuer.
// Will contain a valid user JWT or an error.
// These wil be signed by a NATS server.
type AuthorizationResponseClaims struct {
ClaimsData
AuthorizationResponse `json:"nats"`
}

// Create a new response claim for the given subject.
func NewAuthorizationResponseClaims(subject string) *AuthorizationResponseClaims {
if subject == "" {
return nil
}
var arc AuthorizationResponseClaims
arc.Subject = subject
return &arc
}

// Set's and error description.
func (arc *AuthorizationResponseClaims) SetErrorDescription(errDescription string) {
if arc.Error != nil {
arc.Error.Description = errDescription
} else {
arc.Error = &AuthorizationError{Description: errDescription}
}
}

// Validate checks the generic and specific parts of the auth request jwt.
func (arc *AuthorizationResponseClaims) Validate(vr *ValidationResults) {
if arc.User == nil && arc.Error == nil {
vr.AddError("User or error required")
}
if arc.User != nil && arc.Error != nil {
vr.AddError("User and error can not both be set")
}
arc.ClaimsData.Validate(vr)
}

// Encode tries to turn the auth request claims into a JWT string.
func (arc *AuthorizationResponseClaims) Encode(pair nkeys.KeyPair) (string, error) {
arc.Type = AuthorizationResponseClaim
return arc.ClaimsData.encode(pair, arc)
}

// DecodeAuthorizationResponseClaims tries to parse an auth response claim from a JWT string
func DecodeAuthorizationResponseClaims(token string) (*AuthorizationResponseClaims, error) {
claims, err := Decode(token)
if err != nil {
return nil, err
}
arc, ok := claims.(*AuthorizationResponseClaims)
if !ok {
return nil, errors.New("not an authorization response claim")
}
return arc, nil
}

// ExpectedPrefixes defines the types that can encode an auth response jwt which is accounts.
func (arc *AuthorizationResponseClaims) ExpectedPrefixes() []nkeys.PrefixByte {
return []nkeys.PrefixByte{nkeys.PrefixByteAccount}
}

func (arc *AuthorizationResponseClaims) ClaimType() ClaimType {
return arc.Type
}

// Claims returns the response claims data.
func (arc *AuthorizationResponseClaims) Claims() *ClaimsData {
return &arc.ClaimsData
}

// Payload pulls the request specific payload out of the claims.
func (arc *AuthorizationResponseClaims) Payload() interface{} {
return &arc.AuthorizationResponse
}

func (arc *AuthorizationResponseClaims) String() string {
return arc.ClaimsData.String(arc)
}

func (arc *AuthorizationResponseClaims) updateVersion() {
arc.GenericFields.Version = libVersion
}
66 changes: 65 additions & 1 deletion v2/authorization_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"github.com/nats-io/nkeys"
)

func TestNewAuthorizationClaims(t *testing.T) {
func TestNewAuthorizationRequestClaims(t *testing.T) {
skp, _ := nkeys.CreateServer()
ac := NewAuthorizationRequestClaims("TEST")
ac.Server.Name = "NATS-1"
Expand Down Expand Up @@ -65,3 +65,67 @@ func TestNewAuthorizationClaims(t *testing.T) {
AssertEquals(ac.String(), ac2.String(), t)
AssertEquals(ac.Server.Name, ac2.Server.Name, t)
}

func TestNewAuthorizationResponseClaims(t *testing.T) {
// Make sure one or other is set.
var empty AuthorizationResponseClaims
vr := CreateValidationResults()
empty.Validate(vr)
if vr.IsEmpty() || !vr.IsBlocking(false) {
t.Fatalf("Expected blocking error on an empty authorization response")
}

// Make sure both can not be set.
// Create user, account etc.
akp := createAccountNKey(t)
ukp := createUserNKey(t)

uclaim := NewUserClaims(publicKey(ukp, t))
uclaim.Audience = publicKey(akp, t)

arc := NewAuthorizationResponseClaims("TEST")
arc.User = uclaim
arc.Error = &AuthorizationError{Description: "BAD"}

vr = CreateValidationResults()
arc.Validate(vr)
if vr.IsEmpty() || !vr.IsBlocking(false) {
t.Fatalf("Expected blocking error when both user and error are set")
}

// Clear error and make sure ok.
arc.Error = nil
// should be server public key.
skp := createServerNKey(t)
arc.Audience = publicKey(skp, t)

vr = CreateValidationResults()
arc.Validate(vr)
if !vr.IsEmpty() {
t.Fatal("Valid authorization response will have no validation results")
}

arcJWT := encode(arc, akp, t)
arc2, err := DecodeAuthorizationResponseClaims(arcJWT)
if err != nil {
t.Fatal("error decoding authorization response jwt", err)
}
AssertEquals(arc.String(), arc2.String(), t)

// Check that error constructor works.
arc = NewAuthorizationResponseClaims("TEST")
arc.SetErrorDescription("BAD CERT")

vr = CreateValidationResults()
arc.Validate(vr)
if !vr.IsEmpty() {
t.Fatal("Valid authorization response will have no validation results")
}

arcJWT = encode(arc, akp, t)
arc2, err = DecodeAuthorizationResponseClaims(arcJWT)
if err != nil {
t.Fatal("error decoding authorization response jwt", err)
}
AssertEquals(arc.String(), arc2.String(), t)
}
8 changes: 6 additions & 2 deletions v2/claims.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 The NATS Authors
* Copyright 2018-2022 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -40,7 +40,9 @@ const (
// ActivationClaim is the type of an activation JWT
ActivationClaim = "activation"
// AuthorizationRequestClaim is the type of an auth request claim JWT
AuthorizationRequestClaim = "authorization"
AuthorizationRequestClaim = "authorization_request"
// AuthorizationResponseClaim is the type of an auth response claim JWT
AuthorizationResponseClaim = "authorization_response"
// GenericClaim is a type that doesn't match Operator/Account/User/ActionClaim
GenericClaim = "generic"
)
Expand All @@ -55,6 +57,8 @@ func IsGenericClaimType(s string) bool {
fallthrough
case AuthorizationRequestClaim:
fallthrough
case AuthorizationResponseClaim:
fallthrough
case ActivationClaim:
return false
case GenericClaim:
Expand Down
6 changes: 4 additions & 2 deletions v2/decoder.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 The NATS Authors
* Copyright 2020-2022 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand Down Expand Up @@ -145,7 +145,9 @@ func loadClaims(data []byte) (int, Claims, error) {
case ActivationClaim:
claim, err = loadActivation(data, id.Version())
case AuthorizationRequestClaim:
claim, err = loadAuthorization(data, id.Version())
claim, err = loadAuthorizationRequest(data, id.Version())
case AuthorizationResponseClaim:
claim, err = loadAuthorizationResponse(data, id.Version())
case "cluster":
return -1, nil, errors.New("ClusterClaims are not supported")
case "server":
Expand Down
10 changes: 9 additions & 1 deletion v2/decoder_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ import (
"encoding/json"
)

func loadAuthorization(data []byte, version int) (*AuthorizationRequestClaims, error) {
func loadAuthorizationRequest(data []byte, version int) (*AuthorizationRequestClaims, error) {
var ac AuthorizationRequestClaims
if err := json.Unmarshal(data, &ac); err != nil {
return nil, err
}
return &ac, nil
}

func loadAuthorizationResponse(data []byte, version int) (*AuthorizationResponseClaims, error) {
var arc AuthorizationResponseClaims
if err := json.Unmarshal(data, &arc); err != nil {
return nil, err
}
return &arc, nil
}