commit
						9047920e90
					
				|  | @ -77,6 +77,7 @@ Usage of google_auth_proxy: | ||||||
|   -cookie-expire=168h0m0s: expire timeframe for cookie |   -cookie-expire=168h0m0s: expire timeframe for cookie | ||||||
|   -cookie-httponly=true: set HttpOnly cookie flag |   -cookie-httponly=true: set HttpOnly cookie flag | ||||||
|   -cookie-https-only=true: set secure (HTTPS) cookies (deprecated. use --cookie-secure setting) |   -cookie-https-only=true: set secure (HTTPS) cookies (deprecated. use --cookie-secure setting) | ||||||
|  |   -cookie-refresh=0: refresh the cookie when less than this much time remains before expiration; 0 to disable | ||||||
|   -cookie-secret="": the seed string for secure cookies |   -cookie-secret="": the seed string for secure cookies | ||||||
|   -cookie-secure=true: set secure (HTTPS) cookie flag |   -cookie-secure=true: set secure (HTTPS) cookie flag | ||||||
|   -custom-templates-dir="": path to custom html templates |   -custom-templates-dir="": path to custom html templates | ||||||
|  | @ -96,6 +97,7 @@ Usage of google_auth_proxy: | ||||||
|   -scope="": Oauth scope specification |   -scope="": Oauth scope specification | ||||||
|   -skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times) |   -skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times) | ||||||
|   -upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path |   -upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path | ||||||
|  |   -validate-url="": Access token validation endpoint | ||||||
|   -version=false: print version string |   -version=false: print version string | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -46,12 +46,17 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## Cookie Settings | ## Cookie Settings | ||||||
| ## Secret - the seed string for secure cookies | ## Secret - the seed string for secure cookies; should be 16, 24, or 32 bytes | ||||||
|  | ##          for use with an AES cipher when cookie_refresh or pass_access_code | ||||||
|  | ##          is set | ||||||
| ## Domain - optional cookie domain to force cookies to (ie: .yourcompany.com) | ## Domain - optional cookie domain to force cookies to (ie: .yourcompany.com) | ||||||
| ## Expire - expire timeframe for cookie | ## Expire - expire timeframe for cookie | ||||||
|  | ## Refresh - refresh the cookie when less than this much time remains before | ||||||
|  | ##           expiration; should be less than cookie_expire; set to 0 to disable | ||||||
| # cookie_secret = "" | # cookie_secret = "" | ||||||
| # cookie_domain = "" | # cookie_domain = "" | ||||||
| # cookie_expire = "168h" | # cookie_expire = "168h" | ||||||
|  | # cookie_refresh = "144h" | ||||||
| # cookie_secure = true | # cookie_secure = true | ||||||
| # cookie_httponly = true | # cookie_httponly = true | ||||||
| 
 | # pass_access_code = true | ||||||
|  |  | ||||||
|  | @ -15,11 +15,11 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func validateCookie(cookie *http.Cookie, seed string) (string, bool) { | func validateCookie(cookie *http.Cookie, seed string) (string, time.Time, bool) { | ||||||
| 	// value, timestamp, sig
 | 	// value, timestamp, sig
 | ||||||
| 	parts := strings.Split(cookie.Value, "|") | 	parts := strings.Split(cookie.Value, "|") | ||||||
| 	if len(parts) != 3 { | 	if len(parts) != 3 { | ||||||
| 		return "", false | 		return "", time.Unix(0, 0), false | ||||||
| 	} | 	} | ||||||
| 	sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) | 	sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) | ||||||
| 	if checkHmac(parts[2], sig) { | 	if checkHmac(parts[2], sig) { | ||||||
|  | @ -28,11 +28,11 @@ func validateCookie(cookie *http.Cookie, seed string) (string, bool) { | ||||||
| 			// it's a valid cookie. now get the contents
 | 			// it's a valid cookie. now get the contents
 | ||||||
| 			rawValue, err := base64.URLEncoding.DecodeString(parts[0]) | 			rawValue, err := base64.URLEncoding.DecodeString(parts[0]) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				return string(rawValue), true | 				return string(rawValue), time.Unix(int64(ts), 0), true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return "", false | 	return "", time.Unix(0, 0), false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func signedCookieValue(seed string, key string, value string) string { | func signedCookieValue(seed string, key string, value string) string { | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								main.go
								
								
								
								
							
							
						
						
									
										2
									
								
								main.go
								
								
								
								
							|  | @ -45,6 +45,7 @@ func main() { | ||||||
| 	flagSet.String("cookie-secret", "", "the seed string for secure cookies") | 	flagSet.String("cookie-secret", "", "the seed string for secure cookies") | ||||||
| 	flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*") | 	flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*") | ||||||
| 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") | 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") | ||||||
|  | 	flagSet.Duration("cookie-refresh", time.Duration(0)*time.Hour, "refresh the cookie when less than this much time remains before expiration; 0 to disable") | ||||||
| 	flagSet.Bool("cookie-https-only", true, "set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)") | 	flagSet.Bool("cookie-https-only", true, "set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)") | ||||||
| 	flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") | 	flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") | ||||||
| 	flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") | 	flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") | ||||||
|  | @ -55,6 +56,7 @@ func main() { | ||||||
| 	flagSet.String("login-url", "", "Authentication endpoint") | 	flagSet.String("login-url", "", "Authentication endpoint") | ||||||
| 	flagSet.String("redeem-url", "", "Token redemption endpoint") | 	flagSet.String("redeem-url", "", "Token redemption endpoint") | ||||||
| 	flagSet.String("profile-url", "", "Profile access endpoint") | 	flagSet.String("profile-url", "", "Profile access endpoint") | ||||||
|  | 	flagSet.String("validate-url", "", "Access token validation endpoint") | ||||||
| 	flagSet.String("scope", "", "Oauth scope specification") | 	flagSet.String("scope", "", "Oauth scope specification") | ||||||
| 
 | 
 | ||||||
| 	flagSet.Parse(os.Args[1:]) | 	flagSet.Parse(os.Args[1:]) | ||||||
|  |  | ||||||
							
								
								
									
										103
									
								
								oauthproxy.go
								
								
								
								
							
							
						
						
									
										103
									
								
								oauthproxy.go
								
								
								
								
							|  | @ -34,12 +34,14 @@ type OauthProxy struct { | ||||||
| 	CookieSecure   bool | 	CookieSecure   bool | ||||||
| 	CookieHttpOnly bool | 	CookieHttpOnly bool | ||||||
| 	CookieExpire   time.Duration | 	CookieExpire   time.Duration | ||||||
|  | 	CookieRefresh  time.Duration | ||||||
| 	Validator      func(string) bool | 	Validator      func(string) bool | ||||||
| 
 | 
 | ||||||
| 	redirectUrl         *url.URL // the url to receive requests at
 | 	redirectUrl         *url.URL // the url to receive requests at
 | ||||||
| 	provider            providers.Provider | 	provider            providers.Provider | ||||||
| 	oauthRedemptionUrl  *url.URL // endpoint to redeem the code
 | 	oauthRedemptionUrl  *url.URL // endpoint to redeem the code
 | ||||||
| 	oauthLoginUrl       *url.URL // to redirect the user to
 | 	oauthLoginUrl       *url.URL // to redirect the user to
 | ||||||
|  | 	oauthValidateUrl    *url.URL // to validate the access token
 | ||||||
| 	oauthScope          string | 	oauthScope          string | ||||||
| 	clientID            string | 	clientID            string | ||||||
| 	clientSecret        string | 	clientSecret        string | ||||||
|  | @ -48,6 +50,7 @@ type OauthProxy struct { | ||||||
| 	DisplayHtpasswdForm bool | 	DisplayHtpasswdForm bool | ||||||
| 	serveMux            http.Handler | 	serveMux            http.Handler | ||||||
| 	PassBasicAuth       bool | 	PassBasicAuth       bool | ||||||
|  | 	PassAccessToken     bool | ||||||
| 	AesCipher           cipher.Block | 	AesCipher           cipher.Block | ||||||
| 	skipAuthRegex       []string | 	skipAuthRegex       []string | ||||||
| 	compiledRegex       []*regexp.Regexp | 	compiledRegex       []*regexp.Regexp | ||||||
|  | @ -121,12 +124,12 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { | ||||||
| 	log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain) | 	log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain) | ||||||
| 
 | 
 | ||||||
| 	var aes_cipher cipher.Block | 	var aes_cipher cipher.Block | ||||||
| 	if opts.PassAccessToken { | 	if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) { | ||||||
| 		var err error | 		var err error | ||||||
| 		aes_cipher, err = aes.NewCipher([]byte(opts.CookieSecret)) | 		aes_cipher, err = aes.NewCipher([]byte(opts.CookieSecret)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Fatal("error creating AES cipher with "+ | 			log.Fatal("error creating AES cipher with "+ | ||||||
| 				"pass_access_token == true: %s", err) | 				"cookie-secret ", opts.CookieSecret, ": ", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -137,6 +140,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { | ||||||
| 		CookieSecure:   opts.CookieSecure, | 		CookieSecure:   opts.CookieSecure, | ||||||
| 		CookieHttpOnly: opts.CookieHttpOnly, | 		CookieHttpOnly: opts.CookieHttpOnly, | ||||||
| 		CookieExpire:   opts.CookieExpire, | 		CookieExpire:   opts.CookieExpire, | ||||||
|  | 		CookieRefresh:  opts.CookieRefresh, | ||||||
| 		Validator:      validator, | 		Validator:      validator, | ||||||
| 
 | 
 | ||||||
| 		clientID:           opts.ClientID, | 		clientID:           opts.ClientID, | ||||||
|  | @ -145,11 +149,13 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { | ||||||
| 		provider:           opts.provider, | 		provider:           opts.provider, | ||||||
| 		oauthRedemptionUrl: opts.provider.Data().RedeemUrl, | 		oauthRedemptionUrl: opts.provider.Data().RedeemUrl, | ||||||
| 		oauthLoginUrl:      opts.provider.Data().LoginUrl, | 		oauthLoginUrl:      opts.provider.Data().LoginUrl, | ||||||
|  | 		oauthValidateUrl:   opts.provider.Data().ValidateUrl, | ||||||
| 		serveMux:           serveMux, | 		serveMux:           serveMux, | ||||||
| 		redirectUrl:        redirectUrl, | 		redirectUrl:        redirectUrl, | ||||||
| 		skipAuthRegex:      opts.SkipAuthRegex, | 		skipAuthRegex:      opts.SkipAuthRegex, | ||||||
| 		compiledRegex:      opts.CompiledRegex, | 		compiledRegex:      opts.CompiledRegex, | ||||||
| 		PassBasicAuth:      opts.PassBasicAuth, | 		PassBasicAuth:      opts.PassBasicAuth, | ||||||
|  | 		PassAccessToken:    opts.PassAccessToken, | ||||||
| 		AesCipher:          aes_cipher, | 		AesCipher:          aes_cipher, | ||||||
| 		templates:          loadTemplates(opts.CustomTemplatesDir), | 		templates:          loadTemplates(opts.CustomTemplatesDir), | ||||||
| 	} | 	} | ||||||
|  | @ -224,7 +230,7 @@ func (p *OauthProxy) redeemCode(host, code string) (string, string, error) { | ||||||
| 	return access_token, email, nil | 	return access_token, email, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) { | func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time.Duration) *http.Cookie { | ||||||
| 	domain := req.Host | 	domain := req.Host | ||||||
| 	if h, _, err := net.SplitHostPort(domain); err == nil { | 	if h, _, err := net.SplitHostPort(domain); err == nil { | ||||||
| 		domain = h | 		domain = h | ||||||
|  | @ -235,40 +241,76 @@ func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) { | ||||||
| 		} | 		} | ||||||
| 		domain = p.CookieDomain | 		domain = p.CookieDomain | ||||||
| 	} | 	} | ||||||
| 	cookie := &http.Cookie{ | 
 | ||||||
|  | 	if value != "" { | ||||||
|  | 		value = signedCookieValue(p.CookieSeed, p.CookieKey, value) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &http.Cookie{ | ||||||
| 		Name:     p.CookieKey, | 		Name:     p.CookieKey, | ||||||
| 		Value:    "", | 		Value:    value, | ||||||
| 		Path:     "/", | 		Path:     "/", | ||||||
| 		Domain:   domain, | 		Domain:   domain, | ||||||
| 		HttpOnly: p.CookieHttpOnly, | 		HttpOnly: p.CookieHttpOnly, | ||||||
| 		Secure:   p.CookieSecure, | 		Secure:   p.CookieSecure, | ||||||
| 		Expires:  time.Now().Add(time.Duration(1) * time.Hour * -1), | 		Expires:  time.Now().Add(expiration), | ||||||
| 	} | 	} | ||||||
| 	http.SetCookie(rw, cookie) | } | ||||||
|  | 
 | ||||||
|  | func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) { | ||||||
|  | 	http.SetCookie(rw, p.MakeCookie(req, "", time.Duration(1)*time.Hour*-1)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) { | func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) { | ||||||
|  | 	http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire)) | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	domain := req.Host | func (p *OauthProxy) ValidateToken(access_token string) bool { | ||||||
| 	if h, _, err := net.SplitHostPort(domain); err == nil { | 	if access_token == "" || p.oauthValidateUrl == nil { | ||||||
| 		domain = h | 		return false | ||||||
| 	} | 	} | ||||||
| 	if p.CookieDomain != "" { | 
 | ||||||
| 		if !strings.HasSuffix(domain, p.CookieDomain) { | 	req, err := http.NewRequest("GET", | ||||||
| 			log.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain) | 		p.oauthValidateUrl.String()+"?access_token="+access_token, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("failed building token validation request: %s", err) | ||||||
|  | 		return false | ||||||
| 	} | 	} | ||||||
| 		domain = p.CookieDomain | 
 | ||||||
|  | 	httpclient := &http.Client{} | ||||||
|  | 	resp, err := httpclient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("token validation request failed: %s", err) | ||||||
|  | 		return false | ||||||
| 	} | 	} | ||||||
| 	cookie := &http.Cookie{ | 	return resp.StatusCode == 200 | ||||||
| 		Name:     p.CookieKey, |  | ||||||
| 		Value:    signedCookieValue(p.CookieSeed, p.CookieKey, val), |  | ||||||
| 		Path:     "/", |  | ||||||
| 		Domain:   domain, |  | ||||||
| 		HttpOnly: p.CookieHttpOnly, |  | ||||||
| 		Secure:   p.CookieSecure, |  | ||||||
| 		Expires:  time.Now().Add(p.CookieExpire), |  | ||||||
| } | } | ||||||
| 	http.SetCookie(rw, cookie) | 
 | ||||||
|  | func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) { | ||||||
|  | 	var value string | ||||||
|  | 	var timestamp time.Time | ||||||
|  | 	cookie, err := req.Cookie(p.CookieKey) | ||||||
|  | 	if err == nil { | ||||||
|  | 		value, timestamp, ok = validateCookie(cookie, p.CookieSeed) | ||||||
|  | 		if ok { | ||||||
|  | 			email, user, access_token, err = parseCookieValue( | ||||||
|  | 				value, p.AesCipher) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf(err.Error()) | ||||||
|  | 		ok = false | ||||||
|  | 	} else if p.CookieRefresh != time.Duration(0) { | ||||||
|  | 		expires := timestamp.Add(p.CookieExpire) | ||||||
|  | 		refresh_threshold := time.Now().Add(p.CookieRefresh) | ||||||
|  | 		if refresh_threshold.Unix() > expires.Unix() { | ||||||
|  | 			ok = p.Validator(email) && p.ValidateToken(access_token) | ||||||
|  | 			if ok { | ||||||
|  | 				p.SetCookie(rw, req, value) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *OauthProxy) RobotsTxt(rw http.ResponseWriter) { | func (p *OauthProxy) RobotsTxt(rw http.ResponseWriter) { | ||||||
|  | @ -451,18 +493,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		cookie, err := req.Cookie(p.CookieKey) | 		email, user, access_token, ok = p.ProcessCookie(rw, req) | ||||||
| 		if err == nil { |  | ||||||
| 			var value string |  | ||||||
| 			value, ok = validateCookie(cookie, p.CookieSeed) |  | ||||||
| 			if ok { |  | ||||||
| 				email, user, access_token, err = parseCookieValue( |  | ||||||
| 					value, p.AesCipher) |  | ||||||
| 				if err != nil { |  | ||||||
| 					log.Printf(err.Error()) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !ok { | 	if !ok { | ||||||
|  | @ -480,7 +511,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | ||||||
| 		req.Header["X-Forwarded-User"] = []string{user} | 		req.Header["X-Forwarded-User"] = []string{user} | ||||||
| 		req.Header["X-Forwarded-Email"] = []string{email} | 		req.Header["X-Forwarded-Email"] = []string{email} | ||||||
| 	} | 	} | ||||||
| 	if access_token != "" { | 	if p.PassAccessToken { | ||||||
| 		req.Header["X-Forwarded-Access-Token"] = []string{access_token} | 		req.Header["X-Forwarded-Access-Token"] = []string{access_token} | ||||||
| 	} | 	} | ||||||
| 	if email == "" { | 	if email == "" { | ||||||
|  |  | ||||||
|  | @ -321,3 +321,260 @@ func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) { | ||||||
| 		t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`) | 		t.Fatal(`expected redirect to "/", but was "` + match[1] + `"`) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type ValidateTokenTest struct { | ||||||
|  | 	opts          *Options | ||||||
|  | 	proxy         *OauthProxy | ||||||
|  | 	backend       *httptest.Server | ||||||
|  | 	response_code int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewValidateTokenTest() *ValidateTokenTest { | ||||||
|  | 	var vt_test ValidateTokenTest | ||||||
|  | 
 | ||||||
|  | 	vt_test.opts = NewOptions() | ||||||
|  | 	vt_test.opts.Upstreams = append(vt_test.opts.Upstreams, "unused") | ||||||
|  | 	vt_test.opts.CookieSecret = "foobar" | ||||||
|  | 	vt_test.opts.ClientID = "bazquux" | ||||||
|  | 	vt_test.opts.ClientSecret = "xyzzyplugh" | ||||||
|  | 	vt_test.opts.Validate() | ||||||
|  | 
 | ||||||
|  | 	vt_test.backend = httptest.NewServer( | ||||||
|  | 		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			switch r.URL.Path { | ||||||
|  | 			case "/oauth/tokeninfo": | ||||||
|  | 				w.WriteHeader(vt_test.response_code) | ||||||
|  | 				w.Write([]byte("only code matters; contents disregarded")) | ||||||
|  | 			default: | ||||||
|  | 				w.WriteHeader(500) | ||||||
|  | 				w.Write([]byte("unknown URL")) | ||||||
|  | 			} | ||||||
|  | 		})) | ||||||
|  | 	backend_url, _ := url.Parse(vt_test.backend.URL) | ||||||
|  | 	vt_test.opts.provider.Data().ValidateUrl = &url.URL{ | ||||||
|  | 		Scheme: "http", | ||||||
|  | 		Host:   backend_url.Host, | ||||||
|  | 		Path:   "/oauth/tokeninfo", | ||||||
|  | 	} | ||||||
|  | 	vt_test.response_code = 200 | ||||||
|  | 
 | ||||||
|  | 	vt_test.proxy = NewOauthProxy(vt_test.opts, func(email string) bool { | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 	return &vt_test | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (vt_test *ValidateTokenTest) Close() { | ||||||
|  | 	vt_test.backend.Close() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateTokenEmptyToken(t *testing.T) { | ||||||
|  | 	vt_test := NewValidateTokenTest() | ||||||
|  | 	defer vt_test.Close() | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, false, vt_test.proxy.ValidateToken("")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateTokenEmptyValidateUrl(t *testing.T) { | ||||||
|  | 	vt_test := NewValidateTokenTest() | ||||||
|  | 	defer vt_test.Close() | ||||||
|  | 
 | ||||||
|  | 	vt_test.proxy.oauthValidateUrl = nil | ||||||
|  | 	assert.Equal(t, false, vt_test.proxy.ValidateToken("foobar")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateTokenRequestNetworkFailure(t *testing.T) { | ||||||
|  | 	vt_test := NewValidateTokenTest() | ||||||
|  | 	// Close immediately to simulate a network failure
 | ||||||
|  | 	vt_test.Close() | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, false, vt_test.proxy.ValidateToken("foobar")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateTokenExpiredToken(t *testing.T) { | ||||||
|  | 	vt_test := NewValidateTokenTest() | ||||||
|  | 	defer vt_test.Close() | ||||||
|  | 
 | ||||||
|  | 	vt_test.response_code = 401 | ||||||
|  | 	assert.Equal(t, false, vt_test.proxy.ValidateToken("foobar")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateTokenValidToken(t *testing.T) { | ||||||
|  | 	vt_test := NewValidateTokenTest() | ||||||
|  | 	defer vt_test.Close() | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, true, vt_test.proxy.ValidateToken("foobar")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ProcessCookieTest struct { | ||||||
|  | 	opts          *Options | ||||||
|  | 	proxy         *OauthProxy | ||||||
|  | 	rw            *httptest.ResponseRecorder | ||||||
|  | 	req           *http.Request | ||||||
|  | 	backend       *httptest.Server | ||||||
|  | 	response_code int | ||||||
|  | 	validate_user bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewProcessCookieTest() *ProcessCookieTest { | ||||||
|  | 	var pc_test ProcessCookieTest | ||||||
|  | 
 | ||||||
|  | 	pc_test.opts = NewOptions() | ||||||
|  | 	pc_test.opts.Upstreams = append(pc_test.opts.Upstreams, "unused") | ||||||
|  | 	pc_test.opts.CookieSecret = "foobar" | ||||||
|  | 	pc_test.opts.ClientID = "bazquux" | ||||||
|  | 	pc_test.opts.ClientSecret = "xyzzyplugh" | ||||||
|  | 	pc_test.opts.CookieSecret = "0123456789abcdef" | ||||||
|  | 	// First, set the CookieRefresh option so proxy.AesCipher is created,
 | ||||||
|  | 	// needed to encrypt the access_token.
 | ||||||
|  | 	pc_test.opts.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	pc_test.opts.Validate() | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy = NewOauthProxy(pc_test.opts, func(email string) bool { | ||||||
|  | 		return pc_test.validate_user | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	// Now, zero-out proxy.CookieRefresh for the cases that don't involve
 | ||||||
|  | 	// access_token validation.
 | ||||||
|  | 	pc_test.proxy.CookieRefresh = time.Duration(0) | ||||||
|  | 	pc_test.rw = httptest.NewRecorder() | ||||||
|  | 	pc_test.req, _ = http.NewRequest("GET", "/", strings.NewReader("")) | ||||||
|  | 	pc_test.validate_user = true | ||||||
|  | 	return &pc_test | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *ProcessCookieTest) InstantiateBackend() { | ||||||
|  | 	p.backend = httptest.NewServer( | ||||||
|  | 		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 			w.WriteHeader(p.response_code) | ||||||
|  | 		})) | ||||||
|  | 	backend_url, _ := url.Parse(p.backend.URL) | ||||||
|  | 	p.proxy.oauthValidateUrl = &url.URL{ | ||||||
|  | 		Scheme: "http", | ||||||
|  | 		Host:   backend_url.Host, | ||||||
|  | 		Path:   "/oauth/tokeninfo", | ||||||
|  | 	} | ||||||
|  | 	p.response_code = 200 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *ProcessCookieTest) Close() { | ||||||
|  | 	p.backend.Close() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *ProcessCookieTest) MakeCookie(value, access_token string) *http.Cookie { | ||||||
|  | 	cookie_value, _ := buildCookieValue( | ||||||
|  | 		value, p.proxy.AesCipher, access_token) | ||||||
|  | 	return p.proxy.MakeCookie(p.req, cookie_value, p.opts.CookieExpire) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *ProcessCookieTest) AddCookie(value, access_token string) { | ||||||
|  | 	p.req.AddCookie(p.MakeCookie(value, access_token)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *ProcessCookieTest) ProcessCookie() (email, user, access_token string, ok bool) { | ||||||
|  | 	return p.proxy.ProcessCookie(p.rw, p.req) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookie(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 
 | ||||||
|  | 	pc_test.AddCookie("michael.bland@gsa.gov", "my_access_token") | ||||||
|  | 	email, user, access_token, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.Equal(t, "michael.bland@gsa.gov", email) | ||||||
|  | 	assert.Equal(t, "michael.bland", user) | ||||||
|  | 	assert.Equal(t, "my_access_token", access_token) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieNoCookieError(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieFailIfParsingCookieValueFails(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	value, _ := buildCookieValue("michael.bland@gsa.gov", | ||||||
|  | 		pc_test.proxy.AesCipher, "my_access_token") | ||||||
|  | 	pc_test.req.AddCookie(pc_test.proxy.MakeCookie( | ||||||
|  | 		pc_test.req, value+"some bogus bytes", | ||||||
|  | 		pc_test.opts.CookieExpire)) | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieRefreshNotSet(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	pc_test.InstantiateBackend() | ||||||
|  | 	defer pc_test.Close() | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "") | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieRefresh(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	pc_test.InstantiateBackend() | ||||||
|  | 	defer pc_test.Close() | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token") | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.NotEqual(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieRefreshThresholdNotCrossed(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	pc_test.InstantiateBackend() | ||||||
|  | 	defer pc_test.Close() | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieExpire = time.Duration(25) * time.Hour | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token") | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieFailIfRefreshSetAndTokenNoLongerValid(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	pc_test.InstantiateBackend() | ||||||
|  | 	defer pc_test.Close() | ||||||
|  | 	pc_test.response_code = 401 | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token") | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | 	assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieFailIfRefreshSetAndUserNoLongerValid(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	pc_test.InstantiateBackend() | ||||||
|  | 	defer pc_test.Close() | ||||||
|  | 	pc_test.validate_user = false | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token") | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, false, ok) | ||||||
|  | 	assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								options.go
								
								
								
								
							
							
						
						
									
										18
									
								
								options.go
								
								
								
								
							|  | @ -26,6 +26,7 @@ type Options struct { | ||||||
| 	CookieSecret    string        `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"` | 	CookieSecret    string        `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"` | ||||||
| 	CookieDomain    string        `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"` | 	CookieDomain    string        `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"` | ||||||
| 	CookieExpire    time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"` | 	CookieExpire    time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"` | ||||||
|  | 	CookieRefresh   time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"GOOGLE_AUTH_PROXY_COOKIE_REFRESH"` | ||||||
| 	CookieHttpsOnly bool          `flag:"cookie-https-only" cfg:"cookie_https_only"` // deprecated use cookie-secure
 | 	CookieHttpsOnly bool          `flag:"cookie-https-only" cfg:"cookie_https_only"` // deprecated use cookie-secure
 | ||||||
| 	CookieSecure    bool          `flag:"cookie-secure" cfg:"cookie_secure"` | 	CookieSecure    bool          `flag:"cookie-secure" cfg:"cookie_secure"` | ||||||
| 	CookieHttpOnly  bool          `flag:"cookie-httponly" cfg:"cookie_httponly"` | 	CookieHttpOnly  bool          `flag:"cookie-httponly" cfg:"cookie_httponly"` | ||||||
|  | @ -42,6 +43,7 @@ type Options struct { | ||||||
| 	LoginUrl    string `flag:"login-url" cfg:"login_url"` | 	LoginUrl    string `flag:"login-url" cfg:"login_url"` | ||||||
| 	RedeemUrl   string `flag:"redeem-url" cfg:"redeem_url"` | 	RedeemUrl   string `flag:"redeem-url" cfg:"redeem_url"` | ||||||
| 	ProfileUrl  string `flag:"profile-url" cfg:"profile_url"` | 	ProfileUrl  string `flag:"profile-url" cfg:"profile_url"` | ||||||
|  | 	ValidateUrl string `flag:"validate-url" cfg:"validate_url"` | ||||||
| 	Scope       string `flag:"scope" cfg:"scope"` | 	Scope       string `flag:"scope" cfg:"scope"` | ||||||
| 
 | 
 | ||||||
| 	RequestLogging bool `flag:"request-logging" cfg:"request_logging"` | 	RequestLogging bool `flag:"request-logging" cfg:"request_logging"` | ||||||
|  | @ -61,6 +63,7 @@ func NewOptions() *Options { | ||||||
| 		CookieSecure:        true, | 		CookieSecure:        true, | ||||||
| 		CookieHttpOnly:      true, | 		CookieHttpOnly:      true, | ||||||
| 		CookieExpire:        time.Duration(168) * time.Hour, | 		CookieExpire:        time.Duration(168) * time.Hour, | ||||||
|  | 		CookieRefresh:       time.Duration(0), | ||||||
| 		PassBasicAuth:       true, | 		PassBasicAuth:       true, | ||||||
| 		PassAccessToken:     false, | 		PassAccessToken:     false, | ||||||
| 		PassHostHeader:      true, | 		PassHostHeader:      true, | ||||||
|  | @ -117,7 +120,7 @@ func (o *Options) Validate() error { | ||||||
| 	} | 	} | ||||||
| 	msgs = parseProviderInfo(o, msgs) | 	msgs = parseProviderInfo(o, msgs) | ||||||
| 
 | 
 | ||||||
| 	if o.PassAccessToken { | 	if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) { | ||||||
| 		valid_cookie_secret_size := false | 		valid_cookie_secret_size := false | ||||||
| 		for _, i := range []int{16, 24, 32} { | 		for _, i := range []int{16, 24, 32} { | ||||||
| 			if len(o.CookieSecret) == i { | 			if len(o.CookieSecret) == i { | ||||||
|  | @ -128,12 +131,20 @@ func (o *Options) Validate() error { | ||||||
| 			msgs = append(msgs, fmt.Sprintf( | 			msgs = append(msgs, fmt.Sprintf( | ||||||
| 				"cookie_secret must be 16, 24, or 32 bytes "+ | 				"cookie_secret must be 16, 24, or 32 bytes "+ | ||||||
| 					"to create an AES cipher when "+ | 					"to create an AES cipher when "+ | ||||||
| 					"pass_access_token == true, "+ | 					"pass_access_token == true or "+ | ||||||
| 					"but is %d bytes", | 					"cookie_refresh != 0, but is %d bytes", | ||||||
| 				len(o.CookieSecret))) | 				len(o.CookieSecret))) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if o.CookieRefresh >= o.CookieExpire { | ||||||
|  | 		msgs = append(msgs, fmt.Sprintf( | ||||||
|  | 			"cookie_refresh (%s) must be less than "+ | ||||||
|  | 				"cookie_expire (%s)", | ||||||
|  | 			o.CookieRefresh.String(), | ||||||
|  | 			o.CookieExpire.String())) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if len(msgs) != 0 { | 	if len(msgs) != 0 { | ||||||
| 		return fmt.Errorf("Invalid configuration:\n  %s", | 		return fmt.Errorf("Invalid configuration:\n  %s", | ||||||
| 			strings.Join(msgs, "\n  ")) | 			strings.Join(msgs, "\n  ")) | ||||||
|  | @ -146,6 +157,7 @@ func parseProviderInfo(o *Options, msgs []string) []string { | ||||||
| 	p.LoginUrl, msgs = parseUrl(o.LoginUrl, "login", msgs) | 	p.LoginUrl, msgs = parseUrl(o.LoginUrl, "login", msgs) | ||||||
| 	p.RedeemUrl, msgs = parseUrl(o.RedeemUrl, "redeem", msgs) | 	p.RedeemUrl, msgs = parseUrl(o.RedeemUrl, "redeem", msgs) | ||||||
| 	p.ProfileUrl, msgs = parseUrl(o.ProfileUrl, "profile", msgs) | 	p.ProfileUrl, msgs = parseUrl(o.ProfileUrl, "profile", msgs) | ||||||
|  | 	p.ValidateUrl, msgs = parseUrl(o.ValidateUrl, "validate", msgs) | ||||||
| 	o.provider = providers.New(o.Provider, p) | 	o.provider = providers.New(o.Provider, p) | ||||||
| 	return msgs | 	return msgs | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/bmizerany/assert" | 	"github.com/bmizerany/assert" | ||||||
| ) | ) | ||||||
|  | @ -112,6 +113,10 @@ func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) { | ||||||
| 	o.CookieSecret = "cookie of invalid length-" | 	o.CookieSecret = "cookie of invalid length-" | ||||||
| 	assert.NotEqual(t, nil, o.Validate()) | 	assert.NotEqual(t, nil, o.Validate()) | ||||||
| 
 | 
 | ||||||
|  | 	o.PassAccessToken = false | ||||||
|  | 	o.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	assert.NotEqual(t, nil, o.Validate()) | ||||||
|  | 
 | ||||||
| 	o.CookieSecret = "16 bytes AES-128" | 	o.CookieSecret = "16 bytes AES-128" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, o.Validate()) | ||||||
| 
 | 
 | ||||||
|  | @ -121,3 +126,15 @@ func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) { | ||||||
| 	o.CookieSecret = "32 byte secret for AES-256------" | 	o.CookieSecret = "32 byte secret for AES-256------" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, o.Validate()) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) { | ||||||
|  | 	o := testOptions() | ||||||
|  | 	assert.Equal(t, nil, o.Validate()) | ||||||
|  | 
 | ||||||
|  | 	o.CookieSecret = "0123456789abcdef" | ||||||
|  | 	o.CookieRefresh = o.CookieExpire | ||||||
|  | 	assert.NotEqual(t, nil, o.Validate()) | ||||||
|  | 
 | ||||||
|  | 	o.CookieRefresh -= time.Duration(1) | ||||||
|  | 	assert.Equal(t, nil, o.Validate()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -24,6 +24,11 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider { | ||||||
| 			Host: "accounts.google.com", | 			Host: "accounts.google.com", | ||||||
| 			Path: "/o/oauth2/token"} | 			Path: "/o/oauth2/token"} | ||||||
| 	} | 	} | ||||||
|  | 	if p.ValidateUrl.String() == "" { | ||||||
|  | 		p.ValidateUrl = &url.URL{Scheme: "https", | ||||||
|  | 			Host: "www.googleapis.com", | ||||||
|  | 			Path: "/oauth2/v1/tokeninfo"} | ||||||
|  | 	} | ||||||
| 	if p.Scope == "" { | 	if p.Scope == "" { | ||||||
| 		p.Scope = "profile email" | 		p.Scope = "profile email" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ func newGoogleProvider() *GoogleProvider { | ||||||
| 			LoginUrl:     &url.URL{}, | 			LoginUrl:     &url.URL{}, | ||||||
| 			RedeemUrl:    &url.URL{}, | 			RedeemUrl:    &url.URL{}, | ||||||
| 			ProfileUrl:   &url.URL{}, | 			ProfileUrl:   &url.URL{}, | ||||||
|  | 			ValidateUrl:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -26,6 +27,8 @@ func TestGoogleProviderDefaults(t *testing.T) { | ||||||
| 		p.Data().LoginUrl.String()) | 		p.Data().LoginUrl.String()) | ||||||
| 	assert.Equal(t, "https://accounts.google.com/o/oauth2/token", | 	assert.Equal(t, "https://accounts.google.com/o/oauth2/token", | ||||||
| 		p.Data().RedeemUrl.String()) | 		p.Data().RedeemUrl.String()) | ||||||
|  | 	assert.Equal(t, "https://www.googleapis.com/oauth2/v1/tokeninfo", | ||||||
|  | 		p.Data().ValidateUrl.String()) | ||||||
| 	assert.Equal(t, "", p.Data().ProfileUrl.String()) | 	assert.Equal(t, "", p.Data().ProfileUrl.String()) | ||||||
| 	assert.Equal(t, "profile email", p.Data().Scope) | 	assert.Equal(t, "profile email", p.Data().Scope) | ||||||
| } | } | ||||||
|  | @ -45,6 +48,10 @@ func TestGoogleProviderOverrides(t *testing.T) { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "example.com", | 				Host:   "example.com", | ||||||
| 				Path:   "/oauth/profile"}, | 				Path:   "/oauth/profile"}, | ||||||
|  | 			ValidateUrl: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com", | ||||||
|  | 				Path:   "/oauth/tokeninfo"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "Google", p.Data().ProviderName) | 	assert.Equal(t, "Google", p.Data().ProviderName) | ||||||
|  | @ -54,6 +61,8 @@ func TestGoogleProviderOverrides(t *testing.T) { | ||||||
| 		p.Data().RedeemUrl.String()) | 		p.Data().RedeemUrl.String()) | ||||||
| 	assert.Equal(t, "https://example.com/oauth/profile", | 	assert.Equal(t, "https://example.com/oauth/profile", | ||||||
| 		p.Data().ProfileUrl.String()) | 		p.Data().ProfileUrl.String()) | ||||||
|  | 	assert.Equal(t, "https://example.com/oauth/tokeninfo", | ||||||
|  | 		p.Data().ValidateUrl.String()) | ||||||
| 	assert.Equal(t, "profile", p.Data().Scope) | 	assert.Equal(t, "profile", p.Data().Scope) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,6 +32,11 @@ func NewMyUsaProvider(p *ProviderData) *MyUsaProvider { | ||||||
| 			Host: myUsaHost, | 			Host: myUsaHost, | ||||||
| 			Path: "/api/v1/profile"} | 			Path: "/api/v1/profile"} | ||||||
| 	} | 	} | ||||||
|  | 	if p.ValidateUrl.String() == "" { | ||||||
|  | 		p.ValidateUrl = &url.URL{Scheme: "https", | ||||||
|  | 			Host: myUsaHost, | ||||||
|  | 			Path: "/api/v1/tokeninfo"} | ||||||
|  | 	} | ||||||
| 	if p.Scope == "" { | 	if p.Scope == "" { | ||||||
| 		p.Scope = "profile.email" | 		p.Scope = "profile.email" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -21,11 +21,13 @@ func testMyUsaProvider(hostname string) *MyUsaProvider { | ||||||
| 			LoginUrl:     &url.URL{}, | 			LoginUrl:     &url.URL{}, | ||||||
| 			RedeemUrl:    &url.URL{}, | 			RedeemUrl:    &url.URL{}, | ||||||
| 			ProfileUrl:   &url.URL{}, | 			ProfileUrl:   &url.URL{}, | ||||||
|  | 			ValidateUrl:  &url.URL{}, | ||||||
| 			Scope:        ""}) | 			Scope:        ""}) | ||||||
| 	if hostname != "" { | 	if hostname != "" { | ||||||
| 		updateUrl(p.Data().LoginUrl, hostname) | 		updateUrl(p.Data().LoginUrl, hostname) | ||||||
| 		updateUrl(p.Data().RedeemUrl, hostname) | 		updateUrl(p.Data().RedeemUrl, hostname) | ||||||
| 		updateUrl(p.Data().ProfileUrl, hostname) | 		updateUrl(p.Data().ProfileUrl, hostname) | ||||||
|  | 		updateUrl(p.Data().ValidateUrl, hostname) | ||||||
| 	} | 	} | ||||||
| 	return p | 	return p | ||||||
| } | } | ||||||
|  | @ -56,6 +58,8 @@ func TestMyUsaProviderDefaults(t *testing.T) { | ||||||
| 		p.Data().RedeemUrl.String()) | 		p.Data().RedeemUrl.String()) | ||||||
| 	assert.Equal(t, "https://alpha.my.usa.gov/api/v1/profile", | 	assert.Equal(t, "https://alpha.my.usa.gov/api/v1/profile", | ||||||
| 		p.Data().ProfileUrl.String()) | 		p.Data().ProfileUrl.String()) | ||||||
|  | 	assert.Equal(t, "https://alpha.my.usa.gov/api/v1/tokeninfo", | ||||||
|  | 		p.Data().ValidateUrl.String()) | ||||||
| 	assert.Equal(t, "profile.email", p.Data().Scope) | 	assert.Equal(t, "profile.email", p.Data().Scope) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -74,6 +78,10 @@ func TestMyUsaProviderOverrides(t *testing.T) { | ||||||
| 				Scheme: "https", | 				Scheme: "https", | ||||||
| 				Host:   "example.com", | 				Host:   "example.com", | ||||||
| 				Path:   "/oauth/profile"}, | 				Path:   "/oauth/profile"}, | ||||||
|  | 			ValidateUrl: &url.URL{ | ||||||
|  | 				Scheme: "https", | ||||||
|  | 				Host:   "example.com", | ||||||
|  | 				Path:   "/oauth/tokeninfo"}, | ||||||
| 			Scope: "profile"}) | 			Scope: "profile"}) | ||||||
| 	assert.NotEqual(t, nil, p) | 	assert.NotEqual(t, nil, p) | ||||||
| 	assert.Equal(t, "MyUSA", p.Data().ProviderName) | 	assert.Equal(t, "MyUSA", p.Data().ProviderName) | ||||||
|  | @ -83,6 +91,8 @@ func TestMyUsaProviderOverrides(t *testing.T) { | ||||||
| 		p.Data().RedeemUrl.String()) | 		p.Data().RedeemUrl.String()) | ||||||
| 	assert.Equal(t, "https://example.com/oauth/profile", | 	assert.Equal(t, "https://example.com/oauth/profile", | ||||||
| 		p.Data().ProfileUrl.String()) | 		p.Data().ProfileUrl.String()) | ||||||
|  | 	assert.Equal(t, "https://example.com/oauth/tokeninfo", | ||||||
|  | 		p.Data().ValidateUrl.String()) | ||||||
| 	assert.Equal(t, "profile", p.Data().Scope) | 	assert.Equal(t, "profile", p.Data().Scope) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ type ProviderData struct { | ||||||
| 	LoginUrl     *url.URL | 	LoginUrl     *url.URL | ||||||
| 	RedeemUrl    *url.URL | 	RedeemUrl    *url.URL | ||||||
| 	ProfileUrl   *url.URL | 	ProfileUrl   *url.URL | ||||||
|  | 	ValidateUrl  *url.URL | ||||||
| 	Scope        string | 	Scope        string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue