Skip to content

Commit

Permalink
Merge pull request #189 from nats-io/simplify-response
Browse files Browse the repository at this point in the history
simplify AuthorizationResponse
  • Loading branch information
wallyqs authored Mar 16, 2023
2 parents e3e39d6 + 7b33986 commit 7018e50
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 119 deletions.
112 changes: 52 additions & 60 deletions v2/authorization_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type AuthorizationRequest struct {
ClientInformation ClientInformation `json:"client_info"`
ConnectOptions ConnectOptions `json:"connect_opts"`
TLS *ClientTLS `json:"client_tls,omitempty"`
RequestNonce string `json:"request_nonce,omitempty"`
GenericFields
}

Expand Down Expand Up @@ -156,100 +157,91 @@ 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"`
Jwt string `json:"jwt,omitempty"`
Error string `json:"error,omitempty"`
// IssuerAccount stores the public key for the account the issuer represents.
// When set, the claim was issued by a signing key.
IssuerAccount string `json:"issuer_account,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)
var ac AuthorizationResponseClaims
ac.Subject = subject
return &ac
}

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

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

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

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

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

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

func (arc *AuthorizationResponseClaims) updateVersion() {
arc.GenericFields.Version = libVersion
func (ar *AuthorizationResponseClaims) updateVersion() {
ar.GenericFields.Version = libVersion
}

// Validate checks the generic and specific parts of the auth request jwt.
func (ar *AuthorizationResponseClaims) Validate(vr *ValidationResults) {
if !nkeys.IsValidPublicUserKey(ar.Subject) {
vr.AddError("Subject must be a user public key")
}
if !nkeys.IsValidPublicServerKey(ar.Audience) {
vr.AddError("Audience must be a server public key")
}
if ar.Error == "" && ar.Jwt == "" {
vr.AddError("Error or Jwt is required")
}
if ar.Error != "" && ar.Jwt != "" {
vr.AddError("Only Error or Jwt can be set")
}
if ar.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(ar.IssuerAccount) {
vr.AddError("issuer_account is not an account public key")
}
ar.ClaimsData.Validate(vr)
}

// Encode tries to turn the auth request claims into a JWT string.
func (ar *AuthorizationResponseClaims) Encode(pair nkeys.KeyPair) (string, error) {
ar.Type = AuthorizationResponseClaim
return ar.ClaimsData.encode(pair, ar)
}
136 changes: 81 additions & 55 deletions v2/authorization_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ import (

func TestNewAuthorizationRequestClaims(t *testing.T) {
skp, _ := nkeys.CreateServer()
ac := NewAuthorizationRequestClaims("TEST")

kp, err := nkeys.CreateUser()
if err != nil {
t.Fatalf("Error creating user: %v", err)
}
pub, _ := kp.PublicKey()

// the subject of the claim is the user we are generating an authorization response
ac := NewAuthorizationRequestClaims(pub)
ac.Server.Name = "NATS-1"

vr := CreateValidationResults()
Expand All @@ -42,12 +50,6 @@ func TestNewAuthorizationRequestClaims(t *testing.T) {
t.Fatalf("Expected blocking error on invalid user nkey")
}

kp, err := nkeys.CreateUser()
if err != nil {
t.Fatalf("Error creating user: %v", err)
}
pub, _ := kp.PublicKey()

ac.UserNkey = pub
vr = CreateValidationResults()
ac.Validate(vr)
Expand All @@ -66,66 +68,90 @@ func TestNewAuthorizationRequestClaims(t *testing.T) {
AssertEquals(ac.Server.Name, ac2.Server.Name, t)
}

func TestNewAuthorizationResponseClaims(t *testing.T) {
// Make sure one or other is set.
var empty AuthorizationResponseClaims
func TestAuthorizationResponse_EmptyShouldFail(t *testing.T) {
rc := NewAuthorizationResponseClaims("$G")
vr := CreateValidationResults()
empty.Validate(vr)
rc.Validate(vr)
if vr.IsEmpty() || !vr.IsBlocking(false) {
t.Fatalf("Expected blocking error on an empty authorization response")
t.Fatal("Expected blocking errors")
}
errs := vr.Errors()
AssertEquals(3, len(errs), t)
AssertEquals("Subject must be a user public key", errs[0].Error(), t)
AssertEquals("Audience must be a server public key", errs[1].Error(), t)
AssertEquals("Error or Jwt is required", errs[2].Error(), t)
}

// 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)
func TestAuthorizationResponse_SubjMustBeServer(t *testing.T) {
rc := NewAuthorizationResponseClaims(publicKey(createUserNKey(t), t))
rc.Error = "bad"
vr := CreateValidationResults()
rc.Validate(vr)
if vr.IsEmpty() || !vr.IsBlocking(false) {
t.Fatalf("Expected blocking error when both user and error are set")
t.Fatal("Expected blocking errors")
}
errs := vr.Errors()
AssertEquals(1, len(errs), t)
AssertEquals("Audience must be a server public key", errs[0].Error(), t)

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

rc = NewAuthorizationResponseClaims(publicKey(createUserNKey(t), t))
rc.Audience = publicKey(createServerNKey(t), t)
rc.Error = "bad"
vr = CreateValidationResults()
arc.Validate(vr)
if !vr.IsEmpty() {
t.Fatal("Valid authorization response will have no validation results")
}
rc.Validate(vr)
AssertEquals(true, vr.IsEmpty(), t)
}

arcJWT := encode(arc, akp, t)
arc2, err := DecodeAuthorizationResponseClaims(arcJWT)
if err != nil {
t.Fatal("error decoding authorization response jwt", err)
func TestAuthorizationResponse_OneOfErrOrJwt(t *testing.T) {
rc := NewAuthorizationResponseClaims(publicKey(createUserNKey(t), t))
rc.Audience = publicKey(createServerNKey(t), t)
rc.Error = "bad"
rc.Jwt = "jwt"
vr := CreateValidationResults()
rc.Validate(vr)
if vr.IsEmpty() || !vr.IsBlocking(false) {
t.Fatal("Expected blocking errors")
}
AssertEquals(arc.String(), arc2.String(), t)
errs := vr.Errors()
AssertEquals(1, len(errs), t)
AssertEquals("Only Error or Jwt can be set", errs[0].Error(), t)
}

// Check that error constructor works.
arc = NewAuthorizationResponseClaims("TEST")
arc.SetErrorDescription("BAD CERT")
func TestAuthorizationResponse_IssuerAccount(t *testing.T) {
rc := NewAuthorizationResponseClaims(publicKey(createUserNKey(t), t))
rc.Audience = publicKey(createServerNKey(t), t)
rc.Jwt = "jwt"
rc.IssuerAccount = rc.Subject
vr := CreateValidationResults()
rc.Validate(vr)
if vr.IsEmpty() || !vr.IsBlocking(false) {
t.Fatal("Expected blocking errors")
}
errs := vr.Errors()
AssertEquals(1, len(errs), t)
AssertEquals("issuer_account is not an account public key", errs[0].Error(), t)

akp := createAccountNKey(t)
rc.IssuerAccount = publicKey(akp, t)
vr = CreateValidationResults()
arc.Validate(vr)
if !vr.IsEmpty() {
t.Fatal("Valid authorization response will have no validation results")
}
rc.Validate(vr)
AssertEquals(true, vr.IsEmpty(), t)
}

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)
func TestAuthorizationResponse_Decode(t *testing.T) {
rc := NewAuthorizationResponseClaims(publicKey(createUserNKey(t), t))
rc.Audience = publicKey(createServerNKey(t), t)
rc.Jwt = "jwt"
akp := createAccountNKey(t)
tok, err := rc.Encode(akp)
AssertNoError(err, t)

r, err := DecodeAuthorizationResponseClaims(tok)
AssertNoError(err, t)
vr := CreateValidationResults()
r.Validate(vr)
AssertEquals(true, vr.IsEmpty(), t)
AssertEquals("jwt", r.Jwt, t)
AssertTrue(nkeys.IsValidPublicUserKey(r.Subject), t)
AssertTrue(nkeys.IsValidPublicServerKey(r.Audience), t)
}
2 changes: 1 addition & 1 deletion v2/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const (
ActivationClaim = "activation"
// AuthorizationRequestClaim is the type of an auth request claim JWT
AuthorizationRequestClaim = "authorization_request"
// AuthorizationResponseClaim is the type of an auth response claim JWT
// AuthorizationResponseClaim is the response for an auth request
AuthorizationResponseClaim = "authorization_response"
// GenericClaim is a type that doesn't match Operator/Account/User/ActionClaim
GenericClaim = "generic"
Expand Down
6 changes: 3 additions & 3 deletions v2/decoder_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ func loadAuthorizationRequest(data []byte, version int) (*AuthorizationRequestCl
}

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

0 comments on commit 7018e50

Please sign in to comment.