blob: e6fcb3239ddc5563a4e108c1915bc7ceac2bcfb4 [file] [log] [blame] [edit]
// Copyright 2017 Google Inc.
//
// 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 verifyApiKey
import (
"encoding/json"
"github.com/apid/apid-core/util"
"github.com/apid/apidApiMetadata/common"
"io"
"io/ioutil"
"net/http"
"strings"
)
type ApiManagerInterface interface {
InitAPI()
HandleRequest(w http.ResponseWriter, r *http.Request)
verifyAPIKey(verifyApiKeyReq VerifyApiKeyRequest) (*VerifyApiKeySuccessResponse, *common.ErrorResponse)
}
type ApiManager struct {
DbMan DbManagerInterface
VerifiersEndpoint string
apiInitialized bool
}
func (a *ApiManager) InitAPI() {
if a.apiInitialized {
return
}
services.API().HandleFunc(a.VerifiersEndpoint, a.HandleRequest).Methods("POST")
a.apiInitialized = true
log.Debug("API endpoints initialized")
}
// handle client API
func (a *ApiManager) HandleRequest(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var returnValue interface{}
verifyApiKeyReq, err := validateRequest(r.Body, w)
if err != nil {
errorResponse, jsonErr := json.Marshal(errorResponse("Bad_REQUEST", err.Error(), http.StatusBadRequest))
if jsonErr != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(jsonErr.Error()))
}
w.WriteHeader(http.StatusBadRequest)
w.Write(errorResponse)
return
}
verifyApiKeyResponse, errorResponse := a.verifyAPIKey(verifyApiKeyReq)
if errorResponse != nil {
setResponseHeader(errorResponse, w)
returnValue = errorResponse
} else {
returnValue = verifyApiKeyResponse
}
b, _ := json.Marshal(returnValue)
log.Debugf("handleVerifyAPIKey result %s", b)
w.Write(b)
}
func setResponseHeader(errorResponse *common.ErrorResponse, w http.ResponseWriter) {
if errorResponse.StatusCode != 0 {
w.WriteHeader(errorResponse.StatusCode)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
}
func validateRequest(requestBody io.ReadCloser, w http.ResponseWriter) (VerifyApiKeyRequest, error) {
defer requestBody.Close()
// 1. read request boby
var verifyApiKeyReq VerifyApiKeyRequest
body, err := ioutil.ReadAll(requestBody)
if err != nil {
return verifyApiKeyReq, err
}
log.Debug("request body: ", string(body))
// 2. umarshall json to struct
err = json.Unmarshal(body, &verifyApiKeyReq)
if err != nil {
return verifyApiKeyReq, err
}
log.Debug(verifyApiKeyReq)
// 2. verify params
if isValid, err := verifyApiKeyReq.validate(); !isValid {
return verifyApiKeyReq, err
}
return verifyApiKeyReq, nil
}
// returns []byte to be written to client
func (apiM ApiManager) verifyAPIKey(verifyApiKeyReq VerifyApiKeyRequest) (*VerifyApiKeySuccessResponse, *common.ErrorResponse) {
dataWrapper := VerifyApiKeyRequestResponseDataWrapper{
verifyApiKeyRequest: verifyApiKeyReq,
}
dataWrapper.verifyApiKeySuccessResponse.ClientId.ClientId = verifyApiKeyReq.Key
dataWrapper.verifyApiKeySuccessResponse.Environment = verifyApiKeyReq.EnvironmentName
err := apiM.DbMan.getApiKeyDetails(&dataWrapper)
switch {
case err != nil && err.Error() == "InvalidApiKey":
reason := "API Key verify failed for (" + verifyApiKeyReq.Key + ", " + verifyApiKeyReq.OrganizationName + ")"
errorCode := "oauth.v2.InvalidApiKey"
errResponse := errorResponse(reason, errorCode, http.StatusOK)
return nil, &errResponse
case err != nil:
reason := err.Error()
errorCode := "SEARCH_INTERNAL_ERROR"
errResponse := errorResponse(reason, errorCode, http.StatusInternalServerError)
return nil, &errResponse
}
dataWrapper.verifyApiKeySuccessResponse.ApiProduct = shortListApiProduct(dataWrapper.apiProducts, verifyApiKeyReq)
/*
* Perform all validations
*/
errResponse := apiM.performValidations(dataWrapper)
if errResponse != nil {
return nil, errResponse
}
apiM.enrichAttributes(&dataWrapper)
setDevOrCompanyInResponseBasedOnCtype(dataWrapper.ctype, dataWrapper.tempDeveloperDetails, &dataWrapper.verifyApiKeySuccessResponse)
return &dataWrapper.verifyApiKeySuccessResponse, nil
}
func setDevOrCompanyInResponseBasedOnCtype(ctype string, tempDeveloperDetails DeveloperDetails, response *VerifyApiKeySuccessResponse) {
if ctype == "developer" {
response.Developer = tempDeveloperDetails
} else {
response.Company = CompanyDetails{
Id: tempDeveloperDetails.Id,
Name: tempDeveloperDetails.FirstName,
DisplayName: tempDeveloperDetails.UserName,
Status: tempDeveloperDetails.Status,
CreatedAt: tempDeveloperDetails.CreatedAt,
CreatedBy: tempDeveloperDetails.CreatedBy,
LastmodifiedAt: tempDeveloperDetails.LastmodifiedAt,
LastmodifiedBy: tempDeveloperDetails.LastmodifiedBy,
Attributes: tempDeveloperDetails.Attributes,
}
}
}
func shortListApiProduct(details []ApiProductDetails, verifyApiKeyReq VerifyApiKeyRequest) ApiProductDetails {
var bestMathcedProduct ApiProductDetails
rankedProducts := make([][]ApiProductDetails, 2)
for _, apiProd := range details {
if len(apiProd.Resources) == 0 || validatePath(apiProd.Resources, verifyApiKeyReq.UriPath) {
if len(apiProd.Apiproxies) == 0 || util.Contains(apiProd.Apiproxies, verifyApiKeyReq.ApiProxyName) {
if len(apiProd.Environments) == 0 || util.Contains(apiProd.Environments, verifyApiKeyReq.EnvironmentName) {
bestMathcedProduct = apiProd
return bestMathcedProduct
// set rank 1 or just return
} else {
// set rank to 2
rankedProducts[0] = append(rankedProducts[0], apiProd)
}
} else {
// set rank to 3,
rankedProducts[1] = append(rankedProducts[1], apiProd)
}
}
}
if len(rankedProducts[0]) > 0 {
return rankedProducts[0][0]
}
if len(rankedProducts[1]) > 0 {
return rankedProducts[1][0]
}
return bestMathcedProduct
}
func (apiM ApiManager) performValidations(dataWrapper VerifyApiKeyRequestResponseDataWrapper) *common.ErrorResponse {
clientIdDetails := dataWrapper.verifyApiKeySuccessResponse.ClientId
verifyApiKeyReq := dataWrapper.verifyApiKeyRequest
appDetails := dataWrapper.verifyApiKeySuccessResponse.App
tempDeveloperDetails := dataWrapper.tempDeveloperDetails
cType := dataWrapper.ctype
apiProductDetails := dataWrapper.verifyApiKeySuccessResponse.ApiProduct
var reason, errorCode string
if !strings.EqualFold("APPROVED", clientIdDetails.Status) {
reason = "API Key verify failed for (" + verifyApiKeyReq.Key + ", " + verifyApiKeyReq.OrganizationName + ")"
errorCode = "oauth.v2.ApiKeyNotApproved"
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
if !strings.EqualFold("APPROVED", appDetails.Status) {
reason = "API Key verify failed for (" + verifyApiKeyReq.Key + ", " + verifyApiKeyReq.OrganizationName + ")"
errorCode = "keymanagement.service.invalid_client-app_not_approved"
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
if !strings.EqualFold("ACTIVE", tempDeveloperDetails.Status) {
reason = "API Key verify failed for (" + verifyApiKeyReq.Key + ", " + verifyApiKeyReq.OrganizationName + ")"
errorCode = "keymanagement.service.DeveloperStatusNotActive"
if cType == "company" {
errorCode = "keymanagement.service.CompanyStatusNotActive"
}
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
if dataWrapper.verifyApiKeySuccessResponse.ApiProduct.Id == "" {
reason = "Path Validation Failed. Product not resolved"
errorCode = "oauth.v2.InvalidApiKeyForGivenResource"
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
result := len(apiProductDetails.Resources) == 0 || validatePath(apiProductDetails.Resources, verifyApiKeyReq.UriPath)
if !result {
reason = "Path Validation Failed (" + strings.Join(apiProductDetails.Resources, ", ") + " vs " + verifyApiKeyReq.UriPath + ")"
errorCode = "oauth.v2.InvalidApiKeyForGivenResource"
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
if verifyApiKeyReq.ValidateAgainstApiProxiesAndEnvs && (len(apiProductDetails.Apiproxies) > 0 && !util.Contains(apiProductDetails.Apiproxies, verifyApiKeyReq.ApiProxyName)) {
reason = "Proxy Validation Failed (" + strings.Join(apiProductDetails.Apiproxies, ", ") + " vs " + verifyApiKeyReq.ApiProxyName + ")"
errorCode = "oauth.v2.InvalidApiKeyForGivenResource"
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
/* Verify if the ENV matches */
if verifyApiKeyReq.ValidateAgainstApiProxiesAndEnvs && (len(apiProductDetails.Environments) > 0 && !util.Contains(apiProductDetails.Environments, verifyApiKeyReq.EnvironmentName)) {
reason = "ENV Validation Failed (" + strings.Join(apiProductDetails.Environments, ", ") + " vs " + verifyApiKeyReq.EnvironmentName + ")"
errorCode = "oauth.v2.InvalidApiKeyForGivenResource"
log.Debug("Validation error occoured ", errorCode, " ", reason)
ee := errorResponse(reason, errorCode, http.StatusOK)
return &ee
}
return nil
}
func (a *ApiManager) enrichAttributes(dataWrapper *VerifyApiKeyRequestResponseDataWrapper) {
attributeMap := a.DbMan.GetKmsAttributes(dataWrapper.tenant_id, dataWrapper.verifyApiKeySuccessResponse.ClientId.ClientId, dataWrapper.tempDeveloperDetails.Id, dataWrapper.verifyApiKeySuccessResponse.ApiProduct.Id, dataWrapper.verifyApiKeySuccessResponse.App.Id)
clientIdAttributes := attributeMap[dataWrapper.verifyApiKeySuccessResponse.ClientId.ClientId]
developerAttributes := attributeMap[dataWrapper.tempDeveloperDetails.Id]
appAttributes := attributeMap[dataWrapper.verifyApiKeySuccessResponse.App.Id]
apiProductAttributes := attributeMap[dataWrapper.verifyApiKeySuccessResponse.ApiProduct.Id]
dataWrapper.verifyApiKeySuccessResponse.ClientId.Attributes = clientIdAttributes
dataWrapper.verifyApiKeySuccessResponse.App.Attributes = appAttributes
dataWrapper.verifyApiKeySuccessResponse.ApiProduct.Attributes = apiProductAttributes
dataWrapper.tempDeveloperDetails.Attributes = developerAttributes
}
func errorResponse(reason, errorCode string, statusCode int) common.ErrorResponse {
if errorCode == "SEARCH_INTERNAL_ERROR" {
log.Error(reason)
} else {
log.Debug(reason)
}
resp := common.ErrorResponse{
ResponseCode: errorCode,
ResponseMessage: reason,
StatusCode: statusCode,
}
return resp
}