micro/auth/service/service.go

263 lines
6.2 KiB
Go
Raw Normal View History

package service
import (
"context"
"strings"
"sync"
"time"
"github.com/micro/go-micro/v2/auth"
2020-05-20 13:59:01 +03:00
"github.com/micro/go-micro/v2/auth/rules"
pb "github.com/micro/go-micro/v2/auth/service/proto"
"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"
2020-05-21 14:33:58 +03:00
"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
jwt token.Provider
rules map[string][]*auth.Rule
sync.Mutex
}
func (s *svc) String() string {
return "service"
}
func (s *svc) Init(opts ...auth.Option) {
for _, o := range opts {
o(&s.options)
}
2020-05-11 19:57:39 +03:00
if s.options.Client == nil {
s.options.Client = client.DefaultClient
}
2020-05-13 19:35:57 +03:00
2020-05-11 19:57:39 +03:00
s.auth = pb.NewAuthService("go.micro.auth", s.options.Client)
s.rule = 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
// and not have to make an RPC call
if key := s.options.PublicKey; len(key) > 0 {
s.jwt = jwt.NewTokenProvider(token.WithPublicKey(key))
}
}
func (s *svc) Options() auth.Options {
2020-03-31 14:44:34 +03:00
s.Lock()
defer s.Unlock()
return s.options
}
// Generate a new account
2020-04-01 19:20:02 +03:00
func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
options := auth.NewGenerateOptions(opts...)
rsp, err := s.auth.Generate(context.TODO(), &pb.GenerateRequest{
Id: id,
Type: options.Type,
Secret: options.Secret,
Scopes: options.Scopes,
Metadata: options.Metadata,
Provider: options.Provider,
})
if err != nil {
return nil, err
}
return serializeAccount(rsp.Account), nil
}
// Grant access to a resource
2020-05-20 13:59:01 +03:00
func (s *svc) Grant(rule *auth.Rule) error {
2020-05-21 18:41:55 +03:00
access := pb.Access_UNKNOWN
if rule.Access == auth.AccessGranted {
access = pb.Access_GRANTED
} else if rule.Access == auth.AccessDenied {
access = pb.Access_DENIED
}
_, err := s.rule.Create(context.TODO(), &pb.CreateRequest{
2020-05-20 13:59:01 +03:00
Rule: &pb.Rule{
Id: rule.ID,
2020-05-21 16:56:17 +03:00
Scope: rule.Scope,
2020-05-20 13:59:01 +03:00
Priority: rule.Priority,
2020-05-21 18:41:55 +03:00
Access: access,
2020-05-20 13:59:01 +03:00
Resource: &pb.Resource{
Type: rule.Resource.Type,
Name: rule.Resource.Name,
Endpoint: rule.Resource.Endpoint,
},
},
})
2020-05-21 18:41:55 +03:00
go s.loadRules(s.options.Namespace)
return err
}
// Revoke access to a resource
2020-05-20 13:59:01 +03:00
func (s *svc) Revoke(rule *auth.Rule) error {
_, err := s.rule.Delete(context.TODO(), &pb.DeleteRequest{
2020-05-21 14:07:22 +03:00
Id: rule.ID,
})
2020-05-21 18:41:55 +03:00
go s.loadRules(s.options.Namespace)
return err
}
2020-05-20 13:59:01 +03:00
func (s *svc) Rules() ([]*auth.Rule, error) {
return s.rules[s.options.Namespace], nil
2020-05-20 13:59:01 +03:00
}
// Verify an account has access to a resource
2020-05-20 18:49:52 +03:00
func (s *svc) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
2020-05-21 18:41:55 +03:00
var options auth.VerifyOptions
2020-05-20 18:49:52 +03:00
for _, o := range opts {
o(&options)
}
2020-05-13 19:07:46 +03:00
// load the rules if none are loaded
2020-05-21 18:41:55 +03:00
s.loadRulesIfEmpty(s.Options().Namespace)
2020-05-20 18:49:52 +03:00
2020-05-20 13:59:01 +03:00
// verify the request using the rules
2020-05-21 18:41:55 +03:00
return rules.Verify(s.rules[s.Options().Namespace], acc, res)
}
// Inspect a token
func (s *svc) Inspect(token string) (*auth.Account, error) {
2020-04-01 16:25:00 +03:00
// try to decode JWT locally and fall back to srv if an error occurs
if len(strings.Split(token, ".")) == 3 && s.jwt != nil {
2020-04-07 18:24:51 +03:00
return s.jwt.Inspect(token)
}
2020-04-07 18:24:51 +03:00
// the token is not a JWT or we do not have the keys to decode it,
// fall back to the auth service
rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token})
if err != nil {
return nil, err
}
return serializeAccount(rsp.Account), nil
}
// Token generation using an account ID and secret
2020-04-01 16:25:00 +03:00
func (s *svc) Token(opts ...auth.TokenOption) (*auth.Token, error) {
options := auth.NewTokenOptions(opts...)
rsp, err := s.auth.Token(context.Background(), &pb.TokenRequest{
2020-04-01 16:25:00 +03:00
Id: options.ID,
Secret: options.Secret,
RefreshToken: options.RefreshToken,
TokenExpiry: int64(options.Expiry.Seconds()),
})
if err != nil {
return nil, err
}
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))
2020-05-20 13:59:01 +03:00
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{
2020-05-20 13:59:01 +03:00
ID: r.Id,
2020-05-21 16:56:17 +03:00
Scope: r.Scope,
2020-05-20 13:59:01 +03:00
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) {
2020-05-13 19:07:46 +03:00
s.Lock()
rules := s.rules
s.Unlock()
if _, ok := rules[namespace]; !ok {
s.loadRules(namespace)
2020-05-13 19:07:46 +03:00
}
}
func serializeToken(t *pb.Token) *auth.Token {
return &auth.Token{
2020-04-01 16:25:00 +03:00
AccessToken: t.AccessToken,
RefreshToken: t.RefreshToken,
Created: time.Unix(t.Created, 0),
Expiry: time.Unix(t.Expiry, 0),
}
}
func serializeAccount(a *pb.Account) *auth.Account {
return &auth.Account{
ID: a.Id,
Secret: a.Secret,
2020-05-21 18:41:55 +03:00
Issuer: a.Issuer,
Metadata: a.Metadata,
Scopes: a.Scopes,
}
}
2020-05-13 19:35:57 +03:00
// 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
}
2020-05-13 19:58:03 +03:00
service := &svc{
2020-05-13 19:35:57 +03:00
auth: pb.NewAuthService("go.micro.auth", options.Client),
rule: pb.NewRulesService("go.micro.auth", options.Client),
rules: make(map[string][]*auth.Rule),
2020-05-13 19:35:57 +03:00
options: options,
}
2020-05-13 19:58:03 +03:00
// load rules periodically from the auth service
go func() {
ruleTimer := time.NewTicker(time.Second * 30)
for {
2020-05-14 15:30:21 +03:00
<-ruleTimer.C
2020-05-13 19:58:03 +03:00
time.Sleep(jitter.Do(time.Second * 5))
for ns := range service.rules {
service.loadRules(ns)
}
2020-05-13 19:58:03 +03:00
}
}()
return service
2020-05-13 19:35:57 +03:00
}