Skip to content

Commit

Permalink
Added import/exports - reworked structure of the account JWT.
Browse files Browse the repository at this point in the history
  • Loading branch information
aricart committed Oct 19, 2018
1 parent 6dd9291 commit 9e34f00
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 76 deletions.
6 changes: 2 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ build:
go build

test:
go fmt ./...
gofmt -s -w *.go
goimports -w *.go
go vet ./...
go test -v
go test -v --race
Expand All @@ -15,6 +16,3 @@ fmt:
cover:
go test -v -covermode=count -coverprofile=coverage.out
go tool cover -html=coverage.out

test:
go test -v
5 changes: 3 additions & 2 deletions account_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ func (a *AccountClaims) Payload() interface{} {
}

func (a *AccountClaims) Valid() error {
if err := a.ClaimsData.Valid(); err != nil {
var err error
if err = a.ClaimsData.Valid(); err != nil {
return err
}
if err := a.Account.Valid(); err != nil {
if err = a.Account.Valid(); err != nil {
return err
}
return nil
Expand Down
16 changes: 13 additions & 3 deletions account_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestNewAccountClaims(t *testing.T) {
account.Expires = time.Now().Add(time.Duration(time.Hour * 24 * 365)).Unix()

okp := createOperatorNKey(t)
account.AppendActivation(encode(activation, okp, t))
account.Access = encode(activation, okp, t)

actJwt := encode(account, akp, t)

Expand Down Expand Up @@ -85,9 +85,19 @@ func TestInvalidAccountSubjects(t *testing.T) {
{"cluster", createClusterNKey(t), false},
}

operator := createOperatorNKey(t)

for _, i := range inputs {
c := NewAccountClaims(publicKey(i.kp, t))
_, err := c.Encode(createOperatorNKey(t))
pk := publicKey(i.kp, t)
activation := NewActivationClaims(pk)
var err error

c := NewAccountClaims(pk)
c.Access, err = activation.Encode(operator)
if i.ok && err != nil {
t.Fatalf("error encoding activation: %v", err)
}
_, err = c.Encode(i.kp)
if i.ok && err != nil {
t.Fatal(fmt.Sprintf("unexpected error for %q: %v", i.name, err))
}
Expand Down
65 changes: 65 additions & 0 deletions exports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package jwt

import (
"fmt"
"strings"
)

type Export struct {
NamedSubject
}

func (e *Export) Valid() error {
return e.NamedSubject.Valid()
}

type ExportedStream struct {
Export
}

func (es *ExportedStream) Valid() error {
return es.Export.Valid()
}

type ExportedStreams []ExportedStream

func (es *ExportedStreams) Valid() error {
for _, v := range *es {
if err := v.Valid(); err != nil {
return err
}
}
return nil
}

type ExportedService struct {
Export
}

func (es *ExportedService) Valid() error {
if err := es.NamedSubject.Valid(); err != nil {
return err
}
if strings.HasSuffix(es.Subject, ".>") ||
strings.HasSuffix(es.Subject, ".*") ||
strings.Contains(es.Subject, ".*.") {
return fmt.Errorf("services cannot contain wildcards: %q", es.Subject)
}
return nil
}

type ExportedServices []ExportedService

func (es *ExportedServices) Valid() error {
for _, v := range *es {
if err := v.Valid(); err != nil {
return err
}
}
return nil
}

type Exports struct {
Streams ExportedStreams `json:"streams,omitempty"`
Services ExportedServices `json:"services,omitempty"`
}
143 changes: 143 additions & 0 deletions imports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package jwt

import (
"errors"
"fmt"
"strings"

"github.com/nats-io/nkeys"
)

type NamedSubject struct {
Name string `json:"name,omitempty"`
Subject string `json:"subject,omitempty"`
}

func (ns *NamedSubject) Valid() error {
if ns.Subject == "" {
return errors.New("subject cannot be empty")
}

return nil
}

type ImportDescriptor struct {
NamedSubject
Account string `json:"account,omitempty"`
To string `json:"to,omitempty"`
Prefix string `json:"prefix,omitempty"`
}

func (a *ImportDescriptor) Valid() error {
if err := a.NamedSubject.Valid(); err != nil {
return err
}
if a.Account != "public" && !nkeys.IsValidPublicAccountKey(a.Account) {
return fmt.Errorf("account %q is not a valid account public key", a.Account)
}

return nil
}

type ImportedService struct {
ImportDescriptor
}

type ImportedServices []ImportedService

func (s *ImportedServices) Valid() error {
for _, t := range *s {
if err := t.Valid(); err != nil {
return err
}
}
return nil
}

func (s *ImportedService) Valid() error {
if err := s.ImportDescriptor.Valid(); err != nil {
return err
}
if strings.HasSuffix(s.Subject, ".>") ||
strings.HasSuffix(s.Subject, ".*") ||
strings.Contains(s.Subject, ".*.") {
return fmt.Errorf("services cannot contain wildcards: %q", s.Subject)
}
return nil
}

type ImportedStream struct {
ImportDescriptor
}

type ImportedStreams []ImportedStream

func (s *ImportedStreams) Valid() error {
for _, t := range *s {
if err := t.Valid(); err != nil {
return err
}
}
return nil
}

func (s *ImportedStream) Valid() error {
return s.ImportDescriptor.Valid()
}

type Imports struct {
Streams ImportedStreams `json:"streams,omitempty"`
Services ImportedServices `json:"services,omitempty"`
Act []string `json:"act,omitempty"`
}

func (i *Imports) AppendActivation(act string) {
i.Act = append(i.Act, act)
}

func (i *Imports) Activations() ([]*ActivationClaims, error) {
var buf []*ActivationClaims
for i, s := range i.Act {
ac, err := DecodeActivationClaims(s)
if err != nil {
return nil, fmt.Errorf("error decoding activation [%d]: %v", i, err)
}
buf = append(buf, ac)
}
return buf, nil
}

func (i *Imports) Valid() error {
if err := i.Streams.Valid(); err != nil {
return err
}
if err := i.Services.Valid(); err != nil {
return err
}

activations, err := i.Activations()
if err != nil {
return err
}

tokenMap := make(map[string]bool)
tokenMap["public"] = true

for _, t := range activations {
tokenMap[t.Name] = true
}

for _, t := range i.Streams {
if !tokenMap[t.Account] {
return fmt.Errorf("imported stream references account %q - but provides no matching activation", t.Account)
}
}

for _, t := range i.Streams {
if !tokenMap[t.Account] {
return fmt.Errorf("import service references account %q - but provides no matching activation", t.Account)
}
}

return nil
}
76 changes: 9 additions & 67 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,24 @@ package jwt
import (
"errors"
"fmt"

"github.com/nats-io/nkeys"
)

type ImportExportType string

const ImportExportTypeStream = "stream"
const ImportExportTypeService = "service"

type Import struct {
Type ImportExportType `json:"type,omitempty"`
Account string `json:"account,omitempty"`
Subject string `json:"subject,omitempty"`
To string `json:"to,omitempty"`
Prefix string `json:"prefix,omitempty"`
}

func (a *Import) Valid() error {
if a.Type != ImportExportTypeService && a.Type != ImportExportTypeStream {
return fmt.Errorf("import type %q is invalid", a.Type)
}
return nil
}

type Account struct {
Imports []Import `json:"imports,omitempty"`
Act []string `json:"act,omitempty"`
}

func (a *Account) AppendActivation(act string) {
a.Act = append(a.Act, act)
}

func (a *Account) Activations() ([]*ActivationClaims, error) {
var buf []*ActivationClaims
for i, s := range a.Act {
ac, err := DecodeActivationClaims(s)
if err != nil {
return nil, fmt.Errorf("error decoding activation [%d]: %v", i, err)
}
buf = append(buf, ac)
}
return buf, nil
Imports Imports `json:"imports,omitempty"`
Exports Exports `json:"exports,omitempty"`
Access string `json:"access,omitempty"`
}

func (a *Account) Valid() error {
activations, err := a.Activations()
if err != nil {
return err
if a.Access == "" {
return errors.New("account jwts require an access token")
}

tokenMap := make(map[string]bool)
for _, t := range activations {
tokenMap[t.Name] = true
if _, err := DecodeActivationClaims(a.Access); err != nil {
return fmt.Errorf("access is not valid: %v", err)
}

for _, t := range a.Imports {
if !nkeys.IsValidPublicAccountKey(t.Account) && !tokenMap[t.Account] {
return fmt.Errorf("import references account %q - but it is not an account pk nor an activation token name", t.Account)
}
if err := a.Imports.Valid(); err != nil {
return err
}

return nil
Expand Down Expand Up @@ -126,22 +84,6 @@ func (u *Permissions) Valid() error {
return nil
}

type Export struct {
Type ImportExportType `json:"type,omitempty"`
Subject string `json:"subject,omitempty"`
}

func (e *Export) Valid() error {
if e.Type != ImportExportTypeService && e.Type != ImportExportTypeStream {
return fmt.Errorf("export type %q is invalid", e.Type)
}
if e.Subject == "" {
return errors.New("export subject is empty")
}

return nil
}

type Activation struct {
Exports []Export `json:"exports,omitempty"`
Limits
Expand Down

0 comments on commit 9e34f00

Please sign in to comment.