changing layout
This commit is contained in:
parent
17b5f751e2
commit
b1c6aa659b
37 changed files with 695 additions and 1018 deletions
73
internal/security/token.go
Normal file
73
internal/security/token.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Package security decides whether a request carries the identity asserted by
|
||||
// the upstream single-sign-on proxy. The CMS never signs users in itself: the
|
||||
// proxy authenticates the owner and forwards a header on every accepted
|
||||
// request. Requests without that header are anonymous.
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ITokenVerifier reports whether a request was authenticated by the SSO proxy.
|
||||
type ITokenVerifier interface {
|
||||
Authenticated(*http.Request) bool
|
||||
HeaderName() string
|
||||
}
|
||||
|
||||
// SSOTokenVerifier trusts the header injected by the identity-aware proxy. An
|
||||
// empty Header disables header lookups; Cookie optionally names a cookie that
|
||||
// carries the same value. When Token is non-empty the forwarded value must
|
||||
// match it exactly, otherwise the presence of any non-empty value is accepted.
|
||||
type SSOTokenVerifier struct {
|
||||
Header string
|
||||
Token string
|
||||
Cookie string
|
||||
}
|
||||
|
||||
// NewSSOTokenVerifier builds a verifier for the given header, optional expected
|
||||
// token, and optional cookie fallback.
|
||||
func NewSSOTokenVerifier(header, token, cookie string) SSOTokenVerifier {
|
||||
return SSOTokenVerifier{Header: header, Token: token, Cookie: cookie}
|
||||
}
|
||||
|
||||
// HeaderName returns the proxy header carrying the SSO token.
|
||||
func (v SSOTokenVerifier) HeaderName() string { return v.Header }
|
||||
|
||||
// Authenticated reports whether the request carries an accepted SSO token.
|
||||
func (v SSOTokenVerifier) Authenticated(r *http.Request) bool {
|
||||
return v.accepted(v.presented(r))
|
||||
}
|
||||
|
||||
func (v SSOTokenVerifier) presented(r *http.Request) string {
|
||||
if v.Header != "" {
|
||||
if value := trimBearer(r.Header.Get(v.Header)); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
if v.Cookie != "" {
|
||||
if c, err := r.Cookie(v.Cookie); err == nil {
|
||||
return trimBearer(c.Value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v SSOTokenVerifier) accepted(presented string) bool {
|
||||
if presented == "" {
|
||||
return false
|
||||
}
|
||||
if v.Token == "" {
|
||||
return true
|
||||
}
|
||||
return subtle.ConstantTimeCompare([]byte(presented), []byte(v.Token)) == 1
|
||||
}
|
||||
|
||||
func trimBearer(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) >= len("bearer ") && strings.EqualFold(value[:len("bearer ")], "bearer ") {
|
||||
return strings.TrimSpace(value[len("bearer "):])
|
||||
}
|
||||
return value
|
||||
}
|
||||
42
internal/security/token_test.go
Normal file
42
internal/security/token_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func requestWithHeader(name, value string) *http.Request {
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r.Header.Set(name, value)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestSSOTokenVerifierRequiresProxyHeader(t *testing.T) {
|
||||
v := NewSSOTokenVerifier("X-SSO-Token", "", "")
|
||||
if v.Authenticated(httptest.NewRequest(http.MethodGet, "/", nil)) {
|
||||
t.Fatal("request without proxy header must stay anonymous")
|
||||
}
|
||||
if !v.Authenticated(requestWithHeader("X-SSO-Token", "oauth2-access-token")) {
|
||||
t.Fatal("request with proxy header must be authenticated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSOTokenVerifierChecksExpectedToken(t *testing.T) {
|
||||
v := NewSSOTokenVerifier("X-SSO-Token", "secret", "")
|
||||
if v.Authenticated(requestWithHeader("X-SSO-Token", "other")) {
|
||||
t.Fatal("mismatching token must stay anonymous")
|
||||
}
|
||||
if !v.Authenticated(requestWithHeader("X-SSO-Token", "Bearer secret")) {
|
||||
t.Fatal("bearer-prefixed token must be authenticated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSOTokenVerifierCookieFallback(t *testing.T) {
|
||||
v := NewSSOTokenVerifier("X-SSO-Token", "", "cms_session")
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r.AddCookie(&http.Cookie{Name: "cms_session", Value: "cookie-token"})
|
||||
if !v.Authenticated(r) {
|
||||
t.Fatal("cookie token must be authenticated")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue