diff --git a/adapter/internal/oasparser/config_generator.go b/adapter/internal/oasparser/config_generator.go index efe7a4e36..8a82d4f4b 100644 --- a/adapter/internal/oasparser/config_generator.go +++ b/adapter/internal/oasparser/config_generator.go @@ -463,13 +463,7 @@ func GetJWTRequirements(adapterAPI *model.AdapterInternalAPI, jwtIssuers map[str selectedIssuers = append(selectedIssuers, issuserName) } } - if len(selectedIssuers) == 1 { - return &jwt.JwtRequirement{ - RequiresType: &jwt.JwtRequirement_ProviderName{ - ProviderName: selectedIssuers[0], - }, - } - } else if len(selectedIssuers) > 1 { + if len(selectedIssuers) >= 1 { return &jwt.JwtRequirement{ RequiresType: &jwt.JwtRequirement_RequiresAny{ RequiresAny: &jwt.JwtRequirementOrList{ @@ -482,11 +476,13 @@ func GetJWTRequirements(adapterAPI *model.AdapterInternalAPI, jwtIssuers map[str }, }) } + requirements = append(requirements, &jwt.JwtRequirement{ + RequiresType: &jwt.JwtRequirement_AllowMissingOrFailed{}, + }) return requirements }(), }, - }, - } + }} } return nil } @@ -526,8 +522,8 @@ func getjwtAuthFilters(tokenIssuer *v1alpha1.ResolvedJWTIssuer, issuerName strin jwtProvider := &jwt.JwtProvider{ Issuer: tokenIssuer.Issuer, Forward: true, - FailedStatusInMetadata: "failed_status", - PayloadInMetadata: "payload_in_metadata", + FailedStatusInMetadata: tokenIssuer.Issuer + "-failed", + PayloadInMetadata: tokenIssuer.Issuer + "-payload", } if tokenIssuer.SignatureValidation.JWKS != nil { logger.LoggerOasparser.Infof("JWKS URL: %s", tokenIssuer.SignatureValidation.JWKS.URL) diff --git a/adapter/internal/oasparser/envoyconf/routes_configs.go b/adapter/internal/oasparser/envoyconf/routes_configs.go index 2113d0ecf..e1b801196 100644 --- a/adapter/internal/oasparser/envoyconf/routes_configs.go +++ b/adapter/internal/oasparser/envoyconf/routes_configs.go @@ -48,7 +48,7 @@ const ( DescriptorKeyForPolicy = "policy" DescriptorKeyForOrganization = "organization" extAuthzFilterName = "envoy.filters.http.ext_authz" - extProcFilterName = "envoy.filters.http.ext_proc" + extProcFilterName = "envoy.filters.http.ext_proc" descriptorMetadataKeyForSubscription = "ratelimit:subscription" descriptorMetadataKeyForUsagePolicy = "ratelimit:usage-policy" @@ -77,13 +77,13 @@ const ( DescriptorKeyForAISubscription = "subscription" ) -func generateRouteConfig(routeName string, method *string, match *routev3.RouteMatch, action *routev3.Route_Route, redirectAction *routev3.Route_Redirect, +func generateRouteConfig(apiType string, routeName string, method *string, match *routev3.RouteMatch, action *routev3.Route_Route, redirectAction *routev3.Route_Redirect, metadata *corev3.Metadata, decorator *routev3.Decorator, typedPerFilterConfig map[string]*anypb.Any, requestHeadersToAdd []*corev3.HeaderValueOption, requestHeadersToRemove []string, responseHeadersToAdd []*corev3.HeaderValueOption, responseHeadersToRemove []string, authentication *model.Authentication) *routev3.Route { cloneTypedPerFilterConfig := cloneTypedPerFilterConfig(typedPerFilterConfig) //todo: need to fix it in proper way - if authentication == nil || (authentication != nil && (authentication.Disabled || authentication.Oauth2 == nil)) || (method != nil && strings.ToUpper(*method) == "OPTIONS") { + if apiType == constants.REST && (authentication == nil || (authentication != nil && (authentication.Disabled || authentication.Oauth2 == nil)) || (method != nil && strings.ToUpper(*method) == "OPTIONS")) { logger.LoggerOasparser.Infof("routename%v", routeName) logger.LoggerOasparser.Infof("authentication is nill %v", authentication == nil) if authentication != nil { @@ -128,7 +128,7 @@ func generateRouteMatch(routeRegex string) *routev3.RouteMatch { } func generateRouteAction(apiType string, routeConfig *model.EndpointConfig, ratelimitCriteria *ratelimitCriteria, mirrorClusterNames []string, isBackendBasedAIRatelimitEnabled bool, descriptorValueForBackendBasedAIRatelimit string, weightedCluster *routev3.WeightedCluster_ClusterWeight, isWeighted bool) (action *routev3.Route_Route) { - + if isWeighted { // check if weightedCluster is already in the list exists := false @@ -145,7 +145,7 @@ func generateRouteAction(apiType string, routeConfig *model.EndpointConfig, rate // if not existing, add to the list if !exists { - weightedClusters = append(weightedClusters, weightedCluster) + weightedClusters = append(weightedClusters, weightedCluster) } action = &routev3.Route_Route{ Route: &routev3.RouteAction{ @@ -163,7 +163,7 @@ func generateRouteAction(apiType string, routeConfig *model.EndpointConfig, rate }, }, } - } else { + } else { action = &routev3.Route_Route{ Route: &routev3.RouteAction{ HostRewriteSpecifier: &routev3.RouteAction_AutoHostRewrite{ @@ -189,9 +189,9 @@ func generateRouteAction(apiType string, routeConfig *model.EndpointConfig, rate RetryBackOff: &routev3.RetryPolicy_RetryBackOff{ BaseInterval: durationpb.New(time.Duration(routeConfig.RetryConfig.BaseIntervalInMillis) * time.Millisecond), }, - RetryOn: "retriable-status-codes", + RetryOn: "retriable-status-codes", RetriableStatusCodes: routeConfig.RetryConfig.StatusCodes, - NumRetries: &wrapperspb.UInt32Value{Value: uint32(routeConfig.RetryConfig.Count)}, + NumRetries: &wrapperspb.UInt32Value{Value: uint32(routeConfig.RetryConfig.Count)}, } action.Route.RetryPolicy = retryPolicy } diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 554e6a195..b0bf8b6d2 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -1291,7 +1291,7 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error // Create route1 for current method. // Do not add policies to route config. Send via enforcer method := operation.GetMethod() - route1 := generateRouteConfig(xWso2Basepath+method, &method, match1, action1, requestRedirectAction, metaData, decorator, perRouteFilterConfigs, + route1 := generateRouteConfig(apiType, xWso2Basepath+method, &method, match1, action1, requestRedirectAction, metaData, decorator, perRouteFilterConfigs, nil, requestHeadersToRemove, nil, nil, operation.GetAuthentication()) // Create route2 for new method. @@ -1302,7 +1302,7 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error action2.Route.RegexRewrite = generateRegexMatchAndSubstitute(routePath, resourcePath, pathMatchType) } configToSkipEnforcer := generateFilterConfigToSkipEnforcer() - route2 := generateRouteConfig(xWso2Basepath, &method, match2, action2, requestRedirectAction, metaData, decorator, configToSkipEnforcer, + route2 := generateRouteConfig(apiType, xWso2Basepath, &method, match2, action2, requestRedirectAction, metaData, decorator, configToSkipEnforcer, requestHeadersToAdd, requestHeadersToRemove, responseHeadersToAdd, responseHeadersToRemove, operation.GetAuthentication()) routes = append(routes, route1) @@ -1323,7 +1323,7 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error action.Route.RegexRewrite = generateRegexMatchAndSubstitute(routePath, resourcePath, pathMatchType) } method := operation.GetMethod() - route := generateRouteConfig(xWso2Basepath, &method, match, action, requestRedirectAction, metaData, decorator, perRouteFilterConfigs, + route := generateRouteConfig(apiType, xWso2Basepath, &method, match, action, requestRedirectAction, metaData, decorator, perRouteFilterConfigs, requestHeadersToAdd, requestHeadersToRemove, responseHeadersToAdd, responseHeadersToRemove, operation.GetAuthentication()) routes = append(routes, route) } @@ -1351,7 +1351,7 @@ func createRoutes(params *routeCreateParams) (routes []*routev3.Route, err error } // action.Route.RegexRewrite = generateRegexMatchAndSubstitute(rewritePath, newRoutePath, pathMatchType) } - route := generateRouteConfig(xWso2Basepath, nil, match, action, nil, metaData, decorator, perRouteFilterConfigs, + route := generateRouteConfig(apiType, xWso2Basepath, nil, match, action, nil, metaData, decorator, perRouteFilterConfigs, nil, requestHeadersToRemove, nil, nil, nil) // general headers to add and remove are included in this methods routes = append(routes, route) } diff --git a/gateway/enforcer/internal/authentication/jwt_validator.go b/gateway/enforcer/internal/authentication/jwt_validator.go new file mode 100644 index 000000000..f1d5127ff --- /dev/null +++ b/gateway/enforcer/internal/authentication/jwt_validator.go @@ -0,0 +1,27 @@ +package authentication + +import ( + "encoding/json" + + "github.com/wso2/apk/gateway/enforcer/internal/dto" + "github.com/wso2/apk/gateway/enforcer/internal/requestconfig" + "github.com/wso2/apk/gateway/enforcer/internal/transformer" +) + +// ValidateToken validates the JWT token. +func ValidateToken(rch *requestconfig.Holder, jwtTransformer *transformer.JWTTransformer) *dto.ImmediateResponse { + if rch != nil { + if rch.ExternalProcessingEnvoyMetadata.JwtAuthenticationData != nil { + jwtValidationInfo := jwtTransformer.TransformJWTClaims(rch.MatchedAPI.OrganizationID, rch.ExternalProcessingEnvoyMetadata.JwtAuthenticationData) + if jwtValidationInfo != nil { + if jwtValidationInfo.Valid { + rch.JWTValidationInfo = jwtValidationInfo + return nil + } + } + } + } + errorResponse := &dto.ErrorResponse{ErrorMessage: "Invalid Credentials", Code: "900901", ErrorDescription: "Make sure you have provided the correct security credentials"} + jsonData, _ := json.MarshalIndent(errorResponse, "", " ") + return &dto.ImmediateResponse{StatusCode: 401, Message: string(jsonData)} +} diff --git a/gateway/enforcer/internal/authorization/authorization.go b/gateway/enforcer/internal/authorization/authorization.go index 5d4748017..70a1a0c86 100644 --- a/gateway/enforcer/internal/authorization/authorization.go +++ b/gateway/enforcer/internal/authorization/authorization.go @@ -20,14 +20,19 @@ package authorization import ( "fmt" + "github.com/wso2/apk/gateway/enforcer/internal/authentication" "github.com/wso2/apk/gateway/enforcer/internal/config" "github.com/wso2/apk/gateway/enforcer/internal/datastore" "github.com/wso2/apk/gateway/enforcer/internal/dto" "github.com/wso2/apk/gateway/enforcer/internal/requestconfig" + "github.com/wso2/apk/gateway/enforcer/internal/transformer" ) // Validate performs the authorization. -func Validate(rch *requestconfig.Holder, subAppDataStore *datastore.SubscriptionApplicationDataStore, cfg *config.Server) *dto.ImmediateResponse { +func Validate(rch *requestconfig.Holder, subAppDataStore *datastore.SubscriptionApplicationDataStore, cfg *config.Server, jwtTransformer *transformer.JWTTransformer) *dto.ImmediateResponse { + if immediateResponse := authentication.ValidateToken(rch, jwtTransformer); immediateResponse != nil { + return immediateResponse + } if immediateResponse := ValidateScopes(rch, subAppDataStore, cfg); immediateResponse != nil { return immediateResponse } @@ -36,6 +41,6 @@ func Validate(rch *requestconfig.Holder, subAppDataStore *datastore.Subscription return immediateResponse } cfg.Logger.Info(fmt.Sprintf("Subscription validation successful for the request: %s", rch.MatchedResource.Path)) - + return nil } diff --git a/gateway/enforcer/internal/datastore/jwt_issuer_store.go b/gateway/enforcer/internal/datastore/jwt_issuer_store.go index eb40287c8..a32599103 100644 --- a/gateway/enforcer/internal/datastore/jwt_issuer_store.go +++ b/gateway/enforcer/internal/datastore/jwt_issuer_store.go @@ -66,3 +66,14 @@ func (s *JWTIssuerStore) GetJWTIssuerByOrganizationAndIssuer(organization, issue } return nil } + +// GetJWTISsuersByOrganization returns the JWTIssuers for the given organization. +// This method is thread-safe. +func (s *JWTIssuerStore) GetJWTISsuersByOrganization(organization string) map[string]*subscription.JWTIssuer { + s.mu.RLock() + defer s.mu.RUnlock() + if orgWiseJWTIssuers, ok := s.jwtIssuers[organization]; ok { + return orgWiseJWTIssuers + } + return nil +} diff --git a/gateway/enforcer/internal/dto/error_response.go b/gateway/enforcer/internal/dto/error_response.go new file mode 100644 index 000000000..583adf97a --- /dev/null +++ b/gateway/enforcer/internal/dto/error_response.go @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package dto + +// ErrorResponse represents the error response. +type ErrorResponse struct { + ErrorMessage string `json:"error_message"` + Code string `json:"code"` + ErrorDescription string `json:"error_description"` +} diff --git a/gateway/enforcer/internal/dto/external_processing_envoy_metadata.go b/gateway/enforcer/internal/dto/external_processing_envoy_metadata.go index cd4d36571..ce216bfa3 100644 --- a/gateway/enforcer/internal/dto/external_processing_envoy_metadata.go +++ b/gateway/enforcer/internal/dto/external_processing_envoy_metadata.go @@ -27,13 +27,18 @@ type ExternalProcessingEnvoyMetadata struct { // JwtAuthenticationData represents the JWT authentication data. type JwtAuthenticationData struct { - Status *Status `json:"status"` + SucessData map[string]*JWTAuthenticationSuccessData `json:"sucessData"` + FailedData map[string]*JWTAuthenticationFailureData `json:"failedData"` +} + +// JWTAuthenticationSuccessData represents the success data of the JWT authentication. +type JWTAuthenticationSuccessData struct { Issuer string `json:"issuer"` Claims map[string]interface{} `json:"claims"` } -// Status represents the status of the JWT authentication. -type Status struct { +// JWTAuthenticationFailureData represents the status of the JWT authentication. +type JWTAuthenticationFailureData struct { Code int `json:"code"` Message string `json:"message"` } diff --git a/gateway/enforcer/internal/dto/jwt_validation_info.go b/gateway/enforcer/internal/dto/jwt_validation_info.go index 0f680da83..3c36ca3ee 100644 --- a/gateway/enforcer/internal/dto/jwt_validation_info.go +++ b/gateway/enforcer/internal/dto/jwt_validation_info.go @@ -1,11 +1,33 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package dto // JWTValidationInfo represents the JWT validation info type JWTValidationInfo struct { - Issuer string `json:"issuer"` // Issuer - ClientID string `json:"clientId"` // Client ID - Subject string `json:"subject"` // Subject - Audiences []string `json:"audiences"` // Audiences - Scopes []string `json:"scopes"` // Scopes - Claims map[string]interface{} `json:"claims"` // Claims + Valid bool `json:"valid"` // Valid + ExpiryTime int64 `json:"expiryTime"` // Expiry time + IssuedTime int64 `json:"issuedTime"` // Issued time + JTI string `json:"jti"` // JTI + ValidationCode int `json:"validationCode"` // Validation code + ValidationMessage string `json:"validationMessage"` // Validation message + Issuer string `json:"issuer"` // Issuer + ClientID string `json:"clientId"` // Client ID + Subject string `json:"subject"` // Subject + Audiences []string `json:"audiences"` // Audiences + Scopes []string `json:"scopes"` // Scopes + Claims map[string]interface{} `json:"claims"` // Claims } diff --git a/gateway/enforcer/internal/extproc/ext_proc.go b/gateway/enforcer/internal/extproc/ext_proc.go index c792dfa64..8ce1d7eba 100644 --- a/gateway/enforcer/internal/extproc/ext_proc.go +++ b/gateway/enforcer/internal/extproc/ext_proc.go @@ -26,6 +26,7 @@ import ( "io" "io/ioutil" "strconv" + "strings" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_service_proc_v3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" @@ -228,7 +229,6 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro dynamicMetadataKeyValuePairs[analytics.APIOrganizationIDKey] = requestConfigHolder.MatchedAPI.OrganizationID dynamicMetadataKeyValuePairs[analytics.APICreatorTenantDomainKey] = requestConfigHolder.MatchedAPI.OrganizationID - if requestConfigHolder.MatchedAPI.APIDefinitionPath != "" { definitionPath := requestConfigHolder.MatchedAPI.APIDefinitionPath s.cfg.Logger.Info(fmt.Sprintf("definition Path: %v", definitionPath)) @@ -295,7 +295,7 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro if requestConfigHolder.MatchedResource.Endpoints != nil && len(requestConfigHolder.MatchedResource.Endpoints.URLs) > 0 { dynamicMetadataKeyValuePairs[analytics.DestinationKey] = requestConfigHolder.MatchedResource.Endpoints.URLs[0] } - + metadata, err := extractExternalProcessingMetadata(req.GetMetadataContext()) if err != nil { s.log.Error(err, "failed to extract context metadata") @@ -308,18 +308,27 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro // s.log.Info(fmt.Sprintf("Matched Resource: %v", requestConfigHolder.MatchedResource)) // s.log.Info(fmt.Sprintf("req holderrr: %+v\n s: %+v", &requestConfigHolder, &s)) s.log.Info(fmt.Sprintf("req holderrr: %+v\n s: %+v", requestConfigHolder, s)) - if requestConfigHolder.MatchedResource.AuthenticationConfig != nil && !requestConfigHolder.MatchedResource.AuthenticationConfig.Disabled && !requestConfigHolder.MatchedAPI.DisableAuthentication { - jwtValidationInfo := s.jwtTransformer.TransformJWTClaims(requestConfigHolder.MatchedAPI.OrganizationID, requestConfigHolder.ExternalProcessingEnvoyMetadata) - requestConfigHolder.JWTValidationInfo = &jwtValidationInfo - s.log.Sugar().Infof("jwtValidation==%v", jwtValidationInfo) - if immediateResponse := authorization.Validate(requestConfigHolder, s.subscriptionApplicationDatastore, s.cfg); immediateResponse != nil { + if requestConfigHolder.MatchedResource != nil && requestConfigHolder.MatchedResource.AuthenticationConfig != nil && !requestConfigHolder.MatchedResource.AuthenticationConfig.Disabled && !requestConfigHolder.MatchedAPI.DisableAuthentication { + if immediateResponse := authorization.Validate(requestConfigHolder, s.subscriptionApplicationDatastore, s.cfg, s.jwtTransformer); immediateResponse != nil { + // Update the Content-Type header + headers := &envoy_service_proc_v3.HeaderMutation{ + SetHeaders: []*corev3.HeaderValueOption{ + { + Header: &corev3.HeaderValue{ + Key: "Content-Type", + RawValue: []byte("Application/json"), + }, + }, + }, + } resp = &envoy_service_proc_v3.ProcessingResponse{ Response: &envoy_service_proc_v3.ProcessingResponse_ImmediateResponse{ ImmediateResponse: &envoy_service_proc_v3.ImmediateResponse{ Status: &v32.HttpStatus{ Code: v32.StatusCode(immediateResponse.StatusCode), }, - Body: []byte(immediateResponse.Message), + Body: []byte(immediateResponse.Message), + Headers: headers, }, }, } @@ -427,16 +436,29 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro s.cfg.Logger.Info(fmt.Sprintf("Matched API not found: %s", metadata.MatchedAPIIdentifier)) break } - + rch := &requestconfig.Holder{} + rch.MatchedAPI = matchedAPI + rch.ExternalProcessingEnvoyMetadata = metadata if matchedAPI.IsGraphQLAPI() { - if immediateResponse := graphql.ValidateGraphQLOperation(matchedAPI, s.jwtTransformer, metadata, s.subscriptionApplicationDatastore, s.cfg, string(req.GetRequestBody().Body)); immediateResponse != nil { + if immediateResponse := graphql.ValidateGraphQLOperation(rch, metadata, s.subscriptionApplicationDatastore, s.cfg, string(req.GetRequestBody().Body), s.jwtTransformer); immediateResponse != nil { + headers := &envoy_service_proc_v3.HeaderMutation{ + SetHeaders: []*corev3.HeaderValueOption{ + { + Header: &corev3.HeaderValue{ + Key: "Content-Type", + RawValue: []byte("Application/json"), + }, + }, + }, + } resp = &envoy_service_proc_v3.ProcessingResponse{ Response: &envoy_service_proc_v3.ProcessingResponse_ImmediateResponse{ ImmediateResponse: &envoy_service_proc_v3.ImmediateResponse{ Status: &v32.HttpStatus{ Code: v32.StatusCode(immediateResponse.StatusCode), }, - Body: []byte(immediateResponse.Message), + Body: []byte(immediateResponse.Message), + Headers: headers, }, }, } @@ -953,43 +975,52 @@ func extractExternalProcessingMetadata(data *corev3.Metadata) (*dto.ExternalProc if filterMatadata != nil { externalProcessingEnvoyMetadata := &dto.ExternalProcessingEnvoyMetadata{} jwtFilterdata := filterMatadata["envoy.filters.http.jwt_authn"] + authenticationData := &dto.JwtAuthenticationData{} if jwtFilterdata != nil { - jwtFields, exists := jwtFilterdata.Fields["payload_in_metadata"] - authenticationData := &dto.JwtAuthenticationData{} - if exists { - jwtPayload := jwtFields.GetStructValue() - if jwtPayload != nil { - claims := make(map[string]interface{}) - for key, value := range jwtPayload.GetFields() { - if value != nil { - if key == "iss" { - authenticationData.Issuer = value.GetStringValue() - } - switch value.Kind.(type) { - case *structpb.Value_StringValue: - claims[key] = value.GetStringValue() - case *structpb.Value_NumberValue: - claims[key] = value.GetNumberValue() - case *structpb.Value_BoolValue: - claims[key] = value.GetBoolValue() - case *structpb.Value_ListValue: - claims[key] = value.GetListValue() + for key, structValue := range jwtFilterdata.Fields { + if strings.HasSuffix(key, "-payload") { + sucessData := dto.JWTAuthenticationSuccessData{} + jwtPayload := structValue.GetStructValue() + if jwtPayload != nil { + claims := make(map[string]interface{}) + for key, value := range jwtPayload.GetFields() { + if value != nil { + if key == "iss" { + sucessData.Issuer = value.GetStringValue() + } + switch value.Kind.(type) { + case *structpb.Value_StringValue: + claims[key] = value.GetStringValue() + case *structpb.Value_NumberValue: + claims[key] = value.GetNumberValue() + case *structpb.Value_BoolValue: + claims[key] = value.GetBoolValue() + case *structpb.Value_ListValue: + claims[key] = value.GetListValue() + } } } + sucessData.Claims = claims + } + if authenticationData.SucessData == nil { + authenticationData.SucessData = make(map[string]*dto.JWTAuthenticationSuccessData) } - authenticationData.Claims = claims - fmt.Printf("claims: %v\n", claims) + authenticationData.SucessData[key] = &sucessData } - } - failureStatusFields, exists := jwtFilterdata.Fields["failed_status"] - if exists { - failureStatusStruct := failureStatusFields.GetStructValue() - if failureStatusStruct != nil { - code := failureStatusStruct.Fields["code"].GetNumberValue() - message := failureStatusStruct.Fields["message"].GetStringValue() - authenticationData.Status = &dto.Status{Code: int(code), Message: message} + if strings.HasSuffix(key, "-failed") { + failureStatusStruct := structValue.GetStructValue() + if failureStatusStruct != nil { + code := failureStatusStruct.Fields["code"].GetNumberValue() + message := failureStatusStruct.Fields["message"].GetStringValue() + authenticationFailureData := &dto.JWTAuthenticationFailureData{Code: int(code), Message: message} + if authenticationData.FailedData == nil { + authenticationData.FailedData = make(map[string]*dto.JWTAuthenticationFailureData) + } + authenticationData.FailedData[key] = authenticationFailureData + } } } + externalProcessingEnvoyMetadata.JwtAuthenticationData = authenticationData } if extProcMetadata, exists := filterMatadata[externalProessingMetadataContextKey]; exists { diff --git a/gateway/enforcer/internal/graphql/graphql.go b/gateway/enforcer/internal/graphql/graphql.go index bed0bd1df..8b5ca25f2 100644 --- a/gateway/enforcer/internal/graphql/graphql.go +++ b/gateway/enforcer/internal/graphql/graphql.go @@ -28,8 +28,9 @@ type GQLRequest struct { } // ValidateGraphQLOperation validates/authenticates the incoming GraphQL request. -func ValidateGraphQLOperation(matchedAPI *requestconfig.API, jwtTransformer *transformer.JWTTransformer, metadata *dto.ExternalProcessingEnvoyMetadata, subAppDataStore *datastore.SubscriptionApplicationDataStore, cfg *config.Server, requestBody string) *dto.ImmediateResponse { - schemaBytes := matchedAPI.APIDefinition +func ValidateGraphQLOperation(requestConfigHolder *requestconfig.Holder, metadata *dto.ExternalProcessingEnvoyMetadata, subAppDataStore *datastore.SubscriptionApplicationDataStore, cfg *config.Server, requestBody string, jwtTransformer *transformer.JWTTransformer) *dto.ImmediateResponse { + matchedapi := requestConfigHolder.MatchedAPI + schemaBytes := matchedapi.APIDefinition var sdl string if schemaString, err := unzipGzip(schemaBytes); err != nil { fmt.Println("unzip gzip not working") @@ -90,29 +91,21 @@ func ValidateGraphQLOperation(matchedAPI *requestconfig.API, jwtTransformer *tra for _, operation := range document.Operations { for _, selection := range operation.SelectionSet { - res := findMatchedResource(matchedAPI.Resources, operation, selection) + res := findMatchedResource(matchedapi.Resources, operation, selection) if res == nil { return &dto.ImmediateResponse{ StatusCode: 404, Message: "bad request - resource not found in schema", } } - rch := &requestconfig.Holder{} - rch.MatchedAPI = matchedAPI - rch.MatchedResource = res - if res.AuthenticationConfig != nil && !res.AuthenticationConfig.Disabled && !matchedAPI.DisableAuthentication { - jwtValidationInfo := jwtTransformer.TransformJWTClaims(matchedAPI.OrganizationID, metadata) - rch.JWTValidationInfo = &jwtValidationInfo - if immediateResponse := authorization.ValidateScopes(rch, subAppDataStore, cfg); immediateResponse != nil { + requestConfigHolder.MatchedResource = res + if res.AuthenticationConfig != nil && !res.AuthenticationConfig.Disabled && !matchedapi.DisableAuthentication { + immediateResponse := authorization.Validate(requestConfigHolder, subAppDataStore, cfg, jwtTransformer) + if immediateResponse != nil { return immediateResponse } - cfg.Logger.Info(fmt.Sprintf("Scope validation successful for the request: %s", rch.MatchedResource.Path)) - if immediateResponse := authorization.ValidateSubscription(rch, subAppDataStore, cfg); immediateResponse != nil { - return immediateResponse - } - cfg.Logger.Info(fmt.Sprintf("Subscription validation successful for the request: %s", rch.MatchedResource.Path)) } else { - cfg.Logger.Info(fmt.Sprintf("Skipping authentication for the resource: %s", rch.MatchedResource.Path)) + cfg.Logger.Info(fmt.Sprintf("Skipping authentication for the resource: %s", requestConfigHolder.MatchedResource.Path)) } } } diff --git a/gateway/enforcer/internal/transformer/jwtTransformer.go b/gateway/enforcer/internal/transformer/jwtTransformer.go index af99b5d6d..f4821db78 100644 --- a/gateway/enforcer/internal/transformer/jwtTransformer.go +++ b/gateway/enforcer/internal/transformer/jwtTransformer.go @@ -19,70 +19,76 @@ func NewJWTTransformer(jwtIssuerDatastore *datastore.JWTIssuerStore) *JWTTransfo } // TransformJWTClaims transforms the JWT claims -func (transformer *JWTTransformer) TransformJWTClaims(organization string, externalProcessingEnvoyMetadata *dto.ExternalProcessingEnvoyMetadata) dto.JWTValidationInfo { - if externalProcessingEnvoyMetadata == nil { - fmt.Printf("External processing envoy metadata is nil\n") - return dto.JWTValidationInfo{} - } - if externalProcessingEnvoyMetadata.JwtAuthenticationData == nil { +func (transformer *JWTTransformer) TransformJWTClaims(organization string, jwtAuthenticationData *dto.JwtAuthenticationData) *dto.JWTValidationInfo { + if jwtAuthenticationData == nil { fmt.Printf("JWT authentication data is nil\n") - return dto.JWTValidationInfo{} + return nil } - if externalProcessingEnvoyMetadata.JwtAuthenticationData.Claims == nil { - fmt.Printf("JWT claims are nil\n") - return dto.JWTValidationInfo{} + tokenissuers := transformer.tokenissuerStore.GetJWTISsuersByOrganization(organization) + if tokenissuers == nil { + fmt.Printf("Token issuers are nil\n") + return nil } - fmt.Printf("Organization: %v\n", organization) - fmt.Printf("External processing envoy metadata: %v\n", externalProcessingEnvoyMetadata) - fmt.Printf("JWT authentication data: %v\n", externalProcessingEnvoyMetadata.JwtAuthenticationData) - tokenIssuer := transformer.tokenissuerStore.GetJWTIssuerByOrganizationAndIssuer(organization, externalProcessingEnvoyMetadata.JwtAuthenticationData.Issuer) - jwtValidationInfo := dto.JWTValidationInfo{Issuer: externalProcessingEnvoyMetadata.JwtAuthenticationData.Issuer, Claims: make(map[string]interface{})} - if tokenIssuer != nil { - fmt.Printf("Token issuer: %v\n", tokenIssuer) - remoteClaims := externalProcessingEnvoyMetadata.JwtAuthenticationData.Claims - if remoteClaims != nil { - fmt.Printf("Remote claims: %v\n", remoteClaims) - audienceClaim := remoteClaims["aud"] - if audienceClaim != nil { - fmt.Printf("Audience claim: %v\n", audienceClaim) - switch audienceClaim.(type) { - case string: - audiences := []string{remoteClaims["aud"].(string)} - jwtValidationInfo.Audiences = audiences - case []string: - audiences := remoteClaims["aud"].([]string) - jwtValidationInfo.Audiences = audiences + var jwtValidationInfo dto.JWTValidationInfo + for _, tokenissuer := range tokenissuers { + fmt.Printf("Token issuer: %v\n", tokenissuer) + jwtAuthenticationDataSuccess, exists := jwtAuthenticationData.SucessData[tokenissuer.Issuer+"-payload"] + if exists { + jwtValidationInfo = dto.JWTValidationInfo{Valid: true, Issuer: jwtAuthenticationDataSuccess.Issuer, Claims: make(map[string]interface{})} + remoteClaims := jwtAuthenticationDataSuccess.Claims + if remoteClaims != nil { + issuedTime := remoteClaims["iat"] + if issuedTime != nil { + jwtValidationInfo.IssuedTime = int64(issuedTime.(float64)) } - } - remoteScopes := remoteClaims[tokenIssuer.ScopesClaim] - if remoteScopes != nil { - fmt.Printf("Remote scopes: %v\n", remoteScopes) - switch remoteScopes := remoteScopes.(type) { - case string: - scopes := strings.Split(remoteScopes, " ") - jwtValidationInfo.Scopes = scopes - case []string: - scopes := remoteScopes - jwtValidationInfo.Scopes = scopes + expiryTime := remoteClaims["exp"] + if expiryTime != nil { + jwtValidationInfo.ExpiryTime = int64(expiryTime.(float64)) } - } - remoteClientID := remoteClaims[tokenIssuer.ConsumerKeyClaim] - if remoteClientID != nil { - fmt.Printf("Remote client ID: %v\n", remoteClientID) - jwtValidationInfo.ClientID = remoteClientID.(string) - } - for claimKey, claimValue := range remoteClaims { - fmt.Printf("Claim key: %v, Claim value: %v\n", claimKey, claimValue) - if localClaim, ok := tokenIssuer.ClaimMapping[claimKey]; ok { - jwtValidationInfo.Claims[localClaim] = claimValue - } else { - jwtValidationInfo.Claims[claimKey] = claimValue + jti := remoteClaims["jti"] + if jti != nil { + jwtValidationInfo.JTI = jti.(string) + } + audienceClaim := remoteClaims["aud"] + if audienceClaim != nil { + switch audienceClaim.(type) { + case string: + audiences := []string{remoteClaims["aud"].(string)} + jwtValidationInfo.Audiences = audiences + case []string: + audiences := remoteClaims["aud"].([]string) + jwtValidationInfo.Audiences = audiences + } + } + remoteScopes := remoteClaims[tokenissuer.ScopesClaim] + if remoteScopes != nil { + switch remoteScopes := remoteScopes.(type) { + case string: + scopes := strings.Split(remoteScopes, " ") + jwtValidationInfo.Scopes = scopes + case []string: + scopes := remoteScopes + jwtValidationInfo.Scopes = scopes + } + } + remoteClientID := remoteClaims[tokenissuer.ConsumerKeyClaim] + if remoteClientID != nil { + jwtValidationInfo.ClientID = remoteClientID.(string) + } + for claimKey, claimValue := range remoteClaims { + if localClaim, ok := tokenissuer.ClaimMapping[claimKey]; ok { + jwtValidationInfo.Claims[localClaim] = claimValue + } else { + jwtValidationInfo.Claims[claimKey] = claimValue + } } } + return &jwtValidationInfo + } + jwtAuthenticationDataFailure, exists := jwtAuthenticationData.FailedData[tokenissuer.Issuer+"-failed"] + if exists { + jwtValidationInfo = dto.JWTValidationInfo{Valid: false, ValidationCode: jwtAuthenticationDataFailure.Code, ValidationMessage: jwtAuthenticationDataFailure.Message} } - } else { - fmt.Printf("Token issuer is nil\n") } - fmt.Printf("JWT validation info: %v\n", jwtValidationInfo) - return jwtValidationInfo + return &jwtValidationInfo }