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 }