blob: b239409de40b7a27f09d67e351c38e390defc87d [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 accessEntity
import (
"encoding/json"
"fmt"
"github.com/apid/apidApiMetadata/common"
"net/http"
"strconv"
"strings"
)
const (
AccessEntityPath = "/entities"
EndpointApp = "/apps"
EndpointApiProduct = "/apiproducts"
EndpointCompany = "/companies"
EndpointCompanyDeveloper = "/companydevelopers"
EndpointDeveloper = "/developers"
EndpointAppCredentials = "/appcredentials"
)
const (
IdentifierAppId = "appid"
IdentifierApiProductName = "apiproductname"
IdentifierAppName = "appname"
IdentifierApiResource = "apiresource"
IdentifierDeveloperId = "developerid"
IdentifierDeveloperEmail = "developeremail"
IdentifierConsumerKey = "consumerkey"
IdentifierCompanyName = "companyname"
IdentifierOrganization = "organization"
)
const (
TypeDeveloper = "developer"
TypeCompany = "company"
TypeApp = "app"
TypeConsumerKey = "consumerkey"
)
const (
AppTypeDeveloper = "DEVELOPER"
AppTypeCompany = "COMPANY"
)
const (
StatusApproved = "APPROVED"
StatusRevoked = "REVOKED"
StatusExpired = "EXPIRED"
)
var (
Identifiers = map[string]bool{
"appid": true,
"apiproductname": true,
"appname": true,
"apiresource": true,
"developerid": true,
"companyname": true,
"developeremail": true,
"consumerkey": true,
}
ErrInvalidPar = &common.ErrorResponse{
ResponseCode: strconv.Itoa(INVALID_PARAMETERS),
ResponseMessage: "Invalid Identifiers",
StatusCode: http.StatusBadRequest,
}
ErrNotFound = &common.ErrorResponse{
ResponseCode: strconv.Itoa(NOT_FOUND),
ResponseMessage: "Resource Not Found",
StatusCode: http.StatusNotFound,
}
IdentifierTree = map[string]map[string][]string{
EndpointApiProduct: {
IdentifierApiProductName: {},
IdentifierAppId: {IdentifierApiResource},
IdentifierAppName: {IdentifierApiResource, IdentifierDeveloperEmail, IdentifierDeveloperId, IdentifierCompanyName},
IdentifierConsumerKey: {IdentifierApiResource},
},
EndpointApp: {
IdentifierAppId: {},
IdentifierAppName: {IdentifierDeveloperEmail, IdentifierDeveloperId, IdentifierCompanyName},
IdentifierConsumerKey: {},
},
EndpointCompany: {
IdentifierAppId: {},
IdentifierCompanyName: {},
IdentifierConsumerKey: {},
},
EndpointCompanyDeveloper: {
IdentifierCompanyName: {},
},
EndpointAppCredentials: {
IdentifierConsumerKey: {},
},
EndpointDeveloper: {
IdentifierDeveloperEmail: {},
IdentifierAppId: {},
IdentifierDeveloperId: {},
IdentifierConsumerKey: {},
},
}
)
const (
INVALID_PARAMETERS = iota
// Server DB Error
DB_ERROR
// Invalid/Wrong Data in DB data. This probably means something wrong happened in upstream PG/Transicator.
DATA_ERROR
// 404
NOT_FOUND
)
type ApiManager struct {
DbMan DbManagerInterface
AccessEntityPath string
apiInitialized bool
}
func (a *ApiManager) InitAPI() {
if a.apiInitialized {
return
}
services.API().HandleFunc(a.AccessEntityPath+EndpointApp, a.HandleApps).Methods("GET")
services.API().HandleFunc(a.AccessEntityPath+EndpointApiProduct, a.HandleApiProducts).Methods("GET")
services.API().HandleFunc(a.AccessEntityPath+EndpointCompany, a.HandleCompanies).Methods("GET")
services.API().HandleFunc(a.AccessEntityPath+EndpointCompanyDeveloper, a.HandleCompanyDevelopers).Methods("GET")
services.API().HandleFunc(a.AccessEntityPath+EndpointDeveloper, a.HandleDevelopers).Methods("GET")
services.API().HandleFunc(a.AccessEntityPath+EndpointAppCredentials, a.HandleAppCredentials).Methods("GET")
a.apiInitialized = true
log.Debug("API endpoints initialized")
}
func (a *ApiManager) handleEndpoint(endpoint string, w http.ResponseWriter, r *http.Request) {
ids, org, err := extractIdentifiers(r.URL.Query())
if err != nil {
common.WriteError(w, err.Error(), INVALID_PARAMETERS, http.StatusBadRequest)
}
var res interface{}
var errRes *common.ErrorResponse
switch endpoint {
case EndpointApp:
res, errRes = a.getApp(org, ids)
case EndpointApiProduct:
res, errRes = a.getApiProduct(org, ids)
case EndpointCompany:
res, errRes = a.getCompany(org, ids)
case EndpointCompanyDeveloper:
res, errRes = a.getCompanyDeveloper(org, ids)
case EndpointDeveloper:
res, errRes = a.getDeveloper(org, ids)
case EndpointAppCredentials:
res, errRes = a.getAppCredential(org, ids)
}
if errRes != nil {
w.WriteHeader(errRes.StatusCode)
writeJson(errRes, w, r)
return
}
writeJson(res, w, r)
}
func (a *ApiManager) HandleApps(w http.ResponseWriter, r *http.Request) {
a.handleEndpoint(EndpointApp, w, r)
}
func (a *ApiManager) HandleApiProducts(w http.ResponseWriter, r *http.Request) {
a.handleEndpoint(EndpointApiProduct, w, r)
}
func (a *ApiManager) HandleCompanies(w http.ResponseWriter, r *http.Request) {
a.handleEndpoint(EndpointCompany, w, r)
}
func (a *ApiManager) HandleCompanyDevelopers(w http.ResponseWriter, r *http.Request) {
a.handleEndpoint(EndpointCompanyDeveloper, w, r)
}
func (a *ApiManager) HandleDevelopers(w http.ResponseWriter, r *http.Request) {
a.handleEndpoint(EndpointDeveloper, w, r)
}
func (a *ApiManager) HandleAppCredentials(w http.ResponseWriter, r *http.Request) {
a.handleEndpoint(EndpointAppCredentials, w, r)
}
func extractIdentifiers(pars map[string][]string) (map[string]string, string, error) {
m := make(map[string]string)
orgs := pars[IdentifierOrganization]
if len(orgs) == 0 {
return nil, "", fmt.Errorf("no org specified")
}
org := orgs[0]
for k, v := range pars {
k = strings.ToLower(k)
if Identifiers[k] {
if len(v) == 1 {
m[k] = v[0]
} else {
return nil, org, fmt.Errorf("each identifier must have only 1 value")
}
}
}
return m, org, nil
}
func (a *ApiManager) getCompanyDeveloper(org string, ids map[string]string) (*CompanyDevelopersSuccessResponse, *common.ErrorResponse) {
valid, keyVals := parseIdentifiers(EndpointCompanyDeveloper, ids)
if !valid {
return nil, ErrInvalidPar
}
priKey, priVal := keyVals[0], keyVals[1]
devs, err := a.DbMan.GetCompanyDevelopers(org, priKey, priVal, "", "")
if err != nil {
log.Errorf("getCompanyDeveloper: %v", err)
return nil, newDbError(err)
}
if len(devs) == 0 {
return nil, ErrNotFound
}
var details []*CompanyDeveloperDetails
for _, dev := range devs {
comName, err := a.DbMan.GetComNames(dev.CompanyId, TypeCompany)
if err != nil || len(comName) == 0 {
log.Errorf("getCompanyDeveloper: %v", err)
return nil, newDbError(err)
}
email, err := a.DbMan.GetDevEmailByDevId(dev.DeveloperId)
if err != nil {
log.Errorf("getCompanyDeveloper: %v", err)
return nil, newDbError(err)
}
detail := makeComDevDetails(&dev, comName[0], email)
details = append(details, detail)
}
return &CompanyDevelopersSuccessResponse{
CompanyDevelopers: details,
Organization: org,
PrimaryIdentifierType: priKey,
PrimaryIdentifierValue: priVal,
}, nil
}
func (a *ApiManager) getDeveloper(org string, ids map[string]string) (*DeveloperSuccessResponse, *common.ErrorResponse) {
valid, keyVals := parseIdentifiers(EndpointDeveloper, ids)
if !valid {
return nil, ErrInvalidPar
}
priKey, priVal := keyVals[0], keyVals[1]
devs, err := a.DbMan.GetDevelopers(org, priKey, priVal, "", "")
if err != nil {
log.Errorf("getDeveloper: %v", err)
return nil, newDbError(err)
}
if len(devs) == 0 {
return nil, ErrNotFound
}
dev := &devs[0]
attrs := a.DbMan.GetKmsAttributes(dev.TenantId, dev.Id)[dev.Id]
comNames, err := a.DbMan.GetComNames(dev.Id, TypeDeveloper)
if err != nil {
log.Errorf("getDeveloper: %v", err)
return nil, newDbError(err)
}
appNames, err := a.DbMan.GetAppNames(dev.Id, TypeDeveloper)
if err != nil {
log.Errorf("getDeveloper: %v", err)
return nil, newDbError(err)
}
details := makeDevDetails(dev, appNames, comNames, attrs)
return &DeveloperSuccessResponse{
Developer: details,
Organization: org,
PrimaryIdentifierType: priKey,
PrimaryIdentifierValue: priVal,
}, nil
}
func (a *ApiManager) getCompany(org string, ids map[string]string) (*CompanySuccessResponse, *common.ErrorResponse) {
valid, keyVals := parseIdentifiers(EndpointCompany, ids)
if !valid {
return nil, ErrInvalidPar
}
priKey, priVal := keyVals[0], keyVals[1]
coms, err := a.DbMan.GetCompanies(org, priKey, priVal, "", "")
if err != nil {
log.Errorf("getCompany: %v", err)
return nil, newDbError(err)
}
if len(coms) == 0 {
return nil, ErrNotFound
}
com := &coms[0]
attrs := a.DbMan.GetKmsAttributes(com.TenantId, com.Id)[com.Id]
appNames, err := a.DbMan.GetAppNames(com.Id, TypeCompany)
if err != nil {
log.Errorf("getCompany: %v", err)
return nil, newDbError(err)
}
details := makeCompanyDetails(com, appNames, attrs)
return &CompanySuccessResponse{
Company: details,
Organization: org,
PrimaryIdentifierType: priKey,
PrimaryIdentifierValue: priVal,
}, nil
}
func (a *ApiManager) getApiProduct(org string, ids map[string]string) (*ApiProductSuccessResponse, *common.ErrorResponse) {
valid, keyVals := parseIdentifiers(EndpointApiProduct, ids)
if !valid {
return nil, ErrInvalidPar
}
priKey, priVal, secKey, secVal := keyVals[0], keyVals[1], "", ""
if len(keyVals) > 2 {
secKey, secVal = keyVals[2], keyVals[3]
}
prods, err := a.DbMan.GetApiProducts(org, priKey, priVal, secKey, secVal)
if err != nil {
log.Errorf("getApiProduct: %v", err)
return nil, newDbError(err)
}
var attrs []common.Attribute
if len(prods) == 0 {
return nil, ErrNotFound
}
prod := &prods[0]
attrs = a.DbMan.GetKmsAttributes(prod.TenantId, prod.Id)[prod.Id]
details, errRes := makeApiProductDetails(prod, attrs)
if errRes != nil {
return nil, errRes
}
return &ApiProductSuccessResponse{
ApiProduct: details,
Organization: org,
PrimaryIdentifierType: priKey,
PrimaryIdentifierValue: priVal,
SecondaryIdentifierType: secKey,
SecondaryIdentifierValue: secVal,
}, nil
}
func (a *ApiManager) getAppCredential(org string, ids map[string]string) (*AppCredentialSuccessResponse, *common.ErrorResponse) {
valid, keyVals := parseIdentifiers(EndpointApiProduct, ids)
if !valid {
return nil, ErrInvalidPar
}
priKey, priVal := keyVals[0], keyVals[1]
appCreds, err := a.DbMan.GetAppCredentials(org, priKey, priVal, "", "")
if err != nil {
log.Errorf("getAppCredential: %v", err)
return nil, newDbError(err)
}
if len(appCreds) == 0 {
return nil, ErrNotFound
}
appCred := &appCreds[0]
attrs := a.DbMan.GetKmsAttributes(appCred.TenantId, appCred.Id)[appCred.Id]
apps, err := a.DbMan.GetApps(org, IdentifierAppId, appCred.AppId, "", "")
if err != nil {
log.Errorf("getAppCredential: %v", err)
return nil, newDbError(err)
}
if len(apps) == 0 {
log.Errorf("getAppCredential: No App with id=%v", appCred.AppId)
return &AppCredentialSuccessResponse{
AppCredential: nil,
Organization: org,
}, nil
}
app := &apps[0]
cd, errRes := a.getCredDetails(appCred, app.Status)
if errRes != nil {
return nil, errRes
}
devStatus := ""
if app.DeveloperId != "" {
devStatus, err = a.DbMan.GetStatus(app.DeveloperId, AppTypeDeveloper)
if err != nil {
log.Errorf("getAppCredential error get status: %v", err)
return nil, newDbError(err)
}
}
cks := makeConsumerKeyStatusDetails(app, cd, devStatus)
details := makeAppCredentialDetails(appCred, cks, []string{app.CallbackUrl}, attrs)
return &AppCredentialSuccessResponse{
AppCredential: details,
Organization: org,
PrimaryIdentifierType: priKey,
PrimaryIdentifierValue: priVal,
}, nil
}
func (a *ApiManager) getApp(org string, ids map[string]string) (*AppSuccessResponse, *common.ErrorResponse) {
valid, keyVals := parseIdentifiers(EndpointApp, ids)
if !valid {
return nil, ErrInvalidPar
}
priKey, priVal, secKey, secVal := keyVals[0], keyVals[1], "", ""
if len(keyVals) > 2 {
secKey, secVal = keyVals[2], keyVals[3]
}
apps, err := a.DbMan.GetApps(org, priKey, priVal, secKey, secVal)
if err != nil {
log.Errorf("getApp: %v", err)
return nil, newDbError(err)
}
var app *common.App
var attrs []common.Attribute
if len(apps) == 0 {
return nil, ErrNotFound
}
app = &apps[0]
attrs = a.DbMan.GetKmsAttributes(app.TenantId, app.Id)[app.Id]
prods, err := a.DbMan.GetApiProductNames(app.Id, TypeApp)
if err != nil {
log.Errorf("getApp error getting productNames: %v", err)
return nil, newDbError(err)
}
parStatus, err := a.DbMan.GetStatus(app.ParentId, app.Type)
if err != nil {
log.Errorf("getApp error getting parent status: %v", err)
return nil, newDbError(err)
}
creds, err := a.DbMan.GetAppCredentials(org, IdentifierAppId, app.Id, "", "")
if err != nil {
log.Errorf("getApp error getting parent status: %v", err)
return nil, newDbError(err)
}
var credDetails []*CredentialDetails
for _, cred := range creds {
detail, errRes := a.getCredDetails(&cred, app.Status)
if errRes != nil {
return nil, errRes
}
credDetails = append(credDetails, detail)
}
parent, errRes := a.getAppParent(app.ParentId, app.Type)
if errRes != nil {
return nil, errRes
}
details, errRes := makeAppDetails(app, parent, parStatus, prods, credDetails, attrs)
if errRes != nil {
return nil, errRes
}
return &AppSuccessResponse{
App: details,
Organization: org,
PrimaryIdentifierType: priKey,
PrimaryIdentifierValue: priVal,
SecondaryIdentifierType: secKey,
SecondaryIdentifierValue: secVal,
}, nil
}
func (a *ApiManager) getAppParent(id string, parentType string) (string, *common.ErrorResponse) {
switch parentType {
case AppTypeDeveloper:
return id, nil
case AppTypeCompany:
names, err := a.DbMan.GetComNames(id, TypeCompany)
if err != nil {
return "", newDbError(err)
}
if len(names) == 0 {
log.Warnf("getAppParent: No company with id=%v", id)
return "", nil
}
return names[0], nil
}
return "", nil
}
func makeConsumerKeyStatusDetails(app *common.App, c *CredentialDetails, devStatus string) *ConsumerKeyStatusDetails {
return &ConsumerKeyStatusDetails{
AppCredential: c,
AppID: c.AppID,
AppName: app.Name,
AppStatus: app.Status,
AppType: app.Type,
DeveloperID: app.DeveloperId,
DeveloperStatus: devStatus,
IsValidKey: strings.EqualFold(c.Status, StatusApproved),
}
}
func makeAppCredentialDetails(ac *common.AppCredential, cks *ConsumerKeyStatusDetails, redirectUrl []string, attrs []common.Attribute) *AppCredentialDetails {
return &AppCredentialDetails{
AppID: ac.AppId,
AppName: cks.AppName,
Attributes: attrs,
ConsumerKey: ac.Id,
ConsumerKeyStatus: cks,
ConsumerSecret: ac.ConsumerSecret,
DeveloperID: cks.DeveloperID,
RedirectUris: redirectUrl,
Scopes: common.JsonToStringArray(ac.Scopes),
Status: ac.Status,
}
}
func makeApiProductDetails(prod *common.ApiProduct, attrs []common.Attribute) (*ApiProductDetails, *common.ErrorResponse) {
var a *ApiProductDetails
if prod != nil {
var quotaLimit int
var err error
if prod.Quota != "" {
quotaLimit, err = strconv.Atoi(prod.Quota)
if err != nil {
return nil, newDataError(err)
}
}
a = &ApiProductDetails{
ApiProxies: common.JsonToStringArray(prod.Proxies),
ApiResources: common.JsonToStringArray(prod.ApiResources),
ApprovalType: prod.ApprovalType,
Attributes: attrs,
CreatedAt: prod.CreatedAt,
CreatedBy: prod.CreatedBy,
Description: prod.Description,
DisplayName: prod.DisplayName,
Environments: common.JsonToStringArray(prod.Environments),
ID: prod.Id,
LastModifiedAt: prod.UpdatedAt,
LastModifiedBy: prod.UpdatedBy,
Name: prod.Name,
QuotaInterval: prod.QuotaInterval,
QuotaLimit: int64(quotaLimit),
QuotaTimeUnit: prod.QuotaTimeUnit,
Scopes: common.JsonToStringArray(prod.Scopes),
}
} else {
a = new(ApiProductDetails)
}
return a, nil
}
func makeAppDetails(app *common.App, parent string, parentStatus string, prods []string, creds []*CredentialDetails, attrs []common.Attribute) (*AppDetails, *common.ErrorResponse) {
var a *AppDetails
if app != nil {
a = &AppDetails{
AccessType: app.AccessType,
ApiProducts: prods,
AppCredentials: creds,
AppFamily: app.AppFamily,
AppParentID: parent,
AppParentStatus: parentStatus,
AppType: app.Type,
Attributes: attrs,
CallbackUrl: app.CallbackUrl,
CreatedAt: app.CreatedAt,
CreatedBy: app.CreatedBy,
DisplayName: app.DisplayName,
Id: app.Id,
LastModifiedAt: app.UpdatedAt,
LastModifiedBy: app.UpdatedBy,
Name: app.Name,
Status: app.Status,
}
} else {
a = new(AppDetails)
}
return a, nil
}
func makeCompanyDetails(com *common.Company, appNames []string, attrs []common.Attribute) *CompanyDetails {
return &CompanyDetails{
Apps: appNames,
Attributes: attrs,
CreatedAt: com.CreatedAt,
CreatedBy: com.CreatedBy,
DisplayName: com.DisplayName,
ID: com.Id,
LastModifiedAt: com.UpdatedAt,
LastModifiedBy: com.UpdatedBy,
Name: com.Name,
Status: com.Status,
}
}
func makeDevDetails(dev *common.Developer, appNames []string, comNames []string, attrs []common.Attribute) *DeveloperDetails {
return &DeveloperDetails{
Apps: appNames,
Attributes: attrs,
Companies: comNames,
CreatedAt: dev.CreatedAt,
CreatedBy: dev.CreatedBy,
Email: dev.Email,
FirstName: dev.FirstName,
ID: dev.Id,
LastModifiedAt: dev.UpdatedAt,
LastModifiedBy: dev.UpdatedBy,
LastName: dev.LastName,
Password: dev.Password,
Status: dev.Status,
UserName: dev.UserName,
}
}
func makeComDevDetails(comDev *common.CompanyDeveloper, comName, devEmail string) *CompanyDeveloperDetails {
return &CompanyDeveloperDetails{
CompanyName: comName,
CreatedAt: comDev.CreatedAt,
CreatedBy: comDev.CreatedBy,
DeveloperEmail: devEmail,
LastModifiedAt: comDev.UpdatedAt,
LastModifiedBy: comDev.UpdatedBy,
Roles: common.JsonToStringArray(comDev.Roles),
}
}
func (a *ApiManager) getCredDetails(cred *common.AppCredential, appStatus string) (*CredentialDetails, *common.ErrorResponse) {
refs, err := a.DbMan.GetApiProductNames(cred.Id, TypeConsumerKey)
if err != nil {
log.Errorf("Error when getting product reference list")
return nil, newDbError(err)
}
return &CredentialDetails{
ApiProductReferences: refs,
AppID: cred.AppId,
AppStatus: appStatus,
Attributes: a.DbMan.GetKmsAttributes(cred.TenantId, cred.Id)[cred.Id],
ConsumerKey: cred.Id,
ConsumerSecret: cred.ConsumerSecret,
ExpiresAt: cred.ExpiresAt,
IssuedAt: cred.IssuedAt,
MethodType: cred.MethodType,
Scopes: common.JsonToStringArray(cred.Scopes),
Status: cred.Status,
}, nil
}
func parseIdentifiers(endpoint string, ids map[string]string) (valid bool, keyVals []string) {
if len(ids) > 2 {
return false, nil
}
if m := IdentifierTree[endpoint]; m != nil {
for key, val := range ids {
if m[key] != nil {
keyVals = append(keyVals, key, val)
for _, id := range m[key] {
if ids[id] != "" {
keyVals = append(keyVals, id, ids[id])
return true, keyVals
}
}
if len(ids) == 2 {
return false, nil
}
return true, keyVals
}
}
}
return false, nil
}
func newDbError(err error) *common.ErrorResponse {
return &common.ErrorResponse{
ResponseCode: strconv.Itoa(DB_ERROR),
ResponseMessage: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
func newDataError(err error) *common.ErrorResponse {
return &common.ErrorResponse{
ResponseCode: strconv.Itoa(DATA_ERROR),
ResponseMessage: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
func writeJson(obj interface{}, w http.ResponseWriter, r *http.Request) {
bytes, err := json.Marshal(obj)
if err != nil {
log.Error("unable to marshal errorResponse: " + err.Error())
w.Write([]byte("unable to marshal errorResponse: " + err.Error()))
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(bytes)
}
}