| // 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 apidVerifyApiKey | 
 |  | 
 | import ( | 
 | 	"database/sql" | 
 | 	"encoding/json" | 
 | 	"fmt" | 
 | 	"net/http" | 
 | 	"net/url" | 
 | ) | 
 |  | 
 | type sucResponseDetail struct { | 
 | 	Key             string `json:"key"` | 
 | 	ExpiresAt       int64  `json:"expiresAt"` | 
 | 	IssuedAt        string `json:"issuedAt"` | 
 | 	Status          string `json:"status"` | 
 | 	Type            string `json:"cType"` | 
 | 	RedirectionURIs string `json:"redirectionURIs"` | 
 | 	AppId           string `json:"appId"` | 
 | 	AppName         string `json:"appName"` | 
 | } | 
 |  | 
 | type errResultDetail struct { | 
 | 	ErrorCode string `json:"errorCode"` | 
 | 	Reason    string `json:"reason"` | 
 | } | 
 |  | 
 | type kmsResponseSuccess struct { | 
 | 	RspInfo sucResponseDetail `json:"result"` | 
 | 	Type    string            `json:"type"` | 
 | } | 
 |  | 
 | type kmsResponseFail struct { | 
 | 	ErrInfo errResultDetail `json:"result"` | 
 | 	Type    string          `json:"type"` | 
 | } | 
 |  | 
 | // handle client API | 
 | func handleRequest(w http.ResponseWriter, r *http.Request) { | 
 |  | 
 | 	db := getDB() | 
 | 	if db == nil { | 
 | 		w.WriteHeader(http.StatusServiceUnavailable) | 
 | 		w.Write([]byte("initializing")) | 
 | 		return | 
 | 	} | 
 |  | 
 | 	err := r.ParseForm() | 
 | 	if err != nil { | 
 | 		w.WriteHeader(http.StatusBadRequest) | 
 | 		w.Write([]byte("Unable to parse form")) | 
 | 		return | 
 | 	} | 
 |  | 
 | 	f := r.Form | 
 | 	elems := []string{"action", "key", "uriPath", "scopeuuid"} | 
 | 	for _, elem := range elems { | 
 | 		if f.Get(elem) == "" { | 
 | 			w.WriteHeader(http.StatusBadRequest) | 
 | 			w.Write([]byte(fmt.Sprintf("Missing element: %s", elem))) | 
 | 			return | 
 | 		} | 
 | 	} | 
 |  | 
 | 	w.Header().Set("Content-Type", "application/json") | 
 | 	b, err := verifyAPIKey(f) | 
 | 	if err != nil { | 
 | 		log.Errorf("error: %s", err) | 
 | 		w.WriteHeader(http.StatusInternalServerError) | 
 | 		w.Write([]byte(err.Error())) | 
 | 		return | 
 | 	} | 
 |  | 
 | 	log.Debugf("handleVerifyAPIKey result %s", b) | 
 | 	w.Write(b) | 
 | } | 
 |  | 
 | // returns []byte to be written to client | 
 | func verifyAPIKey(f url.Values) ([]byte, error) { | 
 |  | 
 | 	key := f.Get("key") | 
 | 	scopeuuid := f.Get("scopeuuid") | 
 | 	path := f.Get("uriPath") | 
 | 	action := f.Get("action") | 
 |  | 
 | 	if key == "" || scopeuuid == "" || path == "" || action != "verify" { | 
 | 		log.Debug("Input params Invalid/Incomplete") | 
 | 		reason := "Input Params Incomplete or Invalid" | 
 | 		errorCode := "INCORRECT_USER_INPUT" | 
 | 		return errorResponse(reason, errorCode) | 
 | 	} | 
 |  | 
 | 	db := getDB() | 
 |  | 
 | 	// DANGER: This relies on an external TABLE - EDGEX_DATA_SCOPE is maintained by apidApigeeSync | 
 | 	var env, tenantId string | 
 | 	error := db.QueryRow("SELECT env, scope FROM EDGEX_DATA_SCOPE WHERE id = ?;", scopeuuid).Scan(&env, &tenantId) | 
 |  | 
 | 	switch { | 
 | 	case error == sql.ErrNoRows: | 
 | 		log.Error("verifyAPIKey: sql.ErrNoRows") | 
 | 		reason := "ENV Validation Failed" | 
 | 		errorCode := "ENV_VALIDATION_FAILED" | 
 | 		return errorResponse(reason, errorCode) | 
 | 	case error != nil: | 
 | 		reason := error.Error() | 
 | 		errorCode := "SEARCH_INTERNAL_ERROR" | 
 | 		return errorResponse(reason, errorCode) | 
 | 	} | 
 |  | 
 | 	log.Debug("Found tenant_id='", tenantId, "' with env='", env, "' for scopeuuid='", scopeuuid, "'") | 
 |  | 
 | 	sSql := ` | 
 | 		SELECT | 
 | 			ap.api_resources,  | 
 | 			ap.environments,  | 
 | 			c.issued_at, | 
 | 			c.status, | 
 | 			a.callback_url, | 
 | 			ad.email, | 
 | 			ad.id, | 
 | 			"developer" as ctype | 
 | 		FROM | 
 | 			KMS_APP_CREDENTIAL AS c | 
 | 			INNER JOIN KMS_APP AS a ON c.app_id = a.id | 
 | 			INNER JOIN KMS_DEVELOPER AS ad | 
 | 				ON ad.id = a.developer_id | 
 | 			INNER JOIN KMS_APP_CREDENTIAL_APIPRODUCT_MAPPER as mp | 
 | 				ON mp.appcred_id = c.id  | 
 | 			INNER JOIN KMS_API_PRODUCT as ap ON ap.id = mp.apiprdt_id | 
 | 		WHERE (UPPER(ad.status) = 'ACTIVE'  | 
 | 			AND mp.apiprdt_id = ap.id  | 
 | 			AND mp.app_id = a.id | 
 | 			AND mp.appcred_id = c.id  | 
 | 			AND UPPER(mp.status) = 'APPROVED'  | 
 | 			AND UPPER(a.status) = 'APPROVED' | 
 | 			AND c.id = $1  | 
 | 			AND c.tenant_id = $2) | 
 | 		UNION ALL | 
 | 		SELECT | 
 | 			ap.api_resources, | 
 | 			ap.environments, | 
 | 			c.issued_at, | 
 | 			c.status, | 
 | 			a.callback_url, | 
 | 			ad.name, | 
 | 			ad.id, | 
 | 			"company" as ctype | 
 | 		FROM | 
 | 			KMS_APP_CREDENTIAL AS c | 
 | 			INNER JOIN KMS_APP AS a ON c.app_id = a.id | 
 | 			INNER JOIN KMS_COMPANY AS ad | 
 | 				ON ad.id = a.company_id | 
 | 			INNER JOIN KMS_APP_CREDENTIAL_APIPRODUCT_MAPPER as mp | 
 | 				ON mp.appcred_id = c.id | 
 | 			INNER JOIN KMS_API_PRODUCT as ap ON ap.id = mp.apiprdt_id | 
 | 		WHERE (UPPER(ad.status) = 'ACTIVE' | 
 | 			AND mp.apiprdt_id = ap.id | 
 | 			AND mp.app_id = a.id | 
 | 			AND mp.appcred_id = c.id | 
 | 			AND UPPER(mp.status) = 'APPROVED' | 
 | 			AND UPPER(a.status) = 'APPROVED' | 
 | 			AND c.id = $1 | 
 | 			AND c.tenant_id = $2) | 
 | 	;` | 
 |  | 
 | 	/* these fields need to be nullable types for scanning.  This is because when using json snapshots, | 
 | 	   and therefore being responsible for inserts, we were able to default everything to be not null.  With | 
 | 	   sqlite snapshots, we are not necessarily guaranteed that | 
 | 	*/ | 
 | 	var status, redirectionURIs, appName, appId, resName, resEnv, issuedAt, cType sql.NullString | 
 | 	err := db.QueryRow(sSql, key, tenantId).Scan(&resName, &resEnv, &issuedAt, &status, | 
 | 		&redirectionURIs, &appName, &appId, &cType) | 
 | 	switch { | 
 | 	case err == sql.ErrNoRows: | 
 | 		reason := "API Key verify failed for (" + key + ", " + scopeuuid + ", " + path + ")" | 
 | 		errorCode := "REQ_ENTRY_NOT_FOUND" | 
 | 		return errorResponse(reason, errorCode) | 
 |  | 
 | 	case err != nil: | 
 | 		reason := err.Error() | 
 | 		errorCode := "SEARCH_INTERNAL_ERROR" | 
 | 		return errorResponse(reason, errorCode) | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Perform all validations related to the Query made with the data | 
 | 	 * we just retrieved | 
 | 	 */ | 
 | 	result := validatePath(resName.String, path) | 
 | 	if result == false { | 
 | 		reason := "Path Validation Failed (" + resName.String + " vs " + path + ")" | 
 | 		errorCode := "PATH_VALIDATION_FAILED" | 
 | 		return errorResponse(reason, errorCode) | 
 |  | 
 | 	} | 
 |  | 
 | 	/* Verify if the ENV matches */ | 
 | 	result = validateEnv(resEnv.String, env) | 
 | 	if result == false { | 
 | 		reason := "ENV Validation Failed (" + resEnv.String + " vs " + env + ")" | 
 | 		errorCode := "ENV_VALIDATION_FAILED" | 
 | 		return errorResponse(reason, errorCode) | 
 | 	} | 
 |  | 
 | 	var expiresAt int64 = -1 | 
 | 	resp := kmsResponseSuccess{ | 
 | 		Type: "APIKeyContext", | 
 | 		RspInfo: sucResponseDetail{ | 
 | 			Key:             key, | 
 | 			ExpiresAt:       expiresAt, | 
 | 			IssuedAt:        issuedAt.String, | 
 | 			Status:          status.String, | 
 | 			RedirectionURIs: redirectionURIs.String, | 
 | 			Type:            cType.String, | 
 | 			AppId:           appId.String, | 
 | 			AppName:         appName.String}, | 
 | 	} | 
 | 	return json.Marshal(resp) | 
 | } | 
 |  | 
 | func errorResponse(reason, errorCode string) ([]byte, error) { | 
 | 	if errorCode == "SEARCH_INTERNAL_ERROR" { | 
 | 		log.Error(reason) | 
 | 	} else { | 
 | 		log.Debug(reason) | 
 | 	} | 
 | 	resp := kmsResponseFail{ | 
 | 		Type: "ErrorResult", | 
 | 		ErrInfo: errResultDetail{ | 
 | 			Reason:    reason, | 
 | 			ErrorCode: errorCode}, | 
 | 	} | 
 | 	return json.Marshal(resp) | 
 | } |