Skip to content

Commit

Permalink
Merge master
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-toogood committed May 26, 2020
2 parents b53a2c6 + be5a10a commit 5712cc9
Show file tree
Hide file tree
Showing 17 changed files with 577 additions and 148 deletions.
2 changes: 1 addition & 1 deletion auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Auth interface {
// Revoke access to a resource
Revoke(rule *Rule) error
// Rules returns all the rules used to verify requests
Rules() ([]*Rule, error)
Rules(...RulesOption) ([]*Rule, error)
// String returns the name of the implementation
String() string
}
Expand Down
2 changes: 1 addition & 1 deletion auth/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (n *noop) Revoke(rule *Rule) error {
}

// Rules used to verify requests
func (n *noop) Rules() ([]*Rule, error) {
func (n *noop) Rules(opts ...RulesOption) ([]*Rule, error) {
return []*Rule{}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion auth/jwt/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (j *jwt) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyO
return rules.Verify(j.rules, acc, res)
}

func (j *jwt) Rules() ([]*auth.Rule, error) {
func (j *jwt) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
j.Lock()
defer j.Unlock()
return j.rules, nil
Expand Down
19 changes: 16 additions & 3 deletions auth/options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package auth

import (
"context"
"time"

"github.com/micro/go-micro/v2/auth/provider"
Expand Down Expand Up @@ -226,13 +227,25 @@ func NewTokenOptions(opts ...TokenOption) TokenOptions {
}

type VerifyOptions struct {
Namespace string
Context context.Context
}

type VerifyOption func(o *VerifyOptions)

func VerifyNamespace(ns string) VerifyOption {
func VerifyContext(ctx context.Context) VerifyOption {
return func(o *VerifyOptions) {
o.Namespace = ns
o.Context = ctx
}
}

type RulesOptions struct {
Context context.Context
}

type RulesOption func(o *RulesOptions)

func RulesContext(ctx context.Context) RulesOption {
return func(o *RulesOptions) {
o.Context = ctx
}
}
144 changes: 52 additions & 92 deletions auth/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package service
import (
"context"
"strings"
"sync"
"time"

"github.com/micro/go-micro/v2/auth"
Expand All @@ -12,19 +11,14 @@ import (
"github.com/micro/go-micro/v2/auth/token"
"github.com/micro/go-micro/v2/auth/token/jwt"
"github.com/micro/go-micro/v2/client"
log "github.com/micro/go-micro/v2/logger"
"github.com/micro/go-micro/v2/metadata"
"github.com/micro/go-micro/v2/util/jitter"
)

// svc is the service implementation of the Auth interface
type svc struct {
options auth.Options
auth pb.AuthService
rule pb.RulesService
rules pb.RulesService
jwt token.Provider
rules map[string][]*auth.Rule
sync.Mutex
}

func (s *svc) String() string {
Expand All @@ -41,7 +35,7 @@ func (s *svc) Init(opts ...auth.Option) {
}

s.auth = pb.NewAuthService("go.micro.auth", s.options.Client)
s.rule = pb.NewRulesService("go.micro.auth", s.options.Client)
s.rules = pb.NewRulesService("go.micro.auth", s.options.Client)

// if we have a JWT public key passed as an option,
// we can decode tokens with the type "JWT" locally
Expand All @@ -52,8 +46,6 @@ func (s *svc) Init(opts ...auth.Option) {
}

func (s *svc) Options() auth.Options {
s.Lock()
defer s.Unlock()
return s.options
}

Expand Down Expand Up @@ -85,7 +77,7 @@ func (s *svc) Grant(rule *auth.Rule) error {
access = pb.Access_DENIED
}

_, err := s.rule.Create(context.TODO(), &pb.CreateRequest{
_, err := s.rules.Create(context.TODO(), &pb.CreateRequest{
Rule: &pb.Rule{
Id: rule.ID,
Scope: rule.Scope,
Expand All @@ -99,25 +91,38 @@ func (s *svc) Grant(rule *auth.Rule) error {
},
})

if err == nil {
go s.loadRules(s.options.Namespace)
}

return err
}

// Revoke access to a resource
func (s *svc) Revoke(rule *auth.Rule) error {
_, err := s.rule.Delete(context.TODO(), &pb.DeleteRequest{
_, err := s.rules.Delete(context.TODO(), &pb.DeleteRequest{
Id: rule.ID,
})

go s.loadRules(s.options.Namespace)
return err
}

func (s *svc) Rules() ([]*auth.Rule, error) {
return s.rules[s.options.Namespace], nil
func (s *svc) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
var options auth.RulesOptions
for _, o := range opts {
o(&options)
}
if options.Context == nil {
options.Context = context.TODO()
}

rsp, err := s.rules.List(options.Context, &pb.ListRequest{})
if err != nil {
return nil, err
}

rules := make([]*auth.Rule, len(rsp.Rules))
for i, r := range rsp.Rules {
rules[i] = serializeRule(r)
}

return rules, nil
}

// Verify an account has access to a resource
Expand All @@ -126,15 +131,13 @@ func (s *svc) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyO
for _, o := range opts {
o(&options)
}
if len(options.Namespace) == 0 {
options.Namespace = s.options.Namespace
}

// load the rules if none are loaded
s.loadRulesIfEmpty(options.Namespace)
rs, err := s.Rules(auth.RulesContext(options.Context))
if err != nil {
return err
}

// verify the request using the rules
return rules.Verify(s.rules[options.Namespace], acc, res)
return rules.Verify(rs, acc, res)
}

// Inspect a token
Expand Down Expand Up @@ -170,53 +173,6 @@ func (s *svc) Token(opts ...auth.TokenOption) (*auth.Token, error) {
return serializeToken(rsp.Token), nil
}

// loadRules retrieves the rules from the auth service. Since this implementation is used by micro
// clients, which support muti-tenancy we may have to persist rules in multiple namespaces.
func (s *svc) loadRules(namespace string) {
ctx := metadata.Set(context.TODO(), "Micro-Namespace", namespace)
rsp, err := s.rule.List(ctx, &pb.ListRequest{})
if err != nil {
log.Errorf("Error listing rules: %v", err)
return
}

rules := make([]*auth.Rule, 0, len(rsp.Rules))
for _, r := range rsp.Rules {
var access auth.Access
if r.Access == pb.Access_GRANTED {
access = auth.AccessGranted
} else {
access = auth.AccessDenied
}

rules = append(rules, &auth.Rule{
ID: r.Id,
Scope: r.Scope,
Access: access,
Priority: r.Priority,
Resource: &auth.Resource{
Type: r.Resource.Type,
Name: r.Resource.Name,
Endpoint: r.Resource.Endpoint,
},
})
}

s.Lock()
s.rules[namespace] = rules
s.Unlock()
}

func (s *svc) loadRulesIfEmpty(namespace string) {
s.Lock()
rules := s.rules
s.Unlock()

if _, ok := rules[namespace]; !ok {
s.loadRules(namespace)
}
}

func serializeToken(t *pb.Token) *auth.Token {
return &auth.Token{
AccessToken: t.AccessToken,
Expand All @@ -236,33 +192,37 @@ func serializeAccount(a *pb.Account) *auth.Account {
}
}

func serializeRule(r *pb.Rule) *auth.Rule {
var access auth.Access
if r.Access == pb.Access_GRANTED {
access = auth.AccessGranted
} else {
access = auth.AccessDenied
}

return &auth.Rule{
ID: r.Id,
Scope: r.Scope,
Access: access,
Priority: r.Priority,
Resource: &auth.Resource{
Type: r.Resource.Type,
Name: r.Resource.Name,
Endpoint: r.Resource.Endpoint,
},
}
}

// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
options := auth.NewOptions(opts...)
if options.Client == nil {
options.Client = client.DefaultClient
}

service := &svc{
return &svc{
auth: pb.NewAuthService("go.micro.auth", options.Client),
rule: pb.NewRulesService("go.micro.auth", options.Client),
rules: make(map[string][]*auth.Rule),
rules: pb.NewRulesService("go.micro.auth", options.Client),
options: options,
}

// load rules periodically from the auth service
go func() {
ruleTimer := time.NewTicker(time.Second * 30)

for {
<-ruleTimer.C
time.Sleep(jitter.Do(time.Second * 5))

for ns := range service.rules {
service.loadRules(ns)
}
}
}()

return service
}
66 changes: 66 additions & 0 deletions client/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package client

import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"time"

"github.com/micro/go-micro/v2/metadata"
cache "github.com/patrickmn/go-cache"
)

// NewCache returns an initialised cache.
func NewCache() *Cache {
return &Cache{
cache: cache.New(cache.NoExpiration, 30*time.Second),
}
}

// Cache for responses
type Cache struct {
cache *cache.Cache
}

// Get a response from the cache
func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) {
return c.cache.Get(key(ctx, req))
}

// Set a response in the cache
func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) {
c.cache.Set(key(ctx, req), rsp, expiry)
}

// List the key value pairs in the cache
func (c *Cache) List() map[string]string {
items := c.cache.Items()

rsp := make(map[string]string, len(items))
for k, v := range items {
bytes, _ := json.Marshal(v.Object)
rsp[k] = string(bytes)
}

return rsp
}

// key returns a hash for the context and request
func key(ctx context.Context, req *Request) string {
md, _ := metadata.FromContext(ctx)

bytes, _ := json.Marshal(map[string]interface{}{
"metadata": md,
"request": map[string]interface{}{
"service": (*req).Service(),
"endpoint": (*req).Endpoint(),
"method": (*req).Method(),
"body": (*req).Body(),
},
})

h := fnv.New64()
h.Write(bytes)
return fmt.Sprintf("%x", h.Sum(nil))
}
Loading

0 comments on commit 5712cc9

Please sign in to comment.