From 79221b62f55ad2ecffce03dd05cd88e20736792d Mon Sep 17 00:00:00 2001 From: Sebastian Mancke Date: Wed, 2 Jan 2019 19:19:11 +0100 Subject: [PATCH 1/2] changed to the google userinfo endpoint, because google+ shutdown. This fixes #65 --- README.md | 16 +++++++++------- oauth2/google.go | 43 +++++++++++++++++++++--------------------- oauth2/google_test.go | 44 +++++++++++++++---------------------------- oauth2/manager.go | 2 ++ oauth2/provider.go | 4 ++++ 5 files changed, 52 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 7426a2df..ea5cbe9b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ loginsrv is a standalone minimalistic login server providing a [JWT](https://jwt [![Coverage Status](https://coveralls.io/repos/github/tarent/loginsrv/badge.svg?branch=master)](https://coveralls.io/github/tarent/loginsrv?branch=master) [![Join the chat at https://gitter.im/tarent/loginsrv](https://badges.gitter.im/tarent/loginsrv.svg)](https://gitter.im/tarent/loginsrv?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +__** Attention: Update to v1.2.5 for Google Login Update !!!! **__ + +Google will stop support for the Google+ APIs. So we changed loginsrv to use the standard oauth endpoints for Google login. +Please update loginsrv to the master version or wait for release v1.2.5 if you are using google. + ## Abstract Loginsrv provides a minimal endpoint for authentication. The login is performed against the providers and returned as a JSON Web Token (JWT). @@ -50,9 +56,9 @@ _Note for Caddy users_: Not all parameters are available in Caddy. See the table | -cookie-expiry | string | session | X | Expiry duration for the cookie, e.g. 2h or 3h30m | | -cookie-http-only | boolean | true | X | Set the cookie with the HTTP only flag | | -cookie-name | string | "jwt_token" | X | 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=..] | -| -bitbucket | value | | X | OAuth config in the form: client_id=..,client_secret=..,[,scope=..][redirect_uri=..] | +| -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=..] | +| -bitbucket | value | | X | OAuth config in the form: client_id=..,client_secret=..[,scope=..][,redirect_uri=..] | | -facebook | value | | X | OAuth config in the form: client_id=..,client_secret=..,scope=email..[redirect_uri=..] | | -host | string | "localhost" | - | Host to listen on | | -htpasswd | value | | X | Htpasswd login backend opts: file=/path/to/pwdfile | @@ -315,10 +321,6 @@ if loginsrv is routed through a reverse proxy, if the headers `X-Forwarded-Host` $ docker run -p 80:80 tarent/loginsrv -github client_id=xxx,client_secret=yyy ``` -### Note for Google's OAuth 2 -You can use `scope=https://www.googleapis.com/auth/userinfo.email` [Google Plus API](https://console.cloud.google.com/apis/library/plus.googleapis.com/). When configuring OAuth 2 credentials in Google Cloud Console, don't forget to enable corresponding API's. -For example, for `scope=https://www.googleapis.com/auth/userinfo.profile` [Google People API](https://console.cloud.google.com/apis/library/people.googleapis.com/) must be enabled for your project. Keep in mind that it usually takes a few minutes for this setting to take effect. - ### Note for Facebook's OAuth 2 Make sure you ask for the scope `email` when adding your Facebook config option. Otherwise the provider won't be able to fetch the user's email. diff --git a/oauth2/google.go b/oauth2/google.go index d54880fc..d764504f 100644 --- a/oauth2/google.go +++ b/oauth2/google.go @@ -5,36 +5,35 @@ import ( "fmt" "io/ioutil" "net/http" - "regexp" "strings" "github.com/tarent/loginsrv/model" ) -var googleAPI = "https://www.googleapis.com/plus/v1" +//var googleAPI = "https://www.googleapis.com/plus/v1" + +var googleUserinfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo" func init() { RegisterProvider(providerGoogle) } type GoogleUser struct { - DisplayName string - Emails []struct { - Value string - } - Image struct { - Url string - } - Domain string + Name string `json:"name"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Picture string `json:"picture"` + HostedGsuiteDomain string `json:"hd"` } var providerGoogle = Provider{ - Name: "google", - AuthURL: "https://accounts.google.com/o/oauth2/v2/auth", - TokenURL: "https://www.googleapis.com/oauth2/v4/token", + Name: "google", + AuthURL: "https://accounts.google.com/o/oauth2/v2/auth", + TokenURL: "https://www.googleapis.com/oauth2/v4/token", + DefaultScopes: "email profile", GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) { gu := GoogleUser{} - url := fmt.Sprintf("%v/people/me?alt=json&access_token=%v", googleAPI, token.AccessToken) + url := fmt.Sprintf("%v?access_token=%v", googleUserinfoEndpoint, token.AccessToken) resp, err := http.Get(url) if err != nil { @@ -60,19 +59,21 @@ var providerGoogle = Provider{ return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err) } - if len(gu.Emails) == 0 { + if len(gu.Email) == 0 { return model.UserInfo{}, "", fmt.Errorf("invalid google response: no email address returned.") } - reg := regexp.MustCompile(`\?.*$`) + if !gu.EmailVerified { + return model.UserInfo{}, "", fmt.Errorf("invalid google response: users email address not verified by google.") + } return model.UserInfo{ - Sub: gu.Emails[0].Value, - Picture: reg.ReplaceAllString(gu.Image.Url, "${1}"), - Name: gu.DisplayName, - Email: gu.Emails[0].Value, + Sub: gu.Email, + Picture: gu.Picture, + Name: gu.Name, + Email: gu.Email, Origin: "google", - Domain: gu.Domain, + Domain: gu.HostedGsuiteDomain, }, string(b), nil }, } diff --git a/oauth2/google_test.go b/oauth2/google_test.go index 72b31dde..d0b594b7 100644 --- a/oauth2/google_test.go +++ b/oauth2/google_test.go @@ -9,31 +9,17 @@ import ( ) var googleTestUserResponse = `{ - "kind": "plus#person", - "etag": "\"XX\"", + "sub": "10467329456789", + "name": "Testy Test", + "given_name": "Testy", + "family_name": "Test", + "profile": "https://plus.google.com/10467329456789", + "picture": "https://lh6.googleusercontent.com/-alknmlknzT_YQ/AAAAAAAAAAI/AAAAAAAAABU/4gNvDUeED14/photo.jpg", + "email": "test@example.com", + "email_verified": true, "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, - "domain": "gmail.com" + "locale": "de", + "hd": "example.com" }` func Test_Google_getUserInfo(t *testing.T) { @@ -44,14 +30,14 @@ func Test_Google_getUserInfo(t *testing.T) { })) defer server.Close() - googleAPI = server.URL + googleUserinfoEndpoint = 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, "https://lh3.googleusercontent.com/X/X/X/X/photo.jpg", u.Picture) + Equal(t, "test@example.com", u.Sub) + Equal(t, "test@example.com", u.Email) + Equal(t, "https://lh6.googleusercontent.com/-alknmlknzT_YQ/AAAAAAAAAAI/AAAAAAAAABU/4gNvDUeED14/photo.jpg", u.Picture) Equal(t, "Testy Test", u.Name) - Equal(t, "gmail.com", u.Domain) + Equal(t, "example.com", u.Domain) Equal(t, googleTestUserResponse, rawJSON) } diff --git a/oauth2/manager.go b/oauth2/manager.go index 8a60ef31..19130787 100644 --- a/oauth2/manager.go +++ b/oauth2/manager.go @@ -114,6 +114,8 @@ func (manager *Manager) AddConfig(providerName string, opts map[string]string) e if scope, exist := opts["scope"]; exist { cfg.Scope = scope + } else { + cfg.Scope = p.DefaultScopes } if redirectURI, exist := opts["redirect_uri"]; exist { diff --git a/oauth2/provider.go b/oauth2/provider.go index 30afa95a..7c5a8f68 100644 --- a/oauth2/provider.go +++ b/oauth2/provider.go @@ -15,6 +15,10 @@ type Provider struct { // The url for token exchange TokenURL string + // Default Scopes is a space separated list of oauth scopes to use for this provider. + // This list can be overwritten by configuration. + DefaultScopes string + // GetUserInfo is a provider specific Implementation // for fetching the user information. // Possible keys in the returned map are: From 368f2b389ecd0a88881822dafef4a74ddee4c657 Mon Sep 17 00:00:00 2001 From: Gregor Weckbecker Date: Thu, 3 Jan 2019 21:22:12 +0100 Subject: [PATCH 2/2] remove commented code Co-Authored-By: smancke --- oauth2/google.go | 1 - 1 file changed, 1 deletion(-) diff --git a/oauth2/google.go b/oauth2/google.go index d764504f..bbf01669 100644 --- a/oauth2/google.go +++ b/oauth2/google.go @@ -10,7 +10,6 @@ import ( "github.com/tarent/loginsrv/model" ) -//var googleAPI = "https://www.googleapis.com/plus/v1" var googleUserinfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"