Merge pull request #25 from jehiah/google_oauth_migration_25

/v2/userinfo going away, switch to new scopes and id token payload
This commit is contained in:
Pierce Lopez 2014-08-07 17:21:27 -04:00
commit 1c0989ff58
2 changed files with 41 additions and 44 deletions

View File

@ -5,13 +5,13 @@ import (
"fmt" "fmt"
"log" "log"
"net" "net"
"os"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
) )
const VERSION = "0.0.1" const VERSION = "0.1.0"
var ( var (
showVersion = flag.Bool("version", false, "print version string") showVersion = flag.Bool("version", false, "print version string")

View File

@ -5,7 +5,6 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/bitly/go-simplejson"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
@ -13,6 +12,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"github.com/bitly/go-simplejson"
) )
const signInPath = "/oauth2/sign_in" const signInPath = "/oauth2/sign_in"
@ -27,7 +28,6 @@ type OauthProxy struct {
redirectUrl *url.URL // the url to receive requests at redirectUrl *url.URL // the url to receive requests at
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
oauthUserInfoUrl *url.URL
oauthScope string oauthScope string
clientID string clientID string
clientSecret string clientSecret string
@ -39,7 +39,6 @@ type OauthProxy struct {
func NewOauthProxy(proxyUrls []*url.URL, clientID string, clientSecret string, validator func(string) bool) *OauthProxy { func NewOauthProxy(proxyUrls []*url.URL, clientID string, clientSecret string, validator func(string) bool) *OauthProxy {
login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth") login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth")
redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token") redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token")
info, _ := url.Parse("https://www.googleapis.com/oauth2/v2/userinfo")
serveMux := http.NewServeMux() serveMux := http.NewServeMux()
for _, u := range proxyUrls { for _, u := range proxyUrls {
path := u.Path path := u.Path
@ -54,10 +53,9 @@ func NewOauthProxy(proxyUrls []*url.URL, clientID string, clientSecret string, v
clientID: clientID, clientID: clientID,
clientSecret: clientSecret, clientSecret: clientSecret,
oauthScope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", oauthScope: "profile email",
oauthRedemptionUrl: redeem, oauthRedemptionUrl: redeem,
oauthLoginUrl: login, oauthLoginUrl: login,
oauthUserInfoUrl: info,
serveMux: serveMux, serveMux: serveMux,
} }
} }
@ -102,9 +100,9 @@ func apiRequest(req *http.Request) (*simplejson.Json, error) {
return data, nil return data, nil
} }
func (p *OauthProxy) redeemCode(code string) (string, error) { func (p *OauthProxy) redeemCode(code string) (string, string, error) {
if code == "" { if code == "" {
return "", errors.New("missing code") return "", "", errors.New("missing code")
} }
params := url.Values{} params := url.Values{}
params.Add("redirect_uri", p.redirectUrl.String()) params.Add("redirect_uri", p.redirectUrl.String())
@ -112,46 +110,52 @@ func (p *OauthProxy) redeemCode(code string) (string, error) {
params.Add("client_secret", p.clientSecret) params.Add("client_secret", p.clientSecret)
params.Add("code", code) params.Add("code", code)
params.Add("grant_type", "authorization_code") params.Add("grant_type", "authorization_code")
log.Printf("body is %s", params.Encode())
req, err := http.NewRequest("POST", p.oauthRedemptionUrl.String(), bytes.NewBufferString(params.Encode())) req, err := http.NewRequest("POST", p.oauthRedemptionUrl.String(), bytes.NewBufferString(params.Encode()))
if err != nil { if err != nil {
log.Printf("failed building request %s", err.Error()) log.Printf("failed building request %s", err.Error())
return "", err return "", "", err
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
json, err := apiRequest(req) json, err := apiRequest(req)
if err != nil { if err != nil {
log.Printf("failed making request %s", err.Error()) log.Printf("failed making request %s", err)
return "", err return "", "", err
} }
access_token, err := json.Get("access_token").String() access_token, err := json.Get("access_token").String()
if err != nil { if err != nil {
return "", err return "", "", err
}
return access_token, nil
} }
func (p *OauthProxy) getUserInfo(token string) (string, error) { idToken, err := json.Get("id_token").String()
params := url.Values{}
params.Add("access_token", token)
endpoint := fmt.Sprintf("%s?%s", p.oauthUserInfoUrl.String(), params.Encode())
log.Printf("calling %s", endpoint)
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil { if err != nil {
log.Printf("failed building request %s", err.Error()) return "", "", err
return "", err
} }
json, err := apiRequest(req)
// id_token is a base64 encode ID token payload
// https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo
jwt := strings.Split(idToken, ".")
b, err := jwtDecodeSegment(jwt[1])
if err != nil { if err != nil {
log.Printf("failed making request %s", err.Error()) return "", "", err
return "", err
} }
email, err := json.Get("email").String() data, err := simplejson.NewJson(b)
if err != nil { if err != nil {
log.Printf("failed getting email from response %s", err.Error()) return "", "", err
return "", err
} }
return email, nil email, err := data.Get("email").String()
if err != nil {
return "", "", err
}
return access_token, email, nil
}
func jwtDecodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l > 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
} }
func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) { func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
@ -301,16 +305,9 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return return
} }
token, err := p.redeemCode(req.Form.Get("code")) _, email, err := p.redeemCode(req.Form.Get("code"))
if err != nil { if err != nil {
log.Printf("error redeeming code %s", err.Error()) log.Printf("error redeeming code %s", err)
p.ErrorPage(rw, 500, "Internal Error", err.Error())
return
}
// validate user
email, err := p.getUserInfo(token)
if err != nil {
log.Printf("error redeeming code %s", err.Error())
p.ErrorPage(rw, 500, "Internal Error", err.Error()) p.ErrorPage(rw, 500, "Internal Error", err.Error())
return return
} }