diff --git a/adapter/internal/api/apis_impl.go b/adapter/internal/api/apis_impl.go index fd9e3c6a34..09054c5b84 100644 --- a/adapter/internal/api/apis_impl.go +++ b/adapter/internal/api/apis_impl.go @@ -137,7 +137,7 @@ func extractAPIProject(payload []byte) (apiProject ProjectAPI, err error) { upstreamCerts = append(upstreamCerts, unzippedFileBytes...) upstreamCerts = append(upstreamCerts, newLineByteArray...) } else if (strings.Contains(file.Name, apiYAMLFile) || strings.Contains(file.Name, apiJSONFile)) && - !strings.Contains(file.Name, openAPIDir){ + !strings.Contains(file.Name, openAPIDir) { loggers.LoggerAPI.Debugf("fileName : %v", file.Name) unzippedFileBytes, err := readZipFile(file) if err != nil { @@ -257,7 +257,13 @@ func ApplyAPIProjectFromAPIM(payload []byte, vhostToEnvsMap map[string][]string) } } // first update the API for vhost - updateAPI(vhost, apiInfo, apiProject, environments) + apiContent := generateAPIContent(vhost, apiInfo, apiProject, environments) + // In APIM mode, it is required to skip rather than rejecting the whole API deployment. + // IN APIM scenario, APIM does the validation. Hence the override remains true. + if xds.ValidateAPI(apiContent, true) != nil { + continue + } + xds.UpdateAPI(apiContent) } // undeploy APIs with other vhosts in the same gateway environment @@ -315,17 +321,25 @@ func ApplyAPIProjectInStandaloneMode(payload []byte, override *bool) error { append(vhostToEnvsMap[environment.DeploymentVhost], environment.DeploymentEnvironment) } + // From this all the APIs would be validated prior to update Xds Caches. // TODO: (renuka) optimize to update cache only once when all internal memory maps are updated for vhost, environments := range vhostToEnvsMap { - updateAPI(vhost, apiInfo, apiProject, environments) + apiContent := generateAPIContent(vhost, apiInfo, apiProject, environments) + err := xds.ValidateAPI(apiContent, overrideValue) + if err != nil { + return errors.New(mgw.ValidationFailure + " : " + err.Error()) + } + } + + // After the API is validated against all the vhosts, update xds cache. + for vhost, environments := range vhostToEnvsMap { + apiContent := generateAPIContent(vhost, apiInfo, apiProject, environments) + xds.UpdateAPI(apiContent) } return nil } -func updateAPI(vhost string, apiInfo ApictlProjectInfo, apiProject ProjectAPI, environments []string) { - if len(environments) == 0 { - environments = append(environments, config.DefaultGatewayName) - } +func generateAPIContent(vhost string, apiInfo ApictlProjectInfo, apiProject ProjectAPI, environments []string) config.APIContent { var apiContent config.APIContent apiContent.UUID = apiInfo.ID apiContent.VHost = vhost @@ -350,7 +364,7 @@ func updateAPI(vhost string, apiInfo ApictlProjectInfo, apiProject ProjectAPI, e } else if apiProject.APIType == mgw.WS { apiContent.APIDefinition = apiProject.APIJsn } - xds.UpdateAPI(apiContent) + return apiContent } func extractAPIInformation(apiProject *ProjectAPI, apiObject config.APIJsonData) { diff --git a/adapter/internal/api/models/error.go b/adapter/internal/api/models/error.go index eb9bea8080..8fe04fc710 100644 --- a/adapter/internal/api/models/error.go +++ b/adapter/internal/api/models/error.go @@ -1,6 +1,6 @@ // Code generated by go-swagger; DO NOT EDIT. -// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2021, WSO2 Inc. (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. diff --git a/adapter/internal/api/models/error_list_item.go b/adapter/internal/api/models/error_list_item.go index 1807cd98ba..193ddd0680 100644 --- a/adapter/internal/api/models/error_list_item.go +++ b/adapter/internal/api/models/error_list_item.go @@ -1,6 +1,6 @@ // Code generated by go-swagger; DO NOT EDIT. -// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2021, WSO2 Inc. (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. diff --git a/adapter/internal/api/restserver/config_restapi.go b/adapter/internal/api/restserver/config_restapi.go index 435aa137af..4e50c2e6a9 100644 --- a/adapter/internal/api/restserver/config_restapi.go +++ b/adapter/internal/api/restserver/config_restapi.go @@ -19,12 +19,13 @@ package restserver import ( "crypto/tls" - "github.com/wso2/adapter/internal/discovery/xds" "io/ioutil" "net/http" "strconv" "strings" + "github.com/wso2/adapter/internal/discovery/xds" + "github.com/go-openapi/errors" "github.com/go-openapi/loads" "github.com/go-openapi/runtime" @@ -145,6 +146,14 @@ func configureAPI(api *operations.RestapiAPI) http.Handler { jsonByteArray, _ := ioutil.ReadAll(params.File) err := apiServer.ApplyAPIProjectInStandaloneMode(jsonByteArray, params.Override) if err != nil { + if strings.Contains(err.Error(), constants.ValidationFailure) { + errorResp := api_individual.NewPostApisBadRequest() + payload := &models.Error{ + Description: err.Error(), + } + errorResp.Payload = payload + return errorResp + } switch err.Error() { case constants.AlreadyExists: return api_individual.NewPostApisConflict() diff --git a/adapter/internal/api/restserver/embedded_spec.go b/adapter/internal/api/restserver/embedded_spec.go index 7be6e8c667..e38d9b174c 100644 --- a/adapter/internal/api/restserver/embedded_spec.go +++ b/adapter/internal/api/restserver/embedded_spec.go @@ -158,6 +158,12 @@ func init() { "$ref": "#/definitions/DeployResponse" } }, + "400": { + "description": "Bad Request\nInvalid request or validation error\n", + "schema": { + "$ref": "#/definitions/Error" + } + }, "401": { "$ref": "#/responses/Unauthorized" }, @@ -463,7 +469,6 @@ func init() { "BearerToken": { "type": "oauth2", "flow": "password", - "authorizationUrl": "", "tokenUrl": "/oauth2/token", "scopes": { "admin": "Grants deploy, undeploy, and list access" @@ -603,6 +608,12 @@ func init() { "$ref": "#/definitions/DeployResponse" } }, + "400": { + "description": "Bad Request\nInvalid request or validation error\n", + "schema": { + "$ref": "#/definitions/Error" + } + }, "401": { "description": "Unauthorized. Invalid authentication credentials.", "schema": { @@ -926,7 +937,6 @@ func init() { "BearerToken": { "type": "oauth2", "flow": "password", - "authorizationUrl": "", "tokenUrl": "/oauth2/token", "scopes": { "admin": "Grants deploy, undeploy, and list access" diff --git a/adapter/internal/api/restserver/operations/api_collection/get_apis.go b/adapter/internal/api/restserver/operations/api_collection/get_apis.go index 816586ae1f..a9cd89a50b 100644 --- a/adapter/internal/api/restserver/operations/api_collection/get_apis.go +++ b/adapter/internal/api/restserver/operations/api_collection/get_apis.go @@ -62,7 +62,7 @@ type GetApis struct { func (o *GetApis) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewGetApisParams() uprinc, aCtx, err := o.Context.Authorize(r, route) @@ -71,7 +71,7 @@ func (o *GetApis) ServeHTTP(rw http.ResponseWriter, r *http.Request) { return } if aCtx != nil { - r = aCtx + *r = *aCtx } var principal *models.Principal if uprinc != nil { diff --git a/adapter/internal/api/restserver/operations/api_individual/delete_apis.go b/adapter/internal/api/restserver/operations/api_individual/delete_apis.go index 2077af0ef9..9447108ff8 100644 --- a/adapter/internal/api/restserver/operations/api_individual/delete_apis.go +++ b/adapter/internal/api/restserver/operations/api_individual/delete_apis.go @@ -62,7 +62,7 @@ type DeleteApis struct { func (o *DeleteApis) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewDeleteApisParams() uprinc, aCtx, err := o.Context.Authorize(r, route) @@ -71,7 +71,7 @@ func (o *DeleteApis) ServeHTTP(rw http.ResponseWriter, r *http.Request) { return } if aCtx != nil { - r = aCtx + *r = *aCtx } var principal *models.Principal if uprinc != nil { diff --git a/adapter/internal/api/restserver/operations/api_individual/post_apis.go b/adapter/internal/api/restserver/operations/api_individual/post_apis.go index 49a53873cb..036b9b9fbb 100644 --- a/adapter/internal/api/restserver/operations/api_individual/post_apis.go +++ b/adapter/internal/api/restserver/operations/api_individual/post_apis.go @@ -62,7 +62,7 @@ type PostApis struct { func (o *PostApis) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewPostApisParams() uprinc, aCtx, err := o.Context.Authorize(r, route) @@ -71,7 +71,7 @@ func (o *PostApis) ServeHTTP(rw http.ResponseWriter, r *http.Request) { return } if aCtx != nil { - r = aCtx + *r = *aCtx } var principal *models.Principal if uprinc != nil { diff --git a/adapter/internal/api/restserver/operations/api_individual/post_apis_responses.go b/adapter/internal/api/restserver/operations/api_individual/post_apis_responses.go index c83f363e19..0f621ea2bd 100644 --- a/adapter/internal/api/restserver/operations/api_individual/post_apis_responses.go +++ b/adapter/internal/api/restserver/operations/api_individual/post_apis_responses.go @@ -74,6 +74,52 @@ func (o *PostApisOK) WriteResponse(rw http.ResponseWriter, producer runtime.Prod } } +// PostApisBadRequestCode is the HTTP code returned for type PostApisBadRequest +const PostApisBadRequestCode int = 400 + +/*PostApisBadRequest Bad Request +Invalid request or validation error + + +swagger:response postApisBadRequest +*/ +type PostApisBadRequest struct { + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewPostApisBadRequest creates PostApisBadRequest with default headers values +func NewPostApisBadRequest() *PostApisBadRequest { + + return &PostApisBadRequest{} +} + +// WithPayload adds the payload to the post apis bad request response +func (o *PostApisBadRequest) WithPayload(payload *models.Error) *PostApisBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post apis bad request response +func (o *PostApisBadRequest) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostApisBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + // PostApisUnauthorizedCode is the HTTP code returned for type PostApisUnauthorized const PostApisUnauthorizedCode int = 401 diff --git a/adapter/internal/api/restserver/operations/authorization/post_oauth2_token.go b/adapter/internal/api/restserver/operations/authorization/post_oauth2_token.go index 0bc3bb13ac..f5663c6556 100644 --- a/adapter/internal/api/restserver/operations/authorization/post_oauth2_token.go +++ b/adapter/internal/api/restserver/operations/authorization/post_oauth2_token.go @@ -64,7 +64,7 @@ type PostOauth2Token struct { func (o *PostOauth2Token) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewPostOauth2TokenParams() if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params diff --git a/adapter/internal/api/restserver/server.go b/adapter/internal/api/restserver/server.go index 41d1791962..0542d4d7eb 100644 --- a/adapter/internal/api/restserver/server.go +++ b/adapter/internal/api/restserver/server.go @@ -1,6 +1,6 @@ // Code generated by go-swagger; DO NOT EDIT. -// Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// Copyright (c) 2021, WSO2 Inc. (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. diff --git a/adapter/internal/discovery/xds/server.go b/adapter/internal/discovery/xds/server.go index a94b5a6b07..75ff15b906 100644 --- a/adapter/internal/discovery/xds/server.go +++ b/adapter/internal/discovery/xds/server.go @@ -69,8 +69,12 @@ var ( apiUUIDToGatewayToVhosts map[string]map[string]string // API_UUID -> gateway-env -> vhost (for un-deploying APIs from APIM or Choreo) apiToVhostsMap map[string]map[string]struct{} // APIName:Version -> VHosts set (for un-deploying APIs from API-CTL) + internalSwaggerMap struct { + sync.RWMutex + apiMgwSwaggerMap map[string]mgw.MgwSwagger // Vhost:APIName:Version -> MgwSwagger struct map + vhostBasePathToNameVersionMap map[string]string // Vhost:BasePath -> APIName:Version + } // Vhost:APIName:Version as map key - apiMgwSwaggerMap map[string]mgw.MgwSwagger // Vhost:APIName:Version -> MgwSwagger struct map openAPIEnvoyMap map[string][]string // Vhost:APIName:Version -> Envoy Label Array map openAPIRoutesMap map[string][]*routev3.Route // Vhost:APIName:Version -> Envoy Routes map openAPIClustersMap map[string][]*clusterv3.Cluster // Vhost:APIName:Version -> Envoy Clusters map @@ -135,7 +139,9 @@ func init() { apiUUIDToGatewayToVhosts = make(map[string]map[string]string) apiToVhostsMap = make(map[string]map[string]struct{}) - apiMgwSwaggerMap = make(map[string]mgw.MgwSwagger) + + internalSwaggerMap.apiMgwSwaggerMap = make(map[string]mgw.MgwSwagger) + internalSwaggerMap.vhostBasePathToNameVersionMap = make(map[string]string) openAPIEnvoyMap = make(map[string][]string) openAPIEnforcerApisMap = make(map[string]types.Resource) openAPIRoutesMap = make(map[string][]*routev3.Route) @@ -215,6 +221,65 @@ func GetEnforcerThrottleDataCache() wso2_cache.SnapshotCache { return enforcerThrottleDataCache } +// ValidateAPI validates the API Content prior to xds update. +func ValidateAPI(apiContent config.APIContent, override bool) error { + // validate API Type + if !(apiContent.APIType == mgw.HTTP || apiContent.APIType == mgw.WS) { + logger.LoggerXds.Errorf("API type : %s not currently supported with WSO2 Microgateway", apiContent.APIType) + return errors.New("API is not applied as provided type is not supported") + } + + mgwSwagger := populateMgwSwaggerFromAPIContent(apiContent) + basePathMapKey := apiContent.VHost + ":" + mgwSwagger.GetXWso2Basepath() + internalSwaggerMap.RLock() + nameVersionEntry, ok := internalSwaggerMap.vhostBasePathToNameVersionMap[basePathMapKey] + internalSwaggerMap.RUnlock() + // If the API is found in basePathMap, it should be invalidated unless override flag is set to true. + if ok { + // if the override is enabled and the title version remains same against the vhost:basepath entry it is valid + if !(override && nameVersionEntry == mgwSwagger.GetTitle()+":"+mgwSwagger.GetVersion()) { + logger.LoggerXds.Errorf("API %s:%s:%s already exists under provided basePath : %s ", + apiContent.VHost, mgwSwagger.GetTitle(), mgwSwagger.GetVersion(), mgwSwagger.GetXWso2Basepath()) + return errors.New("API is not applied as basePath is already used") + } + } + + validationErr := mgwSwagger.Validate() + if validationErr != nil { + logger.LoggerOasparser.Errorf("Validation failed for the API. %s:%s:%s", + apiContent.VHost, mgwSwagger.GetTitle(), mgwSwagger.GetVersion()) + return validationErr + } + return nil +} + +// TODO: (VirajSalaka) Optimize such that the same method is not called twice over one API Update. +func populateMgwSwaggerFromAPIContent(apiContent config.APIContent) mgw.MgwSwagger { + var modifiedMgwSwagger mgw.MgwSwagger + if apiContent.APIType == mgw.HTTP { + modifiedMgwSwagger = operator.GetMgwSwagger(apiContent.APIDefinition) + modifiedMgwSwagger.SetID(apiContent.UUID) + modifiedMgwSwagger.SetName(apiContent.Name) + modifiedMgwSwagger.SetVersion(apiContent.Version) + modifiedMgwSwagger.SetSecurityScheme(apiContent.SecurityScheme) + modifiedMgwSwagger.SetXWso2AuthHeader(apiContent.AuthHeader) + modifiedMgwSwagger.OrganizationID = apiContent.OrganizationID + } else if apiContent.APIType == mgw.WS { + modifiedMgwSwagger = operator.GetMgwSwaggerWebSocket(apiContent.APIDefinition) + modifiedMgwSwagger.OrganizationID = apiContent.OrganizationID + } else { + // Unreachable else condition. Added in case previous apiType check fails due to any modifications. + return modifiedMgwSwagger + } + + if (len(modifiedMgwSwagger.GetProdEndpoints()) == 0 || modifiedMgwSwagger.GetProdEndpoints()[0].Host == "/") && + (len(modifiedMgwSwagger.GetSandEndpoints()) == 0 || modifiedMgwSwagger.GetSandEndpoints()[0].Host == "/") { + modifiedMgwSwagger.SetXWso2ProductionEndpointMgwSwagger(apiContent.ProductionEndpoint) + modifiedMgwSwagger.SetXWso2SandboxEndpointForMgwSwagger(apiContent.SandboxEndpoint) + } + return modifiedMgwSwagger +} + // UpdateAPI updates the Xds Cache when OpenAPI Json content is provided func UpdateAPI(apiContent config.APIContent) { var newLabels []string @@ -228,46 +293,14 @@ func UpdateAPI(apiContent config.APIContent) { l.Lock() defer l.Unlock() - if apiContent.APIType == mgw.HTTP { - mgwSwagger = operator.GetMgwSwagger(apiContent.APIDefinition) - mgwSwagger.SetID(apiContent.UUID) - mgwSwagger.SetName(apiContent.Name) - mgwSwagger.SetVersion(apiContent.Version) - mgwSwagger.SetSecurityScheme(apiContent.SecurityScheme) - mgwSwagger.SetXWso2AuthHeader(apiContent.AuthHeader) - mgwSwagger.OrganizationID = apiContent.OrganizationID - } else if apiContent.APIType == mgw.WS { - mgwSwagger = operator.GetMgwSwaggerWebSocket(apiContent.APIDefinition) - mgwSwagger.OrganizationID = apiContent.OrganizationID - } else { - // Unreachable else condition. Added in case previous apiType check fails due to any modifications. - logger.LoggerXds.Error("API type not currently supported with WSO2 Microgateway") - } + mgwSwagger = populateMgwSwaggerFromAPIContent(apiContent) - if (len(mgwSwagger.GetProdEndpoints()) == 0 || mgwSwagger.GetProdEndpoints()[0].Host == "/") && - (len(mgwSwagger.GetSandEndpoints()) == 0 || mgwSwagger.GetSandEndpoints()[0].Host == "/") { - mgwSwagger.SetXWso2ProductionEndpointMgwSwagger(apiContent.ProductionEndpoint) - mgwSwagger.SetXWso2SandboxEndpointForMgwSwagger(apiContent.SandboxEndpoint) - } - - validationErr := mgwSwagger.Validate() - if validationErr != nil { - logger.LoggerOasparser.Errorf("Validation failed for the API. %s:%s", mgwSwagger.GetTitle(), mgwSwagger.GetVersion()) + // TODO: (VirajSalaka) Use pointers + if mgwSwagger.GetTitle() == "" { return } - apiIdentifier := GenerateIdentifierForAPI(apiContent.VHost, apiContent.Name, apiContent.Version) - //TODO: (SuKSW) Uncomment the below section depending on MgwSwagger.Resource ids - //TODO: (SuKSW) Update the existing API if the basepath already exists - //existingMgwSwagger, exists := apiMgwSwaggerMap[apiIdentifier] - // if exists { - // if reflect.DeepEqual(mgwSwagger, existingMgwSwagger) { - // logger.LoggerXds.Infof("API %v already exists. No changes to apply.", apiIdentifier) - // return - // } - // } - apiMgwSwaggerMap[apiIdentifier] = mgwSwagger - //TODO: (VirajSalaka) Handle OpenAPIs which does not have label (Current Impl , it will be labelled as default) + addEntryToAPIMgwSwaggerMap(apiContent.VHost, mgwSwagger) // TODO: commented the following line as the implementation is not supported yet. //newLabels = model.GetXWso2Label(openAPIV3Struct.ExtensionProps) //:TODO: since currently labels are not taking from x-wso2-label, I have made it to be taken from the method @@ -386,12 +419,13 @@ func DeleteAPIWithAPIMEvent(uuid, name, version string, environments []string) { // deleteAPI deletes an API, its resources and updates the caches of given environments func deleteAPI(apiIdentifier string, environments []string) error { - _, exists := apiMgwSwaggerMap[apiIdentifier] + internalSwaggerMap.RLock() + _, exists := internalSwaggerMap.apiMgwSwaggerMap[apiIdentifier] + internalSwaggerMap.RUnlock() if !exists { logger.LoggerXds.Infof("Unable to delete API " + apiIdentifier + ". Does not exist.") return errors.New(mgw.NotFound) } - existingLabels := openAPIEnvoyMap[apiIdentifier] toBeDelEnvs, toBeKeptEnvs := getEnvironmentsToBeDeleted(existingLabels, environments) @@ -414,8 +448,8 @@ func deleteAPI(apiIdentifier string, environments []string) error { //resources that belongs to the remaining APIs updateXdsCacheOnAPIAdd(toBeDelEnvs, []string{}) - delete(openAPIEnvoyMap, apiIdentifier) //delete labels - delete(apiMgwSwaggerMap, apiIdentifier) //delete mgwSwagger + delete(openAPIEnvoyMap, apiIdentifier) //delete labels + deleteFromAPIMgwSwaggerMap(apiIdentifier) //delete mgwSwagger //TODO: (SuKSW) clean any remaining in label wise maps, if this is the last API of that label logger.LoggerXds.Infof("Deleted API. %v", apiIdentifier) return nil @@ -701,14 +735,15 @@ func UpdateXdsCacheWithLock(label string, endpoints []types.Resource, clusters [ // ListApis returns a list of objects that holds info about each API func ListApis(apiType string, limit *int64) *apiModel.APIMeta { var limitValue int + internalSwaggerMap.RLock() if limit == nil { - limitValue = len(apiMgwSwaggerMap) + limitValue = len(internalSwaggerMap.apiMgwSwaggerMap) } else { limitValue = int(*limit) } var apisArray []*apiModel.APIMetaListItem i := 0 - for apiIdentifier, mgwSwagger := range apiMgwSwaggerMap { + for apiIdentifier, mgwSwagger := range internalSwaggerMap.apiMgwSwaggerMap { if i == limitValue { break } @@ -729,7 +764,8 @@ func ListApis(apiType string, limit *int64) *apiModel.APIMeta { } } var apiMetaObject apiModel.APIMeta - apiMetaObject.Total = int64(len(apiMgwSwaggerMap)) + apiMetaObject.Total = int64(len(internalSwaggerMap.apiMgwSwaggerMap)) + internalSwaggerMap.RUnlock() apiMetaObject.Count = int64(len(apisArray)) apiMetaObject.List = apisArray return &apiMetaObject @@ -738,7 +774,9 @@ func ListApis(apiType string, limit *int64) *apiModel.APIMeta { // IsAPIExist returns whether a given API exists func IsAPIExist(vhost, name, version string) (exists bool) { apiIdentifier := GenerateIdentifierForAPI(vhost, name, version) - _, exists = apiMgwSwaggerMap[apiIdentifier] + internalSwaggerMap.RLock() + _, exists = internalSwaggerMap.apiMgwSwaggerMap[apiIdentifier] + internalSwaggerMap.RUnlock() return exists } @@ -864,10 +902,16 @@ func buildAndStoreSuccessState(label string, version string) { // Enforcer resources mutexForCacheUpdate.Lock() defer mutexForCacheUpdate.Unlock() + internalSwaggerMap.RLock() newAPIMgwSwaggerMap := make(map[string]mgw.MgwSwagger) - for k, v := range apiMgwSwaggerMap { + for k, v := range internalSwaggerMap.apiMgwSwaggerMap { newAPIMgwSwaggerMap[k] = v } + newBasePathAPIMap := make(map[string]string) + for k, v := range internalSwaggerMap.vhostBasePathToNameVersionMap { + newBasePathAPIMap[k] = v + } + internalSwaggerMap.RUnlock() newOpenAPIEnforcerApisMap := make(map[string]types.Resource) for k, v := range openAPIEnforcerApisMap { newOpenAPIEnforcerApisMap[k] = v @@ -912,6 +956,7 @@ func buildAndStoreSuccessState(label string, version string) { } enforcerNewState := EnforcerAPIState{ Apis: newAPIMgwSwaggerMap, + BasePathAPIMap: newBasePathAPIMap, OpenAPIEnforcerApisMap: newOpenAPIEnforcerApisMap, APIToVhostsMap: newAPIToVhostsMap, APIUUIDToGatewayToVhosts: newAPIUUIDToGatewayToVhosts, @@ -935,7 +980,10 @@ func restorePreviousState(label string) string { // Initialize new maps and populate the success state from the retrieved state cache. // Enforcer resources - apiMgwSwaggerMap = enforcerOldState.Apis + internalSwaggerMap.Lock() + internalSwaggerMap.apiMgwSwaggerMap = enforcerOldState.Apis + internalSwaggerMap.vhostBasePathToNameVersionMap = enforcerOldState.BasePathAPIMap + internalSwaggerMap.Unlock() openAPIEnforcerApisMap = enforcerOldState.OpenAPIEnforcerApisMap apiToVhostsMap = enforcerOldState.APIToVhostsMap apiUUIDToGatewayToVhosts = enforcerOldState.APIUUIDToGatewayToVhosts @@ -981,3 +1029,26 @@ func watchEnforcerResponse() { } } } + +func addEntryToAPIMgwSwaggerMap(vhost string, mgwSwagger mgw.MgwSwagger) { + apiIdentifier := GenerateIdentifierForAPI(vhost, mgwSwagger.GetTitle(), mgwSwagger.GetVersion()) + internalSwaggerMap.Lock() + internalSwaggerMap.apiMgwSwaggerMap[apiIdentifier] = mgwSwagger + internalSwaggerMap.vhostBasePathToNameVersionMap[vhost+":"+mgwSwagger.GetXWso2Basepath()] = mgwSwagger.GetTitle() + ":" + mgwSwagger.GetVersion() + internalSwaggerMap.Unlock() +} + +func deleteFromAPIMgwSwaggerMap(apiIdentifier string) { + vhost, err := ExtractVhostFromAPIIdentifier(apiIdentifier) + if err != nil { + logger.LoggerXds.Errorf("Error while extracting the vhost for API deletion") + return + } + internalSwaggerMap.Lock() + mgwSwagger, ok := internalSwaggerMap.apiMgwSwaggerMap[apiIdentifier] + if ok { + delete(internalSwaggerMap.apiMgwSwaggerMap, apiIdentifier) //delete labels + delete(internalSwaggerMap.vhostBasePathToNameVersionMap, vhost+":"+mgwSwagger.GetXWso2Basepath()) //delete mgwSwagger + } + internalSwaggerMap.Unlock() +} diff --git a/adapter/internal/discovery/xds/state_cache.go b/adapter/internal/discovery/xds/state_cache.go index ae51542443..b2967dff25 100644 --- a/adapter/internal/discovery/xds/state_cache.go +++ b/adapter/internal/discovery/xds/state_cache.go @@ -38,6 +38,7 @@ type RequestEvent struct { // EnforcerAPIState Stores the last success state of the enforcer apis type EnforcerAPIState struct { Apis map[string]oasModel.MgwSwagger + BasePathAPIMap map[string]string // vhost:basePath -> APIName:APIVersion OpenAPIEnforcerApisMap map[string]types.Resource APIUUIDToGatewayToVhosts map[string]map[string]string APIToVhostsMap map[string]map[string]struct{} diff --git a/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go b/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go index ea751542ee..a10bf3c47a 100644 --- a/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go +++ b/adapter/internal/oasparser/envoyconf/envoyconf_internal_test.go @@ -313,9 +313,27 @@ func TestGenerateRegex(t *testing.T) { { inputpath: "/v2/pet/*", userInputPath: "/v2/pet/", + message: "when the resource ends with *, empty string with / substitution fails.", + isMatched: true, + }, + { + inputpath: "/v2/pet/*", + userInputPath: "/v2/pet", message: "when the resource ends with *, empty string substitution fails.", isMatched: true, }, + { + inputpath: "/v2/pet/*", + userInputPath: "/v2/pet/foo/bar", + message: "when the resource ends with *, multiple trailing slashes substitution fails.", + isMatched: true, + }, + { + inputpath: "/v2/pet/*", + userInputPath: "/v2/pet123", + message: "when the resource ends with *, trailing characters substitution passes", + isMatched: false, + }, { inputpath: "/v2/pet/{petId}.api", userInputPath: "/v2/pet/findByIdstatus=availabe", diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 547bdfd848..f96a02b904 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -18,10 +18,11 @@ package envoyconf import ( - "google.golang.org/protobuf/types/known/durationpb" "net" "regexp" + "google.golang.org/protobuf/types/known/durationpb" + clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" @@ -369,11 +370,11 @@ func createRoute(params *routeCreateParams) *routev3.Route { logger.LoggerOasparser.Debug("creating a route....") var ( - router routev3.Route - action *routev3.Route_Route - match *routev3.RouteMatch - decorator *routev3.Decorator - resourcePath string + router routev3.Route + action *routev3.Route_Route + match *routev3.RouteMatch + decorator *routev3.Decorator + resourcePath string ) // OPTIONS is always added even if it is not listed under resources @@ -689,7 +690,7 @@ func basepathConsistent(basePath string) string { // TODO: (VirajSalaka) Improve regex specifically for strings, integers etc. func generateRegex(fullpath string) string { pathParaRegex := "([^/]+)" - wildCardRegex := "([^/]*)" + wildCardRegex := "((/(.*))*)" endRegex := "(\\?([^/]+))?" newPath := "" @@ -710,7 +711,7 @@ func generateRegex(fullpath string) string { } if strings.HasSuffix(newPath, "/*") { - newPath = strings.TrimSuffix(newPath, "*") + wildCardRegex + newPath = strings.TrimSuffix(newPath, "/*") + wildCardRegex } return "^" + newPath + endRegex + "$" } diff --git a/adapter/internal/oasparser/model/constants.go b/adapter/internal/oasparser/model/constants.go index b8c2788b33..dae9f3f0a6 100644 --- a/adapter/internal/oasparser/model/constants.go +++ b/adapter/internal/oasparser/model/constants.go @@ -48,6 +48,7 @@ const ( // Constants to represent errors const ( - AlreadyExists string = "ALREADY_EXISTS" - NotFound string = "NOT_FOUND" + AlreadyExists string = "ALREADY_EXISTS" + NotFound string = "NOT_FOUND" + ValidationFailure string = "VALIDATION_FAILURE" ) diff --git a/adapter/internal/oasparser/model/mgw_swagger.go b/adapter/internal/oasparser/model/mgw_swagger.go index f96079505c..cc2db9e31e 100644 --- a/adapter/internal/oasparser/model/mgw_swagger.go +++ b/adapter/internal/oasparser/model/mgw_swagger.go @@ -318,6 +318,20 @@ func (swagger *MgwSwagger) Validate() error { return err } } + + err := swagger.validateBasePath() + if err != nil { + logger.LoggerOasparser.Errorf("Error while parsing the API %s:%s - %v", swagger.title, swagger.version, err) + return err + } + return nil +} + +func (swagger *MgwSwagger) validateBasePath() error { + if xWso2BasePath == "" { + return errors.New("Empty Basepath is provided. Either use x-wso2-basePath extension or assign basePath (if OpenAPI v2 definition is used)" + + " / servers entry (if OpenAPI v3 definition is used) with non empty context.") + } return nil } @@ -366,7 +380,6 @@ func getXWso2Endpoints(vendorExtensions map[string]interface{}, endpointType str endpoint := getHostandBasepathandPort(v.(string)) endpoints = append(endpoints, endpoint) } - } return endpoints } diff --git a/adapter/internal/oasparser/model/open_api.go b/adapter/internal/oasparser/model/open_api.go index 733e6a3ff3..9ab5dee8de 100644 --- a/adapter/internal/oasparser/model/open_api.go +++ b/adapter/internal/oasparser/model/open_api.go @@ -54,6 +54,8 @@ func (swagger *MgwSwagger) SetInfoOpenAPI(swagger3 openapi3.Swagger) { } endpoint := getHostandBasepathandPort(serverEntry.URL) swagger.productionUrls = append(swagger.productionUrls, endpoint) + // Basepath is assigned from servers URL's basepath + swagger.xWso2Basepath = endpoint.Basepath } } } diff --git a/adapter/resources/adapterAPI.yaml b/adapter/resources/adapterAPI.yaml index f247b2e0a6..2095abba9a 100644 --- a/adapter/resources/adapterAPI.yaml +++ b/adapter/resources/adapterAPI.yaml @@ -114,7 +114,13 @@ paths: schema: $ref: '#/definitions/DeployResponse' 401: - $ref: '#/responses/Unauthorized' + $ref: '#/responses/Unauthorized' + 400: + description: | + Bad Request + Invalid request or validation error + schema: + $ref: '#/definitions/Error' 409: description: | Conflict. diff --git a/adapter/resources/license.txt b/adapter/resources/license.txt new file mode 100644 index 0000000000..277468b5b3 --- /dev/null +++ b/adapter/resources/license.txt @@ -0,0 +1,13 @@ +Copyright (c) 2021, WSO2 Inc. (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. diff --git a/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/analytics/AnalyticsFilter.java b/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/analytics/AnalyticsFilter.java index f33b8e79b0..d774f4b4ff 100644 --- a/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/analytics/AnalyticsFilter.java +++ b/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/analytics/AnalyticsFilter.java @@ -102,11 +102,11 @@ public void handleSuccessRequest(RequestContext requestContext) { requestContext.addMetadataToMap(MetadataConstants.API_NAME_KEY, apiName); requestContext.addMetadataToMap(MetadataConstants.API_VERSION_KEY, apiVersion); requestContext.addMetadataToMap(MetadataConstants.API_TYPE_KEY, apiType); + + String tenantDomain = FilterUtils.getTenantDomainFromRequestURL( + requestContext.getMatchedAPI().getAPIConfig().getBasePath()); requestContext.addMetadataToMap(MetadataConstants.API_CREATOR_TENANT_DOMAIN_KEY, - FilterUtils.getTenantDomainFromRequestURL( - requestContext.getMatchedAPI().getAPIConfig().getBasePath()) == null - ? APIConstants.SUPER_TENANT_DOMAIN_NAME - : requestContext.getMatchedAPI().getAPIConfig().getBasePath()); + tenantDomain == null ? APIConstants.SUPER_TENANT_DOMAIN_NAME : tenantDomain); // Default Value would be PRODUCTION requestContext.addMetadataToMap(MetadataConstants.APP_KEY_TYPE_KEY, diff --git a/integration/mock-backend-server/src/main/java/org/wso2/choreo/connect/mockbackend/MockBackEndServer.java b/integration/mock-backend-server/src/main/java/org/wso2/choreo/connect/mockbackend/MockBackEndServer.java index 7242a14bbb..aa35d481da 100644 --- a/integration/mock-backend-server/src/main/java/org/wso2/choreo/connect/mockbackend/MockBackEndServer.java +++ b/integration/mock-backend-server/src/main/java/org/wso2/choreo/connect/mockbackend/MockBackEndServer.java @@ -243,6 +243,16 @@ public void configure(HttpsParameters params) { exchange.getResponseBody().write(response); exchange.close(); }); + // For OpenAPI v3 related tests + httpServer.createContext("/v3" + "/pet/findByStatus", exchange -> { + + byte[] response = ResponseConstants.RESPONSE_BODY.getBytes(); + exchange.getResponseHeaders().set(Constants.CONTENT_TYPE, + Constants.CONTENT_TYPE_APPLICATION_JSON); + exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.length); + exchange.getResponseBody().write(response); + exchange.close(); + }); httpServer.start(); backEndServerUrl = "http://localhost:" + backEndServerPort; } catch (Exception ex) { diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithDefaultConf.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithDefaultConf.java index e5010761a5..2d4aee43c1 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithDefaultConf.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithDefaultConf.java @@ -46,6 +46,7 @@ void start() throws Exception { ApictlUtils.createProject( "security_openAPI.yaml", "custom_authheader_petstore", null, null); ApictlUtils.createProject( "vhost1_openAPI.yaml", "vhost1_petstore", null, "vhost1_deploy_env.yaml"); ApictlUtils.createProject( "vhost2_openAPI.yaml", "vhost2_petstore", null, "vhost2_deploy_env.yaml"); + ApictlUtils.createProject( "openAPI_v3_standard_valid.yaml", "apictl_petstore_v3", null, null); ApictlUtils.addEnv("test"); ApictlUtils.login("test"); @@ -57,6 +58,7 @@ void start() throws Exception { ApictlUtils.deployAPI("custom_authheader_petstore", "test"); ApictlUtils.deployAPI("vhost1_petstore", "test"); ApictlUtils.deployAPI("vhost2_petstore", "test"); + ApictlUtils.deployAPI("apictl_petstore_v3", "test"); TimeUnit.SECONDS.sleep(5); } diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithJwtConfig.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithJwtConfig.java index 8f7ae26ef1..701edf9db2 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithJwtConfig.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCaseBefore/MgwWithJwtConfig.java @@ -36,11 +36,13 @@ void start() throws Exception { String confPath = targetDir + TestConstant.TEST_RESOURCES_PATH + File.separator + "jwtGenerator" + File.separator + "config.toml"; ApictlUtils.createProject( "global_cors_openAPI.yaml", "global_cors_petstore", null, null); + ApictlUtils.createProject("backend_tsl_openAPI.yaml", "backend_tls_petstore", "backend_tls.crt", null); super.startMGW(confPath); ApictlUtils.addEnv("test"); ApictlUtils.login("test"); ApictlUtils.deployAPI("petstore", "test"); + ApictlUtils.deployAPI("backend_tls_petstore", "test"); ApictlUtils.deployAPI("global_cors_petstore", "test"); TimeUnit.SECONDS.sleep(5); } diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/apiDeploy/OpenAPIV3TestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/apiDeploy/OpenAPIV3TestCase.java new file mode 100644 index 0000000000..76705d5513 --- /dev/null +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/apiDeploy/OpenAPIV3TestCase.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 org.wso2.choreo.connect.tests.testCases.apiDeploy; + +import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.HttpStatus; +import io.netty.handler.codec.http.HttpHeaderNames; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.choreo.connect.mockbackend.ResponseConstants; +import org.wso2.choreo.connect.tests.common.BaseTestCase; +import org.wso2.choreo.connect.tests.util.HttpResponse; +import org.wso2.choreo.connect.tests.util.HttpsClientRequest; +import org.wso2.choreo.connect.tests.util.TestConstant; +import org.wso2.choreo.connect.tests.util.TokenUtil; +import org.wso2.choreo.connect.tests.util.Utils; + +import java.util.HashMap; +import java.util.Map; + +public class OpenAPIV3TestCase extends BaseTestCase { + protected String jwtToken; + + @BeforeClass(description = "initialise the setup") + void start() throws Exception { + jwtToken = TokenUtil.getJwtForPetstore(TestConstant.KEY_TYPE_PRODUCTION, null, + false); + } + + @Test(description = "Test to check the InternalKey is working") + public void invokeInternalKeyHeaderSuccessTest() throws Exception { + // Set header + Map headers = new HashMap<>(); + headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtToken); + HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v3/pet/findByStatus") , headers); + + Assert.assertNotNull(response); + Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK,"Response code mismatched"); + Assert.assertEquals(response.getData(), ResponseConstants.RESPONSE_BODY, "Response Body Mismatch."); + } +} diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/CustomJwtTransformerTestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/CustomJwtTransformerTestCase.java index 4d190b2575..e813d9bbea 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/CustomJwtTransformerTestCase.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/CustomJwtTransformerTestCase.java @@ -47,7 +47,7 @@ public void testCustomJwtClaimMapping() throws Exception { //test endpoint with token headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtTokenProd); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/jwttoken") , headers); + "/v2/basicAPI/jwttoken") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtGeneratorTestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtGeneratorTestCase.java index b72392daed..3e68f6fb68 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtGeneratorTestCase.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtGeneratorTestCase.java @@ -53,7 +53,7 @@ public void testResponseJWTGenerationHeader() throws Exception { //test endpoint with token headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtTokenProd); HttpResponse response = HttpsClientRequest - .doGet(Utils.getServiceURLHttps("v2/jwtheader"), headers); + .doGet(Utils.getServiceURLHttps("v2/basicAPI/jwtheader"), headers); Assert.assertNotNull(response); Assert.assertEquals(response.getData(), ResponseConstants.VALID_JWT_RESPONSE); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); @@ -65,7 +65,7 @@ public void testResponseJWTGenerationToken() throws Exception { //test endpoint with token headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtTokenProd); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/jwttoken") , headers); + "/v2/basicAPI/jwttoken") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtTransformerTestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtTransformerTestCase.java index ff9d88f1c1..89df6a90ed 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtTransformerTestCase.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtGenerator/JwtTransformerTestCase.java @@ -38,7 +38,7 @@ public class JwtTransformerTestCase { @BeforeClass(description = "initialise the setup") void start() throws Exception { - jwtTokenProd = TokenUtil.getJwtForPetstore(TestConstant.KEY_TYPE_PRODUCTION, null, false); + jwtTokenProd = TokenUtil.getJwtForPetstoreForBasicAPI(TestConstant.KEY_TYPE_PRODUCTION, null, false); } @Test(description = "Test default jwt claim mapping") @@ -47,7 +47,7 @@ public void testDefaultJwtClaimMapping() throws Exception { //test endpoint with token headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtTokenProd); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/jwttoken") , headers); + "/v2/basicAPI/jwttoken") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/InternalKeyTestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/InternalKeyTestCase.java index 7c75ff01af..4d1ea580bc 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/InternalKeyTestCase.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/InternalKeyTestCase.java @@ -36,7 +36,7 @@ public class InternalKeyTestCase extends BaseTestCase { @BeforeClass(description = "initialise the setup") void start() throws Exception { - internalKey = TokenUtil.getJwtForPetstore(TestConstant.KEY_TYPE_PRODUCTION, null, true); + internalKey = TokenUtil.getJwtForPetstoreForBasicAPI(TestConstant.KEY_TYPE_PRODUCTION, null, true); } @Test(description = "Test to check the InternalKey is working") @@ -44,7 +44,7 @@ public void invokeInternalKeyHeaderSuccessTest() throws Exception { // Set header Map headers = new HashMap<>(); headers.put("Internal-Key", internalKey); - HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v2/pet/2") , headers); + HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK,"Response code mismatched"); @@ -55,7 +55,7 @@ public void invokeInternalKeyHeaderInvalidTokenTest() throws Exception { // Set header Map headers = new HashMap<>(); headers.put("Internal-Key", TestConstant.INVALID_JWT_TOKEN); - HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v2/pet/2") , headers); + HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED,"Response code mismatched"); @@ -68,7 +68,7 @@ public void invokeExpiredInternalKeyTest() throws Exception { // Set header Map headers = new HashMap(); headers.put("Internal-Key", TestConstant.EXPIRED_INTERNAL_KEY_TOKEN); - HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v2/pet/2") , headers); + HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps("/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertTrue(response.getData().contains("Invalid Credentials"), "Error response message mismatch"); diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/JwtTestCase.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/JwtTestCase.java index 9c949e4139..dacb586e29 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/JwtTestCase.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/JwtTestCase.java @@ -61,7 +61,7 @@ public void invokeJWTHeaderSuccessTest() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithoutScope); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/pet/2") , headers); + "/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK,"Response code mismatched"); @@ -76,7 +76,7 @@ public void invokeJWTHeaderInvalidTokenTest() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + TestConstant.INVALID_JWT_TOKEN); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/pet/2") , headers); + "/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED, "Response code mismatched"); @@ -91,7 +91,7 @@ public void invokeJWTHeaderInvalidAuthorizationHeader() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Something " + TestConstant.INVALID_JWT_TOKEN); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/pet/2") , headers); + "/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED, "Response code mismatched"); @@ -104,7 +104,7 @@ public void invokeJWTHeaderExpiredTokenTest() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + TestConstant.EXPIRED_JWT_TOKEN); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/pet/2") , headers); + "/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED, @@ -120,7 +120,7 @@ public void invokeJWTHeaderParseExceptionTest() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + "aaa.bbb.ccc"); HttpResponse response = HttpsClientRequest.doGet(Utils.getServiceURLHttps( - "/v2/pet/2") , headers); + "/v2/basicAPI/pet/2") , headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED, diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/ScopeTest.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/ScopeTest.java index c7ed49fc00..51edb0fdb3 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/ScopeTest.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/testCases/jwtValidator/ScopeTest.java @@ -59,7 +59,7 @@ public void testScopeProtectedResourceInvalidJWT() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithoutScope); HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed(Utils.getServiceURLHttps( - "/v2/pets/findByTags"), headers); + "/v2/basicAPI/pets/findByTags"), headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_FORBIDDEN, "Response code mismatched"); Assert.assertTrue( @@ -71,7 +71,8 @@ public void testScopeProtectedResourceInvalidJWT() throws Exception { public void testScopeProtectedResourceValidJWT() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithScope); - HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed(Utils.getServiceURLHttps("/v2/pet/findByStatus"), headers); + HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed( + Utils.getServiceURLHttps("/v2/basicAPI/pet/findByStatus"), headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK, "Response code mismatched"); Assert.assertEquals(response.getData(), ResponseConstants.RESPONSE_BODY, @@ -82,7 +83,8 @@ public void testScopeProtectedResourceValidJWT() throws Exception { public void testMultipleScopeProtectedResourceValidJWT() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithScope); - HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed(Utils.getServiceURLHttps("/v2/pets/findByTags"), headers); + HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed( + Utils.getServiceURLHttps("/v2/basicAPI/pets/findByTags"), headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK, "Response code mismatched"); Assert.assertEquals(response.getData(), ResponseConstants.PET_BY_ID_RESPONSE, @@ -93,7 +95,8 @@ public void testMultipleScopeProtectedResourceValidJWT() throws Exception { public void testMultipleScopeProtectedResourceValidMultiScopeJWT() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithMultipleScopes); - HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed(Utils.getServiceURLHttps("/v2/pets/findByTags"), headers); + HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed( + Utils.getServiceURLHttps("/v2/basicAPI/pets/findByTags"), headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_OK, "Response code mismatched"); Assert.assertEquals(response.getData(), ResponseConstants.PET_BY_ID_RESPONSE, @@ -104,7 +107,8 @@ public void testMultipleScopeProtectedResourceValidMultiScopeJWT() throws Except public void testMultipleScopeProtectedResourceInvalidMultiScopeJWT() throws Exception { Map headers = new HashMap(); headers.put(HttpHeaderNames.AUTHORIZATION.toString(), "Bearer " + jwtWithMultipleInvalidScopes); - HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed(Utils.getServiceURLHttps("/v2/pets/findByTags"), headers); + HttpResponse response = HttpClientRequest.retryGetRequestUntilDeployed( + Utils.getServiceURLHttps("/v2/basicAPI/pets/findByTags"), headers); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), HttpStatus.SC_FORBIDDEN,"Response code mismatched"); Assert.assertTrue( diff --git a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/util/TokenUtil.java b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/util/TokenUtil.java index 9b7029dfcc..52beae9319 100644 --- a/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/util/TokenUtil.java +++ b/integration/test-integration/src/test/java/org/wso2/choreo/connect/tests/util/TokenUtil.java @@ -196,4 +196,20 @@ public static String getJwtForPetstore(String keyType, String scopes, boolean is application.setId((int) (Math.random() * 1000)); return getJWT(api, application, "Unlimited", keyType, 3600, scopes, isInternalKey); } + + public static String getJwtForPetstoreForBasicAPI(String keyType, String scopes, boolean isInternalKey) throws Exception { + API api = new API(); + api.setName("PetStoreAPI"); + api.setContext("v2/basicAPI"); + api.setProdEndpoint(Utils.getMockServiceURLHttp("/echo/prod")); + api.setVersion("1.0.5"); + api.setProvider("admin"); + + //Define application info + ApplicationDTO application = new ApplicationDTO(); + application.setName("jwtApp"); + application.setTier("Unlimited"); + application.setId((int) (Math.random() * 1000)); + return getJWT(api, application, "Unlimited", keyType, 3600, scopes, isInternalKey); + } } diff --git a/integration/test-integration/src/test/resources/openAPIs/openAPI.yaml b/integration/test-integration/src/test/resources/openAPIs/openAPI.yaml index e27bbb3cc6..9f191627af 100644 --- a/integration/test-integration/src/test/resources/openAPIs/openAPI.yaml +++ b/integration/test-integration/src/test/resources/openAPIs/openAPI.yaml @@ -25,7 +25,7 @@ host: 'mockBackend:2383' x-wso2-production-endpoints: urls: - 'http://mockBackend:2383/v2' -x-wso2-basePath: /v2 +x-wso2-basePath: /v2/basicAPI tags: - name: pet description: Everything about your Pets diff --git a/integration/test-integration/src/test/resources/openAPIs/openAPI_v3_standard_valid.yaml b/integration/test-integration/src/test/resources/openAPIs/openAPI_v3_standard_valid.yaml new file mode 100644 index 0000000000..51fd697381 --- /dev/null +++ b/integration/test-integration/src/test/resources/openAPIs/openAPI_v3_standard_valid.yaml @@ -0,0 +1,65 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://mockBackend:2383/v3 +paths: + /pet/findByStatus: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/resources/conf/log_config.toml b/resources/conf/log_config.toml index 1a0746dbc4..b38207517d 100644 --- a/resources/conf/log_config.toml +++ b/resources/conf/log_config.toml @@ -13,11 +13,11 @@ Compress = true # LogLevels = "DEBG", "FATL", "ERRO", "WARN", "INFO", "PANC" [[pkg]] -name = "github.com/wso2/adapter/pkg/adapter" +name = "github.com/wso2/adapter/internal/adapter" logLevel = "INFO" [[pkg]] -name = "github.com/wso2/adapter/pkg/oasparser" +name = "github.com/wso2/adapter/internal/oasparser" logLevel = "INFO"