73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
// 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
|
|
}
|