Skip to content

Commit 2a62830

Browse files
committed
proposal: app engine support
App Engine unfortunately can't make outbound requests without using the appengine urlfetch library and a request-specific context. I don't see a way around this other than to change the signature of the Provider and Session interfaces. This really has a huge amount of downsides obviously, but before I go and fix stuff for providers besides Facebook, G+, and Twitter (Twitter support requires mrjones/oauth#60 to get merged), I figured I'd open a pull request and discuss the tradeoffs. Unfortunately I think we need to do something like this to support App Engine. Perhaps this is an opportunity to version goth using gopkg.in or something?
1 parent a185bfa commit 2a62830

File tree

10 files changed

+110
-38
lines changed

10 files changed

+110
-38
lines changed

client.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// +build !appengine
2+
3+
package goth
4+
5+
import (
6+
"net/http"
7+
8+
"golang.org/x/net/context"
9+
)
10+
11+
var HTTPClient = func(ctx context.Context) (*http.Client, error) {
12+
return http.DefaultClient, nil
13+
}

client_appengine.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// +build appengine
2+
3+
package goth
4+
5+
import (
6+
"net/http"
7+
8+
"golang.org/x/net/context"
9+
"google.golang.org/appengine/urlfetch"
10+
)
11+
12+
var HTTPClient = func(ctx context.Context) (*http.Client, error) {
13+
return urlfetch.Client(ctx), nil
14+
}

provider.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package goth
22

3-
import "fmt"
4-
import "golang.org/x/oauth2"
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/net/context"
7+
"golang.org/x/oauth2"
8+
)
59

610
// Provider needs to be implemented for each 3rd party authentication provider
711
// e.g. Facebook, Twitter, etc...
812
type Provider interface {
913
Name() string
10-
BeginAuth(state string) (Session, error)
14+
BeginAuth(ctx context.Context, state string) (Session, error)
1115
UnmarshalSession(string) (Session, error)
12-
FetchUser(Session) (User, error)
16+
FetchUser(context.Context, Session) (User, error)
1317
Debug(bool)
14-
RefreshToken(refreshToken string) (*oauth2.Token, error) //Get new access token based on the refresh token
15-
RefreshTokenAvailable() bool //Refresh token is provided by auth provider or not
18+
RefreshToken(ctx context.Context, refreshToken string) (*oauth2.Token, error) //Get new access token based on the refresh token
19+
RefreshTokenAvailable() bool //Refresh token is provided by auth provider or not
1620
}
1721

1822
// Providers is list of known/available providers.

providers/facebook/facebook.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"errors"
99
"io"
1010
"io/ioutil"
11-
"net/http"
1211
"net/url"
1312

1413
"github.com/markbates/goth"
14+
"golang.org/x/net/context"
1515
"golang.org/x/oauth2"
1616
)
1717

@@ -51,7 +51,7 @@ func (p *Provider) Name() string {
5151
func (p *Provider) Debug(debug bool) {}
5252

5353
// BeginAuth asks Facebook for an authentication end-point.
54-
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
54+
func (p *Provider) BeginAuth(ctx context.Context, state string) (goth.Session, error) {
5555
url := p.config.AuthCodeURL(state)
5656
session := &Session{
5757
AuthURL: url,
@@ -60,15 +60,21 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) {
6060
}
6161

6262
// FetchUser will go to Facebook and access basic information about the user.
63-
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
63+
func (p *Provider) FetchUser(ctx context.Context, session goth.Session) (
64+
goth.User, error) {
6465
sess := session.(*Session)
6566
user := goth.User{
6667
AccessToken: sess.AccessToken,
6768
Provider: p.Name(),
6869
ExpiresAt: sess.ExpiresAt,
6970
}
7071

71-
response, err := http.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken))
72+
client, err := goth.HTTPClient(ctx)
73+
if err != nil {
74+
return user, err
75+
}
76+
77+
response, err := client.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken))
7278
if err != nil {
7379
return user, err
7480
}
@@ -153,7 +159,7 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config {
153159
}
154160

155161
//RefreshToken refresh token is not provided by facebook
156-
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
162+
func (p *Provider) RefreshToken(ctx context.Context, refreshToken string) (*oauth2.Token, error) {
157163
return nil, errors.New("Refresh token is not provided by facebook")
158164
}
159165

providers/facebook/session.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package facebook
33
import (
44
"encoding/json"
55
"errors"
6-
"github.com/markbates/goth"
7-
"golang.org/x/oauth2"
86
"strings"
97
"time"
8+
9+
"github.com/markbates/goth"
10+
"golang.org/x/net/context"
1011
)
1112

1213
// Session stores data during the auth process with Facebook.
@@ -25,9 +26,9 @@ func (s Session) GetAuthURL() (string, error) {
2526
}
2627

2728
// Authorize the session with Facebook and return the access token to be stored for future use.
28-
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
29+
func (s *Session) Authorize(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
2930
p := provider.(*Provider)
30-
token, err := p.config.Exchange(oauth2.NoContext, params.Get("code"))
31+
token, err := p.config.Exchange(ctx, params.Get("code"))
3132
if err != nil {
3233
return "", err
3334
}

providers/gplus/gplus.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77
"encoding/json"
88
"io"
99
"io/ioutil"
10-
"net/http"
1110
"net/url"
1211
"strings"
1312

1413
"github.com/markbates/goth"
14+
"golang.org/x/net/context"
1515
"golang.org/x/oauth2"
1616
)
1717

@@ -52,7 +52,7 @@ func (p *Provider) Name() string {
5252
func (p *Provider) Debug(debug bool) {}
5353

5454
// BeginAuth asks Google+ for an authentication end-point.
55-
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
55+
func (p *Provider) BeginAuth(ctx context.Context, state string) (goth.Session, error) {
5656
var opts []oauth2.AuthCodeOption
5757
if p.prompt != nil {
5858
opts = append(opts, p.prompt)
@@ -65,7 +65,8 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) {
6565
}
6666

6767
// FetchUser will go to Google+ and access basic information about the user.
68-
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
68+
func (p *Provider) FetchUser(ctx context.Context, session goth.Session) (
69+
goth.User, error) {
6970
sess := session.(*Session)
7071
user := goth.User{
7172
AccessToken: sess.AccessToken,
@@ -74,7 +75,12 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
7475
ExpiresAt: sess.ExpiresAt,
7576
}
7677

77-
response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
78+
client, err := goth.HTTPClient(ctx)
79+
if err != nil {
80+
return user, err
81+
}
82+
83+
response, err := client.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
7884
if err != nil {
7985
return user, err
8086
}
@@ -151,9 +157,10 @@ func (p *Provider) RefreshTokenAvailable() bool {
151157
}
152158

153159
//RefreshToken get new access token based on the refresh token
154-
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
160+
func (p *Provider) RefreshToken(ctx context.Context, refreshToken string) (
161+
*oauth2.Token, error) {
155162
token := &oauth2.Token{RefreshToken: refreshToken}
156-
ts := p.config.TokenSource(oauth2.NoContext, token)
163+
ts := p.config.TokenSource(ctx, token)
157164
newToken, err := ts.Token()
158165
if err != nil {
159166
return nil, err

providers/gplus/session.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package gplus
33
import (
44
"encoding/json"
55
"errors"
6-
"github.com/markbates/goth"
7-
"golang.org/x/oauth2"
86
"strings"
97
"time"
8+
9+
"github.com/markbates/goth"
10+
"golang.org/x/net/context"
1011
)
1112

1213
// Session stores data during the auth process with Facebook.
@@ -26,9 +27,9 @@ func (s Session) GetAuthURL() (string, error) {
2627
}
2728

2829
// Authorize the session with Google+ and return the access token to be stored for future use.
29-
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
30+
func (s *Session) Authorize(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
3031
p := provider.(*Provider)
31-
token, err := p.config.Exchange(oauth2.NoContext, params.Get("code"))
32+
token, err := p.config.Exchange(ctx, params.Get("code"))
3233
if err != nil {
3334
return "", err
3435
}

providers/twitter/session.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package twitter
33
import (
44
"encoding/json"
55
"errors"
6+
"strings"
7+
68
"github.com/markbates/goth"
79
"github.com/mrjones/oauth"
8-
"strings"
10+
"golang.org/x/net/context"
911
)
1012

1113
// Session stores data during the auth process with Twitter.
@@ -24,9 +26,10 @@ func (s Session) GetAuthURL() (string, error) {
2426
}
2527

2628
// Authorize the session with Twitter and return the access token to be stored for future use.
27-
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
29+
func (s *Session) Authorize(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
2830
p := provider.(*Provider)
29-
accessToken, err := p.consumer.AuthorizeToken(s.RequestToken, params.Get("oauth_verifier"))
31+
accessToken, err := p.consumer.AuthorizeTokenWithParamsCtx(
32+
ctx, s.RequestToken, params.Get("oauth_verifier"), p.consumer.AdditionalParams)
3033
if err != nil {
3134
return "", err
3235
}

providers/twitter/twitter.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ import (
66
"bytes"
77
"encoding/json"
88
"errors"
9+
"io/ioutil"
10+
"net/http"
11+
"net/url"
12+
13+
"github.com/jtolds/webhelp/whcompat"
914
"github.com/markbates/goth"
1015
"github.com/mrjones/oauth"
16+
"golang.org/x/net/context"
1117
"golang.org/x/oauth2"
12-
"io/ioutil"
1318
)
1419

1520
var (
@@ -32,6 +37,9 @@ func New(clientKey, secret, callbackURL string) *Provider {
3237
CallbackURL: callbackURL,
3338
}
3439
p.consumer = newConsumer(p, authorizeURL)
40+
p.consumer.HttpClientFunc = func(ctx context.Context) (oauth.HttpClient, error) {
41+
return goth.HTTPClient(ctx)
42+
}
3543
return p
3644
}
3745

@@ -68,8 +76,8 @@ func (p *Provider) Debug(debug bool) {
6876

6977
// BeginAuth asks Twitter for an authentication end-point and a request token for a session.
7078
// Twitter does not support the "state" variable.
71-
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
72-
requestToken, url, err := p.consumer.GetRequestTokenAndUrl(p.CallbackURL)
79+
func (p *Provider) BeginAuth(ctx context.Context, state string) (goth.Session, error) {
80+
requestToken, url, err := p.consumer.GetRequestTokenAndUrlWithParamsCtx(ctx, p.CallbackURL, p.consumer.AdditionalParams)
7381
session := &Session{
7482
AuthURL: url,
7583
RequestToken: requestToken,
@@ -78,16 +86,26 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) {
7886
}
7987

8088
// FetchUser will go to Twitter and access basic information about the user.
81-
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
89+
func (p *Provider) FetchUser(ctx context.Context, session goth.Session) (goth.User, error) {
8290
user := goth.User{
8391
Provider: p.Name(),
8492
}
8593

8694
sess := session.(*Session)
87-
response, err := p.consumer.Get(
88-
endpointProfile,
89-
map[string]string{"include_entities": "false", "skip_status": "true"},
90-
sess.AccessToken)
95+
96+
client, err := p.consumer.MakeHttpClient(sess.AccessToken)
97+
if err != nil {
98+
return user, err
99+
}
100+
101+
req, err := http.NewRequest("GET", endpointProfile+"?"+(url.Values{
102+
"include_entities": []string{"false"},
103+
"skip_status": []string{"true"}}).Encode(), nil)
104+
if err != nil {
105+
return user, err
106+
}
107+
req = whcompat.WithContext(req, ctx)
108+
response, err := client.Do(req)
91109
if err != nil {
92110
return user, err
93111
}
@@ -125,7 +143,8 @@ func newConsumer(provider *Provider, authURL string) *oauth.Consumer {
125143
}
126144

127145
//RefreshToken refresh token is not provided by twitter
128-
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
146+
func (p *Provider) RefreshToken(ctx context.Context, refreshToken string) (
147+
*oauth2.Token, error) {
129148
return nil, errors.New("Refresh token is not provided by twitter")
130149
}
131150

session.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package goth
22

3+
import (
4+
"golang.org/x/net/context"
5+
)
6+
37
// Params is used to pass data to sessions for authorization. An existing
48
// implementation, and the one most likely to be used, is `url.Values`.
59
type Params interface {
@@ -17,5 +21,5 @@ type Session interface {
1721
Marshal() string
1822
// Authorize should validate the data from the provider and return an access token
1923
// that can be stored for later access to the provider.
20-
Authorize(Provider, Params) (string, error)
24+
Authorize(context.Context, Provider, Params) (string, error)
2125
}

0 commit comments

Comments
 (0)