Merge pull request #128 from unistack-org/big_remove
global cleanup
This commit was merged in pull request #128.
	This commit is contained in:
		
							
								
								
									
										141
									
								
								auth/auth.go
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								auth/auth.go
									
									
									
									
									
								
							| @@ -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) |  | ||||||
| } |  | ||||||
							
								
								
									
										79
									
								
								auth/noop.go
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								auth/noop.go
									
									
									
									
									
								
							| @@ -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...)} |  | ||||||
| } |  | ||||||
							
								
								
									
										311
									
								
								auth/options.go
									
									
									
									
									
								
							
							
						
						
									
										311
									
								
								auth/options.go
									
									
									
									
									
								
							| @@ -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 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -27,7 +27,7 @@ var ( | |||||||
| 	// DefaultMaxMsgSize specifies how much data codec can handle | 	// DefaultMaxMsgSize specifies how much data codec can handle | ||||||
| 	DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb | 	DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb | ||||||
| 	// DefaultCodec is the global default codec | 	// DefaultCodec is the global default codec | ||||||
| 	DefaultCodec Codec = NewCodec() | 	DefaultCodec = NewCodec() | ||||||
| 	// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal | 	// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal | ||||||
| 	DefaultTagName = "codec" | 	DefaultTagName = "codec" | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								function.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								function.go
									
									
									
									
									
								
							| @@ -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), |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| */ |  | ||||||
| @@ -8,9 +8,9 @@ import ( | |||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// DefaultLogger variable | 	// 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 used by logger | ||||||
| 	DefaultLevel Level = InfoLevel | 	DefaultLevel = InfoLevel | ||||||
| 	// DefaultCallerSkipCount used by logger | 	// DefaultCallerSkipCount used by logger | ||||||
| 	DefaultCallerSkipCount = 2 | 	DefaultCallerSkipCount = 2 | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import ( | |||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// DefaultMeter is the default meter | 	// DefaultMeter is the default meter | ||||||
| 	DefaultMeter Meter = NewMeter() | 	DefaultMeter = NewMeter() | ||||||
| 	// DefaultAddress data will be made available on this host:port | 	// DefaultAddress data will be made available on this host:port | ||||||
| 	DefaultAddress = ":9090" | 	DefaultAddress = ":9090" | ||||||
| 	// DefaultPath the meter endpoint where the Meter data will be made available | 	// DefaultPath the meter endpoint where the Meter data will be made available | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								options.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								options.go
									
									
									
									
									
								
							| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"go.unistack.org/micro/v3/auth" |  | ||||||
| 	"go.unistack.org/micro/v3/broker" | 	"go.unistack.org/micro/v3/broker" | ||||||
| 	"go.unistack.org/micro/v3/client" | 	"go.unistack.org/micro/v3/client" | ||||||
| 	"go.unistack.org/micro/v3/config" | 	"go.unistack.org/micro/v3/config" | ||||||
| @@ -39,8 +38,6 @@ type Options struct { | |||||||
| 	Configs []config.Config | 	Configs []config.Config | ||||||
| 	// Clients holds clients | 	// Clients holds clients | ||||||
| 	Clients []client.Client | 	Clients []client.Client | ||||||
| 	// Auths holds auths |  | ||||||
| 	Auths []auth.Auth |  | ||||||
| 	// Stores holds stores | 	// Stores holds stores | ||||||
| 	Stores []store.Store | 	Stores []store.Store | ||||||
| 	// Registers holds registers | 	// Registers holds registers | ||||||
| @@ -70,7 +67,6 @@ func NewOptions(opts ...Option) Options { | |||||||
| 		Brokers:   []broker.Broker{broker.DefaultBroker}, | 		Brokers:   []broker.Broker{broker.DefaultBroker}, | ||||||
| 		Registers: []register.Register{register.DefaultRegister}, | 		Registers: []register.Register{register.DefaultRegister}, | ||||||
| 		Routers:   []router.Router{router.DefaultRouter}, | 		Routers:   []router.Router{router.DefaultRouter}, | ||||||
| 		Auths:     []auth.Auth{auth.DefaultAuth}, |  | ||||||
| 		Loggers:   []logger.Logger{logger.DefaultLogger}, | 		Loggers:   []logger.Logger{logger.DefaultLogger}, | ||||||
| 		Tracers:   []tracer.Tracer{tracer.DefaultTracer}, | 		Tracers:   []tracer.Tracer{tracer.DefaultTracer}, | ||||||
| 		Meters:    []meter.Meter{meter.DefaultMeter}, | 		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 | // Config sets the config for the service | ||||||
| func Config(c ...config.Config) Option { | func Config(c ...config.Config) Option { | ||||||
| 	return func(o *Options) error { | 	return func(o *Options) error { | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ var DefaultDomain = "micro" | |||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// DefaultRegister is the global default register | 	// DefaultRegister is the global default register | ||||||
| 	DefaultRegister Register = NewRegister() | 	DefaultRegister = NewRegister() | ||||||
| 	// ErrNotFound returned when LookupService is called and no services found | 	// ErrNotFound returned when LookupService is called and no services found | ||||||
| 	ErrNotFound = errors.New("service not found") | 	ErrNotFound = errors.New("service not found") | ||||||
| 	// ErrWatcherStopped returned when when watcher is stopped | 	// ErrWatcherStopped returned when when watcher is stopped | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import ( | |||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// DefaultRouter is the global default router | 	// DefaultRouter is the global default router | ||||||
| 	DefaultRouter Router = NewRouter() | 	DefaultRouter = NewRouter() | ||||||
| 	// DefaultNetwork is default micro network | 	// DefaultNetwork is default micro network | ||||||
| 	DefaultNetwork = "micro" | 	DefaultNetwork = "micro" | ||||||
| 	// ErrRouteNotFound is returned when no route was found in the routing table | 	// ErrRouteNotFound is returned when no route was found in the routing table | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"go.unistack.org/micro/v3/auth" |  | ||||||
| 	"go.unistack.org/micro/v3/broker" | 	"go.unistack.org/micro/v3/broker" | ||||||
| 	"go.unistack.org/micro/v3/codec" | 	"go.unistack.org/micro/v3/codec" | ||||||
| 	"go.unistack.org/micro/v3/logger" | 	"go.unistack.org/micro/v3/logger" | ||||||
| @@ -32,8 +31,6 @@ type Options struct { | |||||||
| 	Register register.Register | 	Register register.Register | ||||||
| 	// Tracer holds the tracer | 	// Tracer holds the tracer | ||||||
| 	Tracer tracer.Tracer | 	Tracer tracer.Tracer | ||||||
| 	// Auth holds the auth |  | ||||||
| 	Auth auth.Auth |  | ||||||
| 	// Logger holds the logger | 	// Logger holds the logger | ||||||
| 	Logger logger.Logger | 	Logger logger.Logger | ||||||
| 	// Meter holds the meter | 	// Meter holds the meter | ||||||
| @@ -91,7 +88,6 @@ type Options struct { | |||||||
| // NewOptions returns new options struct with default or passed values | // NewOptions returns new options struct with default or passed values | ||||||
| func NewOptions(opts ...Option) Options { | func NewOptions(opts ...Option) Options { | ||||||
| 	options := Options{ | 	options := Options{ | ||||||
| 		Auth:             auth.DefaultAuth, |  | ||||||
| 		Codecs:           make(map[string]codec.Codec), | 		Codecs:           make(map[string]codec.Codec), | ||||||
| 		Context:          context.Background(), | 		Context:          context.Background(), | ||||||
| 		Metadata:         metadata.New(0), | 		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 | // Transport mechanism for communication e.g http, rabbitmq, etc | ||||||
| func Transport(t transport.Transport) Option { | func Transport(t transport.Transport) Option { | ||||||
| 	return func(o *Options) { | 	return func(o *Options) { | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								service.go
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								service.go
									
									
									
									
									
								
							| @@ -1,11 +1,10 @@ | |||||||
| // Package micro is a pluggable framework for microservices | // Package micro is a pluggable framework for microservices | ||||||
| package micro | package micro // import "go.unistack.org/micro/v3" | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"sync" | 	"sync" | ||||||
|  |  | ||||||
| 	"go.unistack.org/micro/v3/auth" |  | ||||||
| 	"go.unistack.org/micro/v3/broker" | 	"go.unistack.org/micro/v3/broker" | ||||||
| 	"go.unistack.org/micro/v3/client" | 	"go.unistack.org/micro/v3/client" | ||||||
| 	"go.unistack.org/micro/v3/config" | 	"go.unistack.org/micro/v3/config" | ||||||
| @@ -27,36 +26,34 @@ type Service interface { | |||||||
| 	Init(...Option) error | 	Init(...Option) error | ||||||
| 	// Options returns the current options | 	// Options returns the current options | ||||||
| 	Options() Options | 	Options() Options | ||||||
| 	// Auth is for handling auth | 	// Logger is for output log from service components | ||||||
| 	Auth(...string) auth.Auth |  | ||||||
| 	// Logger is for logs |  | ||||||
| 	Logger(...string) logger.Logger | 	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 | 	Config(...string) config.Config | ||||||
| 	// Client is for calling services | 	// Client is for sync calling services via RPC | ||||||
| 	Client(...string) client.Client | 	Client(...string) client.Client | ||||||
| 	// Broker is for sending and receiving events | 	// Broker is for sending and receiving async events | ||||||
| 	Broker(...string) broker.Broker | 	Broker(...string) broker.Broker | ||||||
| 	// Server is for handling requests and events | 	// Server is for handling requests and broker unmarshaled events | ||||||
| 	Server(...string) server.Server | 	Server(...string) server.Server | ||||||
| 	// Store is for key/val store | 	// Store is for key/val store | ||||||
| 	Store(...string) store.Store | 	Store(...string) store.Store | ||||||
| 	// Register | 	// Register used by client to lookup other services and server registers on it | ||||||
| 	Register(...string) register.Register | 	Register(...string) register.Register | ||||||
| 	// Tracer | 	// Tracer | ||||||
| 	Tracer(...string) tracer.Tracer | 	Tracer(...string) tracer.Tracer | ||||||
| 	// Router | 	// Router | ||||||
| 	Router(...string) router.Router | 	Router(...string) router.Router | ||||||
| 	// Meter | 	// Meter may be used internally by other component to export metrics | ||||||
| 	Meter(...string) meter.Meter | 	Meter(...string) meter.Meter | ||||||
|  |  | ||||||
| 	// Runtime | 	// Runtime | ||||||
| 	// Runtime(string) (runtime.Runtime, bool) | 	// Runtime(string) (runtime.Runtime, bool) | ||||||
| 	// Profile | 	// Profile | ||||||
| 	// Profile(string) (profile.Profile, bool) | 	// Profile(string) (profile.Profile, bool) | ||||||
| 	// Run the service and wait | 	// Run the service and wait for stop | ||||||
| 	Run() error | 	Run() error | ||||||
| 	// Start the service | 	// Start the service and not wait | ||||||
| 	Start() error | 	Start() error | ||||||
| 	// Stop the service | 	// Stop the service | ||||||
| 	Stop() error | 	Stop() error | ||||||
| @@ -218,14 +215,6 @@ func (s *service) Logger(names ...string) logger.Logger { | |||||||
| 	return s.opts.Loggers[idx] | 	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 { | func (s *service) Router(names ...string) router.Router { | ||||||
| 	idx := 0 | 	idx := 0 | ||||||
| 	if len(names) == 1 { | 	if len(names) == 1 { | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.unistack.org/micro/v3/auth" |  | ||||||
| 	"go.unistack.org/micro/v3/broker" | 	"go.unistack.org/micro/v3/broker" | ||||||
| 	"go.unistack.org/micro/v3/client" | 	"go.unistack.org/micro/v3/client" | ||||||
| 	"go.unistack.org/micro/v3/config" | 	"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) { | func Test_service_Router(t *testing.T) { | ||||||
| 	r := router.NewRouter() | 	r := router.NewRouter() | ||||||
| 	type fields struct { | 	type fields struct { | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ var ( | |||||||
| 	// ErrInvalidKey is returned when a key has empty or have invalid format | 	// ErrInvalidKey is returned when a key has empty or have invalid format | ||||||
| 	ErrInvalidKey = errors.New("invalid key") | 	ErrInvalidKey = errors.New("invalid key") | ||||||
| 	// DefaultStore is the global default store | 	// DefaultStore is the global default store | ||||||
| 	DefaultStore Store = NewStore() | 	DefaultStore = NewStore() | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Store is a data storage interface | // Store is a data storage interface | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| // DefaultTracer is the global default tracer | // DefaultTracer is the global default tracer | ||||||
| var DefaultTracer Tracer = NewTracer() | var DefaultTracer = NewTracer() | ||||||
|  |  | ||||||
| // Tracer is an interface for distributed tracing | // Tracer is an interface for distributed tracing | ||||||
| type Tracer interface { | type Tracer interface { | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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. |  | ||||||
| @@ -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. |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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"]) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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" |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| @@ -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" |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS3dJQkFBS0NBZ0VBOFNiSlA1WGJFaWRSbTViMnNOcExHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkCi9SbDkvMXBNVjdNaU8zTEh3dGhIQzJCUllxcisxd0Zkb1pDR0JZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUKMEJIL2xYUU1xeUVxRjVNSTJ6ZWpDNHpNenIxNU9OK2dFNEpuaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtLwptVWRJVC9MYUY3a1F4eVlLNVZLbitOZ09Xek1sektBQXBDbjdUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsCm85akRqbFk1b0JPY3pmcWVOV0hLNUdYQjdRd3BMTmg5NDZQelpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDUKd2xFcThoTmhtaG01Tk5lL08rR2dqQkROU2ZVaDA2K3E0bmdtYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1bwpSdFFoZ2lZOTEwcFBmOWJhdVhXcXdVQ1VhNHFzSHpqS1IwTC9OMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVCnJnTHJQYkVCOWVnY0drMzgrYnBLczNaNlJyNSt0bkQxQklQSUZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVUKVEdEeFV4OG9qOFZJZVJuV0RxNk1jMWlKcDhVeWNpQklUUnR3NGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMApsYVF6QXVQM2FpV1hJTXAyc2M4U2MrQmwrTGpYbUJveEJyYUJIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YCmdGS1NzSW5IRHJIVk95V1BCZTNmYWRFYzc3YituYi9leE96cjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUEKQVFLQ0FnRUFqUzc1Q2VvUlRRcUtBNzZaaFNiNGEzNVlKRENtcEpSazFsRTNKYnFzNFYxRnhXaDBjZmJYeG9VMgpSdTRRYjUrZWhsdWJGSFQ2a1BxdG9uRWhRVExjMUNmVE9WbHJOb3hocDVZM2ZyUmlQcnNnNXcwK1R3RUtrcFJUCnltanJQTXdQbGxCM2U0NmVaYmVXWGc3R3FFVmptMGcxVFRRK0tocVM4R0w3VGJlTFhRN1ZTem9ydTNCNVRKMVEKeEN6TVB0dnQ2eDYrU3JrcmhvZG1iT3VNRkpDam1TbWxmck9pZzQ4Zkc3NUpERHRObXpLWHBEUVJpYUNodFJhVQpQRHpmUTlTamhYdFFqdkZvWFFFT3BqdkZVRjR2WldNUWNQNUw1VklDM3JRSWp4MFNzQTN6S0FwakVUbjJHNjN2CktZby8zVWttbzhkUCtGRHA3NCs5a3pLNHFFaFJycEl3bEtiN0VOZWtDUXZqUFl1K3pyKzMyUXdQNTJ2L2FveWQKdjJJaUY3M2laTU1vZDhhYjJuQStyVEI2T0cvOVlSYk5kV21tay9VTi9jUHYrN214TmZ6Y1d1ZU1XcThxMXh4eAptNTNpR0NSQ29PQ1lDQk4zcUFkb1JwYW5xd3lCOUxrLzFCQjBHUld3MjgxK3VhNXNYRnZBVDBKeTVURnduMncvClU1MlJKWFlNOXVhMFBvd214b0RDUWRuNFZYVkdNZGdXaHN4aXhHRlYwOUZObWJJQWJaN0xaWGtkS1gzc1ZVbTcKWU1WYWIzVVo2bEhtdXYzT1NzcHNVUlRqN1hiRzZpaVVlaDU1aW91OENWbnRndWtFcnEzQTQwT05FVzhjNDBzOQphVTBGaSs4eWZpQTViaVZHLzF0bWlucUVERkhuQStnWk1xNEhlSkZxcWZxaEZKa1JwRGtDZ2dFQkFQeGR1NGNKCm5Da1duZDdPWFlHMVM3UDdkVWhRUzgwSDlteW9uZFc5bGFCQm84RWRPeTVTZzNOUmsxQ2pNZFZ1a3FMcjhJSnkKeStLWk15SVpvSlJvbllaMEtIUUVMR3ZLbzFOS2NLQ1FJbnYvWHVCdFJpRzBVb1pQNVkwN0RpRFBRQWpYUjlXUwpBc0EzMmQ1eEtFOC91Y3h0MjVQVzJFakNBUmtVeHQ5d0tKazN3bC9JdXVYRlExTDdDWjJsOVlFUjlHeWxUbzhNCmxXUEY3YndtUFV4UVNKaTNVS0FjTzZweTVUU1lkdWQ2aGpQeXJwSXByNU42VGpmTlRFWkVBeU9LbXVpOHVkUkoKMUg3T3RQVEhGZElKQjNrNEJnRDZtRE1HbjB2SXBLaDhZN3NtRUZBbFkvaXlCZjMvOHk5VHVMb1BycEdqR3RHbgp4Y2RpMHFud2p0SGFNbFVDZ2dFQkFQU2Z0dVFCQ2dTU2JLUSswUEFSR2VVeEQyTmlvZk1teENNTmdHUzJ5Ull3CjRGaGV4ZWkwMVJoaFk1NjE3UjduR1dzb0czd1RQa3dvRTJtbE1aQkoxeWEvUU9RRnQ3WG02OVl0RGh0T2FWbDgKL0o4dlVuSTBtWmxtT2pjTlRoYnVPZDlNSDlRdGxIRUMxMlhYdHJNb3Fsb0U2a05TT0pJalNxYm9wcDRXc1BqcApvZTZ0Nkdyd1RhOHBHeUJWWS90Mi85Ym5ORHVPVlpjODBaODdtY2gzcDNQclBqU3h5di9saGxYMFMwYUdHTkhTCk1XVjdUa25OaGo1TWlIRXFnZ1pZemtBWTkyd1JoVENnU1A2M0VNcitUWXFudXVuMXJHbndPYm95TDR2aFRpV0UKcU42UDNCTFlCZ1FpMllDTDludEJrOEl6RHZyd096dW5GVnhhZ0g5SVVoY0NnZ0VCQUwzQXlLa1BlOENWUmR6cQpzL284VkJDZmFSOFhhUGRnSGxTek1BSXZpNXEwNENqckRyMlV3MHZwTVdnM1hOZ0xUT3g5bFJpd3NrYk9SRmxHCmhhd3hRUWlBdkk0SE9WTlBTU0R1WHVNTG5USTQ0S0RFNlMrY2cxU0VMS2pWbDVqcDNFOEpkL1RJMVpLc0xBQUsKZTNHakM5UC9ZbE8xL21ndW4xNjVkWk01cFAwWHBPb2FaeFV2RHFFTktyekR0V1g0RngyOTZlUzdaSFJodFpCNwovQ2t1VUhlcmxrN2RDNnZzdWhTaTh2eTM3c0tPbmQ0K3c4cVM4czhZYVZxSDl3ZzVScUxxakp0bmJBUnc3alVDCm9KQ053M1hNdnc3clhaYzRTbnhVQUNMRGJNV2lLQy9xL1ZGWW9oTEs2WkpUVkJscWd5cjBSYzBRWmpDMlNJb0kKMjRwRWt3VUNnZ0VCQUpqb0FJVVNsVFY0WlVwaExXN3g4WkxPa01UWjBVdFFyd2NPR0hSYndPUUxGeUNGMVFWNQppejNiR2s4SmZyZHpVdk1sTmREZm9uQXVHTHhQa3VTVEUxWlg4L0xVRkJveXhyV3dvZ0cxaUtwME11QTV6em90CjROai9DbUtCQVkvWnh2anA5M2RFS21aZGxWQkdmeUFMeWpmTW5MWUovZXh5L09YSnhPUktZTUttSHg4M08zRWsKMWhvb0FwbTZabTIzMjRGME1iVU1ham5Idld2ZjhHZGJTNk5zcHd4L0dkbk1tYVMrdUJMVUhVMkNLbmc1bEIwVAp4OWJITmY0dXlPbTR0dXRmNzhCd1R5V3UreEdrVW0zZ2VZMnkvR1hqdDZyY2l1ajFGNzFDenZzcXFmZThTcDdJCnd6SHdxcTNzVHR5S2lCYTZuYUdEYWpNR1pKYSt4MVZJV204Q2dnRUJBT001ajFZR25Ba0pxR0czQWJSVDIvNUMKaVVxN0loYkswOGZsSGs5a2YwUlVjZWc0ZVlKY3dIRXJVaE4rdWQyLzE3MC81dDYra0JUdTVZOUg3bkpLREtESQpoeEg5SStyamNlVkR0RVNTRkluSXdDQ1lrOHhOUzZ0cHZMV1U5b0pibGFKMlZsalV2NGRFWGVQb0hkREh1Zk9ZClVLa0lsV2E3Uit1QzNEOHF5U1JrQnFLa3ZXZ1RxcFNmTVNkc1ZTeFIzU2Q4SVhFSHFjTDNUNEtMWGtYNEdEamYKMmZOSTFpZkx6ekhJMTN3Tk5IUTVRNU9SUC9pell2QzVzZkx4U2ZIUXJiMXJZVkpKWkI5ZjVBUjRmWFpHSVFsbApjMG8xd0JmZFlqMnZxVDlpR09IQnNSSTlSL2M2RzJQcUt3aFRpSzJVR2lmVFNEUVFuUkF6b2tpQVkrbE8vUjQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE4U2JKUDVYYkVpZFJtNWIyc05wTApHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkL1JsOS8xcE1WN01pTzNMSHd0aEhDMkJSWXFyKzF3RmRvWkNHCkJZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUwQkgvbFhRTXF5RXFGNU1JMnplakM0ek16cjE1T04rZ0U0Sm4KaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtL21VZElUL0xhRjdrUXh5WUs1VktuK05nT1d6TWx6S0FBcENuNwpUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsbzlqRGpsWTVvQk9jemZxZU5XSEs1R1hCN1F3cExOaDk0NlB6ClpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDV3bEVxOGhOaG1obTVOTmUvTytHZ2pCRE5TZlVoMDYrcTRuZ20KYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1b1J0UWhnaVk5MTBwUGY5YmF1WFdxd1VDVWE0cXNIempLUjBMLwpOMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVcmdMclBiRUI5ZWdjR2szOCticEtzM1o2UnI1K3RuRDFCSVBJCkZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVVUR0R4VXg4b2o4VkllUm5XRHE2TWMxaUpwOFV5Y2lCSVRSdHcKNGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMGxhUXpBdVAzYWlXWElNcDJzYzhTYytCbCtMalhtQm94QnJhQgpIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YZ0ZLU3NJbkhEckhWT3lXUEJlM2ZhZEVjNzdiK25iL2V4T3pyCjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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"` |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user