2020-02-24 15:07:27 +00:00
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
2020-03-24 09:39:33 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2020-02-24 15:07:27 +00:00
|
|
|
"github.com/micro/go-micro/v2/auth"
|
2020-03-23 16:19:30 +00:00
|
|
|
"github.com/micro/go-micro/v2/auth/token"
|
|
|
|
"github.com/micro/go-micro/v2/auth/token/basic"
|
2020-02-24 15:07:27 +00:00
|
|
|
"github.com/micro/go-micro/v2/store"
|
2020-03-23 16:19:30 +00:00
|
|
|
memStore "github.com/micro/go-micro/v2/store/memory"
|
2020-02-24 15:07:27 +00:00
|
|
|
)
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// NewAuth returns a new default registry which is store
|
|
|
|
func NewAuth(opts ...auth.Option) auth.Auth {
|
|
|
|
var s Store
|
|
|
|
s.Init(opts...)
|
|
|
|
return &s
|
2020-02-25 22:15:44 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Store implementation of auth
|
|
|
|
type Store struct {
|
|
|
|
secretProvider token.Provider
|
|
|
|
tokenProvider token.Provider
|
|
|
|
opts auth.Options
|
|
|
|
}
|
2020-02-25 22:15:44 +00:00
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// String returns store
|
|
|
|
func (s *Store) String() string {
|
|
|
|
return "store"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init the auth
|
|
|
|
func (s *Store) Init(opts ...auth.Option) {
|
2020-02-24 15:07:27 +00:00
|
|
|
for _, o := range opts {
|
2020-03-23 16:19:30 +00:00
|
|
|
o(&s.opts)
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// use the default store as a fallback
|
|
|
|
if s.opts.Store == nil {
|
|
|
|
s.opts.Store = store.DefaultStore
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// noop will not work for auth
|
|
|
|
if s.opts.Store.String() == "noop" {
|
|
|
|
s.opts.Store = memStore.NewStore()
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
if s.tokenProvider == nil {
|
|
|
|
s.tokenProvider = basic.NewTokenProvider(token.WithStore(s.opts.Store))
|
|
|
|
}
|
|
|
|
if s.secretProvider == nil {
|
|
|
|
s.secretProvider = basic.NewTokenProvider(token.WithStore(s.opts.Store))
|
|
|
|
}
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Options returns the options
|
|
|
|
func (s *Store) Options() auth.Options {
|
|
|
|
return s.opts
|
|
|
|
}
|
2020-02-24 15:07:27 +00:00
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Generate a new account
|
|
|
|
func (s *Store) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
|
2020-02-24 15:07:27 +00:00
|
|
|
// parse the options
|
|
|
|
options := auth.NewGenerateOptions(opts...)
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Generate a long-lived secret
|
|
|
|
secretOpts := []token.GenerateOption{
|
|
|
|
token.WithExpiry(options.SecretExpiry),
|
|
|
|
token.WithMetadata(options.Metadata),
|
|
|
|
token.WithRoles(options.Roles),
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
2020-03-23 16:19:30 +00:00
|
|
|
secret, err := s.secretProvider.Generate(id, secretOpts...)
|
2020-02-24 15:07:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// return the account
|
|
|
|
return &auth.Account{
|
|
|
|
ID: id,
|
|
|
|
Roles: options.Roles,
|
|
|
|
Metadata: options.Metadata,
|
|
|
|
Secret: secret,
|
|
|
|
}, nil
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Grant access to a resource
|
|
|
|
func (s *Store) Grant(role string, res *auth.Resource) error {
|
|
|
|
r := Rule{role, res}
|
|
|
|
return s.opts.Store.Write(&store.Record{Key: r.Key(), Value: r.Bytes()})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Revoke access to a resource
|
|
|
|
func (s *Store) Revoke(role string, res *auth.Resource) error {
|
|
|
|
r := Rule{role, res}
|
|
|
|
|
|
|
|
err := s.opts.Store.Delete(r.Key())
|
|
|
|
if err == store.ErrNotFound {
|
|
|
|
return auth.ErrNotFound
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
2020-03-23 16:19:30 +00:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify an account has access to a resource
|
|
|
|
func (s *Store) Verify(acc *auth.Account, res *auth.Resource) error {
|
|
|
|
queries := [][]string{
|
|
|
|
{res.Type, "*"}, // check for wildcard resource type, e.g. service.*
|
|
|
|
{res.Type, res.Name, "*"}, // check for wildcard name, e.g. service.foo*
|
|
|
|
{res.Type, res.Name, res.Endpoint, "*"}, // check for wildcard endpoints, e.g. service.foo.ListFoo:*
|
|
|
|
{res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-24 09:39:33 +00:00
|
|
|
// endpoint is a url which can have wildcard excludes, e.g.
|
|
|
|
// "/foo/*" will allow "/foo/bar"
|
|
|
|
if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 {
|
|
|
|
for i := 1; i < len(comps); i++ {
|
|
|
|
wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/"))
|
|
|
|
queries = append(queries, []string{res.Type, res.Name, wildcard})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
for _, q := range queries {
|
|
|
|
rules, err := s.listRules(q...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rule := range rules {
|
|
|
|
if isValidRule(rule, acc, res) {
|
|
|
|
return nil
|
|
|
|
}
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
return auth.ErrForbidden
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Inspect a token
|
|
|
|
func (s *Store) Inspect(t string) (*auth.Account, error) {
|
|
|
|
tok, err := s.tokenProvider.Inspect(t)
|
|
|
|
if err == token.ErrInvalidToken || err == token.ErrNotFound {
|
|
|
|
return nil, auth.ErrInvalidToken
|
2020-02-24 15:07:27 +00:00
|
|
|
} else if err != nil {
|
2020-03-23 16:19:30 +00:00
|
|
|
return nil, err
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
return &auth.Account{
|
|
|
|
ID: tok.Subject,
|
|
|
|
Roles: tok.Roles,
|
|
|
|
Metadata: tok.Metadata,
|
|
|
|
}, nil
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 16:19:30 +00:00
|
|
|
// Refresh an account using a secret
|
|
|
|
func (s *Store) Refresh(secret string, opts ...auth.RefreshOption) (*auth.Token, error) {
|
|
|
|
sec, err := s.secretProvider.Inspect(secret)
|
|
|
|
if err == token.ErrInvalidToken || err == token.ErrNotFound {
|
|
|
|
return nil, auth.ErrInvalidToken
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
options := auth.NewRefreshOptions(opts...)
|
|
|
|
|
|
|
|
return s.tokenProvider.Generate(sec.Subject,
|
|
|
|
token.WithExpiry(options.TokenExpiry),
|
|
|
|
token.WithMetadata(sec.Metadata),
|
|
|
|
token.WithRoles(sec.Roles),
|
|
|
|
)
|
2020-02-24 15:07:27 +00:00
|
|
|
}
|