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