Add comments to exported methods for root package
This commit is contained in:
parent
8ee802d4e5
commit
ee913fb788
@ -6,8 +6,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EnvOptions holds program options loaded from the process environment
|
||||||
type EnvOptions map[string]interface{}
|
type EnvOptions map[string]interface{}
|
||||||
|
|
||||||
|
// LoadEnvForStruct loads environment variables for each field in an options
|
||||||
|
// struct passed into it.
|
||||||
|
//
|
||||||
|
// Fields in the options struct must have an `env` and `cfg` tag to be read
|
||||||
|
// from the environment
|
||||||
func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
|
func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
|
||||||
val := reflect.ValueOf(options).Elem()
|
val := reflect.ValueOf(options).Elem()
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
|
@ -14,10 +14,12 @@ import (
|
|||||||
// Lookup passwords in a htpasswd file
|
// Lookup passwords in a htpasswd file
|
||||||
// Passwords must be generated with -B for bcrypt or -s for SHA1.
|
// Passwords must be generated with -B for bcrypt or -s for SHA1.
|
||||||
|
|
||||||
|
// HtpasswdFile represents the structure of an htpasswd file
|
||||||
type HtpasswdFile struct {
|
type HtpasswdFile struct {
|
||||||
Users map[string]string
|
Users map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewHtpasswdFromFile constructs an HtpasswdFile from the file at the path given
|
||||||
func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
|
func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
|
||||||
r, err := os.Open(path)
|
r, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -27,6 +29,7 @@ func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
|
|||||||
return NewHtpasswd(r)
|
return NewHtpasswd(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewHtpasswd consctructs an HtpasswdFile from an io.Reader (opened file)
|
||||||
func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
|
func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
|
||||||
csvReader := csv.NewReader(file)
|
csvReader := csv.NewReader(file)
|
||||||
csvReader.Comma = ':'
|
csvReader.Comma = ':'
|
||||||
@ -44,6 +47,7 @@ func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
|
|||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate checks a users password against the HtpasswdFile entries
|
||||||
func (h *HtpasswdFile) Validate(user string, password string) bool {
|
func (h *HtpasswdFile) Validate(user string, password string) bool {
|
||||||
realPassword, exists := h.Users[user]
|
realPassword, exists := h.Users[user]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
6
http.go
6
http.go
@ -9,11 +9,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Server represents an HTTP server
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
Opts *Options
|
Opts *Options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
|
||||||
func (s *Server) ListenAndServe() {
|
func (s *Server) ListenAndServe() {
|
||||||
if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
|
if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
|
||||||
s.ServeHTTPS()
|
s.ServeHTTPS()
|
||||||
@ -22,9 +24,10 @@ func (s *Server) ListenAndServe() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeHTTP constructs a net.Listener and starts handling HTTP requests
|
||||||
func (s *Server) ServeHTTP() {
|
func (s *Server) ServeHTTP() {
|
||||||
HTTPAddress := s.Opts.HTTPAddress
|
HTTPAddress := s.Opts.HTTPAddress
|
||||||
scheme := ""
|
var scheme string
|
||||||
|
|
||||||
i := strings.Index(HTTPAddress, "://")
|
i := strings.Index(HTTPAddress, "://")
|
||||||
if i > -1 {
|
if i > -1 {
|
||||||
@ -57,6 +60,7 @@ func (s *Server) ServeHTTP() {
|
|||||||
log.Printf("HTTP: closing %s", listener.Addr())
|
log.Printf("HTTP: closing %s", listener.Addr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
|
||||||
func (s *Server) ServeHTTPS() {
|
func (s *Server) ServeHTTPS() {
|
||||||
addr := s.Opts.HTTPSAddress
|
addr := s.Opts.HTTPSAddress
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
|
@ -27,10 +27,13 @@ type responseLogger struct {
|
|||||||
authInfo string
|
authInfo string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header returns the ResponseWriter's Header
|
||||||
func (l *responseLogger) Header() http.Header {
|
func (l *responseLogger) Header() http.Header {
|
||||||
return l.w.Header()
|
return l.w.Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's
|
||||||
|
// Header
|
||||||
func (l *responseLogger) ExtractGAPMetadata() {
|
func (l *responseLogger) ExtractGAPMetadata() {
|
||||||
upstream := l.w.Header().Get("GAP-Upstream-Address")
|
upstream := l.w.Header().Get("GAP-Upstream-Address")
|
||||||
if upstream != "" {
|
if upstream != "" {
|
||||||
@ -44,6 +47,7 @@ func (l *responseLogger) ExtractGAPMetadata() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write writes the response using the ResponseWriter
|
||||||
func (l *responseLogger) Write(b []byte) (int, error) {
|
func (l *responseLogger) Write(b []byte) (int, error) {
|
||||||
if l.status == 0 {
|
if l.status == 0 {
|
||||||
// The status will be StatusOK if WriteHeader has not been called yet
|
// The status will be StatusOK if WriteHeader has not been called yet
|
||||||
@ -55,16 +59,19 @@ func (l *responseLogger) Write(b []byte) (int, error) {
|
|||||||
return size, err
|
return size, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the status code for the Response
|
||||||
func (l *responseLogger) WriteHeader(s int) {
|
func (l *responseLogger) WriteHeader(s int) {
|
||||||
l.ExtractGAPMetadata()
|
l.ExtractGAPMetadata()
|
||||||
l.w.WriteHeader(s)
|
l.w.WriteHeader(s)
|
||||||
l.status = s
|
l.status = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status returns the response status code
|
||||||
func (l *responseLogger) Status() int {
|
func (l *responseLogger) Status() int {
|
||||||
return l.status
|
return l.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size returns teh response size
|
||||||
func (l *responseLogger) Size() int {
|
func (l *responseLogger) Size() int {
|
||||||
return l.size
|
return l.size
|
||||||
}
|
}
|
||||||
@ -94,6 +101,7 @@ type loggingHandler struct {
|
|||||||
logTemplate *template.Template
|
logTemplate *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoggingHandler provides an http.Handler which logs requests to the HTTP server
|
||||||
func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
|
func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
|
||||||
return loggingHandler{
|
return loggingHandler{
|
||||||
writer: out,
|
writer: out,
|
||||||
|
@ -20,12 +20,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// SignatureHeader is the name of the request header containing the GAP Signature
|
||||||
|
// Part of hmacauth
|
||||||
SignatureHeader = "GAP-Signature"
|
SignatureHeader = "GAP-Signature"
|
||||||
|
|
||||||
httpScheme = "http"
|
httpScheme = "http"
|
||||||
httpsScheme = "https"
|
httpsScheme = "https"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SignatureHeaders contains the headers to be signed by the hmac algorithm
|
||||||
|
// Part of hmacauth
|
||||||
var SignatureHeaders = []string{
|
var SignatureHeaders = []string{
|
||||||
"Content-Length",
|
"Content-Length",
|
||||||
"Content-Md5",
|
"Content-Md5",
|
||||||
@ -39,6 +43,7 @@ var SignatureHeaders = []string{
|
|||||||
"Gap-Auth",
|
"Gap-Auth",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuthProxy is the main authentication proxy
|
||||||
type OAuthProxy struct {
|
type OAuthProxy struct {
|
||||||
CookieSeed string
|
CookieSeed string
|
||||||
CookieName string
|
CookieName string
|
||||||
@ -79,12 +84,15 @@ type OAuthProxy struct {
|
|||||||
Footer string
|
Footer string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpstreamProxy represents an upstream server to proxy to
|
||||||
type UpstreamProxy struct {
|
type UpstreamProxy struct {
|
||||||
upstream string
|
upstream string
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
auth hmacauth.HmacAuth
|
auth hmacauth.HmacAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeHTTP proxies requests to the upstream provider while signing the
|
||||||
|
// request headers
|
||||||
func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("GAP-Upstream-Address", u.upstream)
|
w.Header().Set("GAP-Upstream-Address", u.upstream)
|
||||||
if u.auth != nil {
|
if u.auth != nil {
|
||||||
@ -94,9 +102,12 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
u.handler.ServeHTTP(w, r)
|
u.handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
|
||||||
|
// servers
|
||||||
func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
|
func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
|
||||||
return httputil.NewSingleHostReverseProxy(target)
|
return httputil.NewSingleHostReverseProxy(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
|
func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
|
||||||
director := proxy.Director
|
director := proxy.Director
|
||||||
proxy.Director = func(req *http.Request) {
|
proxy.Director = func(req *http.Request) {
|
||||||
@ -107,6 +118,7 @@ func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
|
|||||||
req.URL.RawQuery = ""
|
req.URL.RawQuery = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setProxyDirector(proxy *httputil.ReverseProxy) {
|
func setProxyDirector(proxy *httputil.ReverseProxy) {
|
||||||
director := proxy.Director
|
director := proxy.Director
|
||||||
proxy.Director = func(req *http.Request) {
|
proxy.Director = func(req *http.Request) {
|
||||||
@ -116,10 +128,13 @@ func setProxyDirector(proxy *httputil.ReverseProxy) {
|
|||||||
req.URL.RawQuery = ""
|
req.URL.RawQuery = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFileServer creates a http.Handler to serve files from the filesystem
|
||||||
func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
|
func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
|
||||||
return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
|
return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
||||||
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||||
serveMux := http.NewServeMux()
|
serveMux := http.NewServeMux()
|
||||||
var auth hmacauth.HmacAuth
|
var auth hmacauth.HmacAuth
|
||||||
@ -214,6 +229,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
|
||||||
|
// redirect clients to once authenticated
|
||||||
func (p *OAuthProxy) GetRedirectURI(host string) string {
|
func (p *OAuthProxy) GetRedirectURI(host string) string {
|
||||||
// default to the request Host if not set
|
// default to the request Host if not set
|
||||||
if p.redirectURL.Host != "" {
|
if p.redirectURL.Host != "" {
|
||||||
@ -259,6 +276,8 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeSessionCookie creates an http.Cookie containing the authenticated user's
|
||||||
|
// authentication details
|
||||||
func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||||
if value != "" {
|
if value != "" {
|
||||||
value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
|
value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
|
||||||
@ -270,6 +289,7 @@ func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expirati
|
|||||||
return p.makeCookie(req, p.CookieName, value, expiration, now)
|
return p.makeCookie(req, p.CookieName, value, expiration, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeCSRFCookie creates a cookie for CSRF
|
||||||
func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||||
return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
|
return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
|
||||||
}
|
}
|
||||||
@ -296,14 +316,19 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's
|
||||||
|
// session
|
||||||
func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
|
||||||
http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
|
http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCSRFCookie adds a CSRF cookie to the response
|
||||||
func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
||||||
http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
|
http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearSessionCookie creates a cookie to unset the user's authentication cookie
|
||||||
|
// stored in the user's session
|
||||||
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
|
||||||
clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
|
clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
|
||||||
http.SetCookie(rw, clr)
|
http.SetCookie(rw, clr)
|
||||||
@ -316,10 +341,12 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSessionCookie adds the user's session cookie to the response
|
||||||
func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
||||||
http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
|
http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadCookiedSession reads the user's authentication details from the request
|
||||||
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
|
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
|
||||||
var age time.Duration
|
var age time.Duration
|
||||||
c, err := req.Cookie(p.CookieName)
|
c, err := req.Cookie(p.CookieName)
|
||||||
@ -341,6 +368,7 @@ func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionSt
|
|||||||
return session, age, nil
|
return session, age, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveSession creates a new session cookie value and sets this on the response
|
||||||
func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
|
func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
|
||||||
value, err := p.provider.CookieForSession(s, p.CookieCipher)
|
value, err := p.provider.CookieForSession(s, p.CookieCipher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -350,16 +378,19 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *p
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RobotsTxt disallows scraping pages from the OAuthProxy
|
||||||
func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
|
func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
|
fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PingPage responds 200 OK to requests
|
||||||
func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
|
func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(rw, "OK")
|
fmt.Fprintf(rw, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorPage writes an error response
|
||||||
func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
|
func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
|
||||||
log.Printf("ErrorPage %d %s %s", code, title, message)
|
log.Printf("ErrorPage %d %s %s", code, title, message)
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
@ -375,6 +406,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m
|
|||||||
p.templates.ExecuteTemplate(rw, "error.html", t)
|
p.templates.ExecuteTemplate(rw, "error.html", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignInPage writes the sing in template to the response
|
||||||
func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
|
func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
|
||||||
p.ClearSessionCookie(rw, req)
|
p.ClearSessionCookie(rw, req)
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
@ -407,6 +439,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
|
|||||||
p.templates.ExecuteTemplate(rw, "sign_in.html", t)
|
p.templates.ExecuteTemplate(rw, "sign_in.html", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ManualSignIn handles basic auth logins to the proxy
|
||||||
func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
|
func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
|
||||||
if req.Method != "POST" || p.HtpasswdFile == nil {
|
if req.Method != "POST" || p.HtpasswdFile == nil {
|
||||||
return "", false
|
return "", false
|
||||||
@ -424,6 +457,8 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRedirect reads the query parameter to get the URL to redirect clients to
|
||||||
|
// once authenticated with the OAuthProxy
|
||||||
func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
|
func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
|
||||||
err = req.ParseForm()
|
err = req.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -438,11 +473,13 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsWhitelistedRequest is used to check if auth should be skipped for this request
|
||||||
func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
|
func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
|
||||||
isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
|
isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
|
||||||
return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
|
return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsWhitelistedPath is used to check if the request path is allowed without auth
|
||||||
func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
|
func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
|
||||||
for _, u := range p.compiledRegex {
|
for _, u := range p.compiledRegex {
|
||||||
ok = u.MatchString(path)
|
ok = u.MatchString(path)
|
||||||
@ -484,6 +521,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignIn serves a page prompting users to sign in
|
||||||
func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
||||||
redirect, err := p.GetRedirect(req)
|
redirect, err := p.GetRedirect(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -505,11 +543,13 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignOut sends a response to clear the authentication cookie
|
||||||
func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
||||||
p.ClearSessionCookie(rw, req)
|
p.ClearSessionCookie(rw, req)
|
||||||
http.Redirect(rw, req, "/", 302)
|
http.Redirect(rw, req, "/", 302)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuthStart starts the OAuth2 authentication flow
|
||||||
func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
||||||
nonce, err := cookie.Nonce()
|
nonce, err := cookie.Nonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -526,6 +566,8 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
|||||||
http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
|
http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuthCallback is the OAuth2 authentication flow callback that finishes the
|
||||||
|
// OAuth2 authentication flow
|
||||||
func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
||||||
remoteAddr := getRemoteAddr(req)
|
remoteAddr := getRemoteAddr(req)
|
||||||
|
|
||||||
@ -587,6 +629,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AuthenticateOnly checks whether the user is currently logged in
|
||||||
func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
|
||||||
status := p.Authenticate(rw, req)
|
status := p.Authenticate(rw, req)
|
||||||
if status == http.StatusAccepted {
|
if status == http.StatusAccepted {
|
||||||
@ -596,6 +639,8 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Proxy proxies the user request if the user is authenticated else it prompts
|
||||||
|
// them to authenticate
|
||||||
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
||||||
status := p.Authenticate(rw, req)
|
status := p.Authenticate(rw, req)
|
||||||
if status == http.StatusInternalServerError {
|
if status == http.StatusInternalServerError {
|
||||||
@ -612,6 +657,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authenticate checks whether a user is authenticated
|
||||||
func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
|
func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
|
||||||
var saveSession, clearSession, revalidated bool
|
var saveSession, clearSession, revalidated bool
|
||||||
remoteAddr := getRemoteAddr(req)
|
remoteAddr := getRemoteAddr(req)
|
||||||
@ -711,6 +757,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
|
|||||||
return http.StatusAccepted
|
return http.StatusAccepted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckBasicAuth checks the requests Authorization header for basic auth
|
||||||
|
// credentials and authenticates these against the proxies HtpasswdFile
|
||||||
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
|
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
|
||||||
if p.HtpasswdFile == nil {
|
if p.HtpasswdFile == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -89,11 +89,13 @@ type Options struct {
|
|||||||
oidcVerifier *oidc.IDTokenVerifier
|
oidcVerifier *oidc.IDTokenVerifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignatureData holds hmacauth signature hash and key
|
||||||
type SignatureData struct {
|
type SignatureData struct {
|
||||||
hash crypto.Hash
|
hash crypto.Hash
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewOptions constructs a new Options with defaulted values
|
||||||
func NewOptions() *Options {
|
func NewOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
ProxyPrefix: "/oauth2",
|
ProxyPrefix: "/oauth2",
|
||||||
@ -126,6 +128,8 @@ func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string
|
|||||||
return parsed, msgs
|
return parsed, msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate checks that required options are set and validates those that they
|
||||||
|
// are of the correct format
|
||||||
func (o *Options) Validate() error {
|
func (o *Options) Validate() error {
|
||||||
if o.SSLInsecureSkipVerify {
|
if o.SSLInsecureSkipVerify {
|
||||||
// TODO: Accept a certificate bundle.
|
// TODO: Accept a certificate bundle.
|
||||||
|
@ -4,13 +4,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StringArray is a type alias for a slice of strings
|
||||||
type StringArray []string
|
type StringArray []string
|
||||||
|
|
||||||
|
// Set appends a string to the StringArray
|
||||||
func (a *StringArray) Set(s string) error {
|
func (a *StringArray) Set(s string) error {
|
||||||
*a = append(*a, s)
|
*a = append(*a, s)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String joins elements of the StringArray into a single comma separated string
|
||||||
func (a *StringArray) String() string {
|
func (a *StringArray) String() string {
|
||||||
return strings.Join(*a, ",")
|
return strings.Join(*a, ",")
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,13 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UserMap holds information from the authenticated emails file
|
||||||
type UserMap struct {
|
type UserMap struct {
|
||||||
usersFile string
|
usersFile string
|
||||||
m unsafe.Pointer
|
m unsafe.Pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewUserMap parses the authenticated emails file into a new UserMap
|
||||||
func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
|
func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
|
||||||
um := &UserMap{usersFile: usersFile}
|
um := &UserMap{usersFile: usersFile}
|
||||||
m := make(map[string]bool)
|
m := make(map[string]bool)
|
||||||
@ -30,12 +32,15 @@ func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
|
|||||||
return um
|
return um
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsValid checks if an email is allowed
|
||||||
func (um *UserMap) IsValid(email string) (result bool) {
|
func (um *UserMap) IsValid(email string) (result bool) {
|
||||||
m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
|
m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
|
||||||
_, result = m[email]
|
_, result = m[email]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadAuthenticatedEmailsFile loads the authenticated emails file from disk
|
||||||
|
// and parses the contents as CSV
|
||||||
func (um *UserMap) LoadAuthenticatedEmailsFile() {
|
func (um *UserMap) LoadAuthenticatedEmailsFile() {
|
||||||
r, err := os.Open(um.usersFile)
|
r, err := os.Open(um.usersFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -91,6 +96,7 @@ func newValidatorImpl(domains []string, usersFile string,
|
|||||||
return validator
|
return validator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewValidator constructs a function to validate email addresses
|
||||||
func NewValidator(domains []string, usersFile string) func(string) bool {
|
func NewValidator(domains []string, usersFile string) func(string) bool {
|
||||||
return newValidatorImpl(domains, usersFile, nil, func() {})
|
return newValidatorImpl(domains, usersFile, nil, func() {})
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
// VERSION contains version information
|
||||||
const VERSION = "2.2.1-alpha"
|
const VERSION = "2.2.1-alpha"
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
fsnotify "gopkg.in/fsnotify/fsnotify.v1"
|
fsnotify "gopkg.in/fsnotify/fsnotify.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WaitForReplacement waits for a file to exist on disk and then starts a watch
|
||||||
|
// for the file
|
||||||
func WaitForReplacement(filename string, op fsnotify.Op,
|
func WaitForReplacement(filename string, op fsnotify.Op,
|
||||||
watcher *fsnotify.Watcher) {
|
watcher *fsnotify.Watcher) {
|
||||||
const sleepInterval = 50 * time.Millisecond
|
const sleepInterval = 50 * time.Millisecond
|
||||||
@ -30,6 +32,7 @@ func WaitForReplacement(filename string, op fsnotify.Op,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WatchForUpdates performs an action every time a file on disk is updated
|
||||||
func WatchForUpdates(filename string, done <-chan bool, action func()) {
|
func WatchForUpdates(filename string, done <-chan bool, action func()) {
|
||||||
filename = filepath.Clean(filename)
|
filename = filepath.Clean(filename)
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
Loading…
Reference in New Issue
Block a user