github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/util/auth/namespace/namespace.go (about)

     1  package namespace
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/tickoalcantara12/micro/v3/service/auth"
     8  	merrors "github.com/tickoalcantara12/micro/v3/service/errors"
     9  )
    10  
    11  var (
    12  	// ErrUnauthorized is returned by Authorize when a context without a blank account tries to access
    13  	// a restricted namespace
    14  	ErrUnauthorized = errors.New("an account is required")
    15  	// ErrForbidden is returned by Authorize when a context is trying to access a namespace it doesn't
    16  	// have access to
    17  	ErrForbidden = errors.New("access denied to namespace")
    18  )
    19  
    20  const (
    21  	// DefaultNamespace used by the server
    22  	DefaultNamespace = "micro"
    23  )
    24  
    25  // AuthorizeAdmin returns a service error if the context is not an admin that can access this namespace
    26  // e.g. either an admin for the this namespace or an admin for micro
    27  func AuthorizeAdmin(ctx context.Context, ns, method string) error {
    28  	if err := Authorize(ctx, ns, method); err != nil {
    29  		return err
    30  	}
    31  
    32  	adminAcc, ok := auth.AccountFromContext(ctx)
    33  	if !ok {
    34  		return merrors.Unauthorized(method, "Unauthorized")
    35  	}
    36  
    37  	// check it's an admin
    38  	if !hasTypeAndScope("user", "admin", adminAcc) && !hasTypeAndScope("service", "service", adminAcc) {
    39  		return merrors.Unauthorized(method, "Unauthorized")
    40  	}
    41  	return nil
    42  }
    43  
    44  // Authorize will return a service error if the context cannot access the given namespace
    45  func Authorize(ctx context.Context, namespace, method string, opts ...AuthorizeOption) error {
    46  	if err := authorize(ctx, namespace); err == ErrForbidden {
    47  		return merrors.Forbidden(method, err.Error())
    48  	} else if err == ErrUnauthorized {
    49  		return merrors.Unauthorized(method, err.Error())
    50  	} else if err != nil {
    51  		return merrors.InternalServerError(method, err.Error())
    52  	}
    53  	return nil
    54  }
    55  
    56  // authorize will return an error if the context cannot access the given namespace
    57  func authorize(ctx context.Context, namespace string, opts ...AuthorizeOption) error {
    58  	// parse the options
    59  	var options AuthorizeOptions
    60  	for _, o := range opts {
    61  		o(&options)
    62  	}
    63  
    64  	// check to see if the namespace was made public
    65  	if namespace == options.PublicNamespace {
    66  		return nil
    67  	}
    68  
    69  	// accounts are always required so we can identify the caller. If auth is not configured, the noop
    70  	// auth implementation will return a blank account with the default namespace set, allowing the caller
    71  	// access to all resources
    72  	acc, ok := auth.AccountFromContext(ctx)
    73  	if !ok {
    74  		return ErrUnauthorized
    75  	}
    76  
    77  	// the server and admins can access all namespaces
    78  	if acc.Issuer == DefaultNamespace && (hasTypeAndScope("service", "service", acc) || hasTypeAndScope("user", "admin", acc)) {
    79  		return nil
    80  	}
    81  
    82  	// ensure the account is requesting access to it's own namespace
    83  	if acc.Issuer != namespace {
    84  		return ErrForbidden
    85  	}
    86  	// account should be of type user or service
    87  	if acc.Type != "user" && acc.Type != "service" {
    88  		return ErrForbidden
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func hasTypeAndScope(atype, scope string, acc *auth.Account) bool {
    95  	if atype != acc.Type {
    96  		return false
    97  	}
    98  	for _, s := range acc.Scopes {
    99  		if s == scope {
   100  			return true
   101  		}
   102  	}
   103  	return false
   104  }
   105  
   106  // AuthorizeOptions are used to configure the Authorize method
   107  type AuthorizeOptions struct {
   108  	PublicNamespace string
   109  }
   110  
   111  // AuthorizeOption sets an attribute on AuthorizeOptions
   112  type AuthorizeOption func(o *AuthorizeOptions)
   113  
   114  // Public indicates a namespace is public and can be accessed by anyone
   115  func Public(ns string) AuthorizeOption {
   116  	return func(o *AuthorizeOptions) {
   117  		o.PublicNamespace = ns
   118  	}
   119  }