|  | /* | 
|  | 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 mock | 
|  |  | 
|  | import ( | 
|  | "compress/gzip" | 
|  | "encoding/json" | 
|  | "fmt" | 
|  | "net" | 
|  | "net/http" | 
|  | "sync" | 
|  | "time" | 
|  |  | 
|  | "github.com/SermoDigital/jose/crypto" | 
|  | "github.com/SermoDigital/jose/jws" | 
|  | "github.com/SermoDigital/jose/jwt" | 
|  | "github.com/apid/istioApigeeAdapter/common" | 
|  | "github.com/julienschmidt/httprouter" | 
|  | ) | 
|  |  | 
|  | var mockInit = &sync.Once{} | 
|  |  | 
|  | const ( | 
|  | ValidAPIKey1          = "12345" | 
|  | ValidAPIKeyNoProducts = "23456" | 
|  | ValidPublishKey       = "aaaaaa" | 
|  | ValidPublishSecret    = "bbbbbb" | 
|  | ) | 
|  |  | 
|  | type MockServer struct { | 
|  | listener         net.Listener | 
|  | analyticsRecords []common.AnalyticsRecord | 
|  | } | 
|  |  | 
|  | func StartMockServer(port int) (*MockServer, error) { | 
|  | mockInit.Do(makeKeys) | 
|  |  | 
|  | l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | ms := &MockServer{ | 
|  | listener: l, | 
|  | } | 
|  |  | 
|  | router := httprouter.New() | 
|  | router.GET("/publicKey", ms.getPublicKey) | 
|  | router.GET("/products", ms.getProducts) | 
|  | router.POST("/verifyApiKey", ms.getAPIKey) | 
|  | router.POST("/edgemicro/axpublisher/organization/:org/environment/:env", ms.publishAnalytics) | 
|  | router.GET("/edgemicro/region/organization/:org/environment/:env", ms.getRegion) | 
|  |  | 
|  | go func() { | 
|  | http.Serve(l, router) | 
|  | }() | 
|  |  | 
|  | return ms, nil | 
|  | } | 
|  |  | 
|  | func (m *MockServer) Address() string { | 
|  | return m.listener.Addr().String() | 
|  | } | 
|  |  | 
|  | func (m *MockServer) Stop() { | 
|  | m.listener.Close() | 
|  | } | 
|  |  | 
|  | func (m *MockServer) GetAnalyticsRecords() []common.AnalyticsRecord { | 
|  | return m.analyticsRecords | 
|  | } | 
|  |  | 
|  | func (m *MockServer) getPublicKey(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | 
|  | w.Header().Set("content-type", "text/plain") | 
|  | w.Write(mockCertPEM) | 
|  | } | 
|  |  | 
|  | func (m *MockServer) getProducts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | 
|  | now := time.Now() | 
|  | p := []common.APIProduct{ | 
|  | { | 
|  | Name:           "TestProduct1", | 
|  | DisplayName:    "First Product", | 
|  | Description:    "First test product", | 
|  | CreatedAt:      now.UnixNano(), | 
|  | CreatedBy:      "foo@bar.com", | 
|  | LastModifiedAt: now.UnixNano(), | 
|  | LastModifiedBy: "foo@bar.com", | 
|  |  | 
|  | APIResources: []string{"/**"}, | 
|  | ApprovalType: "auto", | 
|  | Attributes: []common.Attribute{ | 
|  | { | 
|  | Name:  "access", | 
|  | Value: "public", | 
|  | }, | 
|  | }, | 
|  | Environments:  []string{"test", "prod"}, | 
|  | Proxies:       []string{"ProxyOne"}, | 
|  | Quota:         "10", | 
|  | QuotaInterval: "1", | 
|  | QuotaTimeUnit: "minute", | 
|  | }, | 
|  | } | 
|  | pl := common.APIProductResponse{ | 
|  | Products: p, | 
|  | } | 
|  |  | 
|  | w.Header().Set("content-type", "application/json") | 
|  |  | 
|  | enc := json.NewEncoder(w) | 
|  | enc.Encode(pl) | 
|  | } | 
|  |  | 
|  | func (m *MockServer) getAPIKey(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | 
|  | if r.Header.Get("content-type") != "application/json" { | 
|  | // That's what the existing service does | 
|  | sendFault(w, 404, "Failed to resolve API Key variable apikey", | 
|  | "steps.oauth.v2.FailedToResolveAPIKey") | 
|  | return | 
|  | } | 
|  |  | 
|  | var request common.VerifyAPIKeyRequest | 
|  | defer r.Body.Close() | 
|  | dec := json.NewDecoder(r.Body) | 
|  | err := dec.Decode(&request) | 
|  | if err != nil { | 
|  | // Again, what existing service does | 
|  | sendFault(w, 500, "Failed to execute the ExtractVariables: Extract-API-Key", | 
|  | "steps.extractvariables.ExecutionFailed") | 
|  | return | 
|  | } | 
|  |  | 
|  | var returnedJwt jwt.JWT | 
|  |  | 
|  | switch request.Key { | 
|  | case ValidAPIKey1: | 
|  | returnedJwt = makeJWT1() | 
|  | case ValidAPIKeyNoProducts: | 
|  | returnedJwt = makeJWT2() | 
|  | default: | 
|  | // 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 | 
|  | } | 
|  |  | 
|  | bod, err := returnedJwt.Serialize(mockKey) | 
|  | if err != nil { | 
|  | sendFault(w, 500, err.Error(), "JWT") | 
|  | return | 
|  | } | 
|  |  | 
|  | tok := common.VerifyAPIKeyResponse{ | 
|  | Token: string(bod), | 
|  | } | 
|  | w.Header().Set("content-type", "application/json") | 
|  | enc := json.NewEncoder(w) | 
|  | enc.Encode(&tok) | 
|  | } | 
|  |  | 
|  | func (m *MockServer) publishAnalytics(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | 
|  | user, pw, aok := r.BasicAuth() | 
|  | if !aok || user != ValidPublishKey || pw != ValidPublishSecret { | 
|  | w.WriteHeader(401) | 
|  | return | 
|  | } | 
|  | if r.Header.Get("content-type") != "application/json" { | 
|  | sendAnalyticsResponse(w, 0, 1) | 
|  | return | 
|  | } | 
|  |  | 
|  | var req common.AnalyticsRequest | 
|  | var err error | 
|  | defer r.Body.Close() | 
|  |  | 
|  | if r.Header.Get("content-encoding") == "" { | 
|  | dec := json.NewDecoder(r.Body) | 
|  | err = dec.Decode(&req) | 
|  | } else if r.Header.Get("content-encoding") == "gzip" { | 
|  | zr, _ := gzip.NewReader(r.Body) | 
|  | dec := json.NewDecoder(zr) | 
|  | err = dec.Decode(&req) | 
|  | } | 
|  |  | 
|  | if err != nil { | 
|  | // This is what the existing service does | 
|  | sendFault(w, 500, "Failed to execute JavaCallout. not a JSON Object", | 
|  | "steps.javacallout.ExecutionError") | 
|  | return | 
|  | } | 
|  |  | 
|  | // For now we just accept it all. We'll add some validation in the future. | 
|  | m.analyticsRecords = append(m.analyticsRecords, req.Records...) | 
|  | sendAnalyticsResponse(w, len(req.Records), 0) | 
|  | } | 
|  |  | 
|  | func sendAnalyticsResponse(w http.ResponseWriter, accepted, rejected int) { | 
|  | resp := &common.AnalyticsResponse{ | 
|  | Accepted: accepted, | 
|  | Rejected: rejected, | 
|  | } | 
|  | w.Header().Set("content-type", "application/json") | 
|  | enc := json.NewEncoder(w) | 
|  | enc.Encode(resp) | 
|  | } | 
|  |  | 
|  | func (m *MockServer) getRegion(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | 
|  | user, pw, aok := r.BasicAuth() | 
|  | if !aok || user != ValidPublishKey || pw != ValidPublishSecret { | 
|  | w.WriteHeader(401) | 
|  | return | 
|  | } | 
|  |  | 
|  | rr := common.RegionResponse{ | 
|  | Region: "test", | 
|  | Host:   m.Address(), | 
|  | } | 
|  |  | 
|  | w.Header().Set("content-type", "application/json") | 
|  | enc := json.NewEncoder(w) | 
|  | enc.Encode(&rr) | 
|  | } | 
|  |  | 
|  | func sendFault(w http.ResponseWriter, errorCode int, fault, code string) { | 
|  | w.Header().Set("content-type", "application/json") | 
|  | w.WriteHeader(errorCode) | 
|  | f := &common.APIFaultMessage{ | 
|  | Fault: common.APIFault{ | 
|  | FaultString: fault, | 
|  | Detail: common.APIFaultDetail{ | 
|  | ErrorCode: code, | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | enc := json.NewEncoder(w) | 
|  | enc.Encode(f) | 
|  | } | 
|  |  | 
|  | func makeJWT1() jwt.JWT { | 
|  | now := time.Now() | 
|  | c := jws.Claims{} | 
|  | c.SetIssuedAt(now) | 
|  | c.SetNotBefore(now) | 
|  | c.Set("audience", "microgateway") | 
|  | c.Set("jti", "52137037-6ce1-426e-a255-e471f94854e5") | 
|  | c.Set("iss", "https://mock.foo.net/verifyApiKey") | 
|  | c.Set("client_id", ValidAPIKey1) | 
|  | c.Set("application_name", "TestApp1") | 
|  | c.Set("api_product_list", []string{ | 
|  | "TestProduct1", | 
|  | }) | 
|  |  | 
|  | return jws.NewJWT(c, crypto.SigningMethodRS256) | 
|  | } | 
|  |  | 
|  | func makeJWT2() jwt.JWT { | 
|  | now := time.Now() | 
|  | c := jws.Claims{} | 
|  | c.SetIssuedAt(now) | 
|  | c.SetNotBefore(now) | 
|  | c.Set("audience", "microgateway") | 
|  | c.Set("jti", "52137037-6ce1-426e-a255-e471f94854e5") | 
|  | c.Set("iss", "https://mock.foo.net/verifyApiKey") | 
|  | c.Set("client_id", ValidAPIKeyNoProducts) | 
|  | c.Set("application_name", "TestApp2") | 
|  | c.Set("api_product_list", []string{ | 
|  | "UnknownProduct1", | 
|  | }) | 
|  |  | 
|  | return jws.NewJWT(c, crypto.SigningMethodRS256) | 
|  | } |