git.iko.soy/typeable/oauth2-proxy
on master help sign in
Github provider
commit37b38dd2f44023d25687b8cf9ec139f6d85ce012 authorJehiah Czebotar <jehiah@gmail.com> dateWed 20 May 2015, 23:23 (11 years ago) parent8471f97
17 files changed+304−112
README.md+6 api/api.go+1−2 main.go+3 oauthproxy.go+17−37 oauthproxy_test.go+11−9 options.go+8−1 providers/github.go+136 providers/google.go+18−12 providers/google_test.go+38−16 providers/internal_util_test.go+1−4 providers/linkedin.go+1−3 providers/linkedin_test.go+3−9 providers/myusa.go+1−3 providers/myusa_test.go+3−10 providers/provider_data.go+2 providers/provider_default.go+51 providers/providers.go+4−6
README.md+6
@@ -101,6 +101,8 @@ Usage of google_auth_proxy:
101101 -version=false: print version string
102102```
103103
104+See below for provider specific options
105+
104106### Environment variables
105107
106108The environment variables `GOOGLE_AUTH_PROXY_CLIENT_ID`, `GOOGLE_AUTH_PROXY_CLIENT_SECRET`, `GOOGLE_AUTH_PROXY_COOKIE_SECRET`, `GOOGLE_AUTH_PROXY_COOKIE_DOMAIN` and `GOOGLE_AUTH_PROXY_COOKIE_EXPIRE` can be used in place of the corresponding command-line arguments.
@@ -173,6 +175,10 @@ directive. Right now this includes:
173175* `myusa` - The [MyUSA](https://alpha.my.usa.gov) authentication service
174176 ([GitHub](https://github.com/18F/myusa))
175177* `linkedin` - The [LinkedIn](https://developer.linkedin.com/docs/signin-with-linkedin) Sign In service.
178+* `github` - Via [Github][https://github.com/settings/developers] OAuth App. Also supports restricting via org and team.
179+
180+ -github-org="": restrict logins to members of this organisation
181+ -github-team="": restrict logins to members of this team
176182
177183## Adding a new Provider
178184
api/api.go+1−2
@@ -10,8 +10,7 @@ import (
1010)
1111
1212func Request(req *http.Request) (*simplejson.Json, error) {
13− httpclient := &http.Client{}
14− resp, err := httpclient.Do(req)
13+ resp, err := http.DefaultClient.Do(req)
1514 if err != nil {
1615 return nil, err
1716 }
main.go+3
@@ -17,6 +17,7 @@ import (
1717)
1818
1919func main() {
20+ log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
2021 flagSet := flag.NewFlagSet("google_auth_proxy", flag.ExitOnError)
2122
2223 googleAppsDomains := StringArray{}
@@ -35,6 +36,8 @@ func main() {
3536 flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
3637
3738 flagSet.Var(&googleAppsDomains, "google-apps-domain", "authenticate against the given Google apps domain (may be given multiple times)")
39+ flagSet.String("github-org", "", "restrict logins to members of this organisation")
40+ flagSet.String("github-team", "", "restrict logins to members of this team")
3841 flagSet.String("client-id", "", "the Google OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
3942 flagSet.String("client-secret", "", "the OAuth Client Secret")
4043 flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
oauthproxy.go+17−37
@@ -1,7 +1,6 @@
11package main
22
33import (
4− "bytes"
54 "crypto/aes"
65 "crypto/cipher"
76 "encoding/base64"
@@ -17,7 +16,6 @@ import (
1716 "strings"
1817 "time"
1918
20− "github.com/bitly/google_auth_proxy/api"
2119 "github.com/bitly/google_auth_proxy/providers"
2220)
2321
@@ -39,7 +37,6 @@ type OauthProxy struct {
3937
4038 redirectUrl *url.URL // the url to receive requests at
4139 provider providers.Provider
42− oauthRedemptionUrl *url.URL // endpoint to redeem the code
4340 oauthLoginUrl *url.URL // to redirect the user to
4441 oauthValidateUrl *url.URL // to validate the access token
4542 oauthScope string
@@ -143,21 +140,20 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
143140 CookieRefresh: opts.CookieRefresh,
144141 Validator: validator,
145142
146− clientID: opts.ClientID,
147− clientSecret: opts.ClientSecret,
148− oauthScope: opts.provider.Data().Scope,
149− provider: opts.provider,
150− oauthRedemptionUrl: opts.provider.Data().RedeemUrl,
151− oauthLoginUrl: opts.provider.Data().LoginUrl,
152− oauthValidateUrl: opts.provider.Data().ValidateUrl,
153− serveMux: serveMux,
154− redirectUrl: redirectUrl,
155− skipAuthRegex: opts.SkipAuthRegex,
156− compiledRegex: opts.CompiledRegex,
157− PassBasicAuth: opts.PassBasicAuth,
158− PassAccessToken: opts.PassAccessToken,
159− AesCipher: aes_cipher,
160− templates: loadTemplates(opts.CustomTemplatesDir),
143+ clientID: opts.ClientID,
144+ clientSecret: opts.ClientSecret,
145+ oauthScope: opts.provider.Data().Scope,
146+ provider: opts.provider,
147+ oauthLoginUrl: opts.provider.Data().LoginUrl,
148+ oauthValidateUrl: opts.provider.Data().ValidateUrl,
149+ serveMux: serveMux,
150+ redirectUrl: redirectUrl,
151+ skipAuthRegex: opts.SkipAuthRegex,
152+ compiledRegex: opts.CompiledRegex,
153+ PassBasicAuth: opts.PassBasicAuth,
154+ PassAccessToken: opts.PassAccessToken,
155+ AesCipher: aes_cipher,
156+ templates: loadTemplates(opts.CustomTemplatesDir),
161157 }
162158}
163159
@@ -200,29 +196,13 @@ func (p *OauthProxy) redeemCode(host, code string) (string, string, error) {
200196 if code == "" {
201197 return "", "", errors.New("missing code")
202198 }
203− params := url.Values{}
204− params.Add("redirect_uri", p.GetRedirectUrl(host))
205− params.Add("client_id", p.clientID)
206− params.Add("client_secret", p.clientSecret)
207− params.Add("code", code)
208− params.Add("grant_type", "authorization_code")
209− req, err := http.NewRequest("POST", p.oauthRedemptionUrl.String(), bytes.NewBufferString(params.Encode()))
210− if err != nil {
211− log.Printf("failed building request %s", err.Error())
212− return "", "", err
213− }
214− req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
215− json, err := api.Request(req)
216− if err != nil {
217− log.Printf("failed making request %s", err)
218− return "", "", err
219− }
220− access_token, err := json.Get("access_token").String()
199+ redirectUri := p.GetRedirectUrl(host)
200+ body, access_token, err := p.provider.Redeem(redirectUri, code)
221201 if err != nil {
222202 return "", "", err
223203 }
224204
225− email, err := p.provider.GetEmailAddress(json, access_token)
205+ email, err := p.provider.GetEmailAddress(body, access_token)
226206 if err != nil {
227207 return "", "", err
228208 }
oauthproxy_test.go+11−9
@@ -1,10 +1,10 @@
11package main
22
33import (
4− "github.com/bitly/go-simplejson"
54 "github.com/bitly/google_auth_proxy/providers"
65 "github.com/bmizerany/assert"
76 "io/ioutil"
7+ "log"
88 "net"
99 "net/http"
1010 "net/http/httptest"
@@ -15,6 +15,11 @@ import (
1515 "time"
1616)
1717
18+func init() {
19+ log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
20+
21+}
22+
1823func TestNewReverseProxy(t *testing.T) {
1924 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2025 w.WriteHeader(200)
@@ -89,8 +94,7 @@ type TestProvider struct {
8994 ValidToken bool
9095}
9196
92−func (tp *TestProvider) GetEmailAddress(unused_auth_response *simplejson.Json,
93− unused_access_token string) (string, error) {
97+func (tp *TestProvider) GetEmailAddress(body []byte, access_token string) (string, error) {
9498 return tp.EmailAddress, nil
9599}
96100
@@ -113,16 +117,15 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
113117
114118 t.provider_server = httptest.NewServer(
115119 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
120+ log.Printf("%#v", r)
116121 url := r.URL
117122 payload := ""
118123 switch url.Path {
119124 case "/oauth/token":
120125 payload = `{"access_token": "my_auth_token"}`
121126 default:
122− token_header := r.Header["X-Forwarded-Access-Token"]
123− if len(token_header) != 0 {
124− payload = token_header[0]
125− } else {
127+ payload = r.Header.Get("X-Forwarded-Access-Token")
128+ if payload == "" {
126129 payload = "No access token found."
127130 }
128131 }
@@ -189,8 +192,7 @@ func (pat_test *PassAccessTokenTest) getCallbackEndpoint() (http_code int,
189192 return rw.Code, rw.HeaderMap["Set-Cookie"][0]
190193}
191194
192−func (pat_test *PassAccessTokenTest) getRootEndpoint(
193− cookie string) (http_code int, access_token string) {
195+func (pat_test *PassAccessTokenTest) getRootEndpoint(cookie string) (http_code int, access_token string) {
194196 cookie_key := pat_test.proxy.CookieKey
195197 var value string
196198 key_prefix := cookie_key + "="
options.go+8−1
@@ -19,6 +19,8 @@ type Options struct {
1919
2020 AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
2121 GoogleAppsDomains []string `flag:"google-apps-domain" cfg:"google_apps_domains"`
22+ GitHubOrg string `flag:"github-org" cfg:"github_org"`
23+ GitHubTeam string `flag:"github-team" cfg:"github_team"`
2224 HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
2325 DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
2426 CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir"`
@@ -153,11 +155,16 @@ func (o *Options) Validate() error {
153155}
154156
155157func parseProviderInfo(o *Options, msgs []string) []string {
156− p := &providers.ProviderData{Scope: o.Scope}
158+ p := &providers.ProviderData{Scope: o.Scope, ClientID: o.ClientID, ClientSecret: o.ClientSecret}
157159 p.LoginUrl, msgs = parseUrl(o.LoginUrl, "login", msgs)
158160 p.RedeemUrl, msgs = parseUrl(o.RedeemUrl, "redeem", msgs)
159161 p.ProfileUrl, msgs = parseUrl(o.ProfileUrl, "profile", msgs)
160162 p.ValidateUrl, msgs = parseUrl(o.ValidateUrl, "validate", msgs)
163+
161164 o.provider = providers.New(o.Provider, p)
165+ switch p := o.provider.(type) {
166+ case *providers.GitHubProvider:
167+ p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
168+ }
162169 return msgs
163170}
providers/github.gonew file+136
@@ -0,0 +1,136 @@
1+package providers
2+
3+import (
4+ "encoding/json"
5+ "io/ioutil"
6+ "net/http"
7+ "net/url"
8+)
9+
10+type GitHubProvider struct {
11+ *ProviderData
12+ Org string
13+ Team string
14+}
15+
16+func NewGitHubProvider(p *ProviderData) *GitHubProvider {
17+ p.ProviderName = "GitHub"
18+ if p.LoginUrl.String() == "" {
19+ p.LoginUrl = &url.URL{
20+ Scheme: "https",
21+ Host: "github.com",
22+ Path: "/login/oauth/authorize",
23+ }
24+ }
25+ if p.RedeemUrl.String() == "" {
26+ p.RedeemUrl = &url.URL{
27+ Scheme: "https",
28+ Host: "github.com",
29+ Path: "/login/oauth/access_token",
30+ }
31+ }
32+ if p.ValidateUrl.String() == "" {
33+ p.ValidateUrl = &url.URL{
34+ Scheme: "https",
35+ Host: "api.github.com",
36+ Path: "/user/emails",
37+ }
38+ }
39+ if p.Scope == "" {
40+ p.Scope = "user:email"
41+ }
42+ return &GitHubProvider{ProviderData: p}
43+}
44+func (p *GitHubProvider) SetOrgTeam(org, team string) {
45+ p.Org = org
46+ p.Team = team
47+ if org != "" || team != "" {
48+ p.Scope += " read:org"
49+ }
50+}
51+
52+func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
53+
54+ var teams []struct {
55+ Name string `json:"name"`
56+ Slug string `json:"slug"`
57+ Org struct {
58+ Login string `json:"login"`
59+ } `json:"organization"`
60+ }
61+
62+ params := url.Values{
63+ "access_token": {accessToken},
64+ }
65+
66+ req, _ := http.NewRequest("GET", "https://api.github.com/user/teams?"+params.Encode(), nil)
67+ req.Header.Set("Accept", "application/vnd.github.moondragon+json")
68+ resp, err := http.DefaultClient.Do(req)
69+ if err != nil {
70+ return false, err
71+ }
72+
73+ body, err := ioutil.ReadAll(resp.Body)
74+ resp.Body.Close()
75+ if err != nil {
76+ return false, err
77+ }
78+
79+ if err := json.Unmarshal(body, &teams); err != nil {
80+ return false, err
81+ }
82+
83+ for _, team := range teams {
84+ if p.Org == team.Org.Login {
85+ if p.Team == "" || p.Team == team.Slug {
86+ return true, nil
87+ }
88+ }
89+ }
90+ return false, nil
91+}
92+
93+func (p *GitHubProvider) GetEmailAddress(body []byte, access_token string) (string, error) {
94+
95+ var emails []struct {
96+ Email string `json:"email"`
97+ Primary bool `json:"primary"`
98+ }
99+
100+ params := url.Values{
101+ "access_token": {access_token},
102+ }
103+
104+ // if we require an Org or Team, check that first
105+ if p.Org != "" || p.Team != "" {
106+ if ok, err := p.hasOrgAndTeam(access_token); err != nil || !ok {
107+ return "", err
108+ }
109+ }
110+
111+ resp, err := http.DefaultClient.Get("https://api.github.com/user/emails?" + params.Encode())
112+ if err != nil {
113+ return "", err
114+ }
115+ body, err = ioutil.ReadAll(resp.Body)
116+ resp.Body.Close()
117+ if err != nil {
118+ return "", err
119+ }
120+
121+ if err := json.Unmarshal(body, &emails); err != nil {
122+ return "", err
123+ }
124+
125+ for _, email := range emails {
126+ if email.Primary {
127+ return email.Email, nil
128+ }
129+ }
130+
131+ return "", nil
132+}
133+
134+func (p *GitHubProvider) ValidateToken(access_token string) bool {
135+ return validateToken(p, access_token, nil)
136+}
providers/google.go+18−12
@@ -2,10 +2,10 @@ package providers
22
33import (
44 "encoding/base64"
5+ "encoding/json"
6+ "errors"
57 "net/url"
68 "strings"
7−
8− "github.com/bitly/go-simplejson"
99)
1010
1111type GoogleProvider struct {
@@ -35,28 +35,34 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider {
3535 return &GoogleProvider{ProviderData: p}
3636}
3737
38−func (s *GoogleProvider) GetEmailAddress(auth_response *simplejson.Json,
39− unused_access_token string) (string, error) {
40− idToken, err := auth_response.Get("id_token").String()
41− if err != nil {
38+func (s *GoogleProvider) GetEmailAddress(body []byte, access_token string) (string, error) {
39+ var response struct {
40+ IdToken string `json:"id_token"`
41+ }
42+
43+ if err := json.Unmarshal(body, &response); err != nil {
4244 return "", err
4345 }
46+
4447 // id_token is a base64 encode ID token payload
4548 // https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo
46− jwt := strings.Split(idToken, ".")
49+ jwt := strings.Split(response.IdToken, ".")
4750 b, err := jwtDecodeSegment(jwt[1])
4851 if err != nil {
4952 return "", err
5053 }
51− data, err := simplejson.NewJson(b)
52− if err != nil {
53− return "", err
54+
55+ var email struct {
56+ Email string `json:"email"`
5457 }
55− email, err := data.Get("email").String()
58+ err = json.Unmarshal(b, &email)
5659 if err != nil {
5760 return "", err
5861 }
59− return email, nil
62+ if email.Email == "" {
63+ return "", errors.New("missing email")
64+ }
65+ return email.Email, nil
6066}
6167
6268func jwtDecodeSegment(seg string) ([]byte, error) {
providers/google_test.go+38−16
@@ -2,7 +2,7 @@ package providers
22
33import (
44 "encoding/base64"
5− "github.com/bitly/go-simplejson"
5+ "encoding/json"
66 "github.com/bmizerany/assert"
77 "net/url"
88 "testing"
@@ -68,39 +68,61 @@ func TestGoogleProviderOverrides(t *testing.T) {
6868
6969func TestGoogleProviderGetEmailAddress(t *testing.T) {
7070 p := newGoogleProvider()
71− j := simplejson.New()
72− j.Set("id_token", "ignored prefix."+base64.URLEncoding.EncodeToString(
73− []byte("{\"email\": \"michael.bland@gsa.gov\"}")))
74− email, err := p.GetEmailAddress(j, "ignored access_token")
71+ body, err := json.Marshal(
72+ struct {
73+ IdToken string `json:"id_token"`
74+ }{
75+ IdToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"email": "michael.bland@gsa.gov"}`)),
76+ },
77+ )
78+ assert.Equal(t, nil, err)
79+ email, err := p.GetEmailAddress(body, "ignored access_token")
7580 assert.Equal(t, "michael.bland@gsa.gov", email)
7681 assert.Equal(t, nil, err)
7782}
7883
7984func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) {
8085 p := newGoogleProvider()
81− j := simplejson.New()
82− j.Set("id_token", "ignored prefix.{\"email\": \"michael.bland@gsa.gov\"}")
83− email, err := p.GetEmailAddress(j, "ignored access_token")
86+ body, err := json.Marshal(
87+ struct {
88+ IdToken string `json:"id_token"`
89+ }{
90+ IdToken: "ignored prefix." + `{"email": "michael.bland@gsa.gov"}`,
91+ },
92+ )
93+ assert.Equal(t, nil, err)
94+ email, err := p.GetEmailAddress(body, "ignored access_token")
8495 assert.Equal(t, "", email)
8596 assert.NotEqual(t, nil, err)
8697}
8798
8899func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) {
89100 p := newGoogleProvider()
90− j := simplejson.New()
91− j.Set("id_token", "ignored prefix."+base64.URLEncoding.EncodeToString(
92− []byte("{email: michael.bland@gsa.gov}")))
93− email, err := p.GetEmailAddress(j, "ignored access_token")
101+
102+ body, err := json.Marshal(
103+ struct {
104+ IdToken string `json:"id_token"`
105+ }{
106+ IdToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"email": michael.bland@gsa.gov}`)),
107+ },
108+ )
109+ assert.Equal(t, nil, err)
110+ email, err := p.GetEmailAddress(body, "ignored access_token")
94111 assert.Equal(t, "", email)
95112 assert.NotEqual(t, nil, err)
96113}
97114
98115func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) {
99116 p := newGoogleProvider()
100− j := simplejson.New()
101− j.Set("id_token", "ignored prefix."+base64.URLEncoding.EncodeToString(
102− []byte("{\"not_email\": \"missing!\"}")))
103− email, err := p.GetEmailAddress(j, "ignored access_token")
117+ body, err := json.Marshal(
118+ struct {
119+ IdToken string `json:"id_token"`
120+ }{
121+ IdToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"not_email": "missing"}`)),
122+ },
123+ )
124+ assert.Equal(t, nil, err)
125+ email, err := p.GetEmailAddress(body, "ignored access_token")
104126 assert.Equal(t, "", email)
105127 assert.NotEqual(t, nil, err)
106128}
providers/internal_util_test.go+1−4
@@ -1,7 +1,6 @@
11package providers
22
33import (
4− "github.com/bitly/go-simplejson"
54 "github.com/bmizerany/assert"
65 "net/http"
76 "net/http/httptest"
@@ -13,9 +12,7 @@ type ValidateTokenTestProvider struct {
1312 *ProviderData
1413}
1514
16−func (tp *ValidateTokenTestProvider) GetEmailAddress(
17− unused_auth_response *simplejson.Json,
18− unused_access_token string) (string, error) {
15+func (tp *ValidateTokenTestProvider) GetEmailAddress(body []byte, access_token string) (string, error) {
1916 return "", nil
2017}
2118
providers/linkedin.go+1−3
@@ -8,7 +8,6 @@ import (
88 "net/http"
99 "net/url"
1010
11− "github.com/bitly/go-simplejson"
1211 "github.com/bitly/google_auth_proxy/api"
1312)
1413
@@ -50,8 +49,7 @@ func getLinkedInHeader(access_token string) http.Header {
5049 return header
5150}
5251
53−func (p *LinkedInProvider) GetEmailAddress(unused_auth_response *simplejson.Json,
54− access_token string) (string, error) {
52+func (p *LinkedInProvider) GetEmailAddress(body []byte, access_token string) (string, error) {
5553 if access_token == "" {
5654 return "", errors.New("missing access token")
5755 }
providers/linkedin_test.go+3−9
@@ -1,7 +1,6 @@
11package providers
22
33import (
4− "github.com/bitly/go-simplejson"
54 "github.com/bmizerany/assert"
65 "net/http"
76 "net/http/httptest"
@@ -97,9 +96,8 @@ func TestLinkedInProviderGetEmailAddress(t *testing.T) {
9796
9897 b_url, _ := url.Parse(b.URL)
9998 p := testLinkedInProvider(b_url.Host)
100− unused_auth_response := simplejson.New()
10199
102− email, err := p.GetEmailAddress(unused_auth_response,
100+ email, err := p.GetEmailAddress([]byte{},
103101 "imaginary_access_token")
104102 assert.Equal(t, nil, err)
105103 assert.Equal(t, "user@linkedin.com", email)
@@ -111,13 +109,11 @@ func TestLinkedInProviderGetEmailAddressFailedRequest(t *testing.T) {
111109
112110 b_url, _ := url.Parse(b.URL)
113111 p := testLinkedInProvider(b_url.Host)
114− unused_auth_response := simplejson.New()
115112
116113 // We'll trigger a request failure by using an unexpected access
117114 // token. Alternatively, we could allow the parsing of the payload as
118115 // JSON to fail.
119− email, err := p.GetEmailAddress(unused_auth_response,
120− "unexpected_access_token")
116+ email, err := p.GetEmailAddress([]byte{}, "unexpected_access_token")
121117 assert.NotEqual(t, nil, err)
122118 assert.Equal(t, "", email)
123119}
@@ -128,10 +124,8 @@ func TestLinkedInProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
128124
129125 b_url, _ := url.Parse(b.URL)
130126 p := testLinkedInProvider(b_url.Host)
131− unused_auth_response := simplejson.New()
132127
133− email, err := p.GetEmailAddress(unused_auth_response,
134− "imaginary_access_token")
128+ email, err := p.GetEmailAddress([]byte{}, "imaginary_access_token")
135129 assert.NotEqual(t, nil, err)
136130 assert.Equal(t, "", email)
137131}
providers/myusa.go+1−3
@@ -5,7 +5,6 @@ import (
55 "net/http"
66 "net/url"
77
8− "github.com/bitly/go-simplejson"
98 "github.com/bitly/google_auth_proxy/api"
109)
1110
@@ -43,8 +42,7 @@ func NewMyUsaProvider(p *ProviderData) *MyUsaProvider {
4342 return &MyUsaProvider{ProviderData: p}
4443}
4544
46−func (p *MyUsaProvider) GetEmailAddress(auth_response *simplejson.Json,
47− access_token string) (string, error) {
45+func (p *MyUsaProvider) GetEmailAddress(body []byte, access_token string) (string, error) {
4846 req, err := http.NewRequest("GET",
4947 p.ProfileUrl.String()+"?access_token="+access_token, nil)
5048 if err != nil {
providers/myusa_test.go+3−10
@@ -1,7 +1,6 @@
11package providers
22
33import (
4− "github.com/bitly/go-simplejson"
54 "github.com/bmizerany/assert"
65 "net/http"
76 "net/http/httptest"
@@ -102,10 +101,8 @@ func TestMyUsaProviderGetEmailAddress(t *testing.T) {
102101
103102 b_url, _ := url.Parse(b.URL)
104103 p := testMyUsaProvider(b_url.Host)
105− unused_auth_response := simplejson.New()
106104
107− email, err := p.GetEmailAddress(unused_auth_response,
108− "imaginary_access_token")
105+ email, err := p.GetEmailAddress([]byte{}, "imaginary_access_token")
109106 assert.Equal(t, nil, err)
110107 assert.Equal(t, "michael.bland@gsa.gov", email)
111108}
@@ -118,13 +115,11 @@ func TestMyUsaProviderGetEmailAddressFailedRequest(t *testing.T) {
118115
119116 b_url, _ := url.Parse(b.URL)
120117 p := testMyUsaProvider(b_url.Host)
121− unused_auth_response := simplejson.New()
122118
123119 // We'll trigger a request failure by using an unexpected access
124120 // token. Alternatively, we could allow the parsing of the payload as
125121 // JSON to fail.
126− email, err := p.GetEmailAddress(unused_auth_response,
127− "unexpected_access_token")
122+ email, err := p.GetEmailAddress([]byte{}, "unexpected_access_token")
128123 assert.NotEqual(t, nil, err)
129124 assert.Equal(t, "", email)
130125}
@@ -135,10 +130,8 @@ func TestMyUsaProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
135130
136131 b_url, _ := url.Parse(b.URL)
137132 p := testMyUsaProvider(b_url.Host)
138− unused_auth_response := simplejson.New()
139133
140− email, err := p.GetEmailAddress(unused_auth_response,
141− "imaginary_access_token")
134+ email, err := p.GetEmailAddress([]byte{}, "imaginary_access_token")
142135 assert.NotEqual(t, nil, err)
143136 assert.Equal(t, "", email)
144137}
providers/provider_data.go+2
@@ -6,6 +6,8 @@ import (
66
77type ProviderData struct {
88 ProviderName string
9+ ClientID string
10+ ClientSecret string
911 LoginUrl *url.URL
1012 RedeemUrl *url.URL
1113 ProfileUrl *url.URL
providers/provider_default.gonew file+51
@@ -0,0 +1,51 @@
1+package providers
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "errors"
7+ "io/ioutil"
8+ "net/http"
9+ "net/url"
10+)
11+
12+func (p *ProviderData) Redeem(redirectUrl, code string) (body []byte, token string, err error) {
13+ if code == "" {
14+ err = errors.New("missing code")
15+ return
16+ }
17+
18+ params := url.Values{}
19+ params.Add("redirect_uri", redirectUrl)
20+ params.Add("client_id", p.ClientID)
21+ params.Add("client_secret", p.ClientSecret)
22+ params.Add("code", code)
23+ params.Add("grant_type", "authorization_code")
24+ req, err := http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
25+ if err != nil {
26+ return nil, "", err
27+ }
28+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
29+
30+ resp, err := http.DefaultClient.Do(req)
31+ if err != nil {
32+ return nil, "", err
33+ }
34+ body, err = ioutil.ReadAll(resp.Body)
35+ resp.Body.Close()
36+ if err != nil {
37+ return nil, "", err
38+ }
39+
40+ // blindly try json and x-www-form-urlencoded
41+ var jsonResponse struct {
42+ AccessToken string `json:"access_token"`
43+ }
44+ err = json.Unmarshal(body, &jsonResponse)
45+ if err == nil {
46+ return body, jsonResponse.AccessToken, nil
47+ }
48+
49+ v, err := url.ParseQuery(string(body))
50+ return body, v.Get("access_token"), err
51+}
providers/providers.go+4−6
@@ -1,13 +1,9 @@
11package providers
22
3−import (
4− "github.com/bitly/go-simplejson"
5−)
6−
73type Provider interface {
84 Data() *ProviderData
9− GetEmailAddress(auth_response *simplejson.Json,
10− access_token string) (string, error)
5+ GetEmailAddress(body []byte, access_token string) (string, error)
6+ Redeem(string, string) ([]byte, string, error)
117 ValidateToken(access_token string) bool
128}
139
@@ -17,6 +13,8 @@ func New(provider string, p *ProviderData) Provider {
1713 return NewMyUsaProvider(p)
1814 case "linkedin":
1915 return NewLinkedInProvider(p)
16+ case "github":
17+ return NewGitHubProvider(p)
2018 default:
2119 return NewGoogleProvider(p)
2220 }