git.iko.soy/typeable/oauth2-proxy
on master help sign in
Move ValidateToken() to Provider
commit8471f972e141a44f2319e7c7f35ad10c6195c2fa authorMike Bland <michael.bland@gsa.gov> dateTue 12 May 2015, 21:48 (11 years ago) parentaca1fe8
11 files changed+285−150
api/api.go+17 api/api_test.go+59 oauthproxy.go+1−22 oauthproxy_test.go+28−125 providers/google.go+4 providers/internal_util.go+24 providers/internal_util_test.go+122 providers/linkedin.go+16−3 providers/linkedin_test.go+9 providers/myusa.go+4 providers/providers.go+1
api/api.go+17
@@ -30,3 +30,20 @@ func Request(req *http.Request) (*simplejson.Json, error) {
3030 }
3131 return data, nil
3232}
33+
34+func RequestUnparsedResponse(url string, header http.Header) (
35+ response *http.Response, err error) {
36+ req, err := http.NewRequest("GET", url, nil)
37+ if err != nil {
38+ return nil, errors.New("failed building request for " +
39+ url + ": " + err.Error())
40+ }
41+ req.Header = header
42+
43+ httpclient := &http.Client{}
44+ if response, err = httpclient.Do(req); err != nil {
45+ return nil, errors.New("request failed for " +
46+ url + ": " + err.Error())
47+ }
48+ return
49+}
api/api_test.go+59
@@ -3,6 +3,7 @@ package api
33import (
44 "github.com/bitly/go-simplejson"
55 "github.com/bmizerany/assert"
6+ "io/ioutil"
67 "net/http"
78 "net/http/httptest"
89 "strings"
@@ -66,3 +67,61 @@ func TestJsonParsingError(t *testing.T) {
6667 assert.Equal(t, (*simplejson.Json)(nil), resp)
6768 assert.NotEqual(t, nil, err)
6869}
70+
71+// Parsing a URL practically never fails, so we won't cover that test case.
72+func TestRequestUnparsedResponseUsingAccessTokenParameter(t *testing.T) {
73+ backend := httptest.NewServer(http.HandlerFunc(
74+ func(w http.ResponseWriter, r *http.Request) {
75+ token := r.FormValue("access_token")
76+ if r.URL.Path == "/" && token == "my_token" {
77+ w.WriteHeader(200)
78+ w.Write([]byte("some payload"))
79+ } else {
80+ w.WriteHeader(403)
81+ }
82+ }))
83+ defer backend.Close()
84+
85+ response, err := RequestUnparsedResponse(
86+ backend.URL+"?access_token=my_token", nil)
87+ assert.Equal(t, nil, err)
88+ assert.Equal(t, 200, response.StatusCode)
89+ body, err := ioutil.ReadAll(response.Body)
90+ assert.Equal(t, nil, err)
91+ response.Body.Close()
92+ assert.Equal(t, "some payload", string(body))
93+}
94+
95+func TestRequestUnparsedResponseUsingAccessTokenParameterFailedResponse(t *testing.T) {
96+ backend := testBackend(200, "some payload")
97+ // Close the backend now to force a request failure.
98+ backend.Close()
99+
100+ response, err := RequestUnparsedResponse(
101+ backend.URL+"?access_token=my_token", nil)
102+ assert.NotEqual(t, nil, err)
103+ assert.Equal(t, (*http.Response)(nil), response)
104+}
105+
106+func TestRequestUnparsedResponseUsingHeaders(t *testing.T) {
107+ backend := httptest.NewServer(http.HandlerFunc(
108+ func(w http.ResponseWriter, r *http.Request) {
109+ if r.URL.Path == "/" && r.Header["Auth"][0] == "my_token" {
110+ w.WriteHeader(200)
111+ w.Write([]byte("some payload"))
112+ } else {
113+ w.WriteHeader(403)
114+ }
115+ }))
116+ defer backend.Close()
117+
118+ headers := make(http.Header)
119+ headers.Set("Auth", "my_token")
120+ response, err := RequestUnparsedResponse(backend.URL, headers)
121+ assert.Equal(t, nil, err)
122+ assert.Equal(t, 200, response.StatusCode)
123+ body, err := ioutil.ReadAll(response.Body)
124+ assert.Equal(t, nil, err)
125+ response.Body.Close()
126+ assert.Equal(t, "some payload", string(body))
127+}
oauthproxy.go+1−22
@@ -265,27 +265,6 @@ func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val st
265265 http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire))
266266}
267267
268−func (p *OauthProxy) ValidateToken(access_token string) bool {
269− if access_token == "" || p.oauthValidateUrl == nil {
270− return false
271− }
272−
273− req, err := http.NewRequest("GET",
274− p.oauthValidateUrl.String()+"?access_token="+access_token, nil)
275− if err != nil {
276− log.Printf("failed building token validation request: %s", err)
277− return false
278− }
279−
280− httpclient := &http.Client{}
281− resp, err := httpclient.Do(req)
282− if err != nil {
283− log.Printf("token validation request failed: %s", err)
284− return false
285− }
286− return resp.StatusCode == 200
287−}
288−
289268func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) {
290269 var value string
291270 var timestamp time.Time
@@ -304,7 +283,7 @@ func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (e
304283 expires := timestamp.Add(p.CookieExpire)
305284 refresh_threshold := time.Now().Add(p.CookieRefresh)
306285 if refresh_threshold.Unix() > expires.Unix() {
307− ok = p.Validator(email) && p.ValidateToken(access_token)
286+ ok = p.Validator(email) && p.provider.ValidateToken(access_token)
308287 if ok {
309288 p.SetCookie(rw, req, value)
310289 }
oauthproxy_test.go+28−125
@@ -86,6 +86,7 @@ func TestRobotsTxt(t *testing.T) {
8686type TestProvider struct {
8787 *providers.ProviderData
8888 EmailAddress string
89+ ValidToken bool
8990}
9091
9192func (tp *TestProvider) GetEmailAddress(unused_auth_response *simplejson.Json,
@@ -93,6 +94,10 @@ func (tp *TestProvider) GetEmailAddress(unused_auth_response *simplejson.Json,
9394 return tp.EmailAddress, nil
9495}
9596
97+func (tp *TestProvider) ValidateToken(access_token string) bool {
98+ return tp.ValidToken
99+}
100+
96101type PassAccessTokenTest struct {
97102 provider_server *httptest.Server
98103 proxy *OauthProxy
@@ -322,101 +327,21 @@ func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
322327 }
323328}
324329
325−type ValidateTokenTest struct {
326− opts *Options
327− proxy *OauthProxy
328− backend *httptest.Server
329− response_code int
330−}
331−
332−func NewValidateTokenTest() *ValidateTokenTest {
333− var vt_test ValidateTokenTest
334−
335− vt_test.opts = NewOptions()
336− vt_test.opts.Upstreams = append(vt_test.opts.Upstreams, "unused")
337− vt_test.opts.CookieSecret = "foobar"
338− vt_test.opts.ClientID = "bazquux"
339− vt_test.opts.ClientSecret = "xyzzyplugh"
340− vt_test.opts.Validate()
341−
342− vt_test.backend = httptest.NewServer(
343− http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
344− switch r.URL.Path {
345− case "/oauth/tokeninfo":
346− w.WriteHeader(vt_test.response_code)
347− w.Write([]byte("only code matters; contents disregarded"))
348− default:
349− w.WriteHeader(500)
350− w.Write([]byte("unknown URL"))
351− }
352− }))
353− backend_url, _ := url.Parse(vt_test.backend.URL)
354− vt_test.opts.provider.Data().ValidateUrl = &url.URL{
355− Scheme: "http",
356− Host: backend_url.Host,
357− Path: "/oauth/tokeninfo",
358− }
359− vt_test.response_code = 200
360−
361− vt_test.proxy = NewOauthProxy(vt_test.opts, func(email string) bool {
362− return true
363− })
364− return &vt_test
365−}
366−
367−func (vt_test *ValidateTokenTest) Close() {
368− vt_test.backend.Close()
369−}
370−
371−func TestValidateTokenEmptyToken(t *testing.T) {
372− vt_test := NewValidateTokenTest()
373− defer vt_test.Close()
374−
375− assert.Equal(t, false, vt_test.proxy.ValidateToken(""))
376−}
377−
378−func TestValidateTokenEmptyValidateUrl(t *testing.T) {
379− vt_test := NewValidateTokenTest()
380− defer vt_test.Close()
381−
382− vt_test.proxy.oauthValidateUrl = nil
383− assert.Equal(t, false, vt_test.proxy.ValidateToken("foobar"))
384−}
385−
386−func TestValidateTokenRequestNetworkFailure(t *testing.T) {
387− vt_test := NewValidateTokenTest()
388− // Close immediately to simulate a network failure
389− vt_test.Close()
390−
391− assert.Equal(t, false, vt_test.proxy.ValidateToken("foobar"))
392−}
393−
394−func TestValidateTokenExpiredToken(t *testing.T) {
395− vt_test := NewValidateTokenTest()
396− defer vt_test.Close()
397−
398− vt_test.response_code = 401
399− assert.Equal(t, false, vt_test.proxy.ValidateToken("foobar"))
400−}
401−
402−func TestValidateTokenValidToken(t *testing.T) {
403− vt_test := NewValidateTokenTest()
404− defer vt_test.Close()
405−
406− assert.Equal(t, true, vt_test.proxy.ValidateToken("foobar"))
407−}
408−
409330type ProcessCookieTest struct {
410331 opts *Options
411332 proxy *OauthProxy
412333 rw *httptest.ResponseRecorder
413334 req *http.Request
414− backend *httptest.Server
335+ provider TestProvider
415336 response_code int
416337 validate_user bool
417338}
418339
419−func NewProcessCookieTest() *ProcessCookieTest {
340+type ProcessCookieTestOpts struct {
341+ provider_validate_cookie_response bool
342+}
343+
344+func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
420345 var pc_test ProcessCookieTest
421346
422347 pc_test.opts = NewOptions()
@@ -433,6 +358,9 @@ func NewProcessCookieTest() *ProcessCookieTest {
433358 pc_test.proxy = NewOauthProxy(pc_test.opts, func(email string) bool {
434359 return pc_test.validate_user
435360 })
361+ pc_test.proxy.provider = &TestProvider{
362+ ValidToken: opts.provider_validate_cookie_response,
363+ }
436364
437365 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
438366 // access_token validation.
@@ -443,22 +371,10 @@ func NewProcessCookieTest() *ProcessCookieTest {
443371 return &pc_test
444372}
445373
446−func (p *ProcessCookieTest) InstantiateBackend() {
447− p.backend = httptest.NewServer(
448− http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
449− w.WriteHeader(p.response_code)
450− }))
451− backend_url, _ := url.Parse(p.backend.URL)
452− p.proxy.oauthValidateUrl = &url.URL{
453− Scheme: "http",
454− Host: backend_url.Host,
455− Path: "/oauth/tokeninfo",
456− }
457− p.response_code = 200
458−}
459−
460−func (p *ProcessCookieTest) Close() {
461− p.backend.Close()
374+func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
375+ return NewProcessCookieTest(ProcessCookieTestOpts{
376+ provider_validate_cookie_response: true,
377+ })
462378}
463379
464380func (p *ProcessCookieTest) MakeCookie(value, access_token string) *http.Cookie {
@@ -476,7 +392,7 @@ func (p *ProcessCookieTest) ProcessCookie() (email, user, access_token string, o
476392}
477393
478394func TestProcessCookie(t *testing.T) {
479− pc_test := NewProcessCookieTest()
395+ pc_test := NewProcessCookieTestWithDefaults()
480396
481397 pc_test.AddCookie("michael.bland@gsa.gov", "my_access_token")
482398 email, user, access_token, ok := pc_test.ProcessCookie()
@@ -487,13 +403,13 @@ func TestProcessCookie(t *testing.T) {
487403}
488404
489405func TestProcessCookieNoCookieError(t *testing.T) {
490− pc_test := NewProcessCookieTest()
406+ pc_test := NewProcessCookieTestWithDefaults()
491407 _, _, _, ok := pc_test.ProcessCookie()
492408 assert.Equal(t, false, ok)
493409}
494410
495411func TestProcessCookieFailIfParsingCookieValueFails(t *testing.T) {
496− pc_test := NewProcessCookieTest()
412+ pc_test := NewProcessCookieTestWithDefaults()
497413 value, _ := buildCookieValue("michael.bland@gsa.gov",
498414 pc_test.proxy.AesCipher, "my_access_token")
499415 pc_test.req.AddCookie(pc_test.proxy.MakeCookie(
@@ -504,10 +420,7 @@ func TestProcessCookieFailIfParsingCookieValueFails(t *testing.T) {
504420}
505421
506422func TestProcessCookieRefreshNotSet(t *testing.T) {
507− pc_test := NewProcessCookieTest()
508− pc_test.InstantiateBackend()
509− defer pc_test.Close()
510−
423+ pc_test := NewProcessCookieTestWithDefaults()
511424 pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
512425 cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "")
513426 pc_test.req.AddCookie(cookie)
@@ -518,10 +431,7 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
518431}
519432
520433func TestProcessCookieRefresh(t *testing.T) {
521− pc_test := NewProcessCookieTest()
522− pc_test.InstantiateBackend()
523− defer pc_test.Close()
524−
434+ pc_test := NewProcessCookieTestWithDefaults()
525435 pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
526436 cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
527437 pc_test.req.AddCookie(cookie)
@@ -533,10 +443,7 @@ func TestProcessCookieRefresh(t *testing.T) {
533443}
534444
535445func TestProcessCookieRefreshThresholdNotCrossed(t *testing.T) {
536− pc_test := NewProcessCookieTest()
537− pc_test.InstantiateBackend()
538− defer pc_test.Close()
539−
446+ pc_test := NewProcessCookieTestWithDefaults()
540447 pc_test.proxy.CookieExpire = time.Duration(25) * time.Hour
541448 cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
542449 pc_test.req.AddCookie(cookie)
@@ -548,11 +455,9 @@ func TestProcessCookieRefreshThresholdNotCrossed(t *testing.T) {
548455}
549456
550457func TestProcessCookieFailIfRefreshSetAndTokenNoLongerValid(t *testing.T) {
551− pc_test := NewProcessCookieTest()
552− pc_test.InstantiateBackend()
553− defer pc_test.Close()
554− pc_test.response_code = 401
555−
458+ pc_test := NewProcessCookieTest(ProcessCookieTestOpts{
459+ provider_validate_cookie_response: false,
460+ })
556461 pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
557462 cookie := pc_test.MakeCookie("michael.bland@gsa.gov", "my_access_token")
558463 pc_test.req.AddCookie(cookie)
@@ -564,9 +469,7 @@ func TestProcessCookieFailIfRefreshSetAndTokenNoLongerValid(t *testing.T) {
564469}
565470
566471func TestProcessCookieFailIfRefreshSetAndUserNoLongerValid(t *testing.T) {
567− pc_test := NewProcessCookieTest()
568− pc_test.InstantiateBackend()
569− defer pc_test.Close()
472+ pc_test := NewProcessCookieTestWithDefaults()
570473 pc_test.validate_user = false
571474
572475 pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
providers/google.go+4
@@ -66,3 +66,7 @@ func jwtDecodeSegment(seg string) ([]byte, error) {
6666
6767 return base64.URLEncoding.DecodeString(seg)
6868}
69+
70+func (p *GoogleProvider) ValidateToken(access_token string) bool {
71+ return validateToken(p, access_token, nil)
72+}
providers/internal_util.gonew file+24
@@ -0,0 +1,24 @@
1+package providers
2+
3+import (
4+ "github.com/bitly/google_auth_proxy/api"
5+ "log"
6+ "net/http"
7+)
8+
9+func validateToken(p Provider, access_token string,
10+ header http.Header) bool {
11+ if access_token == "" || p.Data().ValidateUrl == nil {
12+ return false
13+ }
14+ url := p.Data().ValidateUrl.String()
15+ if len(header) == 0 {
16+ url = url + "?access_token=" + access_token
17+ }
18+ if resp, err := api.RequestUnparsedResponse(url, header); err != nil {
19+ log.Printf("token validation request failed: %s", err)
20+ return false
21+ } else {
22+ return resp.StatusCode == 200
23+ }
24+}
providers/internal_util_test.gonew file+122
@@ -0,0 +1,122 @@
1+package providers
2+
3+import (
4+ "github.com/bitly/go-simplejson"
5+ "github.com/bmizerany/assert"
6+ "net/http"
7+ "net/http/httptest"
8+ "net/url"
9+ "testing"
10+)
11+
12+type ValidateTokenTestProvider struct {
13+ *ProviderData
14+}
15+
16+func (tp *ValidateTokenTestProvider) GetEmailAddress(
17+ unused_auth_response *simplejson.Json,
18+ unused_access_token string) (string, error) {
19+ return "", nil
20+}
21+
22+// Note that we're testing the internal validateToken() used to implement
23+// several Provider's ValidateToken() implementations
24+func (tp *ValidateTokenTestProvider) ValidateToken(access_token string) bool {
25+ return false
26+}
27+
28+type ValidateTokenTest struct {
29+ backend *httptest.Server
30+ response_code int
31+ provider *ValidateTokenTestProvider
32+ header http.Header
33+}
34+
35+func NewValidateTokenTest() *ValidateTokenTest {
36+ var vt_test ValidateTokenTest
37+
38+ vt_test.backend = httptest.NewServer(
39+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40+ if r.URL.Path != "/oauth/tokeninfo" {
41+ w.WriteHeader(500)
42+ w.Write([]byte("unknown URL"))
43+ }
44+ token_param := r.FormValue("access_token")
45+ if token_param == "" {
46+ missing := false
47+ received_headers := r.Header
48+ for k, _ := range vt_test.header {
49+ received := received_headers.Get(k)
50+ expected := vt_test.header.Get(k)
51+ if received == "" || received != expected {
52+ missing = true
53+ }
54+ }
55+ if missing {
56+ w.WriteHeader(500)
57+ w.Write([]byte("no token param and missing or incorrect headers"))
58+ }
59+ }
60+ w.WriteHeader(vt_test.response_code)
61+ w.Write([]byte("only code matters; contents disregarded"))
62+
63+ }))
64+ backend_url, _ := url.Parse(vt_test.backend.URL)
65+ vt_test.provider = &ValidateTokenTestProvider{
66+ ProviderData: &ProviderData{
67+ ValidateUrl: &url.URL{
68+ Scheme: "http",
69+ Host: backend_url.Host,
70+ Path: "/oauth/tokeninfo",
71+ },
72+ },
73+ }
74+ vt_test.response_code = 200
75+ return &vt_test
76+}
77+
78+func (vt_test *ValidateTokenTest) Close() {
79+ vt_test.backend.Close()
80+}
81+
82+func TestValidateTokenValidToken(t *testing.T) {
83+ vt_test := NewValidateTokenTest()
84+ defer vt_test.Close()
85+ assert.Equal(t, true, validateToken(vt_test.provider, "foobar", nil))
86+}
87+
88+func TestValidateTokenValidTokenWithHeaders(t *testing.T) {
89+ vt_test := NewValidateTokenTest()
90+ defer vt_test.Close()
91+ vt_test.header = make(http.Header)
92+ vt_test.header.Set("Authorization", "Bearer foobar")
93+ assert.Equal(t, true,
94+ validateToken(vt_test.provider, "foobar", vt_test.header))
95+}
96+
97+func TestValidateTokenEmptyToken(t *testing.T) {
98+ vt_test := NewValidateTokenTest()
99+ defer vt_test.Close()
100+ assert.Equal(t, false, validateToken(vt_test.provider, "", nil))
101+}
102+
103+func TestValidateTokenEmptyValidateUrl(t *testing.T) {
104+ vt_test := NewValidateTokenTest()
105+ defer vt_test.Close()
106+ vt_test.provider.Data().ValidateUrl = nil
107+ assert.Equal(t, false, validateToken(vt_test.provider, "foobar", nil))
108+}
109+
110+func TestValidateTokenRequestNetworkFailure(t *testing.T) {
111+ vt_test := NewValidateTokenTest()
112+ // Close immediately to simulate a network failure
113+ vt_test.Close()
114+ assert.Equal(t, false, validateToken(vt_test.provider, "foobar", nil))
115+}
116+
117+func TestValidateTokenExpiredToken(t *testing.T) {
118+ vt_test := NewValidateTokenTest()
119+ defer vt_test.Close()
120+ vt_test.response_code = 401
121+ assert.Equal(t, false, validateToken(vt_test.provider, "foobar", nil))
122+}
providers/linkedin.go+16−3
@@ -33,12 +33,23 @@ func NewLinkedInProvider(p *ProviderData) *LinkedInProvider {
3333 Host: "www.linkedin.com",
3434 Path: "/v1/people/~/email-address"}
3535 }
36+ if p.ValidateUrl.String() == "" {
37+ p.ValidateUrl = p.ProfileUrl
38+ }
3639 if p.Scope == "" {
3740 p.Scope = "r_emailaddress r_basicprofile"
3841 }
3942 return &LinkedInProvider{ProviderData: p}
4043}
4144
45+func getLinkedInHeader(access_token string) http.Header {
46+ header := make(http.Header)
47+ header.Set("Accept", "application/json")
48+ header.Set("x-li-format", "json")
49+ header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token))
50+ return header
51+}
52+
4253func (p *LinkedInProvider) GetEmailAddress(unused_auth_response *simplejson.Json,
4354 access_token string) (string, error) {
4455 if access_token == "" {
@@ -49,9 +60,7 @@ func (p *LinkedInProvider) GetEmailAddress(unused_auth_response *simplejson.Json
4960 if err != nil {
5061 return "", err
5162 }
52− req.Header.Set("Accept", "application/json")
53− req.Header.Set("x-li-format", "json")
54− req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token))
63+ req.Header = getLinkedInHeader(access_token)
5564
5665 json, err := api.Request(req)
5766 if err != nil {
@@ -66,3 +75,7 @@ func (p *LinkedInProvider) GetEmailAddress(unused_auth_response *simplejson.Json
6675 }
6776 return email, nil
6877}
78+
79+func (p *LinkedInProvider) ValidateToken(access_token string) bool {
80+ return validateToken(p, access_token, getLinkedInHeader(access_token))
81+}
providers/linkedin_test.go+9
@@ -16,6 +16,7 @@ func testLinkedInProvider(hostname string) *LinkedInProvider {
1616 LoginUrl: &url.URL{},
1717 RedeemUrl: &url.URL{},
1818 ProfileUrl: &url.URL{},
19+ ValidateUrl: &url.URL{},
1920 Scope: ""})
2021 if hostname != "" {
2122 updateUrl(p.Data().LoginUrl, hostname)
@@ -52,6 +53,8 @@ func TestLinkedInProviderDefaults(t *testing.T) {
5253 p.Data().RedeemUrl.String())
5354 assert.Equal(t, "https://www.linkedin.com/v1/people/~/email-address",
5455 p.Data().ProfileUrl.String())
56+ assert.Equal(t, "https://www.linkedin.com/v1/people/~/email-address",
57+ p.Data().ValidateUrl.String())
5558 assert.Equal(t, "r_emailaddress r_basicprofile", p.Data().Scope)
5659}
5760
@@ -70,6 +73,10 @@ func TestLinkedInProviderOverrides(t *testing.T) {
7073 Scheme: "https",
7174 Host: "example.com",
7275 Path: "/oauth/profile"},
76+ ValidateUrl: &url.URL{
77+ Scheme: "https",
78+ Host: "example.com",
79+ Path: "/oauth/tokeninfo"},
7380 Scope: "profile"})
7481 assert.NotEqual(t, nil, p)
7582 assert.Equal(t, "LinkedIn", p.Data().ProviderName)
@@ -79,6 +86,8 @@ func TestLinkedInProviderOverrides(t *testing.T) {
7986 p.Data().RedeemUrl.String())
8087 assert.Equal(t, "https://example.com/oauth/profile",
8188 p.Data().ProfileUrl.String())
89+ assert.Equal(t, "https://example.com/oauth/tokeninfo",
90+ p.Data().ValidateUrl.String())
8291 assert.Equal(t, "profile", p.Data().Scope)
8392}
8493
providers/myusa.go+4
@@ -58,3 +58,7 @@ func (p *MyUsaProvider) GetEmailAddress(auth_response *simplejson.Json,
5858 }
5959 return json.Get("email").String()
6060}
61+
62+func (p *MyUsaProvider) ValidateToken(access_token string) bool {
63+ return validateToken(p, access_token, nil)
64+}
providers/providers.go+1
@@ -8,6 +8,7 @@ type Provider interface {
88 Data() *ProviderData
99 GetEmailAddress(auth_response *simplejson.Json,
1010 access_token string) (string, error)
11+ ValidateToken(access_token string) bool
1112}
1213
1314func New(provider string, p *ProviderData) Provider {