Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changed to the google userinfo endpoint, because google+ shutdown #111

Merged
merged 2 commits into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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.

Expand Down
43 changes: 22 additions & 21 deletions oauth2/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
smancke marked this conversation as resolved.
Show resolved Hide resolved

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 {
Expand All @@ -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
},
}
44 changes: 15 additions & 29 deletions oauth2/google_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
}
2 changes: 2 additions & 0 deletions oauth2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions oauth2/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down