mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-05 13:07:14 +00:00
Implement the rest of an OIDC client CLI library.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
67
internal/httputil/httperr/httperr.go
Normal file
67
internal/httputil/httperr/httperr.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package httperr contains some helpers for nicer error handling in http.Handler implementations.
|
||||
package httperr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Responder represents an error that can emit a useful HTTP error response to an http.ResponseWriter.
|
||||
type Responder interface {
|
||||
error
|
||||
Respond(http.ResponseWriter)
|
||||
}
|
||||
|
||||
// New returns a Responder that emits the given HTTP status code and message.
|
||||
func New(code int, msg string) error {
|
||||
return httpErr{code: code, msg: msg}
|
||||
}
|
||||
|
||||
// Newf returns a Responder that emits the given HTTP status code and fmt.Sprintf formatted message.
|
||||
func Newf(code int, format string, args ...interface{}) error {
|
||||
return httpErr{code: code, msg: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
// Wrap returns a Responder that emits the given HTTP status code and message, and also wraps an internal error.
|
||||
func Wrap(code int, msg string, cause error) error {
|
||||
return httpErr{code: code, msg: msg, cause: cause}
|
||||
}
|
||||
|
||||
type httpErr struct {
|
||||
code int
|
||||
msg string
|
||||
cause error
|
||||
}
|
||||
|
||||
func (e httpErr) Error() string {
|
||||
if e.cause != nil {
|
||||
return fmt.Sprintf("%s: %v", e.msg, e.cause)
|
||||
}
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (e httpErr) Respond(w http.ResponseWriter) {
|
||||
// http.Error is important here because it prevents content sniffing by forcing text/plain.
|
||||
http.Error(w, http.StatusText(e.code)+": "+e.msg, e.code)
|
||||
}
|
||||
|
||||
func (e httpErr) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// HandlerFunc is like http.HandlerFunc, but with a function signature that allows easier error handling.
|
||||
type HandlerFunc func(http.ResponseWriter, *http.Request) error
|
||||
|
||||
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch err := f(w, r).(type) {
|
||||
case nil:
|
||||
return
|
||||
case Responder:
|
||||
err.Respond(w)
|
||||
default:
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
52
internal/httputil/httperr/httperr_test.go
Normal file
52
internal/httputil/httperr/httperr_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package httperr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHTTPErrs(t *testing.T) {
|
||||
t.Run("new", func(t *testing.T) {
|
||||
err := New(http.StatusBadRequest, "bad request error")
|
||||
require.EqualError(t, err, "bad request error")
|
||||
})
|
||||
|
||||
t.Run("newf", func(t *testing.T) {
|
||||
err := Newf(http.StatusMethodNotAllowed, "expected method %s", "POST")
|
||||
require.EqualError(t, err, "expected method POST")
|
||||
})
|
||||
|
||||
t.Run("wrap", func(t *testing.T) {
|
||||
wrappedErr := fmt.Errorf("some internal error")
|
||||
err := Wrap(http.StatusInternalServerError, "unexpected error", wrappedErr)
|
||||
require.EqualError(t, err, "unexpected error: some internal error")
|
||||
require.True(t, errors.Is(err, wrappedErr), "expected error to be wrapped")
|
||||
})
|
||||
|
||||
t.Run("respond", func(t *testing.T) {
|
||||
err := Wrap(http.StatusForbidden, "boring public bits", fmt.Errorf("some secret internal bits"))
|
||||
require.Implements(t, (*Responder)(nil), err)
|
||||
rec := httptest.NewRecorder()
|
||||
err.(Responder).Respond(rec)
|
||||
require.Equal(t, http.StatusForbidden, rec.Code)
|
||||
require.Equal(t, "Forbidden: boring public bits\n", rec.Body.String())
|
||||
require.Equal(t, http.Header{
|
||||
"Content-Type": []string{"text/plain; charset=utf-8"},
|
||||
"X-Content-Type-Options": []string{"nosniff"},
|
||||
}, rec.Header())
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandlerFunc(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
|
||||
})
|
||||
}
|
||||
20
internal/httputil/securityheader/securityheader.go
Normal file
20
internal/httputil/securityheader/securityheader.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package securityheader implements an HTTP middleware for setting security-related response headers.
|
||||
package securityheader
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Wrap the provided http.Handler so it sets appropriate security-related response headers.
|
||||
func Wrap(wrapped http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("X-XSS-Protection", "1; mode=block")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("Referrer-Policy", "no-referrer")
|
||||
wrapped.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
30
internal/httputil/securityheader/securityheader_test.go
Normal file
30
internal/httputil/securityheader/securityheader_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package securityheader
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("hello world"))
|
||||
})
|
||||
rec := httptest.NewRecorder()
|
||||
Wrap(handler).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
require.Equal(t, http.StatusOK, rec.Code)
|
||||
require.Equal(t, "hello world", rec.Body.String())
|
||||
require.EqualValues(t, http.Header{
|
||||
"Content-Security-Policy": []string{"default-src 'none'; frame-ancestors 'none'"},
|
||||
"Content-Type": []string{"text/plain; charset=utf-8"},
|
||||
"Referrer-Policy": []string{"no-referrer"},
|
||||
"X-Content-Type-Options": []string{"nosniff"},
|
||||
"X-Frame-Options": []string{"DENY"},
|
||||
"X-Xss-Protection": []string{"1; mode=block"},
|
||||
}, rec.Header())
|
||||
}
|
||||
Reference in New Issue
Block a user