loginsrv

Unnamed repository; edit this file 'description' to name the repository.
git clone git@jamesshield.xyz:repos/loginsrv.git
Log | Files | Refs | README | LICENSE

commit 00890cf0d0c513b07d3a5bcecff122bc2ba81af7
parent a8e4c02008e20d7153b64e6635b1bb1eb54e818d
Author: Sebastian Mancke <s.mancke@tarent.de>
Date:   Thu, 17 Aug 2017 09:57:07 +0200

Merge branch 'paybase-feature/google'

Diffstat:
MREADME.md | 4+++-
Aoauth2/google.go | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aoauth2/google_test.go | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Moauth2/oauth.go | 2++
Moauth2/oauth_test.go | 2+-
Moauth2/provider_test.go | 7++++++-
6 files changed, 132 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md @@ -29,7 +29,7 @@ The following providers (login backends) are supported. * [Httpupstream](#httpupstream) * [Oauth2](#oauth2) * Github Login - * .. Google and Facebook will come soon .. + * Google Login ## Questions @@ -49,6 +49,7 @@ _Note for Caddy users_: Not all parameters are available in Caddy. See the table | -cookie-http-only | boolean | true | X | Set the cookie with the http only flag | | -cookie-name | string | "jwt_token" | X | The name of the jwt cookie | | -github | value | | X | Oauth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..] | +| -google | value | | X | Oauth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..] | | -host | string | "localhost" | - | The host to listen on | | -htpasswd | value | | X | Htpasswd login backend opts: file=/path/to/pwdfile | | -jwt-expiry | go duration | 24h | X | The expiry duration for the jwt token, e.g. 2h or 3h30m | @@ -239,6 +240,7 @@ The Oauth Web Flow (aka 3-leged-Oauth flow) is also supported. Currently the following oauth Provider is supported: * github +* google An Oauth Provider supports the following parameters: diff --git a/oauth2/google.go b/oauth2/google.go @@ -0,0 +1,67 @@ +package oauth2 + +import ( + "encoding/json" + "fmt" + "github.com/tarent/loginsrv/model" + "io/ioutil" + "net/http" + "strings" +) + +var googleAPI = "https://www.googleapis.com/plus/v1" + +func init() { + RegisterProvider(providerGoogle) +} + +type GoogleUser struct { + DisplayName string + Emails []struct { + Value string + } + Image struct { + Url string + } +} + +var providerGoogle = Provider{ + Name: "google", + AuthURL: "https://accounts.google.com/o/oauth2/v2/auth", + TokenURL: "https://www.googleapis.com/oauth2/v4/token", + GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) { + gu := GoogleUser{} + url := fmt.Sprintf("%v/people/me?alt=json&access_token=%v", googleAPI, token.AccessToken) + resp, err := http.Get(url) + + if err != nil { + return model.UserInfo{}, "", err + } + + if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") { + return model.UserInfo{}, "", fmt.Errorf("wrong content-type on google get user info: %v", resp.Header.Get("Content-Type")) + } + + if resp.StatusCode != 200 { + return model.UserInfo{}, "", fmt.Errorf("got http status %v on google get user info", resp.StatusCode) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return model.UserInfo{}, "", fmt.Errorf("error reading google get user info: %v", err) + } + + err = json.Unmarshal(b, &gu) + if err != nil { + return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err) + } + + return model.UserInfo{ + Sub: gu.Emails[0].Value, + Picture: gu.Image.Url, + Name: gu.DisplayName, + Email: gu.Emails[0].Value, + Origin: "google", + }, string(b), nil + }, +} diff --git a/oauth2/google_test.go b/oauth2/google_test.go @@ -0,0 +1,53 @@ +package oauth2 + +import ( + . "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +var googleTestUserResponse = `{ + "kind": "plus#person", + "etag": "\"XX\"", + "gender": "male", + "emails": [ + { + "value": "test@gmail.com", + "type": "account" + } + ], + "objectType": "person", + "id": "1", + "displayName": "Testy Test", + "name": { + "familyName": "Test", + "givenName": "Testy" + }, + "url": "https://plus.google.com/X", + "image": { + "url": "https://lh3.googleusercontent.com/X/X/X/X/photo.jpg?sz=50", + "isDefault": true + }, + "isPlusUser": true, + "circledByCount": 0, + "verified": false +}` + +func Test_Google_getUserInfo(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Equal(t, "secret", r.FormValue("access_token")) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write([]byte(googleTestUserResponse)) + })) + defer server.Close() + + googleAPI = server.URL + + u, rawJSON, err := providerGoogle.GetUserInfo(TokenInfo{AccessToken: "secret"}) + NoError(t, err) + Equal(t, "test@gmail.com", u.Sub) + Equal(t, "test@gmail.com", u.Email) + Equal(t, "Testy Test", u.Name) + Equal(t, googleTestUserResponse, rawJSON) +} diff --git a/oauth2/oauth.go b/oauth2/oauth.go @@ -113,6 +113,8 @@ func getAccessToken(cfg Config, state, code string) (TokenInfo, error) { values.Set("client_id", cfg.ClientID) values.Set("client_secret", cfg.ClientSecret) values.Set("code", code) + values.Set("redirect_uri", cfg.RedirectURI) + values.Set("grant_type", "authorization_code") r, _ := http.NewRequest("POST", cfg.TokenURL, strings.NewReader(values.Encode())) cntx, cancel := context.WithTimeout(context.Background(), defaultTimeout) diff --git a/oauth2/oauth_test.go b/oauth2/oauth_test.go @@ -50,7 +50,7 @@ func Test_Authenticate(t *testing.T) { Equal(t, "application/json", r.Header.Get("Accept")) body, _ := ioutil.ReadAll(r.Body) - Equal(t, "client_id=client42&client_secret=secret&code=theCode", string(body)) + Equal(t, "client_id=client42&client_secret=secret&code=theCode&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%2Fcallback", string(body)) w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo gist", "token_type":"bearer"}`)) diff --git a/oauth2/provider_test.go b/oauth2/provider_test.go @@ -10,7 +10,12 @@ func Test_ProviderRegistration(t *testing.T) { NotNil(t, github) True(t, exist) + google, exist := GetProvider("google") + NotNil(t, google) + True(t, exist) + list := ProviderList() - Equal(t, 1, len(list)) + Equal(t, 2, len(list)) Equal(t, "github", list[0]) + Equal(t, "google", list[1]) }