From ffdf986aac4e71233e62fcd736b57da6c785ad85 Mon Sep 17 00:00:00 2001 From: ben-toogood Date: Mon, 24 Feb 2020 15:07:27 +0000 Subject: [PATCH] Refactor auth: add token and store implementations (#1230) * Refactor auth: add token and memory implementations * Fix typo * Remove memory auth (implemented already by the store implementation), revert default to noop * Add grpc header * Global Config * config/global => util/config * Rename package to remove confict * Tweak * Improve Error Handling --- auth/default.go | 57 +++++++++---------- auth/store/store.go | 128 ++++++++++++++++++++++++++++++++++++++++++ client/grpc/grpc.go | 5 ++ config/cmd/cmd.go | 2 + go.sum | 1 + util/config/config.go | 88 +++++++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 30 deletions(-) create mode 100644 auth/store/store.go create mode 100644 util/config/config.go diff --git a/auth/default.go b/auth/default.go index 8213fc05..0fe97dfc 100644 --- a/auth/default.go +++ b/auth/default.go @@ -1,39 +1,36 @@ package auth var ( - DefaultAuth Auth = new(noop) + DefaultAuth = NewAuth() ) -type noop struct { - options Options +// NewAuth returns a new default registry which is noop +func NewAuth(opts ...Option) Auth { + return noop{} } -// String name of implementation -func (a *noop) String() string { +type noop struct{} + +func (noop) Init(opts ...Option) error { + return nil +} + +func (noop) Options() Options { + return Options{} +} + +func (noop) Generate(id string, opts ...GenerateOption) (*Account, error) { + return nil, nil +} + +func (noop) Revoke(token string) error { + return nil +} + +func (noop) Validate(token string) (*Account, error) { + return nil, nil +} + +func (noop) String() string { return "noop" } - -// Init the svc -func (a *noop) Init(...Option) error { - return nil -} - -// Options set in init -func (a *noop) Options() Options { - return a.options -} - -// Generate a new auth Account -func (a *noop) Generate(id string, ops ...GenerateOption) (*Account, error) { - return nil, nil -} - -// Revoke an authorization Account -func (a *noop) Revoke(token string) error { - return nil -} - -// Validate a account token -func (a *noop) Validate(token string) (*Account, error) { - return nil, nil -} diff --git a/auth/store/store.go b/auth/store/store.go new file mode 100644 index 00000000..d53e7a14 --- /dev/null +++ b/auth/store/store.go @@ -0,0 +1,128 @@ +package store + +import ( + "bytes" + "encoding/gob" + "time" + + "github.com/google/uuid" + "github.com/micro/go-micro/v2/auth" + + "github.com/micro/go-micro/v2/errors" + "github.com/micro/go-micro/v2/store" +) + +// NewAuth returns an instance of store auth +func NewAuth(opts ...auth.Option) auth.Auth { + options := auth.Options{} + for _, o := range opts { + o(&options) + } + + return &Auth{ + store: store.DefaultStore, + opts: options, + } +} + +type Auth struct { + store store.Store + opts auth.Options +} + +// Init the auth package +func (a *Auth) Init(opts ...auth.Option) error { + for _, o := range opts { + o(&a.opts) + } + return nil +} + +// Options returns the options set +func (a *Auth) Options() auth.Options { + return a.opts +} + +// Generate a new auth Account +func (a *Auth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) { + // generate the token + token, err := uuid.NewUUID() + if err != nil { + return nil, err + } + + // parse the options + options := auth.NewGenerateOptions(opts...) + + // construct the account + sa := auth.Account{ + Id: id, + Token: token.String(), + Created: time.Now(), + Metadata: options.Metadata, + Roles: options.Roles, + } + + // encode the data to bytes + buf := &bytes.Buffer{} + e := gob.NewEncoder(buf) + if err := e.Encode(sa); err != nil { + return nil, err + } + + // write to the store + err = a.store.Write(&store.Record{ + Key: token.String(), + Value: buf.Bytes(), + }) + if err != nil { + return nil, err + } + + // return the result + return &sa, nil +} + +// Revoke an authorization Account +func (a *Auth) Revoke(token string) error { + records, err := a.store.Read(token, store.ReadSuffix()) + if err != nil { + return err + } + if len(records) == 0 { + return errors.BadRequest("go.micro.auth", "token not found") + } + + for _, r := range records { + if err := a.store.Delete(r.Key); err != nil { + return errors.InternalServerError("go.micro.auth", "error deleting from store") + } + } + + return nil +} + +// Validate an account token +func (a *Auth) Validate(token string) (*auth.Account, error) { + // lookup the record by token + records, err := a.store.Read(token, store.ReadSuffix()) + if err == store.ErrNotFound || len(records) == 0 { + return nil, errors.Unauthorized("go.micro.auth", "invalid token") + } else if err != nil { + return nil, errors.InternalServerError("go.micro.auth", "error reading store") + } + + // decode the result + b := bytes.NewBuffer(records[0].Value) + decoder := gob.NewDecoder(b) + var sa auth.Account + err = decoder.Decode(&sa) + + // return the result + return &sa, err +} + +// String returns the implementation +func (a *Auth) String() string { + return "store" +} diff --git a/client/grpc/grpc.go b/client/grpc/grpc.go index 0905eac9..cefb3150 100644 --- a/client/grpc/grpc.go +++ b/client/grpc/grpc.go @@ -18,6 +18,7 @@ import ( "github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/registry" + "github.com/micro/go-micro/v2/util/config" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -128,6 +129,10 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) // set the content type for the request header["x-content-type"] = req.ContentType() + // set the authorization token if one is saved locally + if token, err := config.Get("token"); err == nil && len(token) > 0 { + header["authorization"] = fmt.Sprintf("Bearer %v", token) + } md := gmetadata.New(header) ctx = gmetadata.NewOutgoingContext(ctx, md) diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 3c561146..dc16b7e6 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -65,6 +65,7 @@ import ( // auth jwtAuth "github.com/micro/go-micro/v2/auth/jwt" sAuth "github.com/micro/go-micro/v2/auth/service" + storeAuth "github.com/micro/go-micro/v2/auth/store" ) type Cmd interface { @@ -314,6 +315,7 @@ var ( DefaultAuths = map[string]func(...auth.Option) auth.Auth{ "service": sAuth.NewAuth, + "store": storeAuth.NewAuth, "jwt": jwtAuth.NewAuth, } ) diff --git a/go.sum b/go.sum index d18b6af8..3c3fc7aa 100644 --- a/go.sum +++ b/go.sum @@ -284,6 +284,7 @@ github.com/mholt/certmagic v0.9.3 h1:RmzuNJ5mpFplDbyS41z+gGgE/py24IX6m0nHZ0yNTQU github.com/mholt/certmagic v0.9.3/go.mod h1:nu8jbsbtwK4205EDH/ZUMTKsfYpJA1Q7MKXHfgTihNw= github.com/micro/cli/v2 v2.1.2 h1:43J1lChg/rZCC1rvdqZNFSQDrGT7qfMrtp6/ztpIkEM= github.com/micro/cli/v2 v2.1.2/go.mod h1:EguNh6DAoWKm9nmk+k/Rg0H3lQnDxqzu5x5srOtGtYg= +github.com/micro/go-micro v1.18.0 h1:gP70EZVHpJuUIT0YWth192JmlIci+qMOEByHm83XE9E= github.com/micro/mdns v0.3.0 h1:bYycYe+98AXR3s8Nq5qvt6C573uFTDPIYzJemWON0QE= github.com/micro/mdns v0.3.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= diff --git a/util/config/config.go b/util/config/config.go new file mode 100644 index 00000000..af4eae78 --- /dev/null +++ b/util/config/config.go @@ -0,0 +1,88 @@ +package config + +import ( + "io/ioutil" + "os" + "os/user" + "path/filepath" + + conf "github.com/micro/go-micro/v2/config" + "github.com/micro/go-micro/v2/config/source/file" +) + +// FileName for global micro config +const FileName = ".micro" + +// Get a value from the .micro file +func Get(key string) (string, error) { + // get the filepath + fp, err := filePath() + if err != nil { + return "", err + } + + // create a new config + c, err := conf.NewConfig( + conf.WithSource( + file.NewSource( + file.WithPath(fp), + ), + ), + ) + if err != nil { + return "", err + } + + // load the config + if err := c.Load(); err != nil { + return "", err + } + + // set a value + return c.Get(key).String(""), nil +} + +// Set a value in the .micro file +func Set(key, value string) error { + // get the filepath + fp, err := filePath() + if err != nil { + return err + } + + // write the file if it does not exist + if _, err := os.Stat(fp); os.IsNotExist(err) { + ioutil.WriteFile(fp, []byte{}, 0644) + } + + // create a new config + c, err := conf.NewConfig( + conf.WithSource( + file.NewSource( + file.WithPath(fp), + ), + ), + ) + if err != nil { + return err + } + + // load the config + if err := c.Load(); err != nil { + return err + } + + // set a value + c.Set(value, key) + + // write the file + return ioutil.WriteFile(fp, c.Bytes(), 0644) +} + +func filePath() (string, error) { + usr, err := user.Current() + if err != nil { + return "", err + } + return filepath.Join(usr.HomeDir, FileName), nil +}