blob: 99bbe77b898cf346433d4daeb83d2155457eb1c4 [file] [log] [blame]
/*
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"
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: "First",
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",
},
}
w.Header().Set("content-type", "application/json")
enc := json.NewEncoder(w)
enc.Encode(p)
}
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
}
if request.Key != ValidAPIKey1 {
// 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
}
jwt := makeJWT1()
bod, err := jwt.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)
}