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