Add "attributes" adapter for Apigee.
This sets attributes from the API key. A list checker can then
be used to reject invalid keys. Attributes are also now pushed
to analytics.
Fix CI test for goimports.
diff --git a/adapter/BUILD b/adapter/BUILD
index 66aabee..e6120d7 100644
--- a/adapter/BUILD
+++ b/adapter/BUILD
@@ -6,8 +6,10 @@
name = "go_default_library",
srcs = [
"apigee.go",
+ "apigeeKeyAttributes.go",
"apigeeKeyChecker.go",
"apigeeReport.go",
+ "applications.go",
],
deps = [
"//adapter/config:go_default_library",
@@ -21,9 +23,10 @@
name = "tests",
size = "medium",
srcs = [
- "apigeeKeyChecker_test.go",
- "apigeeReport_test.go",
- ],
+ "apigeeKeyAttributes_test.go",
+ "apigeeKeyChecker_test.go",
+ "apigeeReport_test.go",
+ ],
library = ":go_default_library",
deps = [
"//mock:go_default_library",
diff --git a/adapter/apigee.go b/adapter/apigee.go
index 6fe3853..a1f7b75 100644
--- a/adapter/apigee.go
+++ b/adapter/apigee.go
@@ -27,4 +27,5 @@
r.RegisterListsBuilder(newKeyCheckBuilder())
//r.RegisterApplicationLogsBuilder(newReportBuilder())
r.RegisterAccessLogsBuilder(newReportBuilder())
+ r.RegisterAttributesGeneratorBuilder(newKeyAttrsBuilder())
}
diff --git a/adapter/apigeeKeyAttributes.go b/adapter/apigeeKeyAttributes.go
new file mode 100644
index 0000000..448bb93
--- /dev/null
+++ b/adapter/apigeeKeyAttributes.go
@@ -0,0 +1,98 @@
+/*
+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 adapter
+
+import (
+ "fmt"
+
+ "github.com/apid/istioApigeeAdapter/adapter/config"
+ "istio.io/mixer/pkg/adapter"
+)
+
+const (
+ keyAttrsName = "apigeeKeyAttributes"
+ keyAttrsDesc = "Set attributes based on an Apigee API key"
+
+ keyParam = "apiKey"
+ pathParam = "requestPath"
+ successParam = "success"
+ successStringParam = "successString"
+ clientIDParam = "clientID"
+ appNameParam = "applicationName"
+)
+
+var keyAttrsConf = &config.VerifyKeyParams{}
+
+type keyAttrsBuilder struct {
+ adapter.DefaultBuilder
+}
+
+type keyAttrsGenerator struct {
+ env adapter.Env
+ applications *applicationManager
+}
+
+func newKeyAttrsBuilder() adapter.AttributesGeneratorBuilder {
+ return keyAttrsBuilder{
+ adapter.NewDefaultBuilder(keyAttrsName, keyAttrsDesc, keyAttrsConf),
+ }
+}
+
+func (b keyAttrsBuilder) ValidateConfig(c adapter.Config) *adapter.ConfigErrors {
+ return validateKeyParams(c)
+}
+
+func (b keyAttrsBuilder) BuildAttributesGenerator(env adapter.Env, c adapter.Config) (adapter.AttributesGenerator, error) {
+ cfg := c.(*config.VerifyKeyParams)
+ g := &keyAttrsGenerator{
+ env: env,
+ applications: newApplicationManager(env, defaultAppLifetime, getVerifyPath(cfg)),
+ }
+ env.Logger().Infof("Created Apigee attributes generator to invoke \"%s\"", g.applications.checkURL)
+
+ return g, nil
+}
+
+func (g *keyAttrsGenerator) Generate(in map[string]interface{}) (map[string]interface{}, error) {
+ key := getString(in, keyParam)
+ if key == "" {
+ return nil, fmt.Errorf("Cannot verify API key: value of \"%s\" not found", keyParam)
+ }
+ path := getString(in, pathParam)
+ if path == "" {
+ return nil, fmt.Errorf("Cannot verify API key: value of \"%s\" not found", pathParam)
+ }
+
+ app, err := g.applications.get(key)
+ if err != nil {
+ g.env.Logger().Errorf("Error verifying API key: %s", err)
+ return nil, err
+ }
+
+ out := make(map[string]interface{})
+ out[successParam] = app.valid
+ out[successStringParam] = fmt.Sprintf("%v", app.valid)
+ if app.valid {
+ out[clientIDParam] = app.clientID
+ out[appNameParam] = app.name
+ }
+ return out, nil
+}
+
+func (g *keyAttrsGenerator) Close() error {
+ return nil
+}
diff --git a/adapter/apigeeKeyAttributes_test.go b/adapter/apigeeKeyAttributes_test.go
new file mode 100644
index 0000000..0176917
--- /dev/null
+++ b/adapter/apigeeKeyAttributes_test.go
@@ -0,0 +1,100 @@
+/*
+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 adapter
+
+import (
+ "testing"
+
+ "github.com/apid/istioApigeeAdapter/adapter/config"
+ "github.com/apid/istioApigeeAdapter/mock"
+)
+
+func TestInvalidKeyAttributes(t *testing.T) {
+ cfg := &config.VerifyKeyParams{
+ Organization: "foo",
+ Environment: "test",
+ VerificationURL: "http://" + mockServer.Address(),
+ }
+
+ builder := newKeyAttrsBuilder()
+ ce := builder.ValidateConfig(cfg)
+ if ce != nil {
+ t.Fatalf("Error validating config: %s", ce)
+ }
+
+ ag, err := builder.BuildAttributesGenerator(mockEnv, cfg)
+ if err != nil {
+ t.Fatalf("Error creating aspect: %s", err)
+ }
+ defer ag.Close()
+
+ in := make(map[string]interface{})
+ in[keyParam] = "99999"
+ in[pathParam] = "/"
+
+ out, err := ag.Generate(in)
+ if err != nil {
+ t.Fatalf("Error checking parameters: %s", err)
+ }
+ if out[successParam] != false {
+ t.Fatalf("Expected success to be false")
+ }
+ if out[successStringParam] != "false" {
+ t.Fatalf("Expected success string to be false")
+ }
+}
+
+func TestValidKeyAttributes(t *testing.T) {
+ cfg := &config.VerifyKeyParams{
+ Organization: "foo",
+ Environment: "test",
+ VerificationURL: "http://" + mockServer.Address(),
+ }
+
+ builder := newKeyAttrsBuilder()
+ ce := builder.ValidateConfig(cfg)
+ if ce != nil {
+ t.Fatalf("Error validating config: %s", ce)
+ }
+
+ ag, err := builder.BuildAttributesGenerator(mockEnv, cfg)
+ if err != nil {
+ t.Fatalf("Error creating aspect: %s", err)
+ }
+ defer ag.Close()
+
+ in := make(map[string]interface{})
+ in[keyParam] = mock.ValidAPIKey1
+ in[pathParam] = "/"
+
+ out, err := ag.Generate(in)
+ if err != nil {
+ t.Fatalf("Error checking parameters: %s", err)
+ }
+ if out[successParam] != true {
+ t.Fatalf("Expected success to be true")
+ }
+ if out[successStringParam] != "true" {
+ t.Fatalf("Expected success string to be true")
+ }
+ if out[clientIDParam] != mock.ValidAPIKey1 {
+ t.Fatalf("Invalid client ID %s", out[clientIDParam])
+ }
+ if out[appNameParam] != "TestApp1" {
+ t.Fatalf("Invalid application name %s", out[appNameParam])
+ }
+}
diff --git a/adapter/apigeeKeyChecker.go b/adapter/apigeeKeyChecker.go
index 38e3dff..50f61ac 100644
--- a/adapter/apigeeKeyChecker.go
+++ b/adapter/apigeeKeyChecker.go
@@ -17,14 +17,10 @@
package adapter
import (
- "bytes"
- "encoding/json"
"fmt"
- "net/http"
"net/url"
"github.com/apid/istioApigeeAdapter/adapter/config"
- "github.com/apid/istioApigeeAdapter/common"
"istio.io/mixer/pkg/adapter"
)
@@ -42,8 +38,8 @@
}
type keyChecker struct {
- env adapter.Env
- checkURL string
+ env adapter.Env
+ application *applicationManager
}
func newKeyCheckBuilder() adapter.ListsBuilder {
@@ -52,7 +48,11 @@
}
}
-func (b keyCheckBuilder) ValidateConfig(c adapter.Config) (ce *adapter.ConfigErrors) {
+func (b keyCheckBuilder) ValidateConfig(c adapter.Config) *adapter.ConfigErrors {
+ return validateKeyParams(c)
+}
+
+func validateKeyParams(c adapter.Config) (ce *adapter.ConfigErrors) {
cfg := c.(*config.VerifyKeyParams)
if cfg.Organization == "" {
ce = ce.Appendf("organization", "Organization parameter must be specified")
@@ -71,6 +71,16 @@
func (b keyCheckBuilder) NewListsAspect(env adapter.Env, c adapter.Config) (adapter.ListsAspect, error) {
cfg := c.(*config.VerifyKeyParams)
+ kc := &keyChecker{
+ env: env,
+ application: newApplicationManager(env, defaultAppLifetime, getVerifyPath(cfg)),
+ }
+ env.Logger().Infof("Created Apigee Key Checker to invoke \"%s\"", kc.application.checkURL)
+
+ return kc, nil
+}
+
+func getVerifyPath(cfg *config.VerifyKeyParams) string {
var basePath string
if cfg.VerificationURL == "" {
@@ -78,14 +88,7 @@
} else {
basePath = cfg.VerificationURL
}
-
- kc := &keyChecker{
- env: env,
- checkURL: basePath + verifyKeyPath,
- }
- env.Logger().Infof("Created Apigee Key Checker to invoke \"%s\"", kc.checkURL)
-
- return kc, nil
+ return basePath + verifyKeyPath
}
func (l *keyChecker) Close() error {
@@ -98,22 +101,10 @@
// Test command ./bazel-bin/cmd/client/mixc check -a target.service=f.default.svc.cluster.local --stringmap_attributes request.headers=x-api-key:1tu9pl04Srua2MtsAGtu6ViPxSYSSX2I
func (l *keyChecker) CheckList(symbol string) (bool, error) {
- apiKeyBody := common.VerifyAPIKeyRequest{
- Key: symbol,
- }
- requestBody, _ := json.Marshal(&apiKeyBody)
-
- resp, err := http.DefaultClient.Post(l.checkURL, "application/json",
- bytes.NewBuffer(requestBody))
+ app, err := l.application.get(symbol)
if err != nil {
- l.env.Logger().Errorf("Error contacting verification service: %s", err)
+ l.env.Logger().Errorf("Error verifying API key: %s", err)
return false, err
}
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- return false, nil
- } else {
- return true, nil
- }
+ return app.valid, nil
}
diff --git a/adapter/apigeeReport.go b/adapter/apigeeReport.go
index fc2ae2f..16cfefa 100644
--- a/adapter/apigeeReport.go
+++ b/adapter/apigeeReport.go
@@ -36,6 +36,8 @@
const (
regionLookupURL = "https://edgemicroservices.apigee.net/edgemicro"
nanosPerMillisecond = 1000000
+ reportName = "apigeeReport"
+ reportDesc = "Report API call data to Apigee"
)
type (
@@ -58,8 +60,8 @@
func newReportBuilder() analyticsBuilder {
return analyticsBuilder{
adapter.NewDefaultBuilder(
- "apigeeReport",
- "Report logs to apigee",
+ reportName,
+ reportDesc,
&config.ReportParams{},
)}
}
@@ -121,17 +123,11 @@
var records []common.AnalyticsRecord
for _, entry := range entries {
- sourceIP := getStringLabel(entry, "sourceIP")
path := getStringLabel(entry, "urlPath")
host := getStringLabel(entry, "hostHeader")
- method := getStringLabel(entry, "httpMethod")
- userAgent := getStringLabel(entry, "userAgent")
- proxyName := getStringLabel(entry, "proxyName")
- proxyRevision := getIntLabel(entry, "proxyRevision")
// Convert nanos (Go) to millis (Java)
requestTime := getTimestampLabel(entry, "requestTime") / nanosPerMillisecond
responseTime := getTimestampLabel(entry, "responseTime") / nanosPerMillisecond
- responseCode := getIntLabel(entry, "responseCode")
r := common.AnalyticsRecord{
ClientReceivedStartTimestamp: requestTime,
@@ -140,16 +136,18 @@
ClientSentStartTimestamp: responseTime,
ClientSentEndTimestamp: responseTime + 1,
// Missing: Target times
- ClientIP: sourceIP,
- RequestVerb: method,
- UserAgent: userAgent,
- ResponseStatusCode: responseCode,
+ ClientIP: getStringLabel(entry, "sourceIP"),
+ RequestVerb: getStringLabel(entry, "httpMethod"),
+ UserAgent: getStringLabel(entry, "userAgent"),
+ ResponseStatusCode: getIntLabel(entry, "responseCode"),
// Technically wrong because of no scheme and host header
RequestURI: "http://" + host + path,
RequestPath: strings.Split(path, "?")[0],
- APIProxy: proxyName,
- APIProxyRevision: proxyRevision,
+ APIProxy: getStringLabel(entry, "proxyName"),
+ APIProxyRevision: getIntLabel(entry, "proxyRevision"),
RecordType: "APIAnalytics",
+ DeveloperApp: getStringLabel(entry, "applicationName"),
+ ClientID: getStringLabel(entry, "clientID"),
}
records = append(records, r)
@@ -266,7 +264,11 @@
}
func getStringLabel(le adapter.LogEntry, key string) string {
- v := le.Labels[key]
+ return getString(le.Labels, key)
+}
+
+func getString(m map[string]interface{}, key string) string {
+ v := m[key]
if v == nil {
return ""
}
@@ -279,7 +281,11 @@
}
func getIntLabel(le adapter.LogEntry, key string) int {
- v := le.Labels[key]
+ return getInt(le.Labels, key)
+}
+
+func getInt(m map[string]interface{}, key string) int {
+ v := m[key]
if v == nil {
return 0
}
@@ -294,7 +300,11 @@
}
func getTimestampLabel(le adapter.LogEntry, key string) int64 {
- v := le.Labels[key]
+ return getTimestamp(le.Labels, key)
+}
+
+func getTimestamp(m map[string]interface{}, key string) int64 {
+ v := m[key]
if v == nil {
return 0
}
diff --git a/adapter/applications.go b/adapter/applications.go
new file mode 100644
index 0000000..1088725
--- /dev/null
+++ b/adapter/applications.go
@@ -0,0 +1,164 @@
+/*
+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 adapter
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/apid/istioApigeeAdapter/common"
+ "istio.io/mixer/pkg/adapter"
+)
+
+const (
+ defaultAppLifetime = 5 * time.Second
+)
+
+type application struct {
+ clientID string
+ valid bool
+ expiration time.Time
+ name string
+ apiProducts []string
+}
+
+type applicationJWT struct {
+ ClientID string `json:"client_id"`
+ ApplicationName string `json:"application_name"`
+ APIProducts []string `json:"api_product_list"`
+}
+
+type applicationManager struct {
+ env adapter.Env
+ lifetime time.Duration
+ cache map[string]*application
+ checkURL string
+ latch *sync.Mutex
+}
+
+func newApplicationManager(env adapter.Env, lifetime time.Duration, checkURL string) *applicationManager {
+ return &applicationManager{
+ env: env,
+ lifetime: lifetime,
+ checkURL: checkURL,
+ cache: make(map[string]*application),
+ latch: &sync.Mutex{},
+ }
+}
+
+func (a *applicationManager) get(key string) (*application, error) {
+ app := a.cacheGet(key)
+ if app != nil {
+ return app, nil
+ }
+
+ app, err := a.lookupKey(key)
+ if err != nil {
+ return nil, err
+ }
+ a.cachePut(key, app)
+ return app, nil
+}
+
+func (a *applicationManager) cacheGet(key string) *application {
+ a.latch.Lock()
+ app := a.cache[key]
+ if app != nil && app.expiration.Before(time.Now()) {
+ delete(a.cache, key)
+ app = nil
+ }
+ a.latch.Unlock()
+ return app
+}
+
+func (a *applicationManager) cachePut(key string, app *application) {
+ a.latch.Lock()
+ a.cache[key] = app
+ a.latch.Unlock()
+}
+
+func (a *applicationManager) lookupKey(key string) (*application, error) {
+ apiKeyBody := common.VerifyAPIKeyRequest{
+ Key: key,
+ }
+ requestBody, _ := json.Marshal(&apiKeyBody)
+
+ resp, err := http.DefaultClient.Post(a.checkURL, "application/json",
+ bytes.NewBuffer(requestBody))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == 200 {
+ return a.parseApplication(resp.Body)
+
+ } else if resp.StatusCode == 401 {
+ return &application{
+ clientID: key,
+ valid: false,
+ expiration: time.Now().Add(a.lifetime),
+ }, nil
+
+ } else {
+ return nil, fmt.Errorf("HTTP error looking up application: %d", resp.StatusCode)
+ }
+}
+
+func (a *applicationManager) parseApplication(r io.Reader) (*application, error) {
+ var keyResponse common.VerifyAPIKeyResponse
+ jr := json.NewDecoder(r)
+ err := jr.Decode(&keyResponse)
+ if err != nil {
+ return nil, fmt.Errorf("Invalid JSON in API key response: %s", err)
+ }
+
+ // We got back a JWT, but we don't need to verify it -- just parse the JSON we need
+ parts := strings.Split(keyResponse.Token, ".")
+ if len(parts) != 3 {
+ return nil, errors.New("Invalid JWT returned")
+ }
+ // base64 from Apigee is unpadded, so use "raw" encoding here.
+ rawJWT, err := base64.RawStdEncoding.DecodeString(parts[1])
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding JWT base 64: from \"%s\", %s", keyResponse.Token, err)
+ }
+
+ var jwt applicationJWT
+ err = json.Unmarshal(rawJWT, &jwt)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding JWT json: %s", err)
+ }
+
+ a.env.Logger().Infof("Looked up data for application %s", jwt.ApplicationName)
+
+ return &application{
+ clientID: jwt.ClientID,
+ valid: true,
+ expiration: time.Now().Add(a.lifetime),
+ name: jwt.ApplicationName,
+ apiProducts: jwt.APIProducts,
+ }, nil
+}
diff --git a/mock/mockserver.go b/mock/mockserver.go
index 984d5e0..99bbe77 100644
--- a/mock/mockserver.go
+++ b/mock/mockserver.go
@@ -142,7 +142,8 @@
}
if request.Key != ValidAPIKey1 {
- sendFault(w, 404,
+ // We actually do return 401 in this case!
+ sendFault(w, 401,
fmt.Sprintf("API key %s is not valid", request.Key),
"steps.oauth.v2.FailedToResolveAPIKey")
return
diff --git a/mock/mockserver_test.go b/mock/mockserver_test.go
index e37ee26..49f3207 100644
--- a/mock/mockserver_test.go
+++ b/mock/mockserver_test.go
@@ -151,8 +151,8 @@
t.Fatalf("Network error: %s", err)
}
defer resp.Body.Close()
- if resp.StatusCode != 404 {
- t.Fatalf("Got HTTP status code %d -- expected 404", resp.StatusCode)
+ if resp.StatusCode != 401 {
+ t.Fatalf("Got HTTP status code %d -- expected 401", resp.StatusCode)
}
}
@@ -169,8 +169,8 @@
t.Fatalf("Network error: %s", err)
}
defer resp.Body.Close()
- if resp.StatusCode != 404 {
- t.Fatalf("Got HTTP status code %d -- expected 404", resp.StatusCode)
+ if resp.StatusCode != 401 {
+ t.Fatalf("Got HTTP status code %d -- expected 401", resp.StatusCode)
}
}
diff --git a/testdata/configroot/scopes/global/adapters.yml b/testdata/configroot/scopes/global/adapters.yml
index cd4639f..5079e36 100644
--- a/testdata/configroot/scopes/global/adapters.yml
+++ b/testdata/configroot/scopes/global/adapters.yml
@@ -18,7 +18,7 @@
kind: lists
impl: apigeeKeyChecker
params:
- organization: YOUR_ORG_HERE
+ organization: the-brails
environment: prod
# Add verificationURL to use a different service or URL
#verificationURL: https://YOUR_ORG_HERE-prod.apigee.net/edgemicro-auth
@@ -36,7 +36,24 @@
kind: access-logs
impl: apigeeReport
params:
- organization: YOUR_ORG_HERE
+ organization: the-brails
environment: prod
- key: KEY_FROM_EDGEMICRO_CONFIGURE
- secret: SECRET_FROM_EDGEMICRO_CONFIGURE
+ # the-brails
+ key: da46c48b8987b395d1b0cce228974372c2046a4f052c9d941a06f68e1cd5acc3
+ secret: 2b31064d4f65f3cc8cc42ef29a3742ccbfc75caf53d05bc70c6d1023560597fa
+ # gregbrail
+ #key: cf9b8f76a0cb29232f4a73bef86f97859e67642746a3dcce3efd1f34d773f544
+ #secret: f6326522114cb4041212279fb2863278280a137142bbef35f386c4f767a0e627
+ #collectionURL: https://edgemicroservices-us-east-1.apigee.net/edgemicro
+ - name: apigeeAttributes
+ kind: attributes
+ impl: apigeeKeyAttributes
+ params:
+ organization: the-brails
+ environment: prod
+ - name: authorizationChecker
+ kind: lists
+ impl: genericListChecker
+ params:
+ listEntries:
+ - "true"
diff --git a/testdata/configroot/scopes/global/descriptors.yml b/testdata/configroot/scopes/global/descriptors.yml
index d66e343..c1aed42 100644
--- a/testdata/configroot/scopes/global/descriptors.yml
+++ b/testdata/configroot/scopes/global/descriptors.yml
@@ -79,9 +79,32 @@
valueType: STRING
proxy.revision:
valueType: INT64
+ authorization.success:
+ valueType: BOOL
+ authorization.success.string:
+ valueType: STRING
+ authorization.client.id:
+ valueType: STRING
+ authorization.application.name:
+ valueType: STRING
# DEPRECATED, to be removed. Use request.useragent instead.
request.user-agent:
valueType: STRING
+ - name: apigeeKeyAttributes
+ revision: "1"
+ attributes:
+ apiKey:
+ valueType: STRING
+ requestPath:
+ valueType: STRING
+ success:
+ valueType: BOOL
+ successString:
+ valueType: STRING
+ clientID:
+ valueType: STRING
+ applicationName:
+ valueType: STRING
# Enums as struct fields can be symbolic names.
# However enums inside maps *cannot* be symbolic names.
metrics:
@@ -207,4 +230,6 @@
responseCode: 2
proxyName: 1
proxyRevision: 2
+ clientID: 1
+ applicationName: 1
diff --git a/testdata/configroot/scopes/global/subjects/global/rules.yml b/testdata/configroot/scopes/global/subjects/global/rules.yml
index 32bae3f..5545c95 100644
--- a/testdata/configroot/scopes/global/subjects/global/rules.yml
+++ b/testdata/configroot/scopes/global/subjects/global/rules.yml
@@ -3,10 +3,31 @@
rules:
- selector: # must be empty for preprocessing adapters
aspects:
- - kind: lists
- adapter: apigeeAPIKey
+ # Fetch the API key and use it to set various request attributes.
+ # This is where the adapter should cache, because this one gets called three times
+ # (check, quota, and report)
+ - kind: attributes
+ adapter: apigeeAttributes
params:
- checkExpression: request.headers["apikey"]
+ input_expressions:
+ apiKey: request.headers["apikey"] | "INVALID_KEY"
+ requestPath: request.path | "/"
+ attribute_bindings:
+ authorization.success.string: successString
+ authorization.success: success
+ authorization.client.id: clientID
+ authorization.application.name: applicationName
+ # In "check" reject the request if the key is invalid. It'd be nice if we could
+ # customize the error here. We could build yet another adapter for that purpose!
+ - kind: lists
+ adapter: authorizationChecker
+ params:
+ checkExpression: authorization.success.string
+ # This is a simpler way to achieve the above!
+ #- kind: lists
+ # adapter: apigeeAPIKey
+ # params:
+ # checkExpression: request.headers["apikey"]
- kind: quotas
params:
quotas:
@@ -44,3 +65,5 @@
responseCode: response.code | 200
proxyName: proxy.name | "istio"
proxyRevision: proxy.revision | 1
+ clientID: authorization.client.id | ""
+ applicationName: authorization.application.name | ""
diff --git a/tools/checkfmt.sh b/tools/checkfmt.sh
index c668975..4d0e581 100755
--- a/tools/checkfmt.sh
+++ b/tools/checkfmt.sh
@@ -1,15 +1,28 @@
#!/bin/bash
+CHECKER=goimports
+which goimports > /dev/null
+if [ $? -ne 0 ]
+then
+ which gofmt > /dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'Neither goimports nor gofmt could be found'
+ exit 2
+ fi
+ CHECKER=gofmt
+fi
+
failed=0
lc=`ls *.go 2>/dev/null | wc -l`
if [ $lc -gt 0 ]
then
- lc=`goimports -l . | wc -l`
+ lc=`${CHECKER} -l . | wc -l`
if [ $lc -gt 0 ]
then
- echo "** goimports run required:"
- gofmt -l .
+ echo "** ${CHECKER} run required:"
+ ${CHECKER} -l .
failed=1
fi