Skip to content

Commit

Permalink
Added regex and moved X-*-Region to the context
Browse files Browse the repository at this point in the history
Sometimes instance_id in the URI can have characters such as : or / which are all valid, but
due to gorilla/mux we can land in a 404 not found issue. Added a Regex to the {instance_id} route
thus allowing such instance_id.
Added a middleware layer to parse X(*)-Region to the context under the X-Region key

Signed-off-by: Tapas Sharma <[email protected]>
  • Loading branch information
Tapas Sharma committed Jan 24, 2019
1 parent 23df219 commit c78d354
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 20 deletions.
29 changes: 16 additions & 13 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/sharma-tapas/brokerapi/middlewares/originating_identity_header"
"net/http"
"strconv"

"code.cloudfoundry.org/lager"
"github.com/gorilla/mux"
"github.com/sharma-tapas/brokerapi/auth"
"github.com/sharma-tapas/brokerapi/middlewares/originating_identity_header"
"github.com/sharma-tapas/brokerapi/middlewares/x_region_header"
)

const (
Expand Down Expand Up @@ -85,25 +86,27 @@ func New(serviceBroker ServiceBroker, logger lager.Logger, brokerCredentials Bro
authMiddleware := auth.NewWrapper(brokerCredentials.Username, brokerCredentials.Password).Wrap
router.Use(authMiddleware)
router.Use(originating_identity_header.AddToContext)
router.Use(x_region_header.AddToContext)

return router
}

func AttachRoutes(router *mux.Router, serviceBroker ServiceBroker, logger lager.Logger) {
handler := serviceBrokerHandler{serviceBroker: serviceBroker, logger: logger}
router.HandleFunc("/v2/catalog", handler.catalog).Methods("GET")

router.HandleFunc("/v2/service_instances/{instance_id}", handler.getInstance).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}", handler.provision).Methods("PUT")
router.HandleFunc("/v2/service_instances/{instance_id}", handler.deprovision).Methods("DELETE")
router.HandleFunc("/v2/service_instances/{instance_id}/last_operation", handler.lastOperation).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}", handler.update).Methods("PATCH")

router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", handler.getBinding).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", handler.bind).Methods("PUT")
router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", handler.unbind).Methods("DELETE")

router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}/last_operation", handler.lastBindingOperation).Methods("GET")
//"")*@)?(?:\[(?:(?:(?:[0-9a-f]{1,4}:){6}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|::(?:[0-9a-f]{1,4}:){5}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:[0-9a-f]{1,4}:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|v[0-9a-f]+[-a-z0-9\._~!\$&'\(\)\*\+,;=:]+)\]|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}|(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=@])*)(?::[0-9]*)?(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))*)*|\/(?:(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))*)*)?|(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@]))*)*|(?!(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@])))(?:\?(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@])|[\x{E000}-\x{F8FF}\x{F0000}-\x{FFFFD}\x{100000}-\x{10FFFD}\/\?])*)?(?:\#(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!\$&'\(\)\*\+,;=:@])|[\/\?])*?$/i"
//URLRegex := "[~\xA0-\xD7FF\xF900-\xFDCF\xFDF0-\xFFEF\x10000-\x1FFFD\x20000-\x2FFFD\x30000-\x3FFFD\x40000-\x4FFFD\x50000-\x5FFFD\x60000-\x6FFFD\x70000-\x7FFFD\x80000-\x8FFFD\x90000-\x9FFFD\xA0000-\xAFFFD\xB0000-\xBFFFD\xC0000-\xCFFFD\xD0000-\xDFFFD\xE1000-\xEFFFD!$&,;=:A-Za-z0-9]+"
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.getInstance).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.provision).Methods("PUT")
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.deprovision).Methods("DELETE")
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}/last_operation", handler.lastOperation).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.update).Methods("PATCH")

router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}/service_bindings/{binding_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.getBinding).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}/service_bindings/{binding_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.bind).Methods("PUT")
router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}/service_bindings/{binding_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}", handler.unbind).Methods("DELETE")

router.HandleFunc("/v2/service_instances/{instance_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}/service_bindings/{binding_id:[A-Za-z-0-9?:$&,@;=_!\\-\\.\\+\\*\\'\\(\\)]+}/last_operation", handler.lastBindingOperation).Methods("GET")
}

type serviceBrokerHandler struct {
Expand Down
78 changes: 71 additions & 7 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,12 @@ var _ = Describe("Service Broker API", func() {
})
})

Describe("OriginatingIdentityHeader", func(){
Describe("OriginatingIdentityHeader", func() {

var (
fakeServiceBroker *fakes.AutoFakeServiceBroker
req *http.Request
testServer *httptest.Server
req *http.Request
testServer *httptest.Server
)

BeforeEach(func() {
Expand All @@ -275,8 +275,8 @@ var _ = Describe("Service Broker API", func() {
testServer.Close()
})

When("X-Broker-API-Originating-Identity is passed", func(){
It("Adds it to the context", func(){
When("X-Broker-API-Originating-Identity is passed", func() {
It("Adds it to the context", func() {
originatingIdentity := "Originating Identity Name"
req.Header.Add("X-Broker-API-Originating-Identity", originatingIdentity)

Expand All @@ -289,8 +289,8 @@ var _ = Describe("Service Broker API", func() {

})
})
When("X-Broker-API-Originating-Identity is not passed", func(){
It("Adds empty originatingIdentity to the context", func(){
When("X-Broker-API-Originating-Identity is not passed", func() {
It("Adds empty originatingIdentity to the context", func() {
_, err := http.DefaultClient.Do(req)
Expect(err).NotTo(HaveOccurred())

Expand All @@ -301,6 +301,70 @@ var _ = Describe("Service Broker API", func() {
})
})

Describe("XRegionHeader", func() {

var (
fakeServiceBroker *fakes.AutoFakeServiceBroker
req *http.Request
testServer *httptest.Server
)

BeforeEach(func() {
fakeServiceBroker = new(fakes.AutoFakeServiceBroker)
brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials)

testServer = httptest.NewServer(brokerAPI)
var err error
req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil)
Expect(err).NotTo(HaveOccurred())
req.Header.Add("X-Broker-API-Version", "2.14")
req.SetBasicAuth(credentials.Username, credentials.Password)
})

AfterEach(func() {
testServer.Close()
})

When("X-Region is passed", func() {
It("Adds it to the context", func() {
region := "India"
req.Header.Add("X-Bluemix-Region", region)

_, err := http.DefaultClient.Do(req)
Expect(err).NotTo(HaveOccurred())

Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called")
ctx := fakeServiceBroker.ServicesArgsForCall(0)
Expect(ctx.Value("X-Region")).To(Equal(region))

})
})
When("X-Region is passed", func() {
It("Adds it to the context", func() {
region := "India"
req.Header.Add("X-Region", region)

_, err := http.DefaultClient.Do(req)
Expect(err).NotTo(HaveOccurred())

Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called")
ctx := fakeServiceBroker.ServicesArgsForCall(0)
Expect(ctx.Value("X-Region")).To(Equal(region))

})
})
When("X-Region is not passed", func() {
It("Adds empty X-Region to the context", func() {
_, err := http.DefaultClient.Do(req)
Expect(err).NotTo(HaveOccurred())

Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called")
ctx := fakeServiceBroker.ServicesArgsForCall(0)
Expect(ctx.Value("X-Region")).To(Equal(""))
})
})
})

Describe("catalog endpoint", func() {
makeCatalogRequest := func(apiVersion string, fail bool) *httptest.ResponseRecorder {
recorder := httptest.NewRecorder()
Expand Down
28 changes: 28 additions & 0 deletions middlewares/x_region_header/x_region_header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package x_region_header

import (
"context"
"net/http"
"regexp"
)

const (
xRegionKey = "X-Region"
)

//AddToContext the X-*-Region to the context
func AddToContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
pattern := regexp.MustCompile(`X([-]*[a-zA-Z]*)-Region`)
value := ""
for k := range req.Header {
res := pattern.MatchString(k)
if res {
value = req.Header.Get(k)
break
}
}
newCtx := context.WithValue(req.Context(), xRegionKey, value)
next.ServeHTTP(w, req.WithContext(newCtx))
})
}

0 comments on commit c78d354

Please sign in to comment.