| /* |
| 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 |
| } |