Skip to content
This repository has been archived by the owner on Jan 24, 2019. It is now read-only.

Google - use offline access token #116

Merged
merged 2 commits into from
Jun 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ For Google, the registration steps are:
* Fill in the necessary fields and Save (this is _required_)
5. Take note of the **Client ID** and **Client Secret**

It's recommended to refresh sessions on a short interval (1h) with `cookie-refresh` setting which validates that the account is still authorized.

### GitHub Auth Provider

1. Create a new project: https://github.com/settings/developers
Expand Down Expand Up @@ -100,7 +102,7 @@ Usage of oauth2_proxy:
-cookie-expire=168h0m0s: expire timeframe for cookie
-cookie-httponly=true: set HttpOnly cookie flag
-cookie-key="_oauth2_proxy": the name of the cookie that the oauth_proxy creates
-cookie-refresh=0: refresh the cookie when less than this much time remains before expiration; 0 to disable
-cookie-refresh=0: refresh the cookie after this duration; 0 to disable
-cookie-secret="": the seed string for secure cookies
-cookie-secure=true: set secure (HTTPS) cookie flag
-custom-templates-dir="": path to custom html templates
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func main() {
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.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie when less than this much time remains before expiration; 0 to disable")
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")

Expand Down
9 changes: 8 additions & 1 deletion oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,13 @@ func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (e
} else if ok && p.CookieRefresh != time.Duration(0) {
refresh := timestamp.Add(p.CookieRefresh)
if refresh.Before(time.Now()) {
ok = p.Validator(email) && p.provider.ValidateToken(access_token)
log.Printf("refreshing %s old session for %s (refresh after %s)", time.Now().Sub(timestamp), email, p.CookieRefresh)
ok = p.Validator(email)
log.Printf("re-validating %s valid:%v", email, ok)
if ok {
ok = p.provider.ValidateToken(access_token)
log.Printf("re-validating access token. valid:%v", ok)
}
if ok {
p.SetCookie(rw, req, value)
}
Expand Down Expand Up @@ -432,6 +438,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
http.Redirect(rw, req, redirect, 302)
return
} else {
log.Printf("validating: %s is unauthorized")
p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
return
}
Expand Down
4 changes: 2 additions & 2 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ func TestDefaultProviderApiSettings(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
p := o.provider.Data()
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth",
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
p.LoginUrl.String())
assert.Equal(t, "https://accounts.google.com/o/oauth2/token",
assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
p.RedeemUrl.String())
assert.Equal(t, "", p.ProfileUrl.String())
assert.Equal(t, "profile email", p.Scope)
Expand Down
101 changes: 98 additions & 3 deletions providers/google.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
package providers

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)

type GoogleProvider struct {
*ProviderData
RedeemRefreshUrl *url.URL
}

func NewGoogleProvider(p *ProviderData) *GoogleProvider {
p.ProviderName = "Google"
if p.LoginUrl.String() == "" {
p.LoginUrl = &url.URL{Scheme: "https",
Host: "accounts.google.com",
Path: "/o/oauth2/auth"}
Path: "/o/oauth2/auth",
// to get a refresh token. see https://developers.google.com/identity/protocols/OAuth2WebServer#offline
RawQuery: "access_type=offline",
}
}
if p.RedeemUrl.String() == "" {
p.RedeemUrl = &url.URL{Scheme: "https",
Host: "accounts.google.com",
Path: "/o/oauth2/token"}
Host: "www.googleapis.com",
Path: "/oauth2/v3/token"}
}
if p.ValidateUrl.String() == "" {
p.ValidateUrl = &url.URL{Scheme: "https",
Expand Down Expand Up @@ -76,3 +84,90 @@ func jwtDecodeSegment(seg string) ([]byte, error) {
func (p *GoogleProvider) ValidateToken(access_token string) bool {
return validateToken(p, access_token, nil)
}

func (p *GoogleProvider) Redeem(redirectUrl, code string) (body []byte, token string, err error) {
if code == "" {
err = errors.New("missing code")
return
}

params := url.Values{}
params.Add("redirect_uri", redirectUrl)
params.Add("client_id", p.ClientID)
params.Add("client_secret", p.ClientSecret)
params.Add("code", code)
params.Add("grant_type", "authorization_code")
var req *http.Request
req, err = http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
body, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return
}

if resp.StatusCode != 200 {
err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemUrl.String(), body)
return
}

var jsonResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
err = json.Unmarshal(body, &jsonResponse)
if err != nil {
return
}

token, err = p.redeemRefreshToken(jsonResponse.RefreshToken)
return
}

func (p *GoogleProvider) redeemRefreshToken(refreshToken string) (token string, err error) {
// https://developers.google.com/identity/protocols/OAuth2WebServer#refresh
params := url.Values{}
params.Add("client_id", p.ClientID)
params.Add("client_secret", p.ClientSecret)
params.Add("refresh_token", refreshToken)
params.Add("grant_type", "refresh_token")
var req *http.Request
req, err = http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
var body []byte
body, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return
}

if resp.StatusCode != 200 {
err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemUrl.String(), body)
return
}

var jsonResponse struct {
AccessToken string `json:"access_token"`
}
err = json.Unmarshal(body, &jsonResponse)
if err != nil {
return
}
return jsonResponse.AccessToken, nil
}
4 changes: 2 additions & 2 deletions providers/google_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ func TestGoogleProviderDefaults(t *testing.T) {
p := newGoogleProvider()
assert.NotEqual(t, nil, p)
assert.Equal(t, "Google", p.Data().ProviderName)
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth",
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
p.Data().LoginUrl.String())
assert.Equal(t, "https://accounts.google.com/o/oauth2/token",
assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
p.Data().RedeemUrl.String())
assert.Equal(t, "https://www.googleapis.com/oauth2/v1/tokeninfo",
p.Data().ValidateUrl.String())
Expand Down
16 changes: 10 additions & 6 deletions providers/provider_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,19 @@ func (p *ProviderData) Redeem(redirectUrl, code string) (body []byte, token stri
return body, v.Get("access_token"), err
}

// GetLoginURL with typical oauth parameters
func (p *ProviderData) GetLoginURL(redirectURI, finalRedirect string) string {
params := url.Values{}
params.Add("redirect_uri", redirectURI)
params.Add("approval_prompt", "force")
var a url.URL
a = *p.LoginUrl
params, _ := url.ParseQuery(a.RawQuery)
params.Set("redirect_uri", redirectURI)
params.Set("approval_prompt", "force")
params.Add("scope", p.Scope)
params.Add("client_id", p.ClientID)
params.Add("response_type", "code")
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
if strings.HasPrefix(finalRedirect, "/") {
params.Add("state", finalRedirect)
}
return fmt.Sprintf("%s?%s", p.LoginUrl, params.Encode())
a.RawQuery = params.Encode()
return a.String()
}
1 change: 0 additions & 1 deletion validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ func newValidatorImpl(domains []string, usersFile string,
if allowAll {
valid = true
}
log.Printf("validating: is %s valid? %v", email, valid)
return valid
}
return validator
Expand Down