Skip to content

Commit

Permalink
Merge pull request #1962 from AmaliMatharaarachchi/gql-test-r2
Browse files Browse the repository at this point in the history
Add GQL go integration tests
  • Loading branch information
AmaliMatharaarachchi authored Jan 19, 2024
2 parents 829b4dc + 18df4c8 commit 0ddbf38
Show file tree
Hide file tree
Showing 52 changed files with 418 additions and 179 deletions.
15 changes: 9 additions & 6 deletions adapter/internal/discovery/xds/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,13 +393,19 @@ func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource,
}

envoyGatewayConfig, gwFound := gatewayLabelConfigMap[gatewayName]
// gwFound means that the gateway is configured in the gateway cr.
listeners := envoyGatewayConfig.listeners
if !gwFound || listeners == nil || len(listeners) == 0 {
return nil, nil, nil, nil, nil
}
routeConfigs := make([]*routev3.RouteConfiguration, 0)
// TODO(amali) Revisit the following
// Find the matching listener for each vhost and then only add the routes to the routeConfigs
for _, listener := range listeners {
for vhost, routes := range vhostToRouteArrayMap {
// listener match pass in the following cases
// 1. vhost matches to a hostname in gateway
// 2. listener name matches
matchedListener, found := common.FindElement(dataholder.GetAllGatewayListeners(), func(listenerLocal gwapiv1b1.Listener) bool {
if listenerLocal.Hostname != nil && common.MatchesHostname(vhost, string(*listenerLocal.Hostname)) {
if listener.Name == common.GetEnvoyListenerName(string(listenerLocal.Protocol), uint32(listenerLocal.Port)) {
Expand All @@ -414,19 +420,16 @@ func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource,
routesConfig := oasParser.GetRouteConfigs(map[string][]*routev3.Route{vhost: routes}, routeConfigName, envoyGatewayConfig.customRateLimitPolicies)

routeConfigMatched, alreadyExistsInRouteConfigList := common.FindElement(routeConfigs, func(routeConf *routev3.RouteConfiguration) bool {
if routeConf.Name == routesConfig.Name {
return true
}
return false
return routeConf.Name == routesConfig.Name
})
if alreadyExistsInRouteConfigList {
logger.LoggerAPKOperator.Debugf("Route already exists. %+v", routesConfig.Name)
logger.LoggerAPKOperator.Debugf("Route already exists. %v", routesConfig.Name)
routeConfigMatched.VirtualHosts = append(routeConfigMatched.VirtualHosts, routesConfig.VirtualHosts...)
} else {
routeConfigs = append(routeConfigs, routesConfig)
}
} else {
logger.LoggerAPKOperator.Errorf("Failed to find a matching gateway listener for this vhost: %s", vhost)
logger.LoggerAPKOperator.Errorf("Failed to find a matching gateway listener for this vhost: %s in %v", vhost, listener.Name)
}
}
}
Expand Down
1 change: 0 additions & 1 deletion adapter/internal/oasparser/model/adapter_internal_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,6 @@ func (adapterInternalAPI *AdapterInternalAPI) SetInfoGQLRouteCR(gqlRoute *dpv1al
resourceAuthScheme = concatAuthSchemes(resourceAuthScheme, nil)
resourceRatelimitPolicy = concatRateLimitPolicies(resourceRatelimitPolicy, nil)

loggers.LoggerOasparser.Debugf("Calculating auths for API ..., API_UUID = %v", adapterInternalAPI.UUID)
apiAuth := getSecurity(resourceAuthScheme)

for _, match := range rule.Matches {
Expand Down
124 changes: 59 additions & 65 deletions adapter/internal/operator/controllers/dp/api_controller.go

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions adapter/internal/operator/synchronizer/gql_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package synchronizer

import (
"errors"
"fmt"

"github.com/wso2/apk/adapter/config"
"github.com/wso2/apk/adapter/internal/dataholder"
Expand Down Expand Up @@ -49,7 +48,7 @@ func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, e
environment = conf.Adapter.Environment
}
adapterInternalAPI.SetEnvironment(environment)

adapterInternalAPI.SetXWso2RequestBodyPass(true)
resourceParams := model.ResourceParams{
AuthSchemes: apiState.Authentications,
ResourceAuthSchemes: apiState.ResourceAuthentications,
Expand All @@ -66,10 +65,6 @@ func generateGQLAdapterInternalAPI(apiState APIState, gqlRoute *GQLRouteState, e
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2631, logging.MAJOR, "Error setting GQLRoute CR info to adapterInternalAPI. %v", err))
return nil, nil, err
}
if err := adapterInternalAPI.Validate(); err != nil {
loggers.LoggerAPKOperator.ErrorC(logging.PrintError(logging.Error2632, logging.MAJOR, "Error validating adapterInternalAPI intermediate representation. %v", err))
return nil, nil, err
}
vHosts := getVhostsForGQLAPI(gqlRoute.GQLRouteCombined)
labels := getLabelsForGQLAPI(gqlRoute.GQLRouteCombined)
listeners, relativeSectionNames := getListenersForGQLAPI(gqlRoute.GQLRouteCombined, adapterInternalAPI.UUID)
Expand Down Expand Up @@ -104,7 +99,6 @@ func getVhostsForGQLAPI(gqlRoute *v1alpha2.GQLRoute) []string {
for _, hostName := range gqlRoute.Spec.Hostnames {
vHosts = append(vHosts, string(hostName))
}
fmt.Println("vhosts size: ", len(vHosts))
return vHosts
}

Expand Down
7 changes: 1 addition & 6 deletions adapter/internal/operator/synchronizer/rest_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package synchronizer

import (
"errors"
"fmt"

"github.com/wso2/apk/adapter/config"
"github.com/wso2/apk/adapter/internal/dataholder"
Expand Down Expand Up @@ -131,7 +130,6 @@ func getVhostsForAPI(httpRoute *gwapiv1b1.HTTPRoute) []string {
for _, hostName := range httpRoute.Spec.Hostnames {
vHosts = append(vHosts, string(hostName))
}
fmt.Println("vhosts size: ", len(vHosts))
return vHosts
}

Expand Down Expand Up @@ -166,10 +164,7 @@ func getListenersForAPI(httpRoute *gwapiv1b1.HTTPRoute, apiUUID string) ([]strin
if found {
// find the matching listener
matchedListener, listenerFound := common.FindElement(gateway.Spec.Listeners, func(listener gwapiv1b1.Listener) bool {
if string(listener.Name) == string(*parentRef.SectionName) {
return true
}
return false
return string(listener.Name) == string(*parentRef.SectionName)
})
if listenerFound {
sectionNames = append(sectionNames, string(matchedListener.Name))
Expand Down
2 changes: 0 additions & 2 deletions common-go-libs/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ module github.com/wso2/apk/common-go-libs

go 1.19

replace github.com/wso2/apk/adapter => ../adapter

require (
github.com/envoyproxy/go-control-plane v0.11.2-0.20230802074621-eea0b3bd0f81
github.com/onsi/ginkgo/v2 v2.9.5
Expand Down
2 changes: 1 addition & 1 deletion developer/tryout/samples/sample-api-policy.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: backend-jwt-token-policy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.wso2.apk.enforcer.config.EnforcerConfig;
import org.wso2.apk.enforcer.discovery.api.Api;
import org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo;
import org.wso2.apk.enforcer.discovery.api.Certificate;
import org.wso2.apk.enforcer.discovery.api.Claim;
import org.wso2.apk.enforcer.discovery.api.Operation;
import org.wso2.apk.enforcer.discovery.api.Resource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@
import org.wso2.apk.enforcer.commons.model.ResourceConfig;
import org.wso2.apk.enforcer.config.ConfigHolder;
import org.wso2.apk.enforcer.config.EnforcerConfig;
import org.wso2.apk.enforcer.config.dto.FilterDTO;
import org.wso2.apk.enforcer.constants.APIConstants;
import org.wso2.apk.enforcer.constants.HttpConstants;
import org.wso2.apk.enforcer.cors.CorsFilter;
import org.wso2.apk.enforcer.discovery.api.Api;
import org.wso2.apk.enforcer.discovery.api.BackendJWTTokenInfo;
import org.wso2.apk.enforcer.discovery.api.Certificate;
import org.wso2.apk.enforcer.discovery.api.Claim;
import org.wso2.apk.enforcer.discovery.api.Operation;
import org.wso2.apk.enforcer.discovery.api.Resource;
Expand All @@ -48,12 +46,9 @@
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

/**
* Specific implementation for a Rest API type APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.wso2.apk.enforcer.commons.exception.EnforcerException;
import org.wso2.apk.enforcer.commons.model.APIConfig;
import org.wso2.apk.enforcer.commons.model.AuthenticationContext;
import org.wso2.apk.enforcer.commons.model.JWTAuthenticationConfig;
import org.wso2.apk.enforcer.commons.model.RequestContext;
import org.wso2.apk.enforcer.commons.model.ResourceConfig;
import org.wso2.apk.enforcer.config.ConfigHolder;
Expand Down Expand Up @@ -86,12 +85,10 @@ public JWTAuthenticator(final JWTConfigurationDto jwtConfigurationDto, final boo

@Override
public boolean canAuthenticate(RequestContext requestContext) {
// only getting first operation is enough as all matched resource configs have the same security schemes
// i.e. graphQL apis do not support resource level security yet
JWTAuthenticationConfig jwtAuthenticationConfig =
requestContext.getMatchedResourcePaths().get(0).getAuthenticationConfig().getJwtAuthenticationConfig();
if (jwtAuthenticationConfig != null) {
String authHeaderValue = retrieveAuthHeaderValue(requestContext, jwtAuthenticationConfig);
String authHeader = getTokenHeader(requestContext.getMatchedResourcePaths());

if (!StringUtils.equals(authHeader, "")) {
String authHeaderValue = retrieveAuthHeaderValue(requestContext, authHeader);

// Check keyword bearer in header to prevent conflicts with custom authentication
// (that maybe added with custom filters / interceptors / opa)
Expand Down Expand Up @@ -122,8 +119,8 @@ public AuthenticationContext authenticate(RequestContext requestContext) throws
Utils.setTag(jwtAuthenticatorInfoSpan, APIConstants.LOG_TRACE_ID,
ThreadContext.get(APIConstants.LOG_TRACE_ID));
}
String jwtToken = retrieveAuthHeaderValue(requestContext,
requestContext.getMatchedResourcePaths().get(0).getAuthenticationConfig().getJwtAuthenticationConfig());
String authHeader = getTokenHeader(requestContext.getMatchedResourcePaths());
String jwtToken = retrieveAuthHeaderValue(requestContext, authHeader);
String[] splitToken = jwtToken.split("\\s");
// Extract the token when it is sent as bearer token. i.e Authorization: Bearer <token>
if (splitToken.length > 1) {
Expand Down Expand Up @@ -267,6 +264,16 @@ public AuthenticationContext authenticate(RequestContext requestContext) throws

}

private String getTokenHeader(ArrayList<ResourceConfig> matchedResourceConfigs) {
for (ResourceConfig resourceConfig : matchedResourceConfigs) {
if (resourceConfig.getAuthenticationConfig() != null &&
resourceConfig.getAuthenticationConfig().getJwtAuthenticationConfig() != null) {
return resourceConfig.getAuthenticationConfig().getJwtAuthenticationConfig().getHeader();
}
}
return "";
}

@Override
public String getChallengeString() {

Expand All @@ -285,11 +292,9 @@ public int getPriority() {
return 10;
}

private String retrieveAuthHeaderValue(RequestContext requestContext,
JWTAuthenticationConfig jwtAuthenticationConfig) {

private String retrieveAuthHeaderValue(RequestContext requestContext, String header) {
Map<String, String> headers = requestContext.getHeaders();
return headers.get(jwtAuthenticationConfig.getHeader());
return headers.get(header);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- if and .Values.wso2.apk.dp.enabled .Values.wso2.apk.dp.configdeployer.enabled }}
apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: "{{ template "apk-helm.resource.prefix" . }}-config-api-api-policy"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------
apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: interceptor-policy-api-level
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------
apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: interceptor-policy-gateway-level
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------
apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: interceptor-policy-resource-level
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.
# -----------------------------------------------------------------------

apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: sample-api-policy
Expand Down
2 changes: 1 addition & 1 deletion test/cucumber-tests/CRs/artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ spec:
configMap:
name: "interceptor-service-config-toml"
---
apiVersion: dp.wso2.com/v1alpha1
apiVersion: dp.wso2.com/v1alpha2
kind: APIPolicy
metadata:
name: interceptor-policy-gateway-level
Expand Down
100 changes: 100 additions & 0 deletions test/integration/integration/tests/gql-api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2023, 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 tests

import (
"testing"

"github.com/wso2/apk/test/integration/integration/utils/http"
"github.com/wso2/apk/test/integration/integration/utils/suite"
)

func init() {
IntegrationTests = append(IntegrationTests, GQLAPI)
}

// DisableAPISecurity test
var GQLAPI = suite.IntegrationTest{
ShortName: "GQLAPI",
Description: "Tests GraphQL API",
Manifests: []string{"tests/gql-api.yaml"},
Test: func(t *testing.T, suite *suite.IntegrationTestSuite) {
gwAddr := "gql.test.gw.wso2.com:9095"
token := http.GetTestToken(t)

testCases := []http.ExpectedResponse{
{
Request: http.Request{
Host: "gql.test.gw.wso2.com",
Path: "/gql/v1",
Method: "POST",
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n}","variables":{}}`,
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Method: ""},
},
Response: http.Response{StatusCode: 200},
},
{
Request: http.Request{
Host: "gql.test.gw.wso2.com",
Path: "/gql/v1",
Method: "POST",
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n droid(id:2000){\n name\n friends{\n name\n appearsIn\n }\n }\n}","variables":{}}`,
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Method: "",
},
},
Response: http.Response{StatusCode: 401},
}, {
Request: http.Request{
Host: "gql.test.gw.wso2.com",
Path: "/gql/v1",
Method: "POST",
Headers: map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
Body: `{"query":"query{\n human(id:1000){\n id\n name\n }\n droid(id:2000){\n name\n friends{\n name\n appearsIn\n }\n }\n}","variables":{}}`,
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Method: "",
},
},
Response: http.Response{StatusCode: 200},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
t.Parallel()
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc)
})
}
},
}
Loading

0 comments on commit 0ddbf38

Please sign in to comment.