Light it up!
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54e1ced --- /dev/null +++ b/.gitignore
@@ -0,0 +1,3 @@ +profile.out +cover.html +coverage.txt
diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a05ffb1 --- /dev/null +++ b/LICENSE
@@ -0,0 +1,13 @@ +Copyright 2016 Apigee Corporation + +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/README.md b/README.md new file mode 100644 index 0000000..c7c1436 --- /dev/null +++ b/README.md
@@ -0,0 +1,5 @@ +# apidVerifyAPIKey + +This core plugin for [apid](http://github.com/30x/apid) responds to [apidApigeeSync](https://github.com/30x/apidApigeeSync) +events and publishes an API that allows clients to verify an API key against Apigee. +
diff --git a/api.go b/api.go new file mode 100644 index 0000000..241e472 --- /dev/null +++ b/api.go
@@ -0,0 +1,167 @@ +package apidVerifyApiKey + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" +) + +type sucResponseDetail struct { + Key string `json:"key"` + ExpiresAt int64 `json:"expiresAt"` + IssuedAt int64 `json:"issuedAt"` + Status string `json:"status"` + RedirectionURIs string `json:"redirectionURIs"` + DeveloperAppId string `json:"developerId"` + DeveloperAppNam string `json:"developerAppName"` +} + +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) { + if r.Method != "POST" { + return + } + + err := r.ParseForm() + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Unable to parse form")) + } + + f := r.Form + elems := []string{"action", "key", "uriPath", "organization", "environment"} + for _, elem := range elems { + if f.Get(elem) == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf("Missing element: %s", elem))) + } + } + + org := f.Get("organization") + key := f.Get("key") + path := f.Get("uriPath") + env := f.Get("environment") + action := f.Get("action") + + b, err := verifyAPIKey(key, path, env, org, action) + 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) +} + +// todo: The following was basically just copied from old APID - needs review. + +// returns []byte to be written to client +func verifyAPIKey(key, path, env, org, action string) ([]byte, error) { + var ( + sSql string + status, redirectionURIs string + developerAppName, developerId string + resName, resEnv, reason, errorCode string + issuedAt, expiresAt int64 + ) + + if key == "" || org == "" || path == "" || env == "" || action != "verify" { + log.Error("Input params Invalid/Incomplete") + reason = "Input Params Incomplete or Invalid" + errorCode = "INCORRECT_USER_INPUT" + return errorResponse(reason, errorCode) + } + + db, err := data.DB() + if err != nil { + log.Errorf("Unable to access DB") + reason = err.Error() + errorCode = "SEARCH_INTERNAL_ERROR" + return errorResponse(reason, errorCode) + } + + /* + * NOTE: that here c.expired_at has been commented out because it is not + * kept track of by Cassandra (hence always defaults to -1). FIXME + */ + + sSql = "SELECT ap.res_names, ap.env, c.issued_at, c.status, a.cback_url, d.username, d.id FROM APP_CREDENTIAL AS c INNER JOIN APP AS a ON c.app_id = a.id INNER JOIN DEVELOPER AS d ON a.dev_id = d.id INNER JOIN APP_AND_API_PRODUCT_MAPPER as mp ON mp.app_cred_id = c.id INNER JOIN API_PRODUCT as ap ON ap.id = mp.api_prdt_id WHERE (UPPER(d.sts) = 'ACTIVE' AND mp.api_prdt_id = ap.id AND mp.app_id = a.id AND mp.app_cred_id = c.id AND UPPER(mp.api_prdt_status) = 'APPROVED' AND UPPER(a.status) = 'APPROVED' AND UPPER(c.status) = 'APPROVED' AND c.id = '" + key + "' AND c.org = '" + org + "');" + + err = db.QueryRow(sSql).Scan(&resName, &resEnv, &issuedAt, &status, + &redirectionURIs, &developerAppName, &developerId) + expiresAt = -1 + switch { + case err == sql.ErrNoRows: + reason = "API Key verify failed for (" + key + ", " + org + ", " + path + ", " + env + ")" + 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, path) + if result == false { + reason = "Path Validation Failed (" + resName + " vs " + path + ")" + errorCode = "PATH_VALIDATION_FAILED" + return errorResponse(reason, errorCode) + + } + + /* Verify if the ENV matches */ + result = validateEnv(resEnv, env) + if result == false { + reason = "ENV Validation Failed (" + resEnv + " vs " + env + ")" + errorCode = "ENV_VALIDATION_FAILED" + return errorResponse(reason, errorCode) + } + + resp := kmsResponseSuccess{ + Type: "APIKeyContext", + RspInfo: sucResponseDetail{ + Key: key, + ExpiresAt: expiresAt, + IssuedAt: issuedAt, + Status: status, + RedirectionURIs: redirectionURIs, + DeveloperAppId: developerId, + DeveloperAppNam: developerAppName}, + } + return json.Marshal(resp) +} + +func errorResponse(reason, errorCode string) ([]byte, error) { + + log.Error(reason) + resp := kmsResponseFail{ + Type: "ErrorResult", + ErrInfo: errResultDetail{ + Reason: reason, + ErrorCode: errorCode}, + } + return json.Marshal(resp) +}
diff --git a/api_test.go b/api_test.go new file mode 100644 index 0000000..2d9d921 --- /dev/null +++ b/api_test.go
@@ -0,0 +1,238 @@ +package apidVerifyApiKey + +import ( + "database/sql" + "encoding/json" + "github.com/30x/apid" + "github.com/30x/apid/factory" + . "github.com/30x/apidApigeeSync" // for direct access to Payload types + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strconv" + "strings" +) + +var _ = Describe("api", func() { + + var tmpDir string + var db *sql.DB + var server *httptest.Server + + BeforeSuite(func() { + apid.Initialize(factory.DefaultServicesFactory()) + + config := apid.Config() + + var err error + tmpDir, err = ioutil.TempDir("", "api_test") + Expect(err).NotTo(HaveOccurred()) + + config.Set("data_path", tmpDir) + + // init() will create the tables + apid.InitializePlugins() + + db, err = apid.Data().DB() + Expect(err).NotTo(HaveOccurred()) + insertTestData(db) + + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == apiPath { + handleRequest(w, req) + } + })) + }) + + AfterSuite(func() { + apid.Events().Close() + server.Close() + os.RemoveAll(tmpDir) + }) + + Context("verifyAPIKey() directly", func() { + + It("should reject a bad key", func() { + rsp, err := verifyAPIKey("credential_x", "/test", "Env_0", "Org_0", "verify") + Expect(err).ShouldNot(HaveOccurred()) + + var respj kmsResponseFail + json.Unmarshal(rsp, &respj) + Expect(respj.Type).Should(Equal("ErrorResult")) + Expect(respj.ErrInfo.ErrorCode).Should(Equal("REQ_ENTRY_NOT_FOUND")) + + }) + + It("should reject a key once it's deleted", func() { + pd0 := &DataPayload{ + EntityIdentifier: "credential_0", + } + res := deleteCredential(*pd0, db, "Org_0") + Expect(res).Should(BeTrue()) + + var respj kmsResponseFail + rsp, err := verifyAPIKey("credential_0", "/test", "Env_0", "Org_0", "verify") + Expect(err).ShouldNot(HaveOccurred()) + + json.Unmarshal(rsp, &respj) + Expect(respj.Type).Should(Equal("ErrorResult")) + Expect(respj.ErrInfo.ErrorCode).Should(Equal("REQ_ENTRY_NOT_FOUND")) + }) + + It("should successfully verify good keys", func() { + for i := 1; i < 10; i++ { + resulti := strconv.FormatInt(int64(i), 10) + rsp, err := verifyAPIKey("credential_"+resulti, "/test", "Env_0", "Org_0", "verify") + Expect(err).ShouldNot(HaveOccurred()) + + var respj kmsResponseSuccess + json.Unmarshal(rsp, &respj) + Expect(respj.Type).Should(Equal("APIKeyContext")) + Expect(respj.RspInfo.Key).Should(Equal("credential_" + resulti)) + } + }) + }) + + Context("access via API", func() { + + It("should reject a bad key", func() { + + uri, err := url.Parse(server.URL) + uri.Path = apiPath + + v := url.Values{} + v.Add("organization", "Org_0") + v.Add("key", "credential_x") + v.Add("environment", "Env_0") + v.Add("uriPath", "/test") + v.Add("action", "verify") + + client := &http.Client{} + req, err := http.NewRequest("POST", uri.String(), strings.NewReader(v.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") + + res, err := client.Do(req) + defer res.Body.Close() + Expect(err).ShouldNot(HaveOccurred()) + + var respj kmsResponseFail + body, err := ioutil.ReadAll(res.Body) + Expect(err).ShouldNot(HaveOccurred()) + json.Unmarshal(body, &respj) + Expect(respj.Type).Should(Equal("ErrorResult")) + Expect(respj.ErrInfo.ErrorCode).Should(Equal("REQ_ENTRY_NOT_FOUND")) + }) + + It("should successfully verify a good key", func() { + + uri, err := url.Parse(server.URL) + uri.Path = apiPath + + v := url.Values{} + v.Add("organization", "Org_0") + v.Add("key", "credential_1") + v.Add("environment", "Env_0") + v.Add("uriPath", "/test") + v.Add("action", "verify") + + client := &http.Client{} + req, err := http.NewRequest("POST", uri.String(), strings.NewReader(v.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") + + res, err := client.Do(req) + defer res.Body.Close() + Expect(err).ShouldNot(HaveOccurred()) + + var respj kmsResponseSuccess + body, err := ioutil.ReadAll(res.Body) + Expect(err).ShouldNot(HaveOccurred()) + json.Unmarshal(body, &respj) + Expect(respj.Type).Should(Equal("APIKeyContext")) + Expect(respj.RspInfo.Key).Should(Equal("credential_1")) + }) + }) +}) + +func insertTestData(db *sql.DB) { + + for i := 0; i < 10; i++ { + result := strconv.FormatInt(int64(i), 10) + pd0 := &DataPayload{ + PldCont: Payload{ + AppName: "Api_product_" + result, + Resources: []string{"/**", "/test"}, + Environments: []string{"Env_0", "Env_1"}, + }, + } + + res := insertAPIproduct(*pd0, db, "Org_0") + Expect(res).Should(BeTrue()) + } + + for i := 0; i < 10; i++ { + result := strconv.FormatInt(int64(i), 10) + + pd1 := &DataPayload{ + EntityIdentifier: "developer_id_" + result, + PldCont: Payload{ + Email: "person_0@apigee.com", + Status: "Active", + UserName: "user_0", + FirstName: "user_first_name0", + LastName: "user_last_name0", + }, + } + + res := insertCreateDeveloper(*pd1, db, "Org_0") + Expect(res).Should(BeTrue()) + } + + var j, k int + for i := 0; i < 10; i++ { + resulti := strconv.FormatInt(int64(i), 10) + for j = k; j < 10+k; j++ { + resultj := strconv.FormatInt(int64(j), 10) + pd2 := &DataPayload{ + EntityIdentifier: "application_id_" + resultj, + PldCont: Payload{ + Email: "person_0@apigee.com", + Status: "Approved", + AppName: "application_id_" + resultj, + DeveloperId: "developer_id_" + resulti, + CallbackUrl: "call_back_url_0", + }, + } + + res := insertCreateApplication(*pd2, db, "Org_0") + Expect(res).Should(BeTrue()) + } + k = j + } + + j = 0 + k = 0 + for i := 0; i < 10; i++ { + resulti := strconv.FormatInt(int64(i), 10) + for j = k; j < 10+k; j++ { + resultj := strconv.FormatInt(int64(j), 10) + pd3 := &DataPayload{ + EntityIdentifier: "credential_" + resultj, + PldCont: Payload{ + AppId: "application_id_" + resulti, + Status: "Approved", + ConsumerSecret: "consumer_secret_0", + IssuedAt: 349583485, + ApiProducts: []Apip{{ApiProduct: "Api_product_0", Status: "Approved"}}, + }, + } + + res := insertCreateCredential(*pd3, db, "Org_0") + Expect(res).Should(BeTrue()) + } + k = j + } +}
diff --git a/cover.sh b/cover.sh new file mode 100755 index 0000000..7c312e5 --- /dev/null +++ b/cover.sh
@@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +echo "mode: atomic" > coverage.txt + +for d in $(go list ./... | grep -v vendor); do + go test -coverprofile=profile.out -covermode=atomic $d + if [ -f profile.out ]; then + tail +2 profile.out >> coverage.txt + rm profile.out + fi +done +go tool cover -html=coverage.txt -o cover.html
diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..2dcac69 --- /dev/null +++ b/glide.yaml
@@ -0,0 +1,7 @@ +package: github.com/30x/apidVerifyAPIKey +import: +- package: github.com/30x/apid +- package: github.com/30x/apidApigeeSync +testImport: +- package: github.com/onsi/ginkgo/ginkgo +- package: github.com/onsi/gomega \ No newline at end of file
diff --git a/init.go b/init.go new file mode 100644 index 0000000..a8dd899 --- /dev/null +++ b/init.go
@@ -0,0 +1,57 @@ +package apidVerifyApiKey + +import ( + "database/sql" + "github.com/30x/apid" + "github.com/30x/apidApigeeSync" +) + +const ( + apiPath = "/verifyAPIKey" +) + +var ( + log apid.LogService + data apid.DataService + events apid.EventsService +) + +func init() { + apid.RegisterPlugin(initPlugin) +} + +func initPlugin(services apid.Services) error { + log = services.Log().ForModule("apidVerifyAPIKey") + log.Debug("start init") + + data = services.Data() + events = services.Events() + + db, err := data.DB() + if err != nil { + log.Panic("Unable to access DB", err) + } + + var count int + row := db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table';") + if err := row.Scan(&count); err != nil { + log.Panic("Unable to setup database", err) + } + if count == 0 { + createTables(db) + } + + services.API().HandleFunc(apiPath, handleRequest) + + events.Listen(apidApigeeSync.ApigeeSyncEventSelector, &handler{}) + log.Debug("end init") + + return nil +} + +func createTables(db *sql.DB) { + _, err := db.Exec("CREATE TABLE COMPANY (org varchar(255), id varchar(255), PRIMARY KEY (id, org));CREATE TABLE DEVELOPER (org varchar(255), email varchar(255), id varchar(255), sts varchar(255), username varchar(255), firstname varchar(255), lastname varchar(255), apigee_scope varchar(255), enc_password varchar(255), salt varchar(255), created_at integer, created_by varchar(255), updated_at integer, updated_by varchar(255), PRIMARY KEY (id, org));CREATE TABLE APP (org varchar(255), id varchar(255), dev_id varchar(255) null, cmp_id varchar(255) null, display_name varchar(255), apigee_scope varchar(255), type varchar(255), access_type varchar(255), cback_url varchar(255), status varchar(255), name varchar(255), app_family varchar(255), created_at integer, created_by varchar(255), updated_at integer, updated_by varchar(255), PRIMARY KEY (id, org), FOREIGN KEY (dev_id, org) references DEVELOPER (id, org) ON DELETE CASCADE);CREATE TABLE APP_CREDENTIAL (org varchar(255), id varchar(255), app_id varchar(255), cons_secret varchar(255), method_type varchar(255), status varchar(255), issued_at integer, expire_at integer, created_at integer, created_by varchar(255), updated_at integer, updated_by varchar(255), PRIMARY KEY (id, org), FOREIGN KEY (app_id, org) references app (id, org) ON DELETE CASCADE);CREATE TABLE API_PRODUCT (org varchar(255), id varchar(255), res_names varchar(255), env varchar(255), PRIMARY KEY (id, org));CREATE TABLE COMPANY_DEVELOPER (org varchar(255), dev_id varchar(255), id varchar(255), cmpny_id varchar(255), PRIMARY KEY (id, org), FOREIGN KEY (cmpny_id) references company(id) ON DELETE CASCADE, FOREIGN KEY (dev_id, org) references DEVELOPER(id, org) ON DELETE CASCADE);CREATE TABLE APP_AND_API_PRODUCT_MAPPER (org varchar(255), api_prdt_id varchar(255), app_id varchar(255), app_cred_id varchar(255), api_prdt_status varchar(255), PRIMARY KEY (org, api_prdt_id, app_id, app_cred_id), FOREIGN KEY (api_prdt_id, org) references api_product(id, org) ON DELETE CASCADE, FOREIGN KEY (app_cred_id, org) references app_credential(id, org) ON DELETE CASCADE, FOREIGN KEY (app_id, org) references app(id, org) ON DELETE CASCADE);") + if err != nil { + log.Panic("Unable to initialize DB", err) + } +}
diff --git a/listener.go b/listener.go new file mode 100644 index 0000000..6aec26f --- /dev/null +++ b/listener.go
@@ -0,0 +1,254 @@ +package apidVerifyApiKey + +import ( + "database/sql" + "encoding/json" + "github.com/30x/apid" + "github.com/30x/apidApigeeSync" +) + +type handler struct { +} + +func (h *handler) String() string { + return "verifyAPIKey" +} + +// todo: The following was basically just copied from old APID - needs review. + +func (h *handler) Handle(e apid.Event) { + changeSet, ok := e.(*apidApigeeSync.ChangeSet) + if !ok { + log.Errorf("Received non-ChangeSet event. This shouldn't happen!") + return + } + + log.Debugf("apigeeSyncEvent: %d changes", len(changeSet.Changes)) + + db, err := data.DB() + if err != nil { + panic("help me!") // todo: handle + } + + for _, payload := range changeSet.Changes { + + org := payload.Data.PldCont.Organization + + switch payload.Data.EntityType { + case "developer": + switch payload.Data.Operation { + case "create": + insertCreateDeveloper(payload.Data, db, org) + } + + case "app": + switch payload.Data.Operation { + case "create": + insertCreateApplication(payload.Data, db, org) + } + + case "credential": + switch payload.Data.Operation { + case "create": + insertCreateCredential(payload.Data, db, org) + + case "delete": + deleteCredential(payload.Data, db, org) + } + + case "apiproduct": + switch payload.Data.Operation { + case "create": + insertAPIproduct(payload.Data, db, org) + } + } + + } +} + +/* + * INSERT INTO APP_CREDENTIAL op + */ +func insertCreateCredential(ele apidApigeeSync.DataPayload, db *sql.DB, org string) bool { + + txn, _ := db.Begin() + isPass := true + _, err := txn.Exec("INSERT INTO APP_CREDENTIAL (org, id, app_id, cons_secret, status, issued_at)VALUES(?,?,?,?,?,?);", + org, + ele.EntityIdentifier, + ele.PldCont.AppId, + ele.PldCont.ConsumerSecret, + ele.PldCont.Status, + ele.PldCont.IssuedAt) + + if err != nil { + isPass = false + log.Error("INSERT CRED Failed: ", ele.EntityIdentifier, org, ")", err) + goto OT + } else { + log.Info("INSERT CRED Success: (", ele.EntityIdentifier, org, ")") + } + + /* + * If the credentials has been successfully inserted, insert the + * mapping entries associated with the credential + */ + + for _, elem := range ele.PldCont.ApiProducts { + + _, err = txn.Exec("INSERT INTO APP_AND_API_PRODUCT_MAPPER (org, api_prdt_id, app_id, app_cred_id, api_prdt_status) VALUES(?,?,?,?,?);", + org, + elem.ApiProduct, + ele.PldCont.AppId, + ele.EntityIdentifier, + elem.Status) + + if err != nil { + isPass = false + log.Error("INSERT APP_AND_API_PRODUCT_MAPPER Failed: (", + org, + elem.ApiProduct, + ele.PldCont.AppId, + ele.EntityIdentifier, + ")", + err) + break + } else { + log.Info("INSERT APP_AND_API_PRODUCT_MAPPER Success: (", + org, + elem.ApiProduct, + ele.PldCont.AppId, + ele.EntityIdentifier, + ")") + } + } +OT: + if isPass == true { + txn.Commit() + } else { + txn.Rollback() + } + return isPass + +} + +/* + * DELETE CRED + */ +func deleteCredential(ele apidApigeeSync.DataPayload, db *sql.DB, org string) bool { + + txn, _ := db.Begin() + + _, err := txn.Exec("DELETE FROM APP_CREDENTIAL WHERE org=? AND id=?;", org, ele.EntityIdentifier) + + if err != nil { + log.Error("DELETE CRED Failed: (", ele.EntityIdentifier, org, ")", err) + txn.Rollback() + return false + } else { + log.Info("DELETE CRED Success: (", ele.EntityIdentifier, org, ")") + txn.Commit() + return true + } + +} + +/* + * Helper function to convert string slice in to JSON format + */ +func convertSlicetoStringFormat(inpslice []string) string { + + bytes, _ := json.Marshal(inpslice) + return string(bytes) +} + +/* + * INSERT INTO API product op + */ +func insertAPIproduct(ele apidApigeeSync.DataPayload, db *sql.DB, org string) bool { + + txn, _ := db.Begin() + restr := convertSlicetoStringFormat(ele.PldCont.Resources) + envstr := convertSlicetoStringFormat(ele.PldCont.Environments) + + _, err := txn.Exec("INSERT INTO API_PRODUCT (org, id, res_names, env) VALUES(?,?,?,?)", + org, + ele.PldCont.AppName, + restr, + envstr) + + if err != nil { + log.Error("INSERT API_PRODUCT Failed: (", ele.PldCont.AppName, org, ")", err) + txn.Rollback() + return false + } else { + log.Info("INSERT API_PRODUCT Success: (", ele.PldCont.AppName, org, ")") + txn.Commit() + return true + } + +} + +/* + * INSERT INTO APP op + */ +func insertCreateApplication(ele apidApigeeSync.DataPayload, db *sql.DB, org string) bool { + + txn, _ := db.Begin() + + _, err := txn.Exec("INSERT INTO APP (org, id, dev_id,cback_url,status, name, app_family, created_at, created_by,updated_at, updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?);", + org, + ele.EntityIdentifier, + ele.PldCont.DeveloperId, + ele.PldCont.CallbackUrl, + ele.PldCont.Status, + ele.PldCont.AppName, + ele.PldCont.AppFamily, + ele.PldCont.CreatedAt, + ele.PldCont.CreatedBy, + ele.PldCont.LastModifiedAt, + ele.PldCont.LastModifiedBy) + + if err != nil { + log.Error("INSERT APP Failed: (", ele.EntityIdentifier, org, ")", err) + txn.Rollback() + return false + } else { + log.Info("INSERT APP Success: (", ele.EntityIdentifier, org, ")") + txn.Commit() + return true + } + +} + +/* + * INSERT INTO DEVELOPER op + */ +func insertCreateDeveloper(ele apidApigeeSync.DataPayload, db *sql.DB, org string) bool { + + txn, _ := db.Begin() + + _, err := txn.Exec("INSERT INTO DEVELOPER (org, email, id, sts, username, firstname, lastname, created_at,created_by, updated_at, updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?);", + org, + ele.PldCont.Email, + ele.EntityIdentifier, + ele.PldCont.Status, + ele.PldCont.UserName, + ele.PldCont.FirstName, + ele.PldCont.LastName, + ele.PldCont.CreatedAt, + ele.PldCont.CreatedBy, + ele.PldCont.LastModifiedAt, + ele.PldCont.LastModifiedBy) + + if err != nil { + log.Error("INSERT DEVELOPER Failed: (", ele.PldCont.UserName, org, ")", err) + txn.Rollback() + return false + } else { + log.Info("INSERT DEVELOPER Success: (", ele.PldCont.UserName, org, ")") + txn.Commit() + return true + } + +}
diff --git a/listener_test.go b/listener_test.go new file mode 100644 index 0000000..cb283d7 --- /dev/null +++ b/listener_test.go
@@ -0,0 +1,121 @@ +package apidVerifyApiKey + +import ( + "encoding/json" + "github.com/30x/apid" + . "github.com/30x/apidApigeeSync" // for direct access to Payload types + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("listener", func() { + + It("should store data from ApigeeSync in the database", func(done Done) { + + var event = ChangeSet{} + event.Changes = []ChangePayload{ + { + Data: DataPayload{ + EntityType: "apiproduct", + Operation: "create", + PldCont: Payload{ + Organization: "test_org", + AppName: "Api_product_sync", + Resources: []string{"/**", "/test"}, + Environments: []string{"Env_0", "Env_1"}, + }, + }, + }, + { + Data: DataPayload{ + EntityType: "developer", + Operation: "create", + EntityIdentifier: "developer_id_sync", + PldCont: Payload{ + Organization: "test_org", + Email: "person_sync@apigee.com", + Status: "Active", + UserName: "user_sync", + FirstName: "user_first_name_sync", + LastName: "user_last_name_sync", + }, + }, + }, + { + Data: DataPayload{ + EntityType: "app", + Operation: "create", + EntityIdentifier: "application_id_sync", + PldCont: Payload{ + Organization: "test_org", + Email: "person_sync@apigee.com", + Status: "Approved", + AppName: "application_id_sync", + DeveloperId: "developer_id_sync", + CallbackUrl: "call_back_url", + }, + }, + }, + { + Data: DataPayload{ + EntityType: "credential", + Operation: "create", + EntityIdentifier: "credential_sync", + PldCont: Payload{ + Organization: "test_org", + AppId: "application_id_sync", + Status: "Approved", + ConsumerSecret: "consumer_secret_sync", + IssuedAt: 349583485, + ApiProducts: []Apip{ + { + ApiProduct: "Api_product_sync", + Status: "Approved", + }, + }, + }, + }, + }, + } + + h := &test_handler{ + "checkDatabase", + func(e apid.Event) { + + // ignore the first event, let standard listener process it + changeSet := e.(*ChangeSet) + if len(changeSet.Changes) > 0 { + return + } + + rsp, err := verifyAPIKey("credential_sync", "/test", "Env_0", "test_org", "verify") + Expect(err).ShouldNot(HaveOccurred()) + + var respj kmsResponseSuccess + json.Unmarshal(rsp, &respj) + Expect(respj.Type).Should(Equal("APIKeyContext")) + Expect(respj.RspInfo.Key).Should(Equal("credential_sync")) + + close(done) + }, + } + + apid.Events().Listen(ApigeeSyncEventSelector, h) + apid.Events().Emit(ApigeeSyncEventSelector, &event) // for standard listener + apid.Events().Emit(ApigeeSyncEventSelector, &ChangeSet{}) // for test listener + }) + +}) + +type test_handler struct { + description string + f func(event apid.Event) +} + +func (t *test_handler) String() string { + return t.description +} + +func (t *test_handler) Handle(event apid.Event) { + t.f(event) +}
diff --git a/validate_env.go b/validate_env.go new file mode 100644 index 0000000..df0382d --- /dev/null +++ b/validate_env.go
@@ -0,0 +1,18 @@ +package apidVerifyApiKey + +import "encoding/json" + +/* + * Ensure the ENV matches. + */ +func validateEnv(envLocal, envInPath string) bool { + + var ePaths []string + json.Unmarshal([]byte(envLocal), &ePaths) + for _, a := range ePaths { + if a == envInPath { + return true + } + } + return false +}
diff --git a/validate_env_test.go b/validate_env_test.go new file mode 100644 index 0000000..3942d7a --- /dev/null +++ b/validate_env_test.go
@@ -0,0 +1,26 @@ +package apidVerifyApiKey + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Validate Env", func() { + + It("validation1", func() { + s := validateEnv("[\"foo\",\"bar\"]", "foo") + Expect(s).Should(BeTrue()) + }) + It("validation2", func() { + s := validateEnv("[\"foo\",\"bar\"]", "bar") + Expect(s).Should(BeTrue()) + }) + It("validation3", func() { + s := validateEnv("[\"foo\",\"bar\"]", "xxx") + Expect(s).Should(BeFalse()) + }) + It("validation4", func() { + s := validateEnv("[]", "xxx") + Expect(s).Should(BeFalse()) + }) +})
diff --git a/validate_path.go b/validate_path.go new file mode 100644 index 0000000..f3cfb99 --- /dev/null +++ b/validate_path.go
@@ -0,0 +1,45 @@ +package apidVerifyApiKey + +import ( + "encoding/json" + "regexp" + "strings" +) + +/* + * Check for the base path (API_Product) match with the path + * received in the Request, via the customized regex, where + * "**" gets de-normalized as ".*" and "*" as everything till + * the next "/". + */ +func validatePath(basePath, requestBase string) bool { + + var basePaths []string + json.Unmarshal([]byte(basePath), &basePaths) + for _, a := range basePaths { + str1 := strings.Replace(a, "**", "(.*)", -1) + str2 := strings.Replace(a, "*", "([^/]+)", -1) + if a != str1 { + reg, _ := regexp.Compile(str1) + res := reg.MatchString(requestBase) + if res == true { + return true + } + } else if a != str2 { + reg, _ := regexp.Compile(str2) + res := reg.MatchString(requestBase) + if res == true { + return true + } + } else if requestBase == a { + return true + } + + /* + * FIXME: SINGLE_FORWARD_SLASH_PATTERN not supported yet + */ + } + + /* if the i/p resource is empty, no checks need to be made */ + return len(basePaths) == 0 +}
diff --git a/validate_path_test.go b/validate_path_test.go new file mode 100644 index 0000000..0b76b07 --- /dev/null +++ b/validate_path_test.go
@@ -0,0 +1,94 @@ +package apidVerifyApiKey + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Validate Path", func() { + + It("validation1", func() { + s := validatePath("", "/foo") + Expect(s).Should(BeTrue()) + }) + It("validation2", func() { + s := validatePath("", "foo") + Expect(s).Should(BeTrue()) + }) + It("validation3", func() { + s := validatePath("[]", "foo") + Expect(s).Should(BeTrue()) + }) + It("validation4", func() { + s := validatePath("[\"/**\"]", "/foo") + Expect(s).Should(BeTrue()) + }) + It("validation5", func() { + s := validatePath("[\"/**\"]", "foo") + Expect(s).Should(BeFalse()) + }) + It("validation6", func() { + s := validatePath("[\"/**\"]", "/") + Expect(s).Should(BeTrue()) + }) + It("validation7", func() { + s := validatePath("[\"/foo/**\"]", "/") + Expect(s).Should(BeFalse()) + }) + It("validation8", func() { + s := validatePath("[\"/foo/**\"]", "/foo/") + Expect(s).Should(BeTrue()) + }) + It("validation9", func() { + s := validatePath("[\"/foo/**\"]", "/foo/bar") + Expect(s).Should(BeTrue()) + }) + It("validation10", func() { + s := validatePath("[\"/foo/**\"]", "foo") + Expect(s).Should(BeFalse()) + }) + It("validation11", func() { + s := validatePath("[\"/foo/bar/**\"]", "/foo/bar/xx/yy") + Expect(s).Should(BeTrue()) + }) + It("validation12", func() { + s := validatePath("[\"/foo/bar/*\"]", "/foo/bar/xxx") + Expect(s).Should(BeTrue()) + }) + It("validation13", func() { + s := validatePath("[\"/foo/bar/*/\"]", "/foo/bar/xxx") + Expect(s).Should(BeFalse()) + }) + It("validation14", func() { + s := validatePath("[\"/foo/bar/**\"]", "/foo/bar/xx/yy") + Expect(s).Should(BeTrue()) + }) + It("validation15", func() { + s := validatePath("[\"/foo/*/**/\"]", "/foo/bar") + Expect(s).Should(BeFalse()) + }) + It("validation16", func() { + s := validatePath("[\"/foo/bar/*/xxx\"]", "/foo/bar/yyy/xxx") + Expect(s).Should(BeTrue()) + }) + It("validation17", func() { + s := validatePath("[\"/foo/bar/*/xxx/\"]", "/foo/bar/yyy/xxx") + Expect(s).Should(BeFalse()) + }) + It("validation18", func() { + s := validatePath("[\"/foo/bar/**/xxx/\"]", "/foo/bar/aaa/bbb/xxx/") + Expect(s).Should(BeTrue()) + }) + It("validation19", func() { + s := validatePath("[\"/foo/bar/***/xxx/\"]", "/foo/bar/aaa/bbb/xxx/") + Expect(s).Should(BeTrue()) + }) + It("validation20", func() { + s := validatePath("[\"/foo/\", \"/bar/\"]", "/foo/") + Expect(s).Should(BeTrue()) + }) + It("validation21", func() { + s := validatePath("[\"/foo/bar/yy*/xxx\"]", "/foo/bar/yyy/xxx") + Expect(s).Should(BeTrue()) + }) +})
diff --git a/verifyAPIKey_suite_test.go b/verifyAPIKey_suite_test.go new file mode 100644 index 0000000..c8f47fe --- /dev/null +++ b/verifyAPIKey_suite_test.go
@@ -0,0 +1,13 @@ +package apidVerifyApiKey_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestVerifyAPIKey(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "VerifyAPIKey Suite") +}