diff --git a/auth/auth.go b/auth/auth.go deleted file mode 100644 index 87716480..00000000 --- a/auth/auth.go +++ /dev/null @@ -1,141 +0,0 @@ -// Package auth provides authentication and authorization capability -package auth // import "go.unistack.org/micro/v3/auth" - -import ( - "context" - "errors" - "time" - - "go.unistack.org/micro/v3/metadata" -) - -const ( - // BearerScheme used for Authorization header - BearerScheme = "Bearer " - // ScopePublic is the scope applied to a rule to allow access to the public - ScopePublic = "" - // ScopeAccount is the scope applied to a rule to limit to users with any valid account - ScopeAccount = "*" -) - -var ( - // DefaultAuth holds default auth implementation - DefaultAuth Auth = NewAuth() - // ErrInvalidToken is when the token provided is not valid - ErrInvalidToken = errors.New("invalid token provided") - // ErrForbidden is when a user does not have the necessary scope to access a resource - ErrForbidden = errors.New("resource forbidden") -) - -// Auth provides authentication and authorization -type Auth interface { - // Init the auth - Init(opts ...Option) error - // Options set for auth - Options() Options - // Generate a new account - Generate(id string, opts ...GenerateOption) (*Account, error) - // Verify an account has access to a resource using the rules - Verify(acc *Account, res *Resource, opts ...VerifyOption) error - // Inspect a token - Inspect(token string) (*Account, error) - // Token generated using refresh token or credentials - Token(opts ...TokenOption) (*Token, error) - // Grant access to a resource - Grant(rule *Rule) error - // Revoke access to a resource - Revoke(rule *Rule) error - // Rules returns all the rules used to verify requests - Rules(...RulesOption) ([]*Rule, error) - // String returns the name of the implementation - String() string -} - -// Account provided by an auth provider -type Account struct { - // Metadata any other associated metadata - Metadata metadata.Metadata `json:"metadata"` - // ID of the account e.g. email or id - ID string `json:"id"` - // Type of the account, e.g. service - Type string `json:"type"` - // Issuer of the account - Issuer string `json:"issuer"` - // Secret for the account, e.g. the password - Secret string `json:"secret"` - // Scopes the account has access to - Scopes []string `json:"scopes"` -} - -// Token can be short or long lived -type Token struct { - // Time of token creation - Created time.Time `json:"created"` - // Time of token expiry - Expiry time.Time `json:"expiry"` - // The token to be used for accessing resources - AccessToken string `json:"access_token"` - // RefreshToken to be used to generate a new token - RefreshToken string `json:"refresh_token"` -} - -// Expired returns a boolean indicating if the token needs to be refreshed -func (t *Token) Expired() bool { - return t.Expiry.Unix() < time.Now().Unix() -} - -// Resource is an entity such as a user or -type Resource struct { - // Name of the resource, e.g. go.micro.service.notes - Name string `json:"name"` - // Type of resource, e.g. service - Type string `json:"type"` - // Endpoint resource e.g NotesService.Create - Endpoint string `json:"endpoint"` -} - -// Access defines the type of access a rule grants -type Access int - -const ( - // AccessGranted to a resource - AccessGranted Access = iota - // AccessDenied to a resource - AccessDenied -) - -// Rule is used to verify access to a resource -type Rule struct { - // Resource that rule belongs to - Resource *Resource - // ID of the rule - ID string - // Scope of the rule - Scope string - // Access flag allow/deny - Access Access - // Priority holds the rule priority - Priority int32 -} - -type accountKey struct{} - -// AccountFromContext gets the account from the context, which -// is set by the auth wrapper at the start of a call. If the account -// is not set, a nil account will be returned. The error is only returned -// when there was a problem retrieving an account -func AccountFromContext(ctx context.Context) (*Account, bool) { - if ctx == nil { - return nil, false - } - acc, ok := ctx.Value(accountKey{}).(*Account) - return acc, ok -} - -// ContextWithAccount sets the account in the context -func ContextWithAccount(ctx context.Context, account *Account) context.Context { - if ctx == nil { - ctx = context.Background() - } - return context.WithValue(ctx, accountKey{}, account) -} diff --git a/auth/noop.go b/auth/noop.go deleted file mode 100644 index 7a021927..00000000 --- a/auth/noop.go +++ /dev/null @@ -1,79 +0,0 @@ -package auth - -import ( - "go.unistack.org/micro/v3/util/id" -) - -type noopAuth struct { - opts Options -} - -// String returns the name of the implementation -func (n *noopAuth) String() string { - return "noop" -} - -// Init the auth -func (n *noopAuth) Init(opts ...Option) error { - for _, o := range opts { - o(&n.opts) - } - return nil -} - -// Options set for auth -func (n *noopAuth) Options() Options { - return n.opts -} - -// Generate a new account -func (n *noopAuth) Generate(id string, opts ...GenerateOption) (*Account, error) { - options := NewGenerateOptions(opts...) - - return &Account{ - ID: id, - Secret: options.Secret, - Metadata: options.Metadata, - Scopes: options.Scopes, - Issuer: n.Options().Issuer, - }, nil -} - -// Grant access to a resource -func (n *noopAuth) Grant(rule *Rule) error { - return nil -} - -// Revoke access to a resource -func (n *noopAuth) Revoke(rule *Rule) error { - return nil -} - -// Rules used to verify requests -func (n *noopAuth) Rules(opts ...RulesOption) ([]*Rule, error) { - return []*Rule{}, nil -} - -// Verify an account has access to a resource -func (n *noopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) error { - return nil -} - -// Inspect a token -func (n *noopAuth) Inspect(token string) (*Account, error) { - id, err := id.New() - if err != nil { - return nil, err - } - return &Account{ID: id, Issuer: n.Options().Issuer}, nil -} - -// Token generation using an account id and secret -func (n *noopAuth) Token(opts ...TokenOption) (*Token, error) { - return &Token{}, nil -} - -// NewAuth returns new noop auth -func NewAuth(opts ...Option) Auth { - return &noopAuth{opts: NewOptions(opts...)} -} diff --git a/auth/options.go b/auth/options.go deleted file mode 100644 index 503e4cff..00000000 --- a/auth/options.go +++ /dev/null @@ -1,311 +0,0 @@ -package auth - -import ( - "context" - "time" - - "go.unistack.org/micro/v3/logger" - "go.unistack.org/micro/v3/metadata" - "go.unistack.org/micro/v3/meter" - "go.unistack.org/micro/v3/store" - "go.unistack.org/micro/v3/tracer" -) - -// NewOptions creates Options struct from slice of options -func NewOptions(opts ...Option) Options { - options := Options{ - Tracer: tracer.DefaultTracer, - Logger: logger.DefaultLogger, - Meter: meter.DefaultMeter, - } - for _, o := range opts { - o(&options) - } - return options -} - -// Options struct holds auth options -type Options struct { - // Context holds the external options - Context context.Context - // Meter used for metrics - Meter meter.Meter - // Logger used for logging - Logger logger.Logger - // Tracer used for tracing - Tracer tracer.Tracer - // Store used for stre data - Store store.Store - // Token is the services token used to authenticate itself - Token *Token - // LoginURL is the relative url path where a user can login - LoginURL string - // PrivateKey for encoding JWTs - PrivateKey string - // PublicKey for decoding JWTs - PublicKey string - // Secret is used to authenticate the service - Secret string - // ID is the services auth ID - ID string - // Issuer of the service's account - Issuer string - // Name holds the auth name - Name string - // Addrs sets the addresses of auth - Addrs []string -} - -// Option func -type Option func(o *Options) - -// Addrs is the auth addresses to use -func Addrs(addrs ...string) Option { - return func(o *Options) { - o.Addrs = addrs - } -} - -// Name sets the name -func Name(n string) Option { - return func(o *Options) { - o.Name = n - } -} - -// Issuer of the services account -func Issuer(i string) Option { - return func(o *Options) { - o.Issuer = i - } -} - -// Store to back auth -func Store(s store.Store) Option { - return func(o *Options) { - o.Store = s - } -} - -// PublicKey is the JWT public key -func PublicKey(key string) Option { - return func(o *Options) { - o.PublicKey = key - } -} - -// PrivateKey is the JWT private key -func PrivateKey(key string) Option { - return func(o *Options) { - o.PrivateKey = key - } -} - -// Credentials sets the auth credentials -func Credentials(id, secret string) Option { - return func(o *Options) { - o.ID = id - o.Secret = secret - } -} - -// ClientToken sets the auth token to use when making requests -func ClientToken(token *Token) Option { - return func(o *Options) { - o.Token = token - } -} - -// LoginURL sets the auth LoginURL -func LoginURL(url string) Option { - return func(o *Options) { - o.LoginURL = url - } -} - -// GenerateOptions struct -type GenerateOptions struct { - Metadata metadata.Metadata - Provider string - Type string - Secret string - Issuer string - Scopes []string -} - -// GenerateOption func -type GenerateOption func(o *GenerateOptions) - -// WithSecret for the generated account -func WithSecret(s string) GenerateOption { - return func(o *GenerateOptions) { - o.Secret = s - } -} - -// WithType for the generated account -func WithType(t string) GenerateOption { - return func(o *GenerateOptions) { - o.Type = t - } -} - -// WithMetadata for the generated account -func WithMetadata(md metadata.Metadata) GenerateOption { - return func(o *GenerateOptions) { - o.Metadata = metadata.Copy(md) - } -} - -// WithProvider for the generated account -func WithProvider(p string) GenerateOption { - return func(o *GenerateOptions) { - o.Provider = p - } -} - -// WithScopes for the generated account -func WithScopes(s ...string) GenerateOption { - return func(o *GenerateOptions) { - o.Scopes = s - } -} - -// WithIssuer for the generated account -func WithIssuer(i string) GenerateOption { - return func(o *GenerateOptions) { - o.Issuer = i - } -} - -// NewGenerateOptions from a slice of options -func NewGenerateOptions(opts ...GenerateOption) GenerateOptions { - var options GenerateOptions - for _, o := range opts { - o(&options) - } - return options -} - -// TokenOptions struct -type TokenOptions struct { - ID string - Secret string - RefreshToken string - Issuer string - Expiry time.Duration -} - -// TokenOption func -type TokenOption func(o *TokenOptions) - -// WithExpiry for the token -func WithExpiry(ex time.Duration) TokenOption { - return func(o *TokenOptions) { - o.Expiry = ex - } -} - -// WithCredentials sets tye id and secret -func WithCredentials(id, secret string) TokenOption { - return func(o *TokenOptions) { - o.ID = id - o.Secret = secret - } -} - -// WithToken sets the refresh token -func WithToken(rt string) TokenOption { - return func(o *TokenOptions) { - o.RefreshToken = rt - } -} - -// WithTokenIssuer sets the token issuer option -func WithTokenIssuer(iss string) TokenOption { - return func(o *TokenOptions) { - o.Issuer = iss - } -} - -// NewTokenOptions from a slice of options -func NewTokenOptions(opts ...TokenOption) TokenOptions { - var options TokenOptions - for _, o := range opts { - o(&options) - } - - // set default expiry of token - if options.Expiry == 0 { - options.Expiry = time.Minute - } - - return options -} - -// VerifyOptions struct -type VerifyOptions struct { - Context context.Context - Namespace string -} - -// VerifyOption func -type VerifyOption func(o *VerifyOptions) - -// VerifyContext pass context to verify -func VerifyContext(ctx context.Context) VerifyOption { - return func(o *VerifyOptions) { - o.Context = ctx - } -} - -// VerifyNamespace sets thhe namespace for verify -func VerifyNamespace(ns string) VerifyOption { - return func(o *VerifyOptions) { - o.Namespace = ns - } -} - -// RulesOptions struct -type RulesOptions struct { - Context context.Context - Namespace string -} - -// RulesOption func -type RulesOption func(o *RulesOptions) - -// RulesContext pass rules context -func RulesContext(ctx context.Context) RulesOption { - return func(o *RulesOptions) { - o.Context = ctx - } -} - -// RulesNamespace sets the rule namespace -func RulesNamespace(ns string) RulesOption { - return func(o *RulesOptions) { - o.Namespace = ns - } -} - -// Logger sets the logger -func Logger(l logger.Logger) Option { - return func(o *Options) { - o.Logger = l - } -} - -// Meter sets the meter -func Meter(m meter.Meter) Option { - return func(o *Options) { - o.Meter = m - } -} - -// Tracer sets the meter -func Tracer(t tracer.Tracer) Option { - return func(o *Options) { - o.Tracer = t - } -} diff --git a/auth/rules.go b/auth/rules.go deleted file mode 100644 index 663dc7f1..00000000 --- a/auth/rules.go +++ /dev/null @@ -1,92 +0,0 @@ -package auth - -import ( - "fmt" - "sort" - "strings" -) - -// VerifyAccess an account has access to a resource using the rules provided. If the account does not have -// access an error will be returned. If there are no rules provided which match the resource, an error -// will be returned -//nolint:gocyclo -func VerifyAccess(rules []*Rule, acc *Account, res *Resource) error { - // the rule is only to be applied if the type matches the resource or is catch-all (*) - validTypes := []string{"*", res.Type} - - // the rule is only to be applied if the name matches the resource or is catch-all (*) - validNames := []string{"*", res.Name} - - // rules can have wildcard excludes on endpoints since this can also be a path for web services, - // e.g. /foo/* would include /foo/bar. We also want to check for wildcards and the exact endpoint - validEndpoints := []string{"*", res.Endpoint} - if comps := strings.Split(res.Endpoint, "/"); len(comps) > 1 { - for i := 1; i < len(comps)+1; i++ { - wildcard := fmt.Sprintf("%v/*", strings.Join(comps[0:i], "/")) - validEndpoints = append(validEndpoints, wildcard) - } - } - - // filter the rules to the ones which match the criteria above - filteredRules := make([]*Rule, 0) - for _, rule := range rules { - if !include(validTypes, rule.Resource.Type) { - continue - } - if !include(validNames, rule.Resource.Name) { - continue - } - if !include(validEndpoints, rule.Resource.Endpoint) { - continue - } - filteredRules = append(filteredRules, rule) - } - - // sort the filtered rules by priority, highest to lowest - sort.SliceStable(filteredRules, func(i, j int) bool { - return filteredRules[i].Priority > filteredRules[j].Priority - }) - - // loop through the rules and check for a rule which applies to this account - for _, rule := range filteredRules { - // a blank scope indicates the rule applies to everyone, even nil accounts - if rule.Scope == ScopePublic && rule.Access == AccessDenied { - return ErrForbidden - } else if rule.Scope == ScopePublic && rule.Access == AccessGranted { - return nil - } - - // all further checks require an account - if acc == nil { - continue - } - - // this rule applies to any account - if rule.Scope == ScopeAccount && rule.Access == AccessDenied { - return ErrForbidden - } else if rule.Scope == ScopeAccount && rule.Access == AccessGranted { - return nil - } - - // if the account has the necessary scope - if include(acc.Scopes, rule.Scope) && rule.Access == AccessDenied { - return ErrForbidden - } else if include(acc.Scopes, rule.Scope) && rule.Access == AccessGranted { - return nil - } - } - - // if no rules matched then return forbidden - return ErrForbidden -} - -// include is a helper function which checks to see if the slice contains the value. includes is -// not case sensitive. -func include(slice []string, val string) bool { - for _, s := range slice { - if strings.EqualFold(s, val) { - return true - } - } - return false -} diff --git a/auth/rules_test.go b/auth/rules_test.go deleted file mode 100644 index 12d8a6e7..00000000 --- a/auth/rules_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package auth - -import ( - "testing" -) - -func TestVerify(t *testing.T) { - srvResource := &Resource{ - Type: "service", - Name: "go.micro.service.foo", - Endpoint: "Foo.Bar", - } - - webResource := &Resource{ - Type: "service", - Name: "go.micro.web.foo", - Endpoint: "/foo/bar", - } - - catchallResource := &Resource{ - Type: "*", - Name: "*", - Endpoint: "*", - } - - tt := []struct { - Error error - Account *Account - Resource *Resource - Name string - Rules []*Rule - }{ - { - Name: "NoRules", - Rules: []*Rule{}, - Account: nil, - Resource: srvResource, - Error: ErrForbidden, - }, - { - Name: "CatchallPublicAccount", - Account: &Account{}, - Resource: srvResource, - Rules: []*Rule{ - { - Scope: "", - Resource: catchallResource, - }, - }, - }, - { - Name: "CatchallPublicNoAccount", - Resource: srvResource, - Rules: []*Rule{ - { - Scope: "", - Resource: catchallResource, - }, - }, - }, - { - Name: "CatchallPrivateAccount", - Account: &Account{}, - Resource: srvResource, - Rules: []*Rule{ - { - Scope: "*", - Resource: catchallResource, - }, - }, - }, - { - Name: "CatchallPrivateNoAccount", - Resource: srvResource, - Rules: []*Rule{ - { - Scope: "*", - Resource: catchallResource, - }, - }, - Error: ErrForbidden, - }, - { - Name: "CatchallServiceRuleMatch", - Resource: srvResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: &Resource{ - Type: srvResource.Type, - Name: srvResource.Name, - Endpoint: "*", - }, - }, - }, - }, - { - Name: "CatchallServiceRuleNoMatch", - Resource: srvResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: &Resource{ - Type: srvResource.Type, - Name: "wrongname", - Endpoint: "*", - }, - }, - }, - Error: ErrForbidden, - }, - { - Name: "ExactRuleValidScope", - Resource: srvResource, - Account: &Account{ - Scopes: []string{"neededscope"}, - }, - Rules: []*Rule{ - { - Scope: "neededscope", - Resource: srvResource, - }, - }, - }, - { - Name: "ExactRuleInvalidScope", - Resource: srvResource, - Account: &Account{ - Scopes: []string{"neededscope"}, - }, - Rules: []*Rule{ - { - Scope: "invalidscope", - Resource: srvResource, - }, - }, - Error: ErrForbidden, - }, - { - Name: "CatchallDenyWithAccount", - Resource: srvResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: catchallResource, - Access: AccessDenied, - }, - }, - Error: ErrForbidden, - }, - { - Name: "CatchallDenyWithNoAccount", - Resource: srvResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: catchallResource, - Access: AccessDenied, - }, - }, - Error: ErrForbidden, - }, - { - Name: "RulePriorityGrantFirst", - Resource: srvResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: catchallResource, - Access: AccessGranted, - Priority: 1, - }, - { - Scope: "*", - Resource: catchallResource, - Access: AccessDenied, - Priority: 0, - }, - }, - }, - { - Name: "RulePriorityDenyFirst", - Resource: srvResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: catchallResource, - Access: AccessGranted, - Priority: 0, - }, - { - Scope: "*", - Resource: catchallResource, - Access: AccessDenied, - Priority: 1, - }, - }, - Error: ErrForbidden, - }, - { - Name: "WebExactEndpointValid", - Resource: webResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: webResource, - }, - }, - }, - { - Name: "WebExactEndpointInalid", - Resource: webResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: &Resource{ - Type: webResource.Type, - Name: webResource.Name, - Endpoint: "invalidendpoint", - }, - }, - }, - Error: ErrForbidden, - }, - { - Name: "WebWildcardEndpoint", - Resource: webResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: &Resource{ - Type: webResource.Type, - Name: webResource.Name, - Endpoint: "*", - }, - }, - }, - }, - { - Name: "WebWildcardPathEndpointValid", - Resource: webResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: &Resource{ - Type: webResource.Type, - Name: webResource.Name, - Endpoint: "/foo/*", - }, - }, - }, - }, - { - Name: "WebWildcardPathEndpointInvalid", - Resource: webResource, - Account: &Account{}, - Rules: []*Rule{ - { - Scope: "*", - Resource: &Resource{ - Type: webResource.Type, - Name: webResource.Name, - Endpoint: "/bar/*", - }, - }, - }, - Error: ErrForbidden, - }, - } - - for _, tc := range tt { - t.Run(tc.Name, func(t *testing.T) { - if err := VerifyAccess(tc.Rules, tc.Account, tc.Resource); err != tc.Error { - t.Errorf("Expected %v but got %v", tc.Error, err) - } - }) - } -} diff --git a/build/build.go b/build/build.go deleted file mode 100644 index 372cc8b2..00000000 --- a/build/build.go +++ /dev/null @@ -1,32 +0,0 @@ -// Package build is for building source into a package -package build // import "go.unistack.org/micro/v3/build" - -// Build is an interface for building packages -type Build interface { - // Package builds a package - Package(name string, src *Source) (*Package, error) - // Remove removes the package - Remove(*Package) error -} - -// Source is the source of a build -type Source struct { - // Path to the source if local - Path string - // Language is the language of code - Language string - // Location of the source - Repository string -} - -// Package is packaged format for source -type Package struct { - // Source of the package - Source *Source - // Name of the package - Name string - // Location of the package - Path string - // Type of package e.g tarball, binary, docker - Type string -} diff --git a/build/options.go b/build/options.go deleted file mode 100644 index 93a494b0..00000000 --- a/build/options.go +++ /dev/null @@ -1,17 +0,0 @@ -package build - -// Options struct -type Options struct { - // local path to download source - Path string -} - -// Option func -type Option func(o *Options) - -// Path is the Local path for repository -func Path(p string) Option { - return func(o *Options) { - o.Path = p - } -} diff --git a/codec/codec.go b/codec/codec.go index 1c137c52..89a0bdec 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -27,7 +27,7 @@ var ( // DefaultMaxMsgSize specifies how much data codec can handle DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb // DefaultCodec is the global default codec - DefaultCodec Codec = NewCodec() + DefaultCodec = NewCodec() // DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal DefaultTagName = "codec" ) diff --git a/function.go b/function.go deleted file mode 100644 index d2af4d22..00000000 --- a/function.go +++ /dev/null @@ -1,101 +0,0 @@ -//go:build ignore -// +build ignore - -package micro - -import ( - "context" - "time" - - "go.unistack.org/micro/v3/server" -) - -// Function is a one time executing Service -type Function interface { - // Inherits Service interface - Service - // Done signals to complete execution - Done() error - // Handle registers an RPC handler - Handle(v interface{}) error - // Subscribe registers a subscriber - Subscribe(topic string, v interface{}) error -} - -type function struct { - cancel context.CancelFunc - Service -} - -// NewFunction returns a new Function for a one time executing Service -func NewFunction(opts ...Option) Function { - return newFunction(opts...) -} - -func fnHandlerWrapper(f Function) server.HandlerWrapper { - return func(h server.HandlerFunc) server.HandlerFunc { - return func(ctx context.Context, req server.Request, rsp interface{}) error { - defer f.Done() - return h(ctx, req, rsp) - } - } -} - -func fnSubWrapper(f Function) server.SubscriberWrapper { - return func(s server.SubscriberFunc) server.SubscriberFunc { - return func(ctx context.Context, msg server.Message) error { - defer f.Done() - return s(ctx, msg) - } - } -} - -func newFunction(opts ...Option) Function { - ctx, cancel := context.WithCancel(context.Background()) - - // force ttl/interval - fopts := []Option{ - RegisterTTL(time.Minute), - RegisterInterval(time.Second * 30), - } - - // prepend to opts - fopts = append(fopts, opts...) - - // make context the last thing - fopts = append(fopts, Context(ctx)) - - service := &service{opts: NewOptions(fopts...)} - - fn := &function{ - cancel: cancel, - Service: service, - } - - service.Server().Init( - // ensure the service waits for requests to finish - server.Wait(nil), - // wrap handlers and subscribers to finish execution - server.WrapHandler(fnHandlerWrapper(fn)), - server.WrapSubscriber(fnSubWrapper(fn)), - ) - - return fn -} - -func (f *function) Done() error { - f.cancel() - return nil -} - -func (f *function) Handle(v interface{}) error { - return f.Service.Server().Handle( - f.Service.Server().NewHandler(v), - ) -} - -func (f *function) Subscribe(topic string, v interface{}) error { - return f.Service.Server().Subscribe( - f.Service.Server().NewSubscriber(topic, v), - ) -} diff --git a/function_test.go b/function_test.go deleted file mode 100644 index f15abb99..00000000 --- a/function_test.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build ignore -// +build ignore - -package micro - -/* -import ( - "context" - "sync" - "testing" - - "go.unistack.org/micro/v3/register" -) - -func TestFunction(t *testing.T) { - var wg sync.WaitGroup - wg.Add(1) - - r := register.NewRegister() - ctx := context.TODO() - // create service - fn := NewFunction( - Register(r), - Name("test.function"), - AfterStart(func(ctx context.Context) error { - wg.Done() - return nil - }), - ) - - // we can't test fn.Init as it parses the command line - // fn.Init() - - ch := make(chan error, 2) - - go func() { - // run service - ch <- fn.Run() - }() - - // wait for start - wg.Wait() - - // test call debug - req := fn.Client().NewRequest( - "test.function", - "Debug.Health", - new(proto.HealthRequest), - ) - - rsp := new(proto.HealthResponse) - - err := fn.Client().Call(context.TODO(), req, rsp) - if err != nil { - t.Fatal(err) - } - - if rsp.Status != "ok" { - t.Fatalf("function response: %s", rsp.Status) - } - - if err := <-ch; err != nil { - t.Fatal(err) - } -} - -*/ diff --git a/logger/logger.go b/logger/logger.go index 61379d66..425bedfe 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -8,9 +8,9 @@ import ( var ( // DefaultLogger variable - DefaultLogger Logger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL")))) + DefaultLogger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL")))) // DefaultLevel used by logger - DefaultLevel Level = InfoLevel + DefaultLevel = InfoLevel // DefaultCallerSkipCount used by logger DefaultCallerSkipCount = 2 ) diff --git a/meter/meter.go b/meter/meter.go index 2191d3a0..71da6914 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -11,7 +11,7 @@ import ( var ( // DefaultMeter is the default meter - DefaultMeter Meter = NewMeter() + DefaultMeter = NewMeter() // DefaultAddress data will be made available on this host:port DefaultAddress = ":9090" // DefaultPath the meter endpoint where the Meter data will be made available diff --git a/micro.go b/micro.go deleted file mode 100644 index 8736d7bf..00000000 --- a/micro.go +++ /dev/null @@ -1 +0,0 @@ -package micro // import "go.unistack.org/micro/v3" diff --git a/options.go b/options.go index 89c4a51b..95636364 100644 --- a/options.go +++ b/options.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "go.unistack.org/micro/v3/auth" "go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/config" @@ -39,8 +38,6 @@ type Options struct { Configs []config.Config // Clients holds clients Clients []client.Client - // Auths holds auths - Auths []auth.Auth // Stores holds stores Stores []store.Store // Registers holds registers @@ -70,7 +67,6 @@ func NewOptions(opts ...Option) Options { Brokers: []broker.Broker{broker.DefaultBroker}, Registers: []register.Register{register.DefaultRegister}, Routers: []router.Router{router.DefaultRouter}, - Auths: []auth.Auth{auth.DefaultAuth}, Loggers: []logger.Logger{logger.DefaultLogger}, Tracers: []tracer.Tracer{tracer.DefaultTracer}, Meters: []meter.Meter{meter.DefaultMeter}, @@ -497,19 +493,6 @@ func TracerStore(n string) TracerOption { } } -/* -// Auth sets the auth for the service -func Auth(a auth.Auth) Option { - return func(o *Options) error { - o.Auth = a - if o.Server != nil { - o.Server.Init(server.Auth(a)) - } - return nil - } -} -*/ - // Config sets the config for the service func Config(c ...config.Config) Option { return func(o *Options) error { diff --git a/register/register.go b/register/register.go index bfe0078a..30055da1 100644 --- a/register/register.go +++ b/register/register.go @@ -18,7 +18,7 @@ var DefaultDomain = "micro" var ( // DefaultRegister is the global default register - DefaultRegister Register = NewRegister() + DefaultRegister = NewRegister() // ErrNotFound returned when LookupService is called and no services found ErrNotFound = errors.New("service not found") // ErrWatcherStopped returned when when watcher is stopped diff --git a/router/router.go b/router/router.go index 43874196..2509b7c5 100644 --- a/router/router.go +++ b/router/router.go @@ -7,7 +7,7 @@ import ( var ( // DefaultRouter is the global default router - DefaultRouter Router = NewRouter() + DefaultRouter = NewRouter() // DefaultNetwork is default micro network DefaultNetwork = "micro" // ErrRouteNotFound is returned when no route was found in the routing table diff --git a/runtime/options.go b/runtime/options.go deleted file mode 100644 index ceb420ac..00000000 --- a/runtime/options.go +++ /dev/null @@ -1,309 +0,0 @@ -package runtime - -import ( - "context" - "io" - - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/logger" -) - -// Options configure runtime -type Options struct { - Scheduler Scheduler - Client client.Client - Logger logger.Logger - Type string - Source string - Image string -} - -// Option func signature -type Option func(o *Options) - -// WithLogger sets the logger -func WithLogger(l logger.Logger) Option { - return func(o *Options) { - o.Logger = l - } -} - -// WithSource sets the base image / repository -func WithSource(src string) Option { - return func(o *Options) { - o.Source = src - } -} - -// WithScheduler specifies a scheduler for updates -func WithScheduler(n Scheduler) Option { - return func(o *Options) { - o.Scheduler = n - } -} - -// WithType sets the service type to manage -func WithType(t string) Option { - return func(o *Options) { - o.Type = t - } -} - -// WithImage sets the image to use -func WithImage(t string) Option { - return func(o *Options) { - o.Image = t - } -} - -// WithClient sets the client to use -func WithClient(c client.Client) Option { - return func(o *Options) { - o.Client = c - } -} - -// CreateOption func signature -type CreateOption func(o *CreateOptions) - -// ReadOption func signature -type ReadOption func(o *ReadOptions) - -// CreateOptions configure runtime services -type CreateOptions struct { - Context context.Context - Output io.Writer - Resources *Resources - Secrets map[string]string - Image string - Namespace string - Type string - Command []string - Args []string - Env []string - Retries int -} - -// ReadOptions queries runtime services -type ReadOptions struct { - Context context.Context - Service string - Version string - Type string - Namespace string -} - -// CreateType sets the type of service to create -func CreateType(t string) CreateOption { - return func(o *CreateOptions) { - o.Type = t - } -} - -// CreateImage sets the image to use -func CreateImage(img string) CreateOption { - return func(o *CreateOptions) { - o.Image = img - } -} - -// CreateNamespace sets the namespace -func CreateNamespace(ns string) CreateOption { - return func(o *CreateOptions) { - o.Namespace = ns - } -} - -// CreateContext sets the context -func CreateContext(ctx context.Context) CreateOption { - return func(o *CreateOptions) { - o.Context = ctx - } -} - -// WithSecret sets a secret to provide the service with -func WithSecret(key, value string) CreateOption { - return func(o *CreateOptions) { - if o.Secrets == nil { - o.Secrets = map[string]string{key: value} - } else { - o.Secrets[key] = value - } - } -} - -// WithCommand specifies the command to execute -func WithCommand(cmd ...string) CreateOption { - return func(o *CreateOptions) { - // set command - o.Command = cmd - } -} - -// WithArgs specifies the command to execute -func WithArgs(args ...string) CreateOption { - return func(o *CreateOptions) { - // set command - o.Args = args - } -} - -// WithRetries sets the max retries attempts -func WithRetries(retries int) CreateOption { - return func(o *CreateOptions) { - o.Retries = retries - } -} - -// WithEnv sets the created service environment -func WithEnv(env []string) CreateOption { - return func(o *CreateOptions) { - o.Env = env - } -} - -// WithOutput sets the arg output -func WithOutput(out io.Writer) CreateOption { - return func(o *CreateOptions) { - o.Output = out - } -} - -// ResourceLimits sets the resources for the service to use -func ResourceLimits(r *Resources) CreateOption { - return func(o *CreateOptions) { - o.Resources = r - } -} - -// ReadService returns services with the given name -func ReadService(service string) ReadOption { - return func(o *ReadOptions) { - o.Service = service - } -} - -// ReadVersion confifgures service version -func ReadVersion(version string) ReadOption { - return func(o *ReadOptions) { - o.Version = version - } -} - -// ReadType returns services of the given type -func ReadType(t string) ReadOption { - return func(o *ReadOptions) { - o.Type = t - } -} - -// ReadNamespace sets the namespace -func ReadNamespace(ns string) ReadOption { - return func(o *ReadOptions) { - o.Namespace = ns - } -} - -// ReadContext sets the context -func ReadContext(ctx context.Context) ReadOption { - return func(o *ReadOptions) { - o.Context = ctx - } -} - -// UpdateOption func signature -type UpdateOption func(o *UpdateOptions) - -// UpdateOptions struct -type UpdateOptions struct { - Context context.Context - Secrets map[string]string - Namespace string -} - -// UpdateSecret sets a secret to provide the service with -func UpdateSecret(key, value string) UpdateOption { - return func(o *UpdateOptions) { - if o.Secrets == nil { - o.Secrets = map[string]string{key: value} - } else { - o.Secrets[key] = value - } - } -} - -// UpdateNamespace sets the namespace -func UpdateNamespace(ns string) UpdateOption { - return func(o *UpdateOptions) { - o.Namespace = ns - } -} - -// UpdateContext sets the context -func UpdateContext(ctx context.Context) UpdateOption { - return func(o *UpdateOptions) { - o.Context = ctx - } -} - -// DeleteOption func signature -type DeleteOption func(o *DeleteOptions) - -// DeleteOptions struct -type DeleteOptions struct { - Context context.Context - Namespace string -} - -// DeleteNamespace sets the namespace -func DeleteNamespace(ns string) DeleteOption { - return func(o *DeleteOptions) { - o.Namespace = ns - } -} - -// DeleteContext sets the context -func DeleteContext(ctx context.Context) DeleteOption { - return func(o *DeleteOptions) { - o.Context = ctx - } -} - -// LogsOption configures runtime logging -type LogsOption func(o *LogsOptions) - -// LogsOptions configure runtime logging -type LogsOptions struct { - Context context.Context - Namespace string - Count int64 - Stream bool -} - -// LogsCount confiures how many existing lines to show -func LogsCount(count int64) LogsOption { - return func(l *LogsOptions) { - l.Count = count - } -} - -// LogsStream configures whether to stream new lines -func LogsStream(stream bool) LogsOption { - return func(l *LogsOptions) { - l.Stream = stream - } -} - -// LogsNamespace sets the namespace -func LogsNamespace(ns string) LogsOption { - return func(o *LogsOptions) { - o.Namespace = ns - } -} - -// LogsContext sets the context -func LogsContext(ctx context.Context) LogsOption { - return func(o *LogsOptions) { - o.Context = ctx - } -} diff --git a/runtime/runtime.go b/runtime/runtime.go deleted file mode 100644 index 6397a941..00000000 --- a/runtime/runtime.go +++ /dev/null @@ -1,125 +0,0 @@ -// Package runtime is a service runtime manager -package runtime // import "go.unistack.org/micro/v3/runtime" - -import ( - "errors" - "time" - - "go.unistack.org/micro/v3/metadata" -) - -// ErrAlreadyExists error -var ErrAlreadyExists = errors.New("already exists") - -// Runtime is a service runtime manager -type Runtime interface { - // Init initializes runtime - Init(...Option) error - // Create registers a service - Create(*Service, ...CreateOption) error - // Read returns the service - Read(...ReadOption) ([]*Service, error) - // Update the service in place - Update(*Service, ...UpdateOption) error - // Remove a service - Delete(*Service, ...DeleteOption) error - // Logs returns the logs for a service - Logs(*Service, ...LogsOption) (Logs, error) - // Start starts the runtime - Start() error - // Stop shuts down the runtime - Stop() error - // String describes runtime - String() string -} - -// Logs returns a log stream -type Logs interface { - // Error returns error - Error() error - // Chan return chan log - Chan() chan Log - // Stop stops the log stream - Stop() error -} - -// Log is a log message -type Log struct { - // Metadata holds metadata - Metadata metadata.Metadata - // Message holds the message - Message string -} - -// Scheduler is a runtime service scheduler -type Scheduler interface { - // Notify publishes schedule events - Notify() (<-chan Event, error) - // Close stops the scheduler - Close() error -} - -// EventType defines schedule event -type EventType int - -const ( - // Create is emitted when a new build has been craeted - Create EventType = iota - // Update is emitted when a new update become available - Update - // Delete is emitted when a build has been deleted - Delete -) - -// String returns human readable event type -func (t EventType) String() string { - switch t { - case Create: - return "create" - case Delete: - return "delete" - case Update: - return "update" - default: - return "unknown" - } -} - -// Event is notification event -type Event struct { - // Timestamp of event - Timestamp time.Time - // Service the event relates to - Service *Service - // Options to use when processing the event - Options *CreateOptions - // ID of the event - ID string - // Type is event type - Type EventType -} - -// Service is runtime service -type Service struct { - // Metadata stores metadata - Metadata metadata.Metadata - // Name of the service - Name string - // Version of the service - Version string - // Name of the service - Source string -} - -// Resources which are allocated to a serivce -type Resources struct { - // CPU is the maximum amount of CPU the service will be allocated (unit millicpu) - // e.g. 0.25CPU would be passed as 250 - CPU int - // Mem is the maximum amount of memory the service will be allocated (unit mebibyte) - // e.g. 128 MiB of memory would be passed as 128 - Mem int - // Disk is the maximum amount of disk space the service will be allocated (unit mebibyte) - // e.g. 128 MiB of memory would be passed as 128 - Disk int -} diff --git a/server/options.go b/server/options.go index ea0d78be..73a2ff4b 100644 --- a/server/options.go +++ b/server/options.go @@ -7,7 +7,6 @@ import ( "sync" "time" - "go.unistack.org/micro/v3/auth" "go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/logger" @@ -32,8 +31,6 @@ type Options struct { Register register.Register // Tracer holds the tracer Tracer tracer.Tracer - // Auth holds the auth - Auth auth.Auth // Logger holds the logger Logger logger.Logger // Meter holds the meter @@ -91,7 +88,6 @@ type Options struct { // NewOptions returns new options struct with default or passed values func NewOptions(opts ...Option) Options { options := Options{ - Auth: auth.DefaultAuth, Codecs: make(map[string]codec.Codec), Context: context.Background(), Metadata: metadata.New(0), @@ -211,13 +207,6 @@ func Tracer(t tracer.Tracer) Option { } } -// Auth mechanism for role based access control -func Auth(a auth.Auth) Option { - return func(o *Options) { - o.Auth = a - } -} - // Transport mechanism for communication e.g http, rabbitmq, etc func Transport(t transport.Transport) Option { return func(o *Options) { diff --git a/service.go b/service.go index a235cb2b..60165ced 100644 --- a/service.go +++ b/service.go @@ -1,11 +1,10 @@ // Package micro is a pluggable framework for microservices -package micro +package micro // import "go.unistack.org/micro/v3" import ( "fmt" "sync" - "go.unistack.org/micro/v3/auth" "go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/config" @@ -27,36 +26,34 @@ type Service interface { Init(...Option) error // Options returns the current options Options() Options - // Auth is for handling auth - Auth(...string) auth.Auth - // Logger is for logs + // Logger is for output log from service components Logger(...string) logger.Logger - // Config if for config + // Config if for config handling via load/save methods and also with ability to watch changes Config(...string) config.Config - // Client is for calling services + // Client is for sync calling services via RPC Client(...string) client.Client - // Broker is for sending and receiving events + // Broker is for sending and receiving async events Broker(...string) broker.Broker - // Server is for handling requests and events + // Server is for handling requests and broker unmarshaled events Server(...string) server.Server // Store is for key/val store Store(...string) store.Store - // Register + // Register used by client to lookup other services and server registers on it Register(...string) register.Register // Tracer Tracer(...string) tracer.Tracer // Router Router(...string) router.Router - // Meter + // Meter may be used internally by other component to export metrics Meter(...string) meter.Meter // Runtime // Runtime(string) (runtime.Runtime, bool) // Profile // Profile(string) (profile.Profile, bool) - // Run the service and wait + // Run the service and wait for stop Run() error - // Start the service + // Start the service and not wait Start() error // Stop the service Stop() error @@ -218,14 +215,6 @@ func (s *service) Logger(names ...string) logger.Logger { return s.opts.Loggers[idx] } -func (s *service) Auth(names ...string) auth.Auth { - idx := 0 - if len(names) == 1 { - idx = getNameIndex(names[0], s.opts.Auths) - } - return s.opts.Auths[idx] -} - func (s *service) Router(names ...string) router.Router { idx := 0 if len(names) == 1 { diff --git a/service_test.go b/service_test.go index 6aec3a84..1b96cb0d 100644 --- a/service_test.go +++ b/service_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "go.unistack.org/micro/v3/auth" "go.unistack.org/micro/v3/broker" "go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/config" @@ -537,43 +536,6 @@ func Test_service_Logger(t *testing.T) { } } -func Test_service_Auth(t *testing.T) { - a := auth.NewAuth() - type fields struct { - opts Options - } - type args struct { - names []string - } - tests := []struct { - name string - fields fields - args args - want auth.Auth - }{ - { - name: "service.Auth", - fields: fields{ - opts: Options{Auths: []auth.Auth{a}}, - }, - args: args{ - names: []string{"noop"}, - }, - want: a, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &service{ - opts: tt.fields.opts, - } - if got := s.Auth(tt.args.names...); !reflect.DeepEqual(got, tt.want) { - t.Errorf("service.Auth() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_service_Router(t *testing.T) { r := router.NewRouter() type fields struct { diff --git a/store/store.go b/store/store.go index 01f33f8f..0cc1b6b3 100644 --- a/store/store.go +++ b/store/store.go @@ -12,7 +12,7 @@ var ( // ErrInvalidKey is returned when a key has empty or have invalid format ErrInvalidKey = errors.New("invalid key") // DefaultStore is the global default store - DefaultStore Store = NewStore() + DefaultStore = NewStore() ) // Store is a data storage interface diff --git a/tracer/tracer.go b/tracer/tracer.go index 540b092f..becd6f09 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -6,7 +6,7 @@ import ( ) // DefaultTracer is the global default tracer -var DefaultTracer Tracer = NewTracer() +var DefaultTracer = NewTracer() // Tracer is an interface for distributed tracing type Tracer interface { diff --git a/util/auth/auth.go b/util/auth/auth.go deleted file mode 100644 index 3a6290e9..00000000 --- a/util/auth/auth.go +++ /dev/null @@ -1,87 +0,0 @@ -package auth // import "go.unistack.org/micro/v3/util/auth" - -import ( - "context" - "time" - - "go.unistack.org/micro/v3/auth" - "go.unistack.org/micro/v3/logger" - "go.unistack.org/micro/v3/util/id" -) - -// Verify the auth credentials and refresh the auth token periodically -func Verify(a auth.Auth) error { - // extract the account creds from options, these can be set by flags - accID := a.Options().ID - accSecret := a.Options().Secret - - // if no credentials were provided, self generate an account - if len(accID) == 0 && len(accSecret) == 0 { - opts := []auth.GenerateOption{ - auth.WithType("service"), - auth.WithScopes("service"), - } - - id, err := id.New() - if err != nil { - return err - } - acc, err := a.Generate(id, opts...) - if err != nil { - return err - } - if logger.V(logger.DebugLevel) { - logger.Debug(context.TODO(), "Auth [%v] Generated an auth account: %s", a.String()) - } - - accID = acc.ID - accSecret = acc.Secret - } - - // generate the first token - token, err := a.Token( - auth.WithCredentials(accID, accSecret), - auth.WithExpiry(time.Minute*10), - ) - if err != nil { - return err - } - - // set the credentials and token in auth options - _ = a.Init( - auth.ClientToken(token), - auth.Credentials(accID, accSecret), - ) - - // periodically check to see if the token needs refreshing - go func() { - timer := time.NewTicker(time.Second * 15) - - for { - <-timer.C - - // don't refresh the token if it's not close to expiring - tok := a.Options().Token - if tok.Expiry.Unix() > time.Now().Add(time.Minute).Unix() { - continue - } - - // generate the first token - tok, err := a.Token( - auth.WithToken(tok.RefreshToken), - auth.WithExpiry(time.Minute*10), - ) - if err != nil { - if logger.V(logger.WarnLevel) { - logger.Warn(context.TODO(), "[Auth] Error refreshing token: %v", err) - } - continue - } - - // set the token - _ = a.Init(auth.ClientToken(tok)) - } - }() - - return nil -} diff --git a/util/ctx/ctx.go b/util/ctx/ctx.go deleted file mode 100644 index bf008ce6..00000000 --- a/util/ctx/ctx.go +++ /dev/null @@ -1,26 +0,0 @@ -package ctx // import "go.unistack.org/micro/v3/util/ctx" - -import ( - "context" - "net/http" - "strings" - - "go.unistack.org/micro/v3/metadata" -) - -// FromRequest creates context with metadata from http.Request -func FromRequest(r *http.Request) context.Context { - ctx := r.Context() - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - md = metadata.New(len(r.Header) + 2) - } - for key, val := range r.Header { - md.Set(key, strings.Join(val, ",")) - } - // pass http host - md["Host"] = r.Host - // pass http method - md["Method"] = r.Method - return metadata.NewIncomingContext(ctx, md) -} diff --git a/util/ctx/ctx_test.go b/util/ctx/ctx_test.go deleted file mode 100644 index 22b61bca..00000000 --- a/util/ctx/ctx_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package ctx - -import ( - "net/http" - "testing" - - "go.unistack.org/micro/v3/metadata" -) - -func TestRequestToContext(t *testing.T) { - testData := []struct { - request *http.Request - expect metadata.Metadata - }{ - { - &http.Request{ - Header: http.Header{ - "Foo1": []string{"bar"}, - "Foo2": []string{"bar", "baz"}, - }, - }, - metadata.Metadata{ - "Foo1": "bar", - "Foo2": "bar,baz", - }, - }, - } - - for _, d := range testData { - ctx := FromRequest(d.request) - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - t.Fatalf("Expected metadata for request %+v", d.request) - } - for k, v := range d.expect { - if val := md[k]; val != v { - t.Fatalf("Expected %s for key %s for expected md %+v, got md %+v", v, k, d.expect, md) - } - } - } -} diff --git a/util/qson/LICENSE b/util/qson/LICENSE deleted file mode 100644 index 3e4ba4f7..00000000 --- a/util/qson/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Jon Calhoun - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/util/qson/README.md b/util/qson/README.md deleted file mode 100644 index ad76ced5..00000000 --- a/util/qson/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# qson - -This is copy from https://github.com/joncalhoun/qson -As author says he is not acrivelly maintains the repo and not plan to do that. - -## Usage - -You can either turn a URL query param into a JSON byte array, or unmarshal that directly into a Go object. - -Transforming the URL query param into a JSON byte array: - -```go -import "github.com/joncalhoun/qson" - -func main() { - b, err := qson.ToJSON("bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112") - if err != nil { - panic(err) - } - fmt.Println(string(b)) - // Should output: {"bar":{"one":{"red":112,"two":2}}} -} -``` - -Or unmarshalling directly into a Go object using JSON struct tags: - -```go -import "github.com/joncalhoun/qson" - -type unmarshalT struct { - A string `json:"a"` - B unmarshalB `json:"b"` -} -type unmarshalB struct { - C int `json:"c"` -} - -func main() { - var out unmarshalT - query := "a=xyz&b[c]=456" - err := Unmarshal(&out, query) - if err != nil { - t.Error(err) - } - // out should equal - // unmarshalT{ - // A: "xyz", - // B: unmarshalB{ - // C: 456, - // }, - // } -} -``` - -To get a query string like in the two previous examples you can use the `RawQuery` field on the [net/url.URL](https://golang.org/pkg/net/url/#URL) type. diff --git a/util/qson/merge.go b/util/qson/merge.go deleted file mode 100644 index 64078078..00000000 --- a/util/qson/merge.go +++ /dev/null @@ -1,34 +0,0 @@ -package qson - -// merge merges a with b if they are either both slices -// or map[string]interface{} types. Otherwise it returns b. -func merge(a interface{}, b interface{}) interface{} { - switch aT := a.(type) { - case map[string]interface{}: - return mergeMap(aT, b.(map[string]interface{})) - case []interface{}: - return mergeSlice(aT, b.([]interface{})) - default: - return b - } -} - -// mergeMap merges a with b, attempting to merge any nested -// values in nested maps but eventually overwriting anything -// in a that can't be merged with whatever is in b. -func mergeMap(a map[string]interface{}, b map[string]interface{}) map[string]interface{} { - for bK, bV := range b { - if _, ok := a[bK]; ok { - a[bK] = merge(a[bK], bV) - } else { - a[bK] = bV - } - } - return a -} - -// mergeSlice merges a with b and returns the result. -func mergeSlice(a []interface{}, b []interface{}) []interface{} { - a = append(a, b...) - return a -} diff --git a/util/qson/merge_test.go b/util/qson/merge_test.go deleted file mode 100644 index 9a144db8..00000000 --- a/util/qson/merge_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package qson - -import "testing" - -func TestMergeSlice(t *testing.T) { - a := []interface{}{"a"} - b := []interface{}{"b"} - actual := mergeSlice(a, b) - if len(actual) != 2 { - t.Errorf("Expected size to be 2.") - } - if actual[0] != "a" { - t.Errorf("Expected index 0 to have value a. Actual: %s", actual[0]) - } - if actual[1] != "b" { - t.Errorf("Expected index 1 to have value b. Actual: %s", actual[1]) - } -} - -func TestMergeMap(t *testing.T) { - a := map[string]interface{}{ - "a": "b", - } - b := map[string]interface{}{ - "b": "c", - } - actual := mergeMap(a, b) - if len(actual) != 2 { - t.Errorf("Expected size to be 2.") - } - if actual["a"] != "b" { - t.Errorf("Expected key \"a\" to have value b. Actual: %s", actual["a"]) - } - if actual["b"] != "c" { - t.Errorf("Expected key \"b\" to have value c. Actual: %s", actual["b"]) - } -} diff --git a/util/qson/qson.go b/util/qson/qson.go deleted file mode 100644 index e36c0743..00000000 --- a/util/qson/qson.go +++ /dev/null @@ -1,166 +0,0 @@ -// Package qson implmenets decoding of URL query params -// into JSON and Go values (using JSON struct tags). -// -// See https://golang.org/pkg/encoding/json/ for more -// details on JSON struct tags. -package qson // import "go.unistack.org/micro/v3/util/qson" - -import ( - "encoding/json" - "errors" - "net/url" - "regexp" - "strings" -) - -var ( - // ErrInvalidParam is returned when invalid data is provided to the ToJSON or Unmarshal function. - // Specifically, this will be returned when there is no equals sign present in the URL query parameter. - ErrInvalidParam = errors.New("qson: invalid url query param provided") - - bracketSplitter *regexp.Regexp -) - -func init() { - bracketSplitter = regexp.MustCompile(`\[|\]`) -} - -func btSplitter(str string) []string { - r := bracketSplitter.Split(str, -1) - for idx, s := range r { - if len(s) == 0 { - if len(r) > idx+1 { - copy(r[idx:], r[idx+1:]) - r = r[:len(r)-1] - } - } - } - return r -} - -// Unmarshal will take a dest along with URL -// query params and attempt to first turn the query params -// into JSON and then unmarshal those into the dest variable -// -// BUG(joncalhoun): If a URL query param value is something -// like 123 but is expected to be parsed into a string this -// will currently result in an error because the JSON -// transformation will assume this is intended to be an int. -// This should only affect the Unmarshal function and -// could likely be fixed, but someone will need to submit a -// PR if they want that fixed. -func Unmarshal(dst interface{}, query string) error { - b, err := ToJSON(query) - if err != nil { - return err - } - return json.Unmarshal(b, dst) -} - -// ToJSON will turn a query string like: -// cat=1&bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112 -// Into a JSON object with all the data merged as nicely as -// possible. Eg the example above would output: -// {"bar":{"one":{"two":2,"red":112}}} -func ToJSON(query string) ([]byte, error) { - var builder interface{} = make(map[string]interface{}) - params := strings.Split(query, "&") - for _, part := range params { - tempMap, err := queryToMap(part) - if err != nil { - return nil, err - } - builder = merge(builder, tempMap) - } - return json.Marshal(builder) -} - -// queryToMap turns something like a[b][c]=4 into -// map[string]interface{}{ -// "a": map[string]interface{}{ -// "b": map[string]interface{}{ -// "c": 4, -// }, -// }, -// } -func queryToMap(param string) (map[string]interface{}, error) { - rawKey, rawValue, err := splitKeyAndValue(param) - if err != nil { - return nil, err - } - rawValue, err = url.QueryUnescape(rawValue) - if err != nil { - return nil, err - } - rawKey, err = url.QueryUnescape(rawKey) - if err != nil { - return nil, err - } - - pieces := btSplitter(rawKey) - key := pieces[0] - - // If len==1 then rawKey has no [] chars and we can just - // decode this as key=value into {key: value} - if len(pieces) == 1 { - var value interface{} - // First we try parsing it as an int, bool, null, etc - err = json.Unmarshal([]byte(rawValue), &value) - if err != nil { - // If we got an error we try wrapping the value in - // quotes and processing it as a string - err = json.Unmarshal([]byte("\""+rawValue+"\""), &value) - if err != nil { - // If we can't decode as a string we return the err - return nil, err - } - } - return map[string]interface{}{ - key: value, - }, nil - } - - // If len > 1 then we have something like a[b][c]=2 - // so we need to turn this into {"a": {"b": {"c": 2}}} - // To do this we break our key into two pieces: - // a and b[c] - // and then we set {"a": queryToMap("b[c]", value)} - ret := make(map[string]interface{}) - ret[key], err = queryToMap(buildNewKey(rawKey) + "=" + rawValue) - if err != nil { - return nil, err - } - - // When URL params have a set of empty brackets (eg a[]=1) - // it is assumed to be an array. This will get us the - // correct value for the array item and return it as an - // []interface{} so that it can be merged properly. - if pieces[1] == "" { - temp := ret[key].(map[string]interface{}) - ret[key] = []interface{}{temp[""]} - } - return ret, nil -} - -// buildNewKey will take something like: -// origKey = "bar[one][two]" -// pieces = [bar one two ] -// and return "one[two]" -func buildNewKey(origKey string) string { - pieces := btSplitter(origKey) - - ret := origKey[len(pieces[0])+1:] - ret = ret[:len(pieces[1])] + ret[len(pieces[1])+1:] - return ret -} - -// splitKeyAndValue splits a URL param at the last equal -// sign and returns the two strings. If no equal sign is -// found, the ErrInvalidParam error is returned. -func splitKeyAndValue(param string) (string, string, error) { - li := strings.LastIndex(param, "=") - if li == -1 { - return "", "", ErrInvalidParam - } - return param[:li], param[li+1:], nil -} diff --git a/util/qson/qson_test.go b/util/qson/qson_test.go deleted file mode 100644 index 6bef7b20..00000000 --- a/util/qson/qson_test.go +++ /dev/null @@ -1,179 +0,0 @@ -package qson - -import ( - "testing" -) - -func TestFuzz1(t *testing.T) { - b, err := ToJSON(`[^@hGl5=`) - if err != nil { - t.Fatal(err) - } - _ = b -} - -func ExampleUnmarshal() { - type Ex struct { - A string `json:"a"` - B struct { - C int `json:"c"` - } `json:"b"` - } - var ex Ex - if err := Unmarshal(&ex, "a=xyz&b[c]=456"); err != nil { - panic(err) - } - _ = ex - // fmt.Printf("%+v\n", ex) - // Output: {A:xyz B:{C:456}} -} - -type unmarshalT struct { - A string `json:"a"` - B unmarshalB `json:"b"` -} - -type unmarshalB struct { - D string `json:"D"` - C int `json:"c"` -} - -func TestUnmarshal(t *testing.T) { - query := "a=xyz&b[c]=456" - expected := unmarshalT{ - A: "xyz", - B: unmarshalB{ - C: 456, - }, - } - var actual unmarshalT - err := Unmarshal(&actual, query) - if err != nil { - t.Error(err) - } - if expected != actual { - t.Errorf("Expected: %+v Actual: %+v", expected, actual) - } -} - -func ExampleToJSON() { - _, err := ToJSON("a=xyz&b[c]=456") - if err != nil { - panic(err) - } - // fmt.Printf(string(b)) - // Output: {"a":"xyz","b":{"c":456}} -} - -func TestToJSONNested(t *testing.T) { - query := "bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112" - expected := `{"bar":{"one":{"red":112,"two":2}}}` - actual, err := ToJSON(query) - if err != nil { - t.Error(err) - } - actualStr := string(actual) - if actualStr != expected { - t.Errorf("Expected: %s Actual: %s", expected, actualStr) - } -} - -func TestToJSONPlain(t *testing.T) { - query := "cat=1&dog=2" - expected := `{"cat":1,"dog":2}` - actual, err := ToJSON(query) - if err != nil { - t.Error(err) - } - actualStr := string(actual) - if actualStr != expected { - t.Errorf("Expected: %s Actual: %s", expected, actualStr) - } -} - -func TestToJSONSlice(t *testing.T) { - query := "cat[]=1&cat[]=34" - expected := `{"cat":[1,34]}` - actual, err := ToJSON(query) - if err != nil { - t.Error(err) - } - actualStr := string(actual) - if actualStr != expected { - t.Errorf("Expected: %s Actual: %s", expected, actualStr) - } -} - -func TestToJSONBig(t *testing.T) { - query := "distinct_id=763_1495187301909_3495×tamp=1495187523&event=product_add_cart¶ms%5BproductRefId%5D=8284563078¶ms%5Bapps%5D%5B%5D=precommend¶ms%5Bapps%5D%5B%5D=bsales¶ms%5Bsource%5D=item¶ms%5Boptions%5D%5Bsegment%5D=cart_recommendation¶ms%5Boptions%5D%5Btype%5D=up_sell¶ms%5BtimeExpire%5D=1495187599642¶ms%5Brecommend_system_product_source%5D=item¶ms%5Bproduct_id%5D=8284563078¶ms%5Bvariant_id%5D=27661944134¶ms%5Bsku%5D=00483332%20(black)¶ms%5Bsources%5D%5B%5D=product_recommendation¶ms%5Bcart_token%5D=dc2c336a009edf2762128e65806dfb1d¶ms%5Bquantity%5D=1¶ms%5Bnew_popup_upsell_mobile%5D=false¶ms%5BclientDevice%5D=desktop¶ms%5BclientIsMobile%5D=false¶ms%5BclientIsSmallScreen%5D=false¶ms%5Bnew_popup_crossell_mobile%5D=false&api_key=14c5b7dacea9157029265b174491d340" - expected := `{"api_key":"14c5b7dacea9157029265b174491d340","distinct_id":"763_1495187301909_3495","event":"product_add_cart","params":{"apps":["precommend","bsales"],"cart_token":"dc2c336a009edf2762128e65806dfb1d","clientDevice":"desktop","clientIsMobile":false,"clientIsSmallScreen":false,"new_popup_crossell_mobile":false,"new_popup_upsell_mobile":false,"options":{"segment":"cart_recommendation","type":"up_sell"},"productRefId":8284563078,"product_id":8284563078,"quantity":1,"recommend_system_product_source":"item","sku":"00483332 (black)","source":"item","sources":["product_recommendation"],"timeExpire":1495187599642,"variant_id":27661944134},"timestamp":1495187523}` - actual, err := ToJSON(query) - if err != nil { - t.Error(err) - } - actualStr := string(actual) - if actualStr != expected { - t.Errorf("Expected: %s Actual: %s", expected, actualStr) - } -} - -func TestToJSONDuplicateKey(t *testing.T) { - query := "cat=1&cat=2" - expected := `{"cat":2}` - actual, err := ToJSON(query) - if err != nil { - t.Error(err) - } - actualStr := string(actual) - if actualStr != expected { - t.Errorf("Expected: %s Actual: %s", expected, actualStr) - } -} - -func TestSplitKeyAndValue(t *testing.T) { - param := "a[dog][=cat]=123" - eKey, eValue := "a[dog][=cat]", "123" - aKey, aValue, err := splitKeyAndValue(param) - if err != nil { - t.Error(err) - } - if eKey != aKey { - t.Errorf("Keys do not match. Expected: %s Actual: %s", eKey, aKey) - } - if eValue != aValue { - t.Errorf("Values do not match. Expected: %s Actual: %s", eValue, aValue) - } -} - -func TestEncodedAmpersand(t *testing.T) { - query := "a=xyz&b[d]=ben%26jerry" - expected := unmarshalT{ - A: "xyz", - B: unmarshalB{ - D: "ben&jerry", - }, - } - var actual unmarshalT - err := Unmarshal(&actual, query) - if err != nil { - t.Error(err) - } - if expected != actual { - t.Errorf("Expected: %+v Actual: %+v", expected, actual) - } -} - -func TestEncodedAmpersand2(t *testing.T) { - query := "filter=parent%3Dflow12345%26request%3Dreq12345&meta.limit=20&meta.offset=0" - expected := map[string]interface{}{"filter": "parent=flow12345&request=req12345", "meta.limit": float64(20), "meta.offset": float64(0)} - actual := make(map[string]interface{}) - err := Unmarshal(&actual, query) - if err != nil { - t.Error(err) - } - for k, v := range actual { - if nv, ok := expected[k]; !ok || nv != v { - t.Errorf("Expected: %+v Actual: %+v", expected, actual) - } - } -} diff --git a/util/sync/manager.go b/util/sync/manager.go deleted file mode 100644 index 609fb379..00000000 --- a/util/sync/manager.go +++ /dev/null @@ -1,64 +0,0 @@ -package sync - -import ( - "fmt" - "time" - - "go.unistack.org/micro/v3/store" -) - -func (c *syncStore) syncManager() { - tickerAggregator := make(chan struct{ index int }) - for i, ticker := range c.pendingWriteTickers { - go func(index int, c chan struct{ index int }, t *time.Ticker) { - for range t.C { - c <- struct{ index int }{index: index} - } - }(i, tickerAggregator, ticker) - } - for i := range tickerAggregator { - println(i.index, "ticked") - c.processQueue(i.index) - } -} - -func (c *syncStore) processQueue(index int) { - c.Lock() - defer c.Unlock() - q := c.pendingWrites[index] - for i := 0; i < q.Len(); i++ { - r, ok := q.PopFront() - if !ok { - panic(fmt.Errorf("retrieved an invalid value from the L%d sync queue", index+1)) - } - ir, ok := r.(*internalRecord) - if !ok { - panic(fmt.Errorf("retrieved a non-internal record from the L%d sync queue", index+1)) - } - if !ir.expiresAt.IsZero() && time.Now().After(ir.expiresAt) { - continue - } - var opts []store.WriteOption - if !ir.expiresAt.IsZero() { - opts = append(opts, store.WriteTTL(time.Until(ir.expiresAt))) - } - // Todo = internal queue also has to hold the corresponding store.WriteOptions - if err := c.syncOpts.Stores[index+1].Write(c.storeOpts.Context, ir.key, ir.value, opts...); err != nil { - // some error, so queue for retry and bail - q.PushBack(ir) - return - } - } -} - -func intpow(x, y int64) int64 { - result := int64(1) - for 0 != y { - if 0 != (y & 1) { - result *= x - } - y >>= 1 - x *= x - } - return result -} diff --git a/util/sync/options.go b/util/sync/options.go deleted file mode 100644 index 3e6725b7..00000000 --- a/util/sync/options.go +++ /dev/null @@ -1,46 +0,0 @@ -package sync - -import ( - "time" - - "go.unistack.org/micro/v3/store" -) - -// Options represents Sync options -type Options struct { - // Stores represents layers in the sync in ascending order. L0, L1, L2, etc - Stores []store.Store - // SyncInterval is the duration between syncs from L0 to L1 - SyncInterval time.Duration - // SyncMultiplier is the multiplication factor between each store. - SyncMultiplier int64 -} - -// Option sets Sync Options -type Option func(o *Options) - -// Stores sets the layers that make up the sync -func Stores(stores ...store.Store) Option { - return func(o *Options) { - o.Stores = make([]store.Store, len(stores)) - for i, s := range stores { - o.Stores[i] = s - } - } -} - -// nolint: golint,revive -// SyncInterval sets the duration between syncs from L0 to L1 -func SyncInterval(d time.Duration) Option { - return func(o *Options) { - o.SyncInterval = d - } -} - -// nolint: golint,revive -// SyncMultiplier sets the multiplication factor for time to wait each sync layer -func SyncMultiplier(i int64) Option { - return func(o *Options) { - o.SyncMultiplier = i - } -} diff --git a/util/sync/sync.go b/util/sync/sync.go deleted file mode 100644 index 0102bf7d..00000000 --- a/util/sync/sync.go +++ /dev/null @@ -1,129 +0,0 @@ -// Package sync will sync multiple stores -package sync // import "go.unistack.org/micro/v3/util/sync" - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/ef-ds/deque" - "go.unistack.org/micro/v3/store" -) - -// Sync implements a sync in for stores -type Sync interface { - // Implements the store interface - store.Store - // Force a full sync - Sync() error -} - -type syncStore struct { - storeOpts store.Options - pendingWrites []*deque.Deque - pendingWriteTickers []*time.Ticker - syncOpts Options - sync.RWMutex -} - -// NewSync returns a new Sync -func NewSync(opts ...Option) Sync { - c := &syncStore{} - for _, o := range opts { - o(&c.syncOpts) - } - if c.syncOpts.SyncInterval == 0 { - c.syncOpts.SyncInterval = 1 * time.Minute - } - if c.syncOpts.SyncMultiplier == 0 { - c.syncOpts.SyncMultiplier = 5 - } - return c -} - -func (c *syncStore) Connect(ctx context.Context) error { - return nil -} - -func (c *syncStore) Disconnect(ctx context.Context) error { - return nil -} - -func (c *syncStore) Close(ctx context.Context) error { - return nil -} - -// Init initialises the storeOptions -func (c *syncStore) Init(opts ...store.Option) error { - for _, o := range opts { - o(&c.storeOpts) - } - if len(c.syncOpts.Stores) == 0 { - return fmt.Errorf("the sync has no stores") - } - for _, s := range c.syncOpts.Stores { - if err := s.Init(); err != nil { - return fmt.Errorf("Store %s failed to Init(): %w", s.String(), err) - } - } - c.pendingWrites = make([]*deque.Deque, len(c.syncOpts.Stores)-1) - c.pendingWriteTickers = make([]*time.Ticker, len(c.syncOpts.Stores)-1) - for i := 0; i < len(c.pendingWrites); i++ { - c.pendingWrites[i] = deque.New() - c.pendingWrites[i].Init() - c.pendingWriteTickers[i] = time.NewTicker(c.syncOpts.SyncInterval * time.Duration(intpow(c.syncOpts.SyncMultiplier, int64(i)))) - } - go c.syncManager() - return nil -} - -// Name returns the store name -func (c *syncStore) Name() string { - return c.storeOpts.Name -} - -// Options returns the sync's store options -func (c *syncStore) Options() store.Options { - return c.storeOpts -} - -// String returns a printable string describing the sync -func (c *syncStore) String() string { - backends := make([]string, len(c.syncOpts.Stores)) - for i, s := range c.syncOpts.Stores { - backends[i] = s.String() - } - return fmt.Sprintf("sync %v", backends) -} - -func (c *syncStore) List(ctx context.Context, opts ...store.ListOption) ([]string, error) { - return c.syncOpts.Stores[0].List(ctx, opts...) -} - -func (c *syncStore) Exists(ctx context.Context, key string, opts ...store.ExistsOption) error { - return c.syncOpts.Stores[0].Exists(ctx, key) -} - -func (c *syncStore) Read(ctx context.Context, key string, val interface{}, opts ...store.ReadOption) error { - return c.syncOpts.Stores[0].Read(ctx, key, val, opts...) -} - -func (c *syncStore) Write(ctx context.Context, key string, val interface{}, opts ...store.WriteOption) error { - return c.syncOpts.Stores[0].Write(ctx, key, val, opts...) -} - -// Delete removes a key from the sync -func (c *syncStore) Delete(ctx context.Context, key string, opts ...store.DeleteOption) error { - return c.syncOpts.Stores[0].Delete(ctx, key, opts...) -} - -func (c *syncStore) Sync() error { - return nil -} - -type internalRecord struct { - expiresAt time.Time - key string - value []byte -} diff --git a/util/token/basic/basic.go b/util/token/basic/basic.go deleted file mode 100644 index f9685759..00000000 --- a/util/token/basic/basic.go +++ /dev/null @@ -1,87 +0,0 @@ -package basic // import "go.unistack.org/micro/v3/util/token/basic" - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "go.unistack.org/micro/v3/auth" - "go.unistack.org/micro/v3/store" - "go.unistack.org/micro/v3/util/id" - "go.unistack.org/micro/v3/util/token" -) - -// Basic implementation of token provider, backed by the store -type Basic struct { - store store.Store -} - -// StorePrefix to isolate tokens -var StorePrefix = "tokens/" - -// NewTokenProvider returns an initialized basic provider -func NewTokenProvider(opts ...token.Option) token.Provider { - options := token.NewOptions(opts...) - - if options.Store == nil { - options.Store = store.DefaultStore - } - - return &Basic{ - store: options.Store, - } -} - -// Generate a token for an account -func (b *Basic) Generate(acc *auth.Account, opts ...token.GenerateOption) (*token.Token, error) { - options := token.NewGenerateOptions(opts...) - - // marshal the account to bytes - bytes, err := json.Marshal(acc) - if err != nil { - return nil, err - } - - // write to the store - key, err := id.New() - if err != nil { - return nil, err - } - - err = b.store.Write(context.Background(), fmt.Sprintf("%v%v", StorePrefix, key), bytes, store.WriteTTL(options.Expiry)) - if err != nil { - return nil, err - } - - // return the token - return &token.Token{ - Token: key, - Created: time.Now(), - Expiry: time.Now().Add(options.Expiry), - }, nil -} - -// Inspect a token -func (b *Basic) Inspect(t string) (*auth.Account, error) { - // lookup the token in the store - var val []byte - err := b.store.Read(context.Background(), StorePrefix+t, val) - if err == store.ErrNotFound { - return nil, token.ErrInvalidToken - } else if err != nil { - return nil, err - } - // unmarshal the bytes - var acc *auth.Account - if err := json.Unmarshal(val, &acc); err != nil { - return nil, err - } - - return acc, nil -} - -// String returns basic -func (b *Basic) String() string { - return "basic" -} diff --git a/util/token/basic/basic_test.go b/util/token/basic/basic_test.go deleted file mode 100644 index 2371ebab..00000000 --- a/util/token/basic/basic_test.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build ignore -// +build ignore - -package basic - -import ( - "testing" - - "go.unistack.org/micro/v3/auth" - "go.unistack.org/micro/v3/store/memory" - "go.unistack.org/micro/v3/util/token" -) - -func TestGenerate(t *testing.T) { - store := memory.NewStore() - b := NewTokenProvider(token.WithStore(store)) - - _, err := b.Generate(&auth.Account{ID: "test"}) - if err != nil { - t.Fatalf("Generate returned %v error, expected nil", err) - } - - recs, err := store.List() - if err != nil { - t.Fatalf("Unable to read from store: %v", err) - } - if len(recs) != 1 { - t.Errorf("Generate didn't write to the store, expected 1 record, got %v", len(recs)) - } -} - -func TestInspect(t *testing.T) { - store := memory.NewStore() - b := NewTokenProvider(token.WithStore(store)) - - t.Run("Valid token", func(t *testing.T) { - md := map[string]string{"foo": "bar"} - scopes := []string{"admin"} - subject := "test" - - tok, err := b.Generate(&auth.Account{ID: subject, Scopes: scopes, Metadata: md}) - if err != nil { - t.Fatalf("Generate returned %v error, expected nil", err) - } - - tok2, err := b.Inspect(tok.Token) - if err != nil { - t.Fatalf("Inspect returned %v error, expected nil", err) - } - if tok2.ID != subject { - t.Errorf("Inspect returned %v as the token subject, expected %v", tok2.ID, subject) - } - if len(tok2.Scopes) != len(scopes) { - t.Errorf("Inspect returned %v scopes, expected %v", len(tok2.Scopes), len(scopes)) - } - if len(tok2.Metadata) != len(md) { - t.Errorf("Inspect returned %v as the token metadata, expected %v", tok2.Metadata, md) - } - }) - - t.Run("Invalid token", func(t *testing.T) { - _, err := b.Inspect("Invalid token") - if err != token.ErrInvalidToken { - t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) - } - }) -} diff --git a/util/token/jwt/jwt.go b/util/token/jwt/jwt.go deleted file mode 100644 index dc3c644c..00000000 --- a/util/token/jwt/jwt.go +++ /dev/null @@ -1,110 +0,0 @@ -package jwt // import "go.unistack.org/micro/v3/util/token/jwt" - -import ( - "encoding/base64" - "time" - - "github.com/golang-jwt/jwt/v4" - "go.unistack.org/micro/v3/auth" - "go.unistack.org/micro/v3/metadata" - "go.unistack.org/micro/v3/util/token" -) - -// authClaims to be encoded in the JWT -type authClaims struct { - Metadata metadata.Metadata `json:"metadata"` - jwt.RegisteredClaims - Type string `json:"type"` - Scopes []string `json:"scopes"` -} - -// JWT implementation of token provider -type JWT struct { - opts token.Options -} - -// NewTokenProvider returns an initialized basic provider -func NewTokenProvider(opts ...token.Option) token.Provider { - return &JWT{ - opts: token.NewOptions(opts...), - } -} - -// Generate a new JWT -func (j *JWT) Generate(acc *auth.Account, opts ...token.GenerateOption) (*token.Token, error) { - // decode the private key - priv, err := base64.StdEncoding.DecodeString(j.opts.PrivateKey) - if err != nil { - return nil, err - } - - // parse the private key - key, err := jwt.ParseRSAPrivateKeyFromPEM(priv) - if err != nil { - return nil, token.ErrEncodingToken - } - - // parse the options - options := token.NewGenerateOptions(opts...) - - // generate the JWT - expiry := time.Now().Add(options.Expiry) - t := jwt.NewWithClaims(jwt.SigningMethodRS256, authClaims{ - Type: acc.Type, Scopes: acc.Scopes, Metadata: acc.Metadata, RegisteredClaims: jwt.RegisteredClaims{ - Subject: acc.ID, - Issuer: acc.Issuer, - ExpiresAt: jwt.NewNumericDate(expiry), - }, - }) - tok, err := t.SignedString(key) - if err != nil { - return nil, err - } - - // return the token - return &token.Token{ - Token: tok, - Expiry: expiry, - Created: time.Now(), - }, nil -} - -// Inspect a JWT -func (j *JWT) Inspect(t string) (*auth.Account, error) { - // decode the public key - pub, err := base64.StdEncoding.DecodeString(j.opts.PublicKey) - if err != nil { - return nil, err - } - - // parse the public key - res, err := jwt.ParseWithClaims(t, &authClaims{}, func(token *jwt.Token) (interface{}, error) { - return jwt.ParseRSAPublicKeyFromPEM(pub) - }) - if err != nil { - return nil, token.ErrInvalidToken - } - - // validate the token - if !res.Valid { - return nil, token.ErrInvalidToken - } - claims, ok := res.Claims.(*authClaims) - if !ok { - return nil, token.ErrInvalidToken - } - - // return the token - return &auth.Account{ - ID: claims.Subject, - Issuer: claims.Issuer, - Type: claims.Type, - Scopes: claims.Scopes, - Metadata: claims.Metadata, - }, nil -} - -// String returns JWT -func (j *JWT) String() string { - return "jwt" -} diff --git a/util/token/jwt/jwt_test.go b/util/token/jwt/jwt_test.go deleted file mode 100644 index 996b48b2..00000000 --- a/util/token/jwt/jwt_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package jwt - -import ( - "os" - "testing" - "time" - - "go.unistack.org/micro/v3/auth" - "go.unistack.org/micro/v3/util/token" -) - -func TestGenerate(t *testing.T) { - privKey, err := os.ReadFile("test/sample_key") - if err != nil { - t.Fatalf("Unable to read private key: %v", err) - } - - j := NewTokenProvider( - token.WithPrivateKey(string(privKey)), - ) - - _, err = j.Generate(&auth.Account{ID: "test"}) - if err != nil { - t.Fatalf("Generate returned %v error, expected nil", err) - } -} - -func TestInspect(t *testing.T) { - pubKey, err := os.ReadFile("test/sample_key.pub") - if err != nil { - t.Fatalf("Unable to read public key: %v", err) - } - privKey, err := os.ReadFile("test/sample_key") - if err != nil { - t.Fatalf("Unable to read private key: %v", err) - } - - j := NewTokenProvider( - token.WithPublicKey(string(pubKey)), - token.WithPrivateKey(string(privKey)), - ) - - t.Run("Valid token", func(t *testing.T) { - md := map[string]string{"foo": "bar"} - scopes := []string{"admin"} - subject := "test" - - acc := &auth.Account{ID: subject, Scopes: scopes, Metadata: md} - tok, err := j.Generate(acc) - if err != nil { - t.Fatalf("Generate returned %v error, expected nil", err) - } - - tok2, err := j.Inspect(tok.Token) - if err != nil { - t.Fatalf("Inspect returned %v error, expected nil", err) - } - if acc.ID != subject { - t.Errorf("Inspect returned %v as the token subject, expected %v", acc.ID, subject) - } - if len(tok2.Scopes) != len(scopes) { - t.Errorf("Inspect returned %v scopes, expected %v", len(tok2.Scopes), len(scopes)) - } - if len(tok2.Metadata) != len(md) { - t.Errorf("Inspect returned %v as the token metadata, expected %v", tok2.Metadata, md) - } - }) - - t.Run("Expired token", func(t *testing.T) { - tok, err := j.Generate(&auth.Account{}, token.WithExpiry(-10*time.Second)) - if err != nil { - t.Fatalf("Generate returned %v error, expected nil", err) - } - - if _, err = j.Inspect(tok.Token); err != token.ErrInvalidToken { - t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) - } - }) - - t.Run("Invalid token", func(t *testing.T) { - _, err := j.Inspect("Invalid token") - if err != token.ErrInvalidToken { - t.Fatalf("Inspect returned %v error, expected %v", err, token.ErrInvalidToken) - } - }) -} diff --git a/util/token/jwt/test/sample_key b/util/token/jwt/test/sample_key deleted file mode 100644 index 25488667..00000000 --- a/util/token/jwt/test/sample_key +++ /dev/null @@ -1 +0,0 @@ -LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS3dJQkFBS0NBZ0VBOFNiSlA1WGJFaWRSbTViMnNOcExHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkCi9SbDkvMXBNVjdNaU8zTEh3dGhIQzJCUllxcisxd0Zkb1pDR0JZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUKMEJIL2xYUU1xeUVxRjVNSTJ6ZWpDNHpNenIxNU9OK2dFNEpuaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtLwptVWRJVC9MYUY3a1F4eVlLNVZLbitOZ09Xek1sektBQXBDbjdUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsCm85akRqbFk1b0JPY3pmcWVOV0hLNUdYQjdRd3BMTmg5NDZQelpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDUKd2xFcThoTmhtaG01Tk5lL08rR2dqQkROU2ZVaDA2K3E0bmdtYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1bwpSdFFoZ2lZOTEwcFBmOWJhdVhXcXdVQ1VhNHFzSHpqS1IwTC9OMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVCnJnTHJQYkVCOWVnY0drMzgrYnBLczNaNlJyNSt0bkQxQklQSUZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVUKVEdEeFV4OG9qOFZJZVJuV0RxNk1jMWlKcDhVeWNpQklUUnR3NGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMApsYVF6QXVQM2FpV1hJTXAyc2M4U2MrQmwrTGpYbUJveEJyYUJIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YCmdGS1NzSW5IRHJIVk95V1BCZTNmYWRFYzc3YituYi9leE96cjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUEKQVFLQ0FnRUFqUzc1Q2VvUlRRcUtBNzZaaFNiNGEzNVlKRENtcEpSazFsRTNKYnFzNFYxRnhXaDBjZmJYeG9VMgpSdTRRYjUrZWhsdWJGSFQ2a1BxdG9uRWhRVExjMUNmVE9WbHJOb3hocDVZM2ZyUmlQcnNnNXcwK1R3RUtrcFJUCnltanJQTXdQbGxCM2U0NmVaYmVXWGc3R3FFVmptMGcxVFRRK0tocVM4R0w3VGJlTFhRN1ZTem9ydTNCNVRKMVEKeEN6TVB0dnQ2eDYrU3JrcmhvZG1iT3VNRkpDam1TbWxmck9pZzQ4Zkc3NUpERHRObXpLWHBEUVJpYUNodFJhVQpQRHpmUTlTamhYdFFqdkZvWFFFT3BqdkZVRjR2WldNUWNQNUw1VklDM3JRSWp4MFNzQTN6S0FwakVUbjJHNjN2CktZby8zVWttbzhkUCtGRHA3NCs5a3pLNHFFaFJycEl3bEtiN0VOZWtDUXZqUFl1K3pyKzMyUXdQNTJ2L2FveWQKdjJJaUY3M2laTU1vZDhhYjJuQStyVEI2T0cvOVlSYk5kV21tay9VTi9jUHYrN214TmZ6Y1d1ZU1XcThxMXh4eAptNTNpR0NSQ29PQ1lDQk4zcUFkb1JwYW5xd3lCOUxrLzFCQjBHUld3MjgxK3VhNXNYRnZBVDBKeTVURnduMncvClU1MlJKWFlNOXVhMFBvd214b0RDUWRuNFZYVkdNZGdXaHN4aXhHRlYwOUZObWJJQWJaN0xaWGtkS1gzc1ZVbTcKWU1WYWIzVVo2bEhtdXYzT1NzcHNVUlRqN1hiRzZpaVVlaDU1aW91OENWbnRndWtFcnEzQTQwT05FVzhjNDBzOQphVTBGaSs4eWZpQTViaVZHLzF0bWlucUVERkhuQStnWk1xNEhlSkZxcWZxaEZKa1JwRGtDZ2dFQkFQeGR1NGNKCm5Da1duZDdPWFlHMVM3UDdkVWhRUzgwSDlteW9uZFc5bGFCQm84RWRPeTVTZzNOUmsxQ2pNZFZ1a3FMcjhJSnkKeStLWk15SVpvSlJvbllaMEtIUUVMR3ZLbzFOS2NLQ1FJbnYvWHVCdFJpRzBVb1pQNVkwN0RpRFBRQWpYUjlXUwpBc0EzMmQ1eEtFOC91Y3h0MjVQVzJFakNBUmtVeHQ5d0tKazN3bC9JdXVYRlExTDdDWjJsOVlFUjlHeWxUbzhNCmxXUEY3YndtUFV4UVNKaTNVS0FjTzZweTVUU1lkdWQ2aGpQeXJwSXByNU42VGpmTlRFWkVBeU9LbXVpOHVkUkoKMUg3T3RQVEhGZElKQjNrNEJnRDZtRE1HbjB2SXBLaDhZN3NtRUZBbFkvaXlCZjMvOHk5VHVMb1BycEdqR3RHbgp4Y2RpMHFud2p0SGFNbFVDZ2dFQkFQU2Z0dVFCQ2dTU2JLUSswUEFSR2VVeEQyTmlvZk1teENNTmdHUzJ5Ull3CjRGaGV4ZWkwMVJoaFk1NjE3UjduR1dzb0czd1RQa3dvRTJtbE1aQkoxeWEvUU9RRnQ3WG02OVl0RGh0T2FWbDgKL0o4dlVuSTBtWmxtT2pjTlRoYnVPZDlNSDlRdGxIRUMxMlhYdHJNb3Fsb0U2a05TT0pJalNxYm9wcDRXc1BqcApvZTZ0Nkdyd1RhOHBHeUJWWS90Mi85Ym5ORHVPVlpjODBaODdtY2gzcDNQclBqU3h5di9saGxYMFMwYUdHTkhTCk1XVjdUa25OaGo1TWlIRXFnZ1pZemtBWTkyd1JoVENnU1A2M0VNcitUWXFudXVuMXJHbndPYm95TDR2aFRpV0UKcU42UDNCTFlCZ1FpMllDTDludEJrOEl6RHZyd096dW5GVnhhZ0g5SVVoY0NnZ0VCQUwzQXlLa1BlOENWUmR6cQpzL284VkJDZmFSOFhhUGRnSGxTek1BSXZpNXEwNENqckRyMlV3MHZwTVdnM1hOZ0xUT3g5bFJpd3NrYk9SRmxHCmhhd3hRUWlBdkk0SE9WTlBTU0R1WHVNTG5USTQ0S0RFNlMrY2cxU0VMS2pWbDVqcDNFOEpkL1RJMVpLc0xBQUsKZTNHakM5UC9ZbE8xL21ndW4xNjVkWk01cFAwWHBPb2FaeFV2RHFFTktyekR0V1g0RngyOTZlUzdaSFJodFpCNwovQ2t1VUhlcmxrN2RDNnZzdWhTaTh2eTM3c0tPbmQ0K3c4cVM4czhZYVZxSDl3ZzVScUxxakp0bmJBUnc3alVDCm9KQ053M1hNdnc3clhaYzRTbnhVQUNMRGJNV2lLQy9xL1ZGWW9oTEs2WkpUVkJscWd5cjBSYzBRWmpDMlNJb0kKMjRwRWt3VUNnZ0VCQUpqb0FJVVNsVFY0WlVwaExXN3g4WkxPa01UWjBVdFFyd2NPR0hSYndPUUxGeUNGMVFWNQppejNiR2s4SmZyZHpVdk1sTmREZm9uQXVHTHhQa3VTVEUxWlg4L0xVRkJveXhyV3dvZ0cxaUtwME11QTV6em90CjROai9DbUtCQVkvWnh2anA5M2RFS21aZGxWQkdmeUFMeWpmTW5MWUovZXh5L09YSnhPUktZTUttSHg4M08zRWsKMWhvb0FwbTZabTIzMjRGME1iVU1ham5Idld2ZjhHZGJTNk5zcHd4L0dkbk1tYVMrdUJMVUhVMkNLbmc1bEIwVAp4OWJITmY0dXlPbTR0dXRmNzhCd1R5V3UreEdrVW0zZ2VZMnkvR1hqdDZyY2l1ajFGNzFDenZzcXFmZThTcDdJCnd6SHdxcTNzVHR5S2lCYTZuYUdEYWpNR1pKYSt4MVZJV204Q2dnRUJBT001ajFZR25Ba0pxR0czQWJSVDIvNUMKaVVxN0loYkswOGZsSGs5a2YwUlVjZWc0ZVlKY3dIRXJVaE4rdWQyLzE3MC81dDYra0JUdTVZOUg3bkpLREtESQpoeEg5SStyamNlVkR0RVNTRkluSXdDQ1lrOHhOUzZ0cHZMV1U5b0pibGFKMlZsalV2NGRFWGVQb0hkREh1Zk9ZClVLa0lsV2E3Uit1QzNEOHF5U1JrQnFLa3ZXZ1RxcFNmTVNkc1ZTeFIzU2Q4SVhFSHFjTDNUNEtMWGtYNEdEamYKMmZOSTFpZkx6ekhJMTN3Tk5IUTVRNU9SUC9pell2QzVzZkx4U2ZIUXJiMXJZVkpKWkI5ZjVBUjRmWFpHSVFsbApjMG8xd0JmZFlqMnZxVDlpR09IQnNSSTlSL2M2RzJQcUt3aFRpSzJVR2lmVFNEUVFuUkF6b2tpQVkrbE8vUjQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== \ No newline at end of file diff --git a/util/token/jwt/test/sample_key.pub b/util/token/jwt/test/sample_key.pub deleted file mode 100644 index 77bd153d..00000000 --- a/util/token/jwt/test/sample_key.pub +++ /dev/null @@ -1 +0,0 @@ -LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE4U2JKUDVYYkVpZFJtNWIyc05wTApHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkL1JsOS8xcE1WN01pTzNMSHd0aEhDMkJSWXFyKzF3RmRvWkNHCkJZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUwQkgvbFhRTXF5RXFGNU1JMnplakM0ek16cjE1T04rZ0U0Sm4KaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtL21VZElUL0xhRjdrUXh5WUs1VktuK05nT1d6TWx6S0FBcENuNwpUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsbzlqRGpsWTVvQk9jemZxZU5XSEs1R1hCN1F3cExOaDk0NlB6ClpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDV3bEVxOGhOaG1obTVOTmUvTytHZ2pCRE5TZlVoMDYrcTRuZ20KYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1b1J0UWhnaVk5MTBwUGY5YmF1WFdxd1VDVWE0cXNIempLUjBMLwpOMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVcmdMclBiRUI5ZWdjR2szOCticEtzM1o2UnI1K3RuRDFCSVBJCkZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVVUR0R4VXg4b2o4VkllUm5XRHE2TWMxaUpwOFV5Y2lCSVRSdHcKNGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMGxhUXpBdVAzYWlXWElNcDJzYzhTYytCbCtMalhtQm94QnJhQgpIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YZ0ZLU3NJbkhEckhWT3lXUEJlM2ZhZEVjNzdiK25iL2V4T3pyCjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= \ No newline at end of file diff --git a/util/token/options.go b/util/token/options.go deleted file mode 100644 index b7603148..00000000 --- a/util/token/options.go +++ /dev/null @@ -1,83 +0,0 @@ -package token - -import ( - "time" - - "go.unistack.org/micro/v3/store" -) - -// Options holds the options for token -type Options struct { - // Store to persist the tokens - Store store.Store - // PublicKey base64 encoded, used by JWT - PublicKey string - // PrivateKey base64 encoded, used by JWT - PrivateKey string -} - -// Option func signature -type Option func(o *Options) - -// WithStore sets the token providers store -func WithStore(s store.Store) Option { - return func(o *Options) { - o.Store = s - } -} - -// WithPublicKey sets the JWT public key -func WithPublicKey(key string) Option { - return func(o *Options) { - o.PublicKey = key - } -} - -// WithPrivateKey sets the JWT private key -func WithPrivateKey(key string) Option { - return func(o *Options) { - o.PrivateKey = key - } -} - -// NewOptions returns options struct filled by opts -func NewOptions(opts ...Option) Options { - options := Options{} - for _, o := range opts { - o(&options) - } - // set default store - if options.Store == nil { - options.Store = store.DefaultStore - } - return options -} - -// GenerateOptions holds the generate options -type GenerateOptions struct { - // Expiry for the token - Expiry time.Duration -} - -// GenerateOption func signature -type GenerateOption func(o *GenerateOptions) - -// WithExpiry for the generated account's token expires -func WithExpiry(d time.Duration) GenerateOption { - return func(o *GenerateOptions) { - o.Expiry = d - } -} - -// NewGenerateOptions from a slice of options -func NewGenerateOptions(opts ...GenerateOption) GenerateOptions { - var options GenerateOptions - for _, o := range opts { - o(&options) - } - // set default Expiry of token - if options.Expiry == 0 { - options.Expiry = time.Minute * 15 - } - return options -} diff --git a/util/token/token.go b/util/token/token.go deleted file mode 100644 index af71828f..00000000 --- a/util/token/token.go +++ /dev/null @@ -1,34 +0,0 @@ -package token // import "go.unistack.org/micro/v3/util/token" - -import ( - "errors" - "time" - - "go.unistack.org/micro/v3/auth" -) - -var ( - // ErrNotFound is returned when a token cannot be found - ErrNotFound = errors.New("token not found") - // ErrEncodingToken is returned when the service encounters an error during encoding - ErrEncodingToken = errors.New("error encoding the token") - // ErrInvalidToken is returned when the token provided is not valid - ErrInvalidToken = errors.New("invalid token provided") -) - -// Provider generates and inspects tokens -type Provider interface { - Generate(account *auth.Account, opts ...GenerateOption) (*Token, error) - Inspect(token string) (*auth.Account, error) - String() string -} - -// Token holds the auth token -type Token struct { - // Created time of token created - Created time.Time `json:"created"` - // Expiry ime of the token - Expiry time.Time `json:"expiry"` - // Token holds the actual token - Token string `json:"token"` -}