Skip to content

Commit

Permalink
feat: allow using authorization query parameter (dunglas#655)
Browse files Browse the repository at this point in the history
Signed-off-by: azjezz <azjezz@protonmail.com>
  • Loading branch information
azjezz authored Jun 20, 2022
1 parent e02f18b commit 9764824
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 2 deletions.
11 changes: 10 additions & 1 deletion Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@

{$SERVER_NAME:localhost}

log
log {
format filter {
wrap console
fields {
uri query {
replace authorization REDACTED
}
}
}
}

route {
encode zstd gzip
Expand Down
11 changes: 10 additions & 1 deletion Caddyfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@

{$SERVER_NAME:localhost}

log
log {
format filter {
wrap console
fields {
uri query {
replace authorization REDACTED
}
}
}
}

route {
redir / /.well-known/mercure/ui/
Expand Down
10 changes: 10 additions & 0 deletions authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
var (
// ErrInvalidAuthorizationHeader is returned when the Authorization header is invalid.
ErrInvalidAuthorizationHeader = errors.New(`invalid "Authorization" HTTP header`)
// ErrInvalidAuthorizationQuery is returned when the authorization query parameter is invalid.
ErrInvalidAuthorizationQuery = errors.New(`invalid "authorization" Query parameter`)
// ErrNoOrigin is returned when the cookie authorization mechanism is used and no Origin nor Referer headers are presents.
ErrNoOrigin = errors.New(`an "Origin" or a "Referer" HTTP header must be present to use the cookie-based authorization mechanism`)
// ErrOriginNotAllowed is returned when the Origin is not allowed to post updates.
Expand All @@ -58,6 +60,14 @@ func authorize(r *http.Request, jwtConfig *jwtConfig, publishOrigins []string, c
return validateJWT(authorizationHeaders[0][7:], jwtConfig)
}

if authorizationQuery, queryExists := r.URL.Query()["authorization"]; queryExists {
if len(authorizationQuery) != 1 || len(authorizationQuery[0]) < 41 {
return nil, ErrInvalidAuthorizationQuery
}

return validateJWT(authorizationQuery[0], jwtConfig)
}

cookie, err := r.Cookie(cookieName)
if err != nil {
// Anonymous
Expand Down
127 changes: 127 additions & 0 deletions authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,133 @@ func TestAuthorizeAuthorizationHeaderWrongAlgorithm(t *testing.T) {
assert.Nil(t, claims)
}

func TestAuthorizeAuthorizationQueryTooShort(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", "x")
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte{}, jwt.SigningMethodHS256}, []string{}, defaultCookieName)
assert.EqualError(t, err, "invalid \"authorization\" Query parameter")
assert.Nil(t, claims)
}

func TestAuthorizeAuthorizationQueryInvalidAlg(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", createDummyNoneSignedJWT())
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte{}, jwt.SigningMethodHS256}, []string{}, defaultCookieName)
assert.EqualError(t, err, "unable to parse JWT: 'none' signature type is not allowed")
assert.Nil(t, claims)
}

func TestAuthorizeAuthorizationQueryInvalidKey(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validEmptyHeader)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte{}, jwt.SigningMethodHS256}, []string{}, defaultCookieName)
assert.EqualError(t, err, "unable to parse JWT: signature is invalid")
assert.Nil(t, claims)
}

func TestAuthorizeAuthorizationQueryInvalidKeyRsa(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validEmptyHeaderRsa)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte{}, jwt.SigningMethodRS256}, []string{}, defaultCookieName)
assert.EqualError(t, err, "unable to parse JWT: unable to parse RSA public key: invalid key: Key must be a PEM encoded PKCS1 or PKCS8 key")
assert.Nil(t, claims)
}

func TestAuthorizeAuthorizationQueryNoContent(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validEmptyHeader)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte("!ChangeMe!"), jwt.SigningMethodHS256}, []string{}, defaultCookieName)
require.Nil(t, err)
assert.Nil(t, claims.Mercure.Publish)
assert.Nil(t, claims.Mercure.Subscribe)
}

func TestAuthorizeAuthorizationQueryNoContentRsa(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validEmptyHeaderRsa)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte(publicKeyRsa), jwt.SigningMethodRS256}, []string{}, defaultCookieName)
require.Nil(t, err)
assert.Nil(t, claims.Mercure.Publish)
assert.Nil(t, claims.Mercure.Subscribe)
}

func TestAuthorizeAuthorizationQuery(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validFullHeader)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte("!ChangeMe!"), jwt.SigningMethodHS256}, []string{}, defaultCookieName)
require.Nil(t, err)
assert.Equal(t, []string{"foo", "bar"}, claims.Mercure.Publish)
assert.Equal(t, []string{"foo", "baz"}, claims.Mercure.Subscribe)
}

func TestAuthorizeAuthorizationQueryRsa(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validFullHeaderRsa)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte(publicKeyRsa), jwt.SigningMethodRS256}, []string{}, defaultCookieName)
require.Nil(t, err)
assert.Equal(t, []string{"foo", "bar"}, claims.Mercure.Publish)
assert.Equal(t, []string{"foo", "baz"}, claims.Mercure.Subscribe)
}

func TestAuthorizeAuthorizationQueryNamespacedRsa(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validFullHeaderNamespacedRsa)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte(publicKeyRsa), jwt.SigningMethodRS256}, []string{}, defaultCookieName)
require.Nil(t, err)
assert.Equal(t, []string{"foo", "bar"}, claims.Mercure.Publish)
assert.Equal(t, []string{"foo", "baz"}, claims.Mercure.Subscribe)
}

func TestAuthorizeAuthorizationQueryRsaWithCert(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validFullHeaderRsaForCert)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte(certificateRsa), jwt.SigningMethodRS256}, []string{}, defaultCookieName)
require.Nil(t, err)
assert.Equal(t, []string{"foo", "bar"}, claims.Mercure.Publish)
assert.Equal(t, []string{"foo", "baz"}, claims.Mercure.Subscribe)
}

func TestAuthorizeAuthorizationQueryWrongAlgorithm(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
query := r.URL.Query()
query.Set("authorization", validFullHeaderRsa)
r.URL.RawQuery = query.Encode()

claims, err := authorize(r, &jwtConfig{[]byte(publicKeyRsa), nil}, []string{}, defaultCookieName)
assert.EqualError(t, err, "unable to parse JWT: <nil>: unexpected signing method")
assert.Nil(t, claims)
}

func TestAuthorizeCookieInvalidAlg(t *testing.T) {
r, _ := http.NewRequest("GET", defaultHubURL, nil)
r.AddCookie(&http.Cookie{Name: defaultCookieName, Value: createDummyNoneSignedJWT()})
Expand Down

0 comments on commit 9764824

Please sign in to comment.