Skip to content

Commit

Permalink
proposal: app engine support
Browse files Browse the repository at this point in the history
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?
  • Loading branch information
jtolio committed Dec 29, 2016
1 parent a185bfa commit 2a62830
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 38 deletions.
13 changes: 13 additions & 0 deletions client.go
@@ -0,0 +1,13 @@
// +build !appengine

package goth

import (
"net/http"

"golang.org/x/net/context"
)

var HTTPClient = func(ctx context.Context) (*http.Client, error) {
return http.DefaultClient, nil
}
14 changes: 14 additions & 0 deletions client_appengine.go
@@ -0,0 +1,14 @@
// +build appengine

package goth

import (
"net/http"

"golang.org/x/net/context"
"google.golang.org/appengine/urlfetch"
)

var HTTPClient = func(ctx context.Context) (*http.Client, error) {
return urlfetch.Client(ctx), nil
}
16 changes: 10 additions & 6 deletions provider.go
@@ -1,18 +1,22 @@
package goth

import "fmt"
import "golang.org/x/oauth2"
import (
"fmt"

"golang.org/x/net/context"
"golang.org/x/oauth2"
)

// Provider needs to be implemented for each 3rd party authentication provider
// e.g. Facebook, Twitter, etc...
type Provider interface {
Name() string
BeginAuth(state string) (Session, error)
BeginAuth(ctx context.Context, state string) (Session, error)
UnmarshalSession(string) (Session, error)
FetchUser(Session) (User, error)
FetchUser(context.Context, Session) (User, error)
Debug(bool)
RefreshToken(refreshToken string) (*oauth2.Token, error) //Get new access token based on the refresh token
RefreshTokenAvailable() bool //Refresh token is provided by auth provider or not
RefreshToken(ctx context.Context, refreshToken string) (*oauth2.Token, error) //Get new access token based on the refresh token
RefreshTokenAvailable() bool //Refresh token is provided by auth provider or not
}

// Providers is list of known/available providers.
Expand Down
16 changes: 11 additions & 5 deletions providers/facebook/facebook.go
Expand Up @@ -8,10 +8,10 @@ import (
"errors"
"io"
"io/ioutil"
"net/http"
"net/url"

"github.com/markbates/goth"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -51,7 +51,7 @@ func (p *Provider) Name() string {
func (p *Provider) Debug(debug bool) {}

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

// FetchUser will go to Facebook and access basic information about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
func (p *Provider) FetchUser(ctx context.Context, session goth.Session) (
goth.User, error) {
sess := session.(*Session)
user := goth.User{
AccessToken: sess.AccessToken,
Provider: p.Name(),
ExpiresAt: sess.ExpiresAt,
}

response, err := http.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken))
client, err := goth.HTTPClient(ctx)
if err != nil {
return user, err
}

response, err := client.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken))
if err != nil {
return user, err
}
Expand Down Expand Up @@ -153,7 +159,7 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config {
}

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

Expand Down
9 changes: 5 additions & 4 deletions providers/facebook/session.go
Expand Up @@ -3,10 +3,11 @@ package facebook
import (
"encoding/json"
"errors"
"github.com/markbates/goth"
"golang.org/x/oauth2"
"strings"
"time"

"github.com/markbates/goth"
"golang.org/x/net/context"
)

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

// Authorize the session with Facebook and return the access token to be stored for future use.
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
func (s *Session) Authorize(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
p := provider.(*Provider)
token, err := p.config.Exchange(oauth2.NoContext, params.Get("code"))
token, err := p.config.Exchange(ctx, params.Get("code"))
if err != nil {
return "", err
}
Expand Down
19 changes: 13 additions & 6 deletions providers/gplus/gplus.go
Expand Up @@ -7,11 +7,11 @@ import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"

"github.com/markbates/goth"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -52,7 +52,7 @@ func (p *Provider) Name() string {
func (p *Provider) Debug(debug bool) {}

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

// FetchUser will go to Google+ and access basic information about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
func (p *Provider) FetchUser(ctx context.Context, session goth.Session) (
goth.User, error) {
sess := session.(*Session)
user := goth.User{
AccessToken: sess.AccessToken,
Expand All @@ -74,7 +75,12 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
ExpiresAt: sess.ExpiresAt,
}

response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
client, err := goth.HTTPClient(ctx)
if err != nil {
return user, err
}

response, err := client.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
if err != nil {
return user, err
}
Expand Down Expand Up @@ -151,9 +157,10 @@ func (p *Provider) RefreshTokenAvailable() bool {
}

//RefreshToken get new access token based on the refresh token
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
func (p *Provider) RefreshToken(ctx context.Context, refreshToken string) (
*oauth2.Token, error) {
token := &oauth2.Token{RefreshToken: refreshToken}
ts := p.config.TokenSource(oauth2.NoContext, token)
ts := p.config.TokenSource(ctx, token)
newToken, err := ts.Token()
if err != nil {
return nil, err
Expand Down
9 changes: 5 additions & 4 deletions providers/gplus/session.go
Expand Up @@ -3,10 +3,11 @@ package gplus
import (
"encoding/json"
"errors"
"github.com/markbates/goth"
"golang.org/x/oauth2"
"strings"
"time"

"github.com/markbates/goth"
"golang.org/x/net/context"
)

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

// Authorize the session with Google+ and return the access token to be stored for future use.
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
func (s *Session) Authorize(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
p := provider.(*Provider)
token, err := p.config.Exchange(oauth2.NoContext, params.Get("code"))
token, err := p.config.Exchange(ctx, params.Get("code"))
if err != nil {
return "", err
}
Expand Down
9 changes: 6 additions & 3 deletions providers/twitter/session.go
Expand Up @@ -3,9 +3,11 @@ package twitter
import (
"encoding/json"
"errors"
"strings"

"github.com/markbates/goth"
"github.com/mrjones/oauth"
"strings"
"golang.org/x/net/context"
)

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

// Authorize the session with Twitter and return the access token to be stored for future use.
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
func (s *Session) Authorize(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
p := provider.(*Provider)
accessToken, err := p.consumer.AuthorizeToken(s.RequestToken, params.Get("oauth_verifier"))
accessToken, err := p.consumer.AuthorizeTokenWithParamsCtx(
ctx, s.RequestToken, params.Get("oauth_verifier"), p.consumer.AdditionalParams)
if err != nil {
return "", err
}
Expand Down
37 changes: 28 additions & 9 deletions providers/twitter/twitter.go
Expand Up @@ -6,10 +6,15 @@ import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"

"github.com/jtolds/webhelp/whcompat"
"github.com/markbates/goth"
"github.com/mrjones/oauth"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"io/ioutil"
)

var (
Expand All @@ -32,6 +37,9 @@ func New(clientKey, secret, callbackURL string) *Provider {
CallbackURL: callbackURL,
}
p.consumer = newConsumer(p, authorizeURL)
p.consumer.HttpClientFunc = func(ctx context.Context) (oauth.HttpClient, error) {
return goth.HTTPClient(ctx)
}
return p
}

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

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

// FetchUser will go to Twitter and access basic information about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
func (p *Provider) FetchUser(ctx context.Context, session goth.Session) (goth.User, error) {
user := goth.User{
Provider: p.Name(),
}

sess := session.(*Session)
response, err := p.consumer.Get(
endpointProfile,
map[string]string{"include_entities": "false", "skip_status": "true"},
sess.AccessToken)

client, err := p.consumer.MakeHttpClient(sess.AccessToken)
if err != nil {
return user, err
}

req, err := http.NewRequest("GET", endpointProfile+"?"+(url.Values{
"include_entities": []string{"false"},
"skip_status": []string{"true"}}).Encode(), nil)
if err != nil {
return user, err
}
req = whcompat.WithContext(req, ctx)
response, err := client.Do(req)
if err != nil {
return user, err
}
Expand Down Expand Up @@ -125,7 +143,8 @@ func newConsumer(provider *Provider, authURL string) *oauth.Consumer {
}

//RefreshToken refresh token is not provided by twitter
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
func (p *Provider) RefreshToken(ctx context.Context, refreshToken string) (
*oauth2.Token, error) {
return nil, errors.New("Refresh token is not provided by twitter")
}

Expand Down
6 changes: 5 additions & 1 deletion session.go
@@ -1,5 +1,9 @@
package goth

import (
"golang.org/x/net/context"
)

// Params is used to pass data to sessions for authorization. An existing
// implementation, and the one most likely to be used, is `url.Values`.
type Params interface {
Expand All @@ -17,5 +21,5 @@ type Session interface {
Marshal() string
// Authorize should validate the data from the provider and return an access token
// that can be stored for later access to the provider.
Authorize(Provider, Params) (string, error)
Authorize(context.Context, Provider, Params) (string, error)
}

0 comments on commit 2a62830

Please sign in to comment.