github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/plugin/oauth2/oauth.go (about)

     1  package oauth2
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/Knetic/govaluate"
     9  	"github.com/mitchellh/mapstructure"
    10  
    11  	"github.com/hellofresh/janus/pkg/jwt"
    12  	"github.com/hellofresh/janus/pkg/proxy"
    13  )
    14  
    15  // AccessRequestType is the type for OAuth param `grant_type`
    16  type AccessRequestType string
    17  
    18  // AuthorizeRequestType is the type for OAuth param `response_type`
    19  type AuthorizeRequestType string
    20  
    21  // Spec Holds an api definition and basic options
    22  type Spec struct {
    23  	*OAuth
    24  	Manager Manager
    25  }
    26  
    27  // OAuth holds the configuration for oauth proxies
    28  type OAuth struct {
    29  	Name                   string                 `bson:"name" json:"name" valid:"required"`
    30  	Endpoints              Endpoints              `bson:"oauth_endpoints" json:"oauth_endpoints" mapstructure:"oauth_endpoints"`
    31  	ClientEndpoints        ClientEndpoints        `bson:"oauth_client_endpoints" json:"oauth_client_endpoints" mapstructure:"oauth_client_endpoints"`
    32  	AllowedAccessTypes     []AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types" mapstructure:"allowed_access_types" `
    33  	AllowedAuthorizeTypes  []AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types" mapstructure:"allowed_authorize_types"`
    34  	AuthorizeLoginRedirect string                 `bson:"auth_login_redirect" json:"auth_login_redirect" mapstructure:"auth_login_redirect"`
    35  	Secrets                map[string]string      `bson:"secrets" json:"secrets"`
    36  	CorsMeta               corsMeta               `bson:"cors_meta" json:"cors_meta" mapstructure:"cors_meta"`
    37  	RateLimit              rateLimitMeta          `bson:"rate_limit" json:"rate_limit"`
    38  	TokenStrategy          TokenStrategy          `bson:"token_strategy" json:"token_strategy" mapstructure:"token_strategy"`
    39  	AccessRules            []*AccessRule          `bson:"access_rules" json:"access_rules"`
    40  }
    41  
    42  // Endpoints defines the oauth endpoints that wil be proxied
    43  type Endpoints struct {
    44  	Authorize  *proxy.Definition `bson:"authorize" json:"authorize"`
    45  	Token      *proxy.Definition `bson:"token" json:"token"`
    46  	Introspect *proxy.Definition `bson:"introspect" json:"introspect"`
    47  	Revoke     *proxy.Definition `bson:"revoke" json:"revoke"`
    48  }
    49  
    50  // ClientEndpoints defines the oauth client endpoints that wil be proxied
    51  type ClientEndpoints struct {
    52  	Create *proxy.Definition `bson:"create" json:"create"`
    53  	Remove *proxy.Definition `bson:"remove" json:"remove"`
    54  }
    55  
    56  type rateLimitMeta struct {
    57  	Limit   string `bson:"limit" json:"limit"`
    58  	Enabled bool   `bson:"enabled" json:"enabled"`
    59  }
    60  
    61  type corsMeta struct {
    62  	Domains            []string `mapstructure:"domains" bson:"domains" json:"domains"`
    63  	Methods            []string `mapstructure:"methods" bson:"methods" json:"methods"`
    64  	RequestHeaders     []string `mapstructure:"request_headers" bson:"request_headers" json:"request_headers"`
    65  	ExposedHeaders     []string `mapstructure:"exposed_headers" bson:"exposed_headers" json:"exposed_headers"`
    66  	OptionsPassthrough bool     `mapstructure:"options_passthrough" bson:"options_passthrough" json:"options_passthrough"`
    67  	Enabled            bool     `bson:"enabled" json:"enabled"`
    68  }
    69  
    70  // IntrospectionSettings represents the settings for introspection
    71  type IntrospectionSettings struct {
    72  	UseCustomHeader bool   `mapstructure:"use_custom_header" bson:"use_custom_header" json:"use_custom_header"`
    73  	HeaderName      string `mapstructure:"header_name" bson:"header_name" json:"header_name"`
    74  	UseAuthHeader   bool   `mapstructure:"use_auth_header" bson:"use_auth_header" json:"use_auth_header"`
    75  	AuthHeaderType  string `mapstructure:"auth_header_type" bson:"auth_header_type" json:"auth_header_type"`
    76  	UseBody         bool   `mapstructure:"use_body" bson:"use_body" json:"use_body"`
    77  	ParamName       string `mapstructure:"param_name" bson:"param_name" json:"param_name"`
    78  }
    79  
    80  // TokenStrategy defines the token strategy fields
    81  type TokenStrategy struct {
    82  	Name     string      `bson:"name" json:"name"`
    83  	Settings interface{} `bson:"settings" json:"settings"`
    84  	// TODO: this should become part of the settings, but for "jwt" strategy we expect array of signing methods
    85  	// at the moment, so this will be BC-breaking change. In the next major version we need to turn settings
    86  	// into object/dictionary and make leeway one of the settings.
    87  	Leeway int64 `bson:"leeway" json:"leeway"`
    88  }
    89  
    90  // NewOAuth creates a new instance of OAuth
    91  func NewOAuth() *OAuth {
    92  	return &OAuth{
    93  		Secrets: make(map[string]string),
    94  		Endpoints: Endpoints{
    95  			Authorize:  proxy.NewDefinition(),
    96  			Introspect: proxy.NewDefinition(),
    97  			Revoke:     proxy.NewDefinition(),
    98  			Token:      proxy.NewDefinition(),
    99  		},
   100  	}
   101  }
   102  
   103  // GetIntrospectionSettings returns the settings for introspection
   104  func (t TokenStrategy) GetIntrospectionSettings() (*IntrospectionSettings, error) {
   105  	var settings *IntrospectionSettings
   106  	err := mapstructure.Decode(t.Settings, &settings)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("could not decode introspection settings: %w", err)
   109  	}
   110  	return settings, nil
   111  }
   112  
   113  // GetJWTSigningMethods parses and returns chain of JWT signing methods for token signature validation.
   114  // Supports fallback to legacy format with {"secret": "key"} as single signing method with HS256 alg.
   115  func (t TokenStrategy) GetJWTSigningMethods() ([]jwt.SigningMethod, error) {
   116  	var methods []jwt.SigningMethod
   117  	err := mapstructure.Decode(t.Settings, &methods)
   118  	// TODO: remove legacy format support in couple minor releases
   119  	if err != nil {
   120  		var legacy struct {
   121  			Secret string `json:"secret"`
   122  		}
   123  		err = mapstructure.Decode(t.Settings, &legacy)
   124  		if nil != err {
   125  			return methods, err
   126  		}
   127  		if legacy.Secret == "" {
   128  			return nil, ErrJWTSecretMissing
   129  		}
   130  
   131  		return []jwt.SigningMethod{{Alg: "HS256", Key: legacy.Secret}}, nil
   132  	}
   133  	return methods, err
   134  }
   135  
   136  // AccessRule represents a rule that will be applied to a JWT that could be revoked
   137  type AccessRule struct {
   138  	mu        sync.Mutex
   139  	Predicate string `bson:"predicate" json:"predicate"`
   140  	Action    string `bson:"action" json:"action"`
   141  	parsed    bool
   142  }
   143  
   144  // IsAllowed checks if the rule is allowed to
   145  func (r *AccessRule) IsAllowed(claims map[string]interface{}) (bool, error) {
   146  	var err error
   147  
   148  	if !r.parsed {
   149  		matched, err := r.parse(claims)
   150  		if err != nil {
   151  			return false, err
   152  		}
   153  
   154  		if !matched {
   155  			return true, nil
   156  		}
   157  	}
   158  
   159  	return r.Action == "allow", err
   160  }
   161  
   162  func (r *AccessRule) parse(claims map[string]interface{}) (bool, error) {
   163  	expression, err := govaluate.NewEvaluableExpression(r.Predicate)
   164  	if err != nil {
   165  		return false, errors.New("could not create an expression with this predicate")
   166  	}
   167  
   168  	result, err := expression.Evaluate(claims)
   169  	if err != nil {
   170  		return false, errors.New("cannot evaluate the expression")
   171  	}
   172  
   173  	r.mu.Lock()
   174  	r.parsed = true
   175  	r.mu.Unlock()
   176  
   177  	return result.(bool), nil
   178  }