blob: 9a09920dce8a4fa989f2ed709ee87751f00fece3 [file] [log] [blame]
// 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
err := db.QueryRow("SELECT env, scope FROM EDGEX_DATA_SCOPE WHERE id = ?;", scopeuuid).Scan(&env, &tenantId)
switch err {
case sql.ErrNoRows:
log.Error("verifyAPIKey: sql.ErrNoRows")
reason := "ENV Validation Failed"
errorCode := "ENV_VALIDATION_FAILED"
return errorResponse(reason, errorCode)
case nil:
default:
reason := err.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)
}