git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add comments to exported methods for root package
ee913fbJoel Speed
env_options.go+6
@@ -6,8 +6,14 @@ import (
6 "strings"
6 "strings"
7)
7)
8
8
9+// EnvOptions holds program options loaded from the process environment
9type EnvOptions map[string]interface{}
10type EnvOptions map[string]interface{}
10
11
12+// LoadEnvForStruct loads environment variables for each field in an options
13+// struct passed into it.
14+//
15+// Fields in the options struct must have an `env` and `cfg` tag to be read
16+// from the environment
11func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
17func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
12 val := reflect.ValueOf(options).Elem()
18 val := reflect.ValueOf(options).Elem()
13 typ := val.Type()
19 typ := val.Type()
htpasswd.go+4
@@ -14,10 +14,12 @@ import (
14// Lookup passwords in a htpasswd file
14// Lookup passwords in a htpasswd file
15// Passwords must be generated with -B for bcrypt or -s for SHA1.
15// Passwords must be generated with -B for bcrypt or -s for SHA1.
16
16
17+// HtpasswdFile represents the structure of an htpasswd file
17type HtpasswdFile struct {
18type HtpasswdFile struct {
18 Users map[string]string
19 Users map[string]string
19}
20}
20
21
22+// NewHtpasswdFromFile constructs an HtpasswdFile from the file at the path given
21func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
23func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
22 r, err := os.Open(path)
24 r, err := os.Open(path)
23 if err != nil {
25 if err != nil {
@@ -27,6 +29,7 @@ func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
27 return NewHtpasswd(r)
29 return NewHtpasswd(r)
28}
30}
29
31
32+// NewHtpasswd consctructs an HtpasswdFile from an io.Reader (opened file)
30func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
33func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
31 csvReader := csv.NewReader(file)
34 csvReader := csv.NewReader(file)
32 csvReader.Comma = ':'
35 csvReader.Comma = ':'
@@ -44,6 +47,7 @@ func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
44 return h, nil
47 return h, nil
45}
48}
46
49
50+// Validate checks a users password against the HtpasswdFile entries
47func (h *HtpasswdFile) Validate(user string, password string) bool {
51func (h *HtpasswdFile) Validate(user string, password string) bool {
48 realPassword, exists := h.Users[user]
52 realPassword, exists := h.Users[user]
49 if !exists {
53 if !exists {
http.go+5−1
@@ -9,11 +9,13 @@ import (
9 "time"
9 "time"
10)
10)
11
11
12+// Server represents an HTTP server
12type Server struct {
13type Server struct {
13 Handler http.Handler
14 Handler http.Handler
14 Opts *Options
15 Opts *Options
15}
16}
16
17
18+// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
17func (s *Server) ListenAndServe() {
19func (s *Server) ListenAndServe() {
18 if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
20 if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
19 s.ServeHTTPS()
21 s.ServeHTTPS()
@@ -22,9 +24,10 @@ func (s *Server) ListenAndServe() {
22 }
24 }
23}
25}
24
26
27+// ServeHTTP constructs a net.Listener and starts handling HTTP requests
25func (s *Server) ServeHTTP() {
28func (s *Server) ServeHTTP() {
26 HTTPAddress := s.Opts.HTTPAddress
29 HTTPAddress := s.Opts.HTTPAddress
27− scheme := ""
30+ var scheme string
28
31
29 i := strings.Index(HTTPAddress, "://")
32 i := strings.Index(HTTPAddress, "://")
30 if i > -1 {
33 if i > -1 {
@@ -57,6 +60,7 @@ func (s *Server) ServeHTTP() {
57 log.Printf("HTTP: closing %s", listener.Addr())
60 log.Printf("HTTP: closing %s", listener.Addr())
58}
61}
59
62
63+// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
60func (s *Server) ServeHTTPS() {
64func (s *Server) ServeHTTPS() {
61 addr := s.Opts.HTTPSAddress
65 addr := s.Opts.HTTPSAddress
62 config := &tls.Config{
66 config := &tls.Config{
logging_handler.go+8
@@ -27,10 +27,13 @@ type responseLogger struct {
27 authInfo string
27 authInfo string
28}
28}
29
29
30+// Header returns the ResponseWriter's Header
30func (l *responseLogger) Header() http.Header {
31func (l *responseLogger) Header() http.Header {
31 return l.w.Header()
32 return l.w.Header()
32}
33}
33
34
35+// ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's
36+// Header
34func (l *responseLogger) ExtractGAPMetadata() {
37func (l *responseLogger) ExtractGAPMetadata() {
35 upstream := l.w.Header().Get("GAP-Upstream-Address")
38 upstream := l.w.Header().Get("GAP-Upstream-Address")
36 if upstream != "" {
39 if upstream != "" {
@@ -44,6 +47,7 @@ func (l *responseLogger) ExtractGAPMetadata() {
44 }
47 }
45}
48}
46
49
50+// Write writes the response using the ResponseWriter
47func (l *responseLogger) Write(b []byte) (int, error) {
51func (l *responseLogger) Write(b []byte) (int, error) {
48 if l.status == 0 {
52 if l.status == 0 {
49 // The status will be StatusOK if WriteHeader has not been called yet
53 // The status will be StatusOK if WriteHeader has not been called yet
@@ -55,16 +59,19 @@ func (l *responseLogger) Write(b []byte) (int, error) {
55 return size, err
59 return size, err
56}
60}
57
61
62+// WriteHeader writes the status code for the Response
58func (l *responseLogger) WriteHeader(s int) {
63func (l *responseLogger) WriteHeader(s int) {
59 l.ExtractGAPMetadata()
64 l.ExtractGAPMetadata()
60 l.w.WriteHeader(s)
65 l.w.WriteHeader(s)
61 l.status = s
66 l.status = s
62}
67}
63
68
69+// Status returns the response status code
64func (l *responseLogger) Status() int {
70func (l *responseLogger) Status() int {
65 return l.status
71 return l.status
66}
72}
67
73
74+// Size returns teh response size
68func (l *responseLogger) Size() int {
75func (l *responseLogger) Size() int {
69 return l.size
76 return l.size
70}
77}
@@ -94,6 +101,7 @@ type loggingHandler struct {
94 logTemplate *template.Template
101 logTemplate *template.Template
95}
102}
96
103
104+// LoggingHandler provides an http.Handler which logs requests to the HTTP server
97func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
105func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
98 return loggingHandler{
106 return loggingHandler{
99 writer: out,
107 writer: out,
oauthproxy.go+48
@@ -20,12 +20,16 @@ import (
20)
20)
21
21
22const (
22const (
23+ // SignatureHeader is the name of the request header containing the GAP Signature
24+ // Part of hmacauth
23 SignatureHeader = "GAP-Signature"
25 SignatureHeader = "GAP-Signature"
24
26
25 httpScheme = "http"
27 httpScheme = "http"
26 httpsScheme = "https"
28 httpsScheme = "https"
27)
29)
28
30
31+// SignatureHeaders contains the headers to be signed by the hmac algorithm
32+// Part of hmacauth
29var SignatureHeaders = []string{
33var SignatureHeaders = []string{
30 "Content-Length",
34 "Content-Length",
31 "Content-Md5",
35 "Content-Md5",
@@ -39,6 +43,7 @@ var SignatureHeaders = []string{
39 "Gap-Auth",
43 "Gap-Auth",
40}
44}
41
45
46+// OAuthProxy is the main authentication proxy
42type OAuthProxy struct {
47type OAuthProxy struct {
43 CookieSeed string
48 CookieSeed string
44 CookieName string
49 CookieName string
@@ -79,12 +84,15 @@ type OAuthProxy struct {
79 Footer string
84 Footer string
80}
85}
81
86
87+// UpstreamProxy represents an upstream server to proxy to
82type UpstreamProxy struct {
88type UpstreamProxy struct {
83 upstream string
89 upstream string
84 handler http.Handler
90 handler http.Handler
85 auth hmacauth.HmacAuth
91 auth hmacauth.HmacAuth
86}
92}
87
93
94+// ServeHTTP proxies requests to the upstream provider while signing the
95+// request headers
88func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
96func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
89 w.Header().Set("GAP-Upstream-Address", u.upstream)
97 w.Header().Set("GAP-Upstream-Address", u.upstream)
90 if u.auth != nil {
98 if u.auth != nil {
@@ -94,9 +102,12 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
94 u.handler.ServeHTTP(w, r)
102 u.handler.ServeHTTP(w, r)
95}
103}
96
104
105+// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
106+// servers
97func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
107func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
98 return httputil.NewSingleHostReverseProxy(target)
108 return httputil.NewSingleHostReverseProxy(target)
99}
109}
110+
100func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
111func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
101 director := proxy.Director
112 director := proxy.Director
102 proxy.Director = func(req *http.Request) {
113 proxy.Director = func(req *http.Request) {
@@ -107,6 +118,7 @@ func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
107 req.URL.RawQuery = ""
118 req.URL.RawQuery = ""
108 }
119 }
109}
120}
121+
110func setProxyDirector(proxy *httputil.ReverseProxy) {
122func setProxyDirector(proxy *httputil.ReverseProxy) {
111 director := proxy.Director
123 director := proxy.Director
112 proxy.Director = func(req *http.Request) {
124 proxy.Director = func(req *http.Request) {
@@ -116,10 +128,13 @@ func setProxyDirector(proxy *httputil.ReverseProxy) {
116 req.URL.RawQuery = ""
128 req.URL.RawQuery = ""
117 }
129 }
118}
130}
131+
132+// NewFileServer creates a http.Handler to serve files from the filesystem
119func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
133func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
120 return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
134 return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
121}
135}
122
136
137+// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
123func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
138func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
124 serveMux := http.NewServeMux()
139 serveMux := http.NewServeMux()
125 var auth hmacauth.HmacAuth
140 var auth hmacauth.HmacAuth
@@ -214,6 +229,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
214 }
229 }
215}
230}
216
231
232+// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
233+// redirect clients to once authenticated
217func (p *OAuthProxy) GetRedirectURI(host string) string {
234func (p *OAuthProxy) GetRedirectURI(host string) string {
218 // default to the request Host if not set
235 // default to the request Host if not set
219 if p.redirectURL.Host != "" {
236 if p.redirectURL.Host != "" {
@@ -259,6 +276,8 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
259 return
276 return
260}
277}
261
278
279+// MakeSessionCookie creates an http.Cookie containing the authenticated user's
280+// authentication details
262func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
281func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
263 if value != "" {
282 if value != "" {
264 value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
283 value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
@@ -270,6 +289,7 @@ func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expirati
270 return p.makeCookie(req, p.CookieName, value, expiration, now)
289 return p.makeCookie(req, p.CookieName, value, expiration, now)
271}
290}
272
291
292+// MakeCSRFCookie creates a cookie for CSRF
273func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
293func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
274 return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
294 return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
275}
295}
@@ -296,14 +316,19 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
296 }
316 }
297}
317}
298
318
319+// ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's
320+// session
299func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
321func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
300 http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
322 http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
301}
323}
302
324
325+// SetCSRFCookie adds a CSRF cookie to the response
303func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
326func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
304 http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
327 http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
305}
328}
306
329
330+// ClearSessionCookie creates a cookie to unset the user's authentication cookie
331+// stored in the user's session
307func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
332func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
308 clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
333 clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
309 http.SetCookie(rw, clr)
334 http.SetCookie(rw, clr)
@@ -316,10 +341,12 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
316 }
341 }
317}
342}
318
343
344+// SetSessionCookie adds the user's session cookie to the response
319func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
345func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
320 http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
346 http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
321}
347}
322
348
349+// LoadCookiedSession reads the user's authentication details from the request
323func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
350func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
324 var age time.Duration
351 var age time.Duration
325 c, err := req.Cookie(p.CookieName)
352 c, err := req.Cookie(p.CookieName)
@@ -341,6 +368,7 @@ func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionSt
341 return session, age, nil
368 return session, age, nil
342}
369}
343
370
371+// SaveSession creates a new session cookie value and sets this on the response
344func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
372func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
345 value, err := p.provider.CookieForSession(s, p.CookieCipher)
373 value, err := p.provider.CookieForSession(s, p.CookieCipher)
346 if err != nil {
374 if err != nil {
@@ -350,16 +378,19 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *p
350 return nil
378 return nil
351}
379}
352
380
381+// RobotsTxt disallows scraping pages from the OAuthProxy
353func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
382func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
354 rw.WriteHeader(http.StatusOK)
383 rw.WriteHeader(http.StatusOK)
355 fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
384 fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
356}
385}
357
386
387+// PingPage responds 200 OK to requests
358func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
388func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
359 rw.WriteHeader(http.StatusOK)
389 rw.WriteHeader(http.StatusOK)
360 fmt.Fprintf(rw, "OK")
390 fmt.Fprintf(rw, "OK")
361}
391}
362
392
393+// ErrorPage writes an error response
363func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
394func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
364 log.Printf("ErrorPage %d %s %s", code, title, message)
395 log.Printf("ErrorPage %d %s %s", code, title, message)
365 rw.WriteHeader(code)
396 rw.WriteHeader(code)
@@ -375,6 +406,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m
375 p.templates.ExecuteTemplate(rw, "error.html", t)
406 p.templates.ExecuteTemplate(rw, "error.html", t)
376}
407}
377
408
409+// SignInPage writes the sing in template to the response
378func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
410func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
379 p.ClearSessionCookie(rw, req)
411 p.ClearSessionCookie(rw, req)
380 rw.WriteHeader(code)
412 rw.WriteHeader(code)
@@ -407,6 +439,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
407 p.templates.ExecuteTemplate(rw, "sign_in.html", t)
439 p.templates.ExecuteTemplate(rw, "sign_in.html", t)
408}
440}
409
441
442+// ManualSignIn handles basic auth logins to the proxy
410func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
443func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
411 if req.Method != "POST" || p.HtpasswdFile == nil {
444 if req.Method != "POST" || p.HtpasswdFile == nil {
412 return "", false
445 return "", false
@@ -424,6 +457,8 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
424 return "", false
457 return "", false
425}
458}
426
459
460+// GetRedirect reads the query parameter to get the URL to redirect clients to
461+// once authenticated with the OAuthProxy
427func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
462func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
428 err = req.ParseForm()
463 err = req.ParseForm()
429 if err != nil {
464 if err != nil {
@@ -438,11 +473,13 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
438 return
473 return
439}
474}
440
475
476+// IsWhitelistedRequest is used to check if auth should be skipped for this request
441func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
477func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
442 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
478 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
443 return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
479 return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
444}
480}
445
481
482+// IsWhitelistedPath is used to check if the request path is allowed without auth
446func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
483func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
447 for _, u := range p.compiledRegex {
484 for _, u := range p.compiledRegex {
448 ok = u.MatchString(path)
485 ok = u.MatchString(path)
@@ -484,6 +521,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
484 }
521 }
485}
522}
486
523
524+// SignIn serves a page prompting users to sign in
487func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
525func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
488 redirect, err := p.GetRedirect(req)
526 redirect, err := p.GetRedirect(req)
489 if err != nil {
527 if err != nil {
@@ -505,11 +543,13 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
505 }
543 }
506}
544}
507
545
546+// SignOut sends a response to clear the authentication cookie
508func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
547func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
509 p.ClearSessionCookie(rw, req)
548 p.ClearSessionCookie(rw, req)
510 http.Redirect(rw, req, "/", 302)
549 http.Redirect(rw, req, "/", 302)
511}
550}
512
551
552+// OAuthStart starts the OAuth2 authentication flow
513func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
553func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
514 nonce, err := cookie.Nonce()
554 nonce, err := cookie.Nonce()
515 if err != nil {
555 if err != nil {
@@ -526,6 +566,8 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
526 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
566 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
527}
567}
528
568
569+// OAuthCallback is the OAuth2 authentication flow callback that finishes the
570+// OAuth2 authentication flow
529func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
571func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
530 remoteAddr := getRemoteAddr(req)
572 remoteAddr := getRemoteAddr(req)
531
573
@@ -587,6 +629,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
587 }
629 }
588}
630}
589
631
632+// AuthenticateOnly checks whether the user is currently logged in
590func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
633func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
591 status := p.Authenticate(rw, req)
634 status := p.Authenticate(rw, req)
592 if status == http.StatusAccepted {
635 if status == http.StatusAccepted {
@@ -596,6 +639,8 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
596 }
639 }
597}
640}
598
641
642+// Proxy proxies the user request if the user is authenticated else it prompts
643+// them to authenticate
599func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
644func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
600 status := p.Authenticate(rw, req)
645 status := p.Authenticate(rw, req)
601 if status == http.StatusInternalServerError {
646 if status == http.StatusInternalServerError {
@@ -612,6 +657,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
612 }
657 }
613}
658}
614
659
660+// Authenticate checks whether a user is authenticated
615func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
661func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
616 var saveSession, clearSession, revalidated bool
662 var saveSession, clearSession, revalidated bool
617 remoteAddr := getRemoteAddr(req)
663 remoteAddr := getRemoteAddr(req)
@@ -711,6 +757,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
711 return http.StatusAccepted
757 return http.StatusAccepted
712}
758}
713
759
760+// CheckBasicAuth checks the requests Authorization header for basic auth
761+// credentials and authenticates these against the proxies HtpasswdFile
714func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
762func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
715 if p.HtpasswdFile == nil {
763 if p.HtpasswdFile == nil {
716 return nil, nil
764 return nil, nil
options.go+4
@@ -89,11 +89,13 @@ type Options struct {
89 oidcVerifier *oidc.IDTokenVerifier
89 oidcVerifier *oidc.IDTokenVerifier
90}
90}
91
91
92+// SignatureData holds hmacauth signature hash and key
92type SignatureData struct {
93type SignatureData struct {
93 hash crypto.Hash
94 hash crypto.Hash
94 key string
95 key string
95}
96}
96
97
98+// NewOptions constructs a new Options with defaulted values
97func NewOptions() *Options {
99func NewOptions() *Options {
98 return &Options{
100 return &Options{
99 ProxyPrefix: "/oauth2",
101 ProxyPrefix: "/oauth2",
@@ -126,6 +128,8 @@ func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string
126 return parsed, msgs
128 return parsed, msgs
127}
129}
128
130
131+// Validate checks that required options are set and validates those that they
132+// are of the correct format
129func (o *Options) Validate() error {
133func (o *Options) Validate() error {
130 if o.SSLInsecureSkipVerify {
134 if o.SSLInsecureSkipVerify {
131 // TODO: Accept a certificate bundle.
135 // TODO: Accept a certificate bundle.
string_array.go+3
@@ -4,13 +4,16 @@ import (
4 "strings"
4 "strings"
5)
5)
6
6
7+// StringArray is a type alias for a slice of strings
7type StringArray []string
8type StringArray []string
8
9
10+// Set appends a string to the StringArray
9func (a *StringArray) Set(s string) error {
11func (a *StringArray) Set(s string) error {
10 *a = append(*a, s)
12 *a = append(*a, s)
11 return nil
13 return nil
12}
14}
13
15
16+// String joins elements of the StringArray into a single comma separated string
14func (a *StringArray) String() string {
17func (a *StringArray) String() string {
15 return strings.Join(*a, ",")
18 return strings.Join(*a, ",")
16}
19}
validator.go+6
@@ -10,11 +10,13 @@ import (
10 "unsafe"
10 "unsafe"
11)
11)
12
12
13+// UserMap holds information from the authenticated emails file
13type UserMap struct {
14type UserMap struct {
14 usersFile string
15 usersFile string
15 m unsafe.Pointer
16 m unsafe.Pointer
16}
17}
17
18
19+// NewUserMap parses the authenticated emails file into a new UserMap
18func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
20func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
19 um := &UserMap{usersFile: usersFile}
21 um := &UserMap{usersFile: usersFile}
20 m := make(map[string]bool)
22 m := make(map[string]bool)
@@ -30,12 +32,15 @@ func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
30 return um
32 return um
31}
33}
32
34
35+// IsValid checks if an email is allowed
33func (um *UserMap) IsValid(email string) (result bool) {
36func (um *UserMap) IsValid(email string) (result bool) {
34 m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
37 m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
35 _, result = m[email]
38 _, result = m[email]
36 return
39 return
37}
40}
38
41
42+// LoadAuthenticatedEmailsFile loads the authenticated emails file from disk
43+// and parses the contents as CSV
39func (um *UserMap) LoadAuthenticatedEmailsFile() {
44func (um *UserMap) LoadAuthenticatedEmailsFile() {
40 r, err := os.Open(um.usersFile)
45 r, err := os.Open(um.usersFile)
41 if err != nil {
46 if err != nil {
@@ -91,6 +96,7 @@ func newValidatorImpl(domains []string, usersFile string,
91 return validator
96 return validator
92}
97}
93
98
99+// NewValidator constructs a function to validate email addresses
94func NewValidator(domains []string, usersFile string) func(string) bool {
100func NewValidator(domains []string, usersFile string) func(string) bool {
95 return newValidatorImpl(domains, usersFile, nil, func() {})
101 return newValidatorImpl(domains, usersFile, nil, func() {})
96}
102}
version.go+1
@@ -1,3 +1,4 @@
1package main
1package main
2
2
3+// VERSION contains version information
3const VERSION = "2.2.1-alpha"
4const VERSION = "2.2.1-alpha"
watcher.go+3
@@ -11,6 +11,8 @@ import (
11 fsnotify "gopkg.in/fsnotify/fsnotify.v1"
11 fsnotify "gopkg.in/fsnotify/fsnotify.v1"
12)
12)
13
13
14+// WaitForReplacement waits for a file to exist on disk and then starts a watch
15+// for the file
14func WaitForReplacement(filename string, op fsnotify.Op,
16func WaitForReplacement(filename string, op fsnotify.Op,
15 watcher *fsnotify.Watcher) {
17 watcher *fsnotify.Watcher) {
16 const sleepInterval = 50 * time.Millisecond
18 const sleepInterval = 50 * time.Millisecond
@@ -30,6 +32,7 @@ func WaitForReplacement(filename string, op fsnotify.Op,
30 }
32 }
31}
33}
32
34
35+// WatchForUpdates performs an action every time a file on disk is updated
33func WatchForUpdates(filename string, done <-chan bool, action func()) {
36func WatchForUpdates(filename string, done <-chan bool, action func()) {
34 filename = filepath.Clean(filename)
37 filename = filepath.Clean(filename)
35 watcher, err := fsnotify.NewWatcher()
38 watcher, err := fsnotify.NewWatcher()