Skip to content

Commit

Permalink
feat(hub)!: force the publisher JWT to contain the allowed topics (du…
Browse files Browse the repository at this point in the history
…nglas#671)

* feat(hub)!: force the publisher JWT to contain the allowed topics

* fix lint
  • Loading branch information
dunglas authored Jul 29, 2022
1 parent 1711b96 commit a87ca25
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 24 deletions.
18 changes: 15 additions & 3 deletions publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"go.uber.org/zap"
)

//nolint:funlen
// PublishHandler allows publisher to broadcast updates to all subscribers.
func (h *Hub) PublishHandler(w http.ResponseWriter, r *http.Request) {
var claims *claims
Expand Down Expand Up @@ -47,10 +48,21 @@ func (h *Hub) PublishHandler(w http.ResponseWriter, r *http.Request) {
}

private := len(r.PostForm["private"]) != 0
if private && !canDispatch(h.topicSelectorStore, topics, claims.Mercure.Publish) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
if !canDispatch(h.topicSelectorStore, topics, claims.Mercure.Publish) {
if private {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)

return
return
}

if h.isBackwardCompatiblyEnabledWith(7) {
h.logger.Info("Deprecated: posting public updates to topics not listed in the 'mercure.publish' JWT claim is deprecated since the version 7 of the protocol, use '[\"*\"]' as value to allow publishing on all topics.")
} else {
h.logger.Info("Unsupported: posting public updates to topics not listed in the 'mercure.publish' JWT claim is not supported anymore, use '[\"*\"]' as value to allow publishing on all topics or enable backward compatibility with the version 7 of the protocol.")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)

return
}
}

u := &Update{
Expand Down
68 changes: 53 additions & 15 deletions publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"go.uber.org/zap"
)

func TestNoAuthorizationHeader(t *testing.T) {
func TestPublishNoAuthorizationHeader(t *testing.T) {
hub := createDummy()

req := httptest.NewRequest("POST", defaultHubURL, nil)
Expand Down Expand Up @@ -63,7 +63,7 @@ func TestPublishBadContentType(t *testing.T) {
hub := createDummy()

req := httptest.NewRequest("POST", defaultHubURL, nil)
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{"*"}))
req.Header.Add("Content-Type", "text/plain; boundary=")
w := httptest.NewRecorder()
hub.PublishHandler(w, req)
Expand All @@ -78,7 +78,7 @@ func TestPublishNoTopic(t *testing.T) {
hub := createDummy()

req := httptest.NewRequest("POST", defaultHubURL, nil)
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{"*"}))
w := httptest.NewRecorder()
hub.PublishHandler(w, req)

Expand All @@ -89,11 +89,13 @@ func TestPublishNoTopic(t *testing.T) {
assert.Equal(t, "Missing \"topic\" parameter\n", w.Body.String())
}

func TestPublishNoData(t *testing.T) {
func TestPublishInvalidRetry(t *testing.T) {
hub := createDummy()

form := url.Values{}
form.Add("topic", "http://example.com/books/1")
form.Add("data", "foo")
form.Add("retry", "invalid")

req := httptest.NewRequest("POST", defaultHubURL, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
Expand All @@ -105,42 +107,40 @@ func TestPublishNoData(t *testing.T) {
resp := w.Result()
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
assert.Equal(t, "Invalid \"retry\" parameter\n", w.Body.String())
}

func TestPublishInvalidRetry(t *testing.T) {
func TestPublishNotAuthorizedTopicSelector(t *testing.T) {
hub := createDummy()

form := url.Values{}
form.Add("topic", "http://example.com/books/1")
form.Add("data", "foo")
form.Add("retry", "invalid")
form.Add("private", "on")

req := httptest.NewRequest("POST", defaultHubURL, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{"foo"}))

w := httptest.NewRecorder()
hub.PublishHandler(w, req)

resp := w.Result()
defer resp.Body.Close()

assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
assert.Equal(t, "Invalid \"retry\" parameter\n", w.Body.String())
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}

func TestPublishNotAuthorizedTopicSelector(t *testing.T) {
func TestPublishEmptyTopicSelector(t *testing.T) {
hub := createDummy()

form := url.Values{}
form.Add("topic", "http://example.com/books/1")
form.Add("data", "foo")
form.Add("private", "on")

req := httptest.NewRequest("POST", defaultHubURL, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{"foo"}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{}))

w := httptest.NewRecorder()
hub.PublishHandler(w, req)
Expand All @@ -151,6 +151,25 @@ func TestPublishNotAuthorizedTopicSelector(t *testing.T) {
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}

func TestPublishLegacyAuthorization(t *testing.T) {
hub := createDummy(WithProtocolVersionCompatibility(7))

form := url.Values{}
form.Add("topic", "http://example.com/books/1")

req := httptest.NewRequest("POST", defaultHubURL, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{}))

w := httptest.NewRecorder()
hub.PublishHandler(w, req)

resp := w.Result()
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestPublishOK(t *testing.T) {
hub := createDummy()

Expand Down Expand Up @@ -197,6 +216,25 @@ func TestPublishOK(t *testing.T) {
wg.Wait()
}

func TestPublishNoData(t *testing.T) {
hub := createDummy()

form := url.Values{}
form.Add("topic", "http://example.com/books/1")

req := httptest.NewRequest("POST", defaultHubURL, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(hub, rolePublisher, []string{"*"}))

w := httptest.NewRecorder()
hub.PublishHandler(w, req)

resp := w.Result()
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestPublishGenerateUUID(t *testing.T) {
h := createDummy()

Expand All @@ -222,7 +260,7 @@ func TestPublishGenerateUUID(t *testing.T) {

req := httptest.NewRequest("POST", defaultHubURL, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{"*"}))

w := httptest.NewRecorder()
h.PublishHandler(w, req)
Expand Down
8 changes: 4 additions & 4 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestForwardedHeaders(t *testing.T) {
req, _ := http.NewRequest("POST", testURL, strings.NewReader(body.Encode()))
req.Header.Add("X-Forwarded-For", "192.0.2.1")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{"*"}))

resp2, err := client.Do(req)
require.Nil(t, err)
Expand Down Expand Up @@ -209,7 +209,7 @@ func TestServe(t *testing.T) {
body := url.Values{"topic": {"http://example.com/foo/1", "http://example.com/alt/1"}, "data": {"hello"}, "id": {"first"}}
req, _ := http.NewRequest("POST", testURL, strings.NewReader(body.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{"*"}))

resp2, err := client.Do(req)
require.Nil(t, err)
Expand Down Expand Up @@ -279,7 +279,7 @@ func TestClientClosesThenReconnects(t *testing.T) {
req, err := http.NewRequest("POST", testURL, strings.NewReader(body.Encode()))
require.Nil(t, err)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(h, rolePublisher, []string{"*"}))

resp, err := client.Do(req)
require.Nil(t, err)
Expand Down Expand Up @@ -469,7 +469,7 @@ func (s *testServer) newSubscriber(topic string, keepAlive bool) {
func (s *testServer) publish(body url.Values) {
req, _ := http.NewRequest("POST", testURL, strings.NewReader(body.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(s.h, rolePublisher, []string{}))
req.Header.Add("Authorization", "Bearer "+createDummyAuthorizedJWT(s.h, rolePublisher, []string{"*"}))

resp, err := s.client.Do(req)
require.Nil(s.t, err)
Expand Down
4 changes: 2 additions & 2 deletions subscribe.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ func retrieveLastEventID(r *http.Request, opt *opt, logger Logger) string {

if legacyEventIDValues, present := query["Last-Event-ID"]; present {
if opt.isBackwardCompatiblyEnabledWith(7) {
logger.Info("deprecation: the 'Last-Event-ID' query parameter is deprecated since the version 8 of the protocol, use 'lastEventID' instead.")
logger.Info("Deprecated: the 'Last-Event-ID' query parameter is deprecated since the version 8 of the protocol, use 'lastEventID' instead.")

if len(legacyEventIDValues) != 0 {
return legacyEventIDValues[0]
}
} else {
logger.Info("unsupported: the 'Last-Event-ID' query parameter is not supported anymore, use 'lastEventID' instead or enable backward compatibility with version 7 of the protocol.")
logger.Info("Unsupported: the 'Last-Event-ID' query parameter is not supported anymore, use 'lastEventID' instead or enable backward compatibility with version 7 of the protocol.")
}
}

Expand Down

0 comments on commit a87ca25

Please sign in to comment.