@@ -1,15 +1,20 @@
11package providers
22
33import (
4+ "bytes"
45 "encoding/base64"
56 "encoding/json"
67 "errors"
8+ "fmt"
9+ "io/ioutil"
10+ "net/http"
711 "net/url"
812 "strings"
913)
1014
1115type GoogleProvider struct {
1216 *ProviderData
17+ RedeemRefreshUrl *url.URL
1318}
1419
1520func NewGoogleProvider(p *ProviderData) *GoogleProvider {
@@ -17,12 +22,15 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider {
1722 if p.LoginUrl.String() == "" {
1823 p.LoginUrl = &url.URL{Scheme: "https",
1924 Host: "accounts.google.com",
20− Path: "/o/oauth2/auth"}
25+ Path: "/o/oauth2/auth",
26+ // to get a refresh token. see https://developers.google.com/identity/protocols/OAuth2WebServer#offline
27+ RawQuery: "access_type=offline",
28+ }
2129 }
2230 if p.RedeemUrl.String() == "" {
2331 p.RedeemUrl = &url.URL{Scheme: "https",
24− Host: "accounts.google.com",
25− Path: "/o/oauth2/token"}
32+ Host: "www.googleapis.com",
33+ Path: "/oauth2/v3/token"}
2634 }
2735 if p.ValidateUrl.String() == "" {
2836 p.ValidateUrl = &url.URL{Scheme: "https",
@@ -76,3 +84,90 @@ func jwtDecodeSegment(seg string) ([]byte, error) {
7684func (p *GoogleProvider) ValidateToken(access_token string) bool {
7785 return validateToken(p, access_token, nil)
7886}
87+
88+func (p *GoogleProvider) Redeem(redirectUrl, code string) (body []byte, token string, err error) {
89+ if code == "" {
90+ err = errors.New("missing code")
91+ return
92+ }
93+
94+ params := url.Values{}
95+ params.Add("redirect_uri", redirectUrl)
96+ params.Add("client_id", p.ClientID)
97+ params.Add("client_secret", p.ClientSecret)
98+ params.Add("code", code)
99+ params.Add("grant_type", "authorization_code")
100+ var req *http.Request
101+ req, err = http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
102+ if err != nil {
103+ return
104+ }
105+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
106+
107+ resp, err := http.DefaultClient.Do(req)
108+ if err != nil {
109+ return
110+ }
111+ body, err = ioutil.ReadAll(resp.Body)
112+ resp.Body.Close()
113+ if err != nil {
114+ return
115+ }
116+
117+ if resp.StatusCode != 200 {
118+ err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemUrl.String(), body)
119+ return
120+ }
121+
122+ var jsonResponse struct {
123+ AccessToken string `json:"access_token"`
124+ RefreshToken string `json:"refresh_token"`
125+ }
126+ err = json.Unmarshal(body, &jsonResponse)
127+ if err != nil {
128+ return
129+ }
130+
131+ token, err = p.redeemRefreshToken(jsonResponse.RefreshToken)
132+ return
133+}
134+
135+func (p *GoogleProvider) redeemRefreshToken(refreshToken string) (token string, err error) {
136+ // https://developers.google.com/identity/protocols/OAuth2WebServer#refresh
137+ params := url.Values{}
138+ params.Add("client_id", p.ClientID)
139+ params.Add("client_secret", p.ClientSecret)
140+ params.Add("refresh_token", refreshToken)
141+ params.Add("grant_type", "refresh_token")
142+ var req *http.Request
143+ req, err = http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
144+ if err != nil {
145+ return
146+ }
147+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
148+
149+ resp, err := http.DefaultClient.Do(req)
150+ if err != nil {
151+ return
152+ }
153+ var body []byte
154+ body, err = ioutil.ReadAll(resp.Body)
155+ resp.Body.Close()
156+ if err != nil {
157+ return
158+ }
159+
160+ if resp.StatusCode != 200 {
161+ err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemUrl.String(), body)
162+ return
163+ }
164+
165+ var jsonResponse struct {
166+ AccessToken string `json:"access_token"`
167+ }
168+ err = json.Unmarshal(body, &jsonResponse)
169+ if err != nil {
170+ return
171+ }
172+ return jsonResponse.AccessToken, nil
173+}