func TestGoogleHandler(t *testing.T) { jsonData := `{"id": "900913", "name": "Ben Bitdiddle"}` expectedUser := &google.Userinfoplus{Id: "900913", Name: "Ben Bitdiddle"} proxyClient, server := newGoogleTestServer(jsonData) defer server.Close() // oauth2 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth2.HTTPClient, proxyClient) anyToken := &oauth2.Token{AccessToken: "any-token"} ctx = oauth2Login.WithToken(ctx, anyToken) config := &oauth2.Config{} success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { googleUser, err := UserFromContext(ctx) assert.Nil(t, err) // assert required fields; Userinfoplus contains other raw response info assert.Equal(t, expectedUser.Id, googleUser.Id) assert.Equal(t, expectedUser.Id, googleUser.Id) fmt.Fprintf(w, "success handler called") } failure := testutils.AssertFailureNotCalled(t) // GoogleHandler assert that: // - Token is read from the ctx and passed to the Google API // - google Userinfoplus is obtained from the Google API // - success handler is called // - google Userinfoplus is added to the ctx of the success handler googleHandler := googleHandler(config, ctxh.ContextHandlerFunc(success), failure) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) googleHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "success handler called", w.Body.String()) }
func TestWebHandler(t *testing.T) { proxyClient, _, server := newDigitsTestServer(testAccountJSON) defer server.Close() config := &Config{ ConsumerKey: testConsumerKey, Client: proxyClient, } success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { account, err := AccountFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testDigitsToken, account.AccessToken.Token) assert.Equal(t, testDigitsSecret, account.AccessToken.Secret) assert.Equal(t, "0123456789", account.PhoneNumber) endpoint, header, err := EchoFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testAccountEndpoint, endpoint) assert.Equal(t, testAccountRequestHeader, header) } handler := LoginHandler(config, ctxh.ContextHandlerFunc(success), testutils.AssertFailureNotCalled(t)) ts := httptest.NewServer(ctxh.NewHandler(handler)) // POST OAuth Echo to server under test resp, err := http.PostForm(ts.URL, url.Values{accountEndpointField: {testAccountEndpoint}, accountRequestHeaderField: {testAccountRequestHeader}}) assert.Nil(t, err) if assert.NotNil(t, resp) { assert.Equal(t, http.StatusOK, resp.StatusCode) } }
func TestCallbackHandler_ExchangeError(t *testing.T) { _, server := testutils.NewErrorServer("OAuth2 Service Down", http.StatusInternalServerError) defer server.Close() config := &oauth2.Config{ Endpoint: oauth2.Endpoint{ TokenURL: server.URL, }, } success := testutils.AssertSuccessNotCalled(t) failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { err := gologin.ErrorFromContext(ctx) if assert.NotNil(t, err) { // error from golang.org/x/oauth2 config.Exchange as provider is down assert.True(t, strings.HasPrefix(err.Error(), "oauth2: cannot fetch token")) } fmt.Fprintf(w, "failure handler called") } // CallbackHandler cannot exchange for an Access Token, assert that: // - failure handler is called // - error with the reason the exchange failed is added to the ctx callbackHandler := CallbackHandler(config, success, ctxh.ContextHandlerFunc(failure)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/?code=any_code&state=d4e5f6", nil) ctx := WithState(context.Background(), "d4e5f6") callbackHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "failure handler called", w.Body.String()) }
// TokenHandler receives a Digits access token/secret and calls the Digits // accounts endpoint to get the corresponding Account. If successful, the // access token/secret and Account are added to the ctx and the success handler // is called. Otherwise, the failure handler is called. func TokenHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { success = digitsHandler(config, success, failure) if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { ctx = gologin.WithError(ctx, fmt.Errorf("Method not allowed")) failure.ServeHTTP(ctx, w, req) return } req.ParseForm() accessToken := req.PostForm.Get(accessTokenField) accessSecret := req.PostForm.Get(accessTokenSecretField) err := validateToken(accessToken, accessSecret) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = oauth1Login.WithAccessToken(ctx, accessToken, accessSecret) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
func TestLoginHandler_RequestTokenError(t *testing.T) { _, server := testutils.NewErrorServer("OAuth1 Service Down", http.StatusInternalServerError) defer server.Close() config := &oauth1.Config{ Endpoint: oauth1.Endpoint{ RequestTokenURL: server.URL, }, } success := testutils.AssertSuccessNotCalled(t) failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { err := gologin.ErrorFromContext(ctx) if assert.NotNil(t, err) { // first validation in OAuth1 impl failed assert.Equal(t, "oauth1: oauth_callback_confirmed was not true", err.Error()) } fmt.Fprintf(w, "failure handler called") } // LoginHandler cannot get the OAuth1 request token, assert that: // - failure handler is called // - error is added to the ctx of the failure handler loginHandler := LoginHandler(config, success, ctxh.ContextHandlerFunc(failure)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) loginHandler.ServeHTTP(context.Background(), w, req) assert.Equal(t, "failure handler called", w.Body.String()) }
func TestGithubHandler_ErrorGettingUser(t *testing.T) { proxyClient, server := testutils.NewErrorServer("Github Service Down", http.StatusInternalServerError) defer server.Close() // oauth2 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth2.HTTPClient, proxyClient) anyToken := &oauth2.Token{AccessToken: "any-token"} ctx = oauth2Login.WithToken(ctx, anyToken) config := &oauth2.Config{} success := testutils.AssertSuccessNotCalled(t) failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { err := gologin.ErrorFromContext(ctx) if assert.NotNil(t, err) { assert.Equal(t, ErrUnableToGetGithubUser, err) } fmt.Fprintf(w, "failure handler called") } // GithubHandler cannot get Github User, assert that: // - failure handler is called // - error cannot get Github User added to the failure handler ctx githubHandler := githubHandler(config, success, ctxh.ContextHandlerFunc(failure)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) githubHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "failure handler called", w.Body.String()) }
func TestTokenHandler(t *testing.T) { proxyClient, _, server := newDigitsTestServer(testAccountJSON) defer server.Close() // oauth1 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth1.HTTPClient, proxyClient) config := &oauth1.Config{} success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { account, err := AccountFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testDigitsToken, account.AccessToken.Token) assert.Equal(t, testDigitsSecret, account.AccessToken.Secret) assert.Equal(t, "0123456789", account.PhoneNumber) accessToken, accessSecret, err := oauth1Login.AccessTokenFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testDigitsToken, accessToken) assert.Equal(t, testDigitsSecret, accessSecret) } handler := TokenHandler(config, ctxh.ContextHandlerFunc(success), testutils.AssertFailureNotCalled(t)) ts := httptest.NewServer(ctxh.NewHandlerWithContext(ctx, handler)) // POST Digits access token to server under test resp, err := http.PostForm(ts.URL, url.Values{accessTokenField: {testDigitsToken}, accessTokenSecretField: {testDigitsSecret}}) assert.Nil(t, err) if assert.NotNil(t, resp) { assert.Equal(t, resp.StatusCode, http.StatusOK) } }
// getAccountViaEcho is a ContextHandler that gets the Digits Echo endpoint and // OAuth header from the ctx and calls the endpoint to get the corresponding // Digits Account. If successful, the Account is added to the ctx and the // success handler is called. Otherwise, the failure handler is called. func getAccountViaEcho(config *Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } client := config.Client if client == nil { client = http.DefaultClient } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { endpoint, header, err := EchoFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } // fetch the Digits Account account, resp, err := requestAccount(client, endpoint, header) // validate the Digits Account response err = validateResponse(account, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithAccount(ctx, account) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// LoginHandler receives a Digits OAuth Echo endpoint and OAuth header, // validates the echo, and calls the endpoint to get the corresponding Digits // Account. If successful, the Digits Account is added to the ctx and the // success handler is called. Otherwise, the failure handler is called. func LoginHandler(config *Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { success = getAccountViaEcho(config, success, failure) if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { ctx = gologin.WithError(ctx, fmt.Errorf("Method not allowed")) failure.ServeHTTP(ctx, w, req) return } req.ParseForm() accountEndpoint := req.PostForm.Get(accountEndpointField) accountRequestHeader := req.PostForm.Get(accountRequestHeaderField) // validate POST'ed Digits OAuth Echo data err := validateEcho(accountEndpoint, accountRequestHeader, config.ConsumerKey) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithEcho(ctx, accountEndpoint, accountRequestHeader) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// facebookHandler is a ContextHandler that gets the OAuth2 Token from the ctx // to get the corresponding Facebook User. If successful, the user is added to // the ctx and the success handler is called. Otherwise, the failure handler // is called. func facebookHandler(config *oauth2.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { token, err := oauth2Login.TokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, token) facebookService := newClient(httpClient) user, resp, err := facebookService.Me() err = validateResponse(user, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, user) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// googleHandler is a ContextHandler that gets the OAuth2 Token from the ctx // to get the corresponding Google Userinfoplus. If successful, the user info // is added to the ctx and the success handler is called. Otherwise, the // failure handler is called. func googleHandler(config *oauth2.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { token, err := oauth2Login.TokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, token) googleService, err := google.New(httpClient) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } userInfoPlus, err := googleService.Userinfo.Get().Do() err = validateResponse(userInfoPlus, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, userInfoPlus) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
func TestGithubHandler(t *testing.T) { jsonData := `{"id": 917408, "name": "Alyssa Hacker"}` expectedUser := &github.User{ID: github.Int(917408), Name: github.String("Alyssa Hacker")} proxyClient, server := newGithubTestServer(jsonData) defer server.Close() // oauth2 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth2.HTTPClient, proxyClient) anyToken := &oauth2.Token{AccessToken: "any-token"} ctx = oauth2Login.WithToken(ctx, anyToken) config := &oauth2.Config{} success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { githubUser, err := UserFromContext(ctx) assert.Nil(t, err) assert.Equal(t, expectedUser, githubUser) fmt.Fprintf(w, "success handler called") } failure := testutils.AssertFailureNotCalled(t) // GithubHandler assert that: // - Token is read from the ctx and passed to the Github API // - github User is obtained from the Github API // - success handler is called // - github User is added to the ctx of the success handler githubHandler := githubHandler(config, ctxh.ContextHandlerFunc(success), failure) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) githubHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "success handler called", w.Body.String()) }
func TestCallbackHandler(t *testing.T) { expectedToken := "acces_token" expectedSecret := "access_secret" requestSecret := "request_secret" data := url.Values{} data.Add("oauth_token", expectedToken) data.Add("oauth_token_secret", expectedSecret) server := NewAccessTokenServer(t, data) defer server.Close() config := &oauth1.Config{ Endpoint: oauth1.Endpoint{ AccessTokenURL: server.URL, }, } success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { accessToken, accessSecret, err := AccessTokenFromContext(ctx) assert.Equal(t, expectedToken, accessToken) assert.Equal(t, expectedSecret, accessSecret) assert.Nil(t, err) fmt.Fprintf(w, "success handler called") } failure := testutils.AssertFailureNotCalled(t) // CallbackHandler gets OAuth1 access token, assert that: // - success handler is called // - access token and secret added to the ctx of the success handler // - failure handler is not called callbackHandler := CallbackHandler(config, ctxh.ContextHandlerFunc(success), failure) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/?oauth_token=any_token&oauth_verifier=any_verifier", nil) ctx := WithRequestToken(context.Background(), "", requestSecret) callbackHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "success handler called", w.Body.String()) }
// twitterHandler is a ContextHandler that gets the OAuth1 access token from // the ctx and calls Twitter verify_credentials to get the corresponding User. // If successful, the User is added to the ctx and the success handler is // called. Otherwise, the failure handler is called. func twitterHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { accessToken, accessSecret, err := oauth1Login.AccessTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, oauth1.NewToken(accessToken, accessSecret)) twitterClient := twitter.NewClient(httpClient) accountVerifyParams := &twitter.AccountVerifyParams{ IncludeEntities: twitter.Bool(false), SkipStatus: twitter.Bool(true), IncludeEmail: twitter.Bool(false), } user, resp, err := twitterClient.Accounts.VerifyCredentials(accountVerifyParams) err = validateResponse(user, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, user) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// tumblrHandler is a ContextHandler that gets the OAuth1 access token from // the ctx and obtains the Tumblr User. If successful, the User is added to // the ctx and the success handler is called. Otherwise, the failure handler // is called. func tumblrHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { accessToken, accessSecret, err := oauth1Login.AccessTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } httpClient := config.Client(ctx, oauth1.NewToken(accessToken, accessSecret)) tumblrClient := newClient(httpClient) user, resp, err := tumblrClient.UserInfo() err = validateResponse(user, resp, err) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithUser(ctx, user) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
func TestTokenHandler(t *testing.T) { proxyClient, _, server := newTwitterTestServer(testTwitterUserJSON) defer server.Close() // oauth1 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth1.HTTPClient, proxyClient) config := &oauth1.Config{} success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { accessToken, accessSecret, err := oauth1Login.AccessTokenFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testTwitterToken, accessToken) assert.Equal(t, testTwitterTokenSecret, accessSecret) user, err := UserFromContext(ctx) assert.Nil(t, err) assert.Equal(t, expectedUserID, user.ID) assert.Equal(t, "1234", user.IDStr) } handler := TokenHandler(config, ctxh.ContextHandlerFunc(success), assertFailureNotCalled(t)) ts := httptest.NewServer(ctxh.NewHandlerWithContext(ctx, handler)) // POST token to server under test resp, err := http.PostForm(ts.URL, url.Values{accessTokenField: {testTwitterToken}, accessTokenSecretField: {testTwitterTokenSecret}}) assert.Nil(t, err) if assert.NotNil(t, resp) { assert.Equal(t, resp.StatusCode, http.StatusOK) } }
func TestLoginHandler(t *testing.T) { expectedToken := "request_token" expectedSecret := "request_secret" data := url.Values{} data.Add("oauth_token", expectedToken) data.Add("oauth_token_secret", expectedSecret) data.Add("oauth_callback_confirmed", "true") server := NewRequestTokenServer(t, data) defer server.Close() config := &oauth1.Config{ Endpoint: oauth1.Endpoint{ RequestTokenURL: server.URL, }, } success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { requestToken, requestSecret, err := RequestTokenFromContext(ctx) assert.Equal(t, expectedToken, requestToken) assert.Equal(t, expectedSecret, requestSecret) assert.Nil(t, err) fmt.Fprintf(w, "success handler called") } failure := testutils.AssertFailureNotCalled(t) // LoginHandler gets OAuth1 request token, assert that: // - success handler is called // - request token added to the ctx of the success handler // - request secret added to the ctx of the success handler // - failure handler is not called loginHandler := LoginHandler(config, ctxh.ContextHandlerFunc(success), failure) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) loginHandler.ServeHTTP(context.Background(), w, req) assert.Equal(t, "success handler called", w.Body.String()) }
// CallbackHandler handles OAuth2 redirection URI requests by parsing the auth // code and state, comparing with the state value from the ctx, and obtaining // an OAuth2 Token. func CallbackHandler(config *oauth2.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { authCode, state, err := parseCallback(req) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ownerState, err := StateFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } if state != ownerState || state == "" { ctx = gologin.WithError(ctx, ErrInvalidState) failure.ServeHTTP(ctx, w, req) return } // use the authorization code to get a Token token, err := config.Exchange(ctx, authCode) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithToken(ctx, token) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// CallbackHandler handles OAuth1 callback requests by parsing the oauth token // and verifier, reading the request token secret from the ctx, then obtaining // an access token and adding it to the ctx. func CallbackHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { requestToken, verifier, err := oauth1.ParseAuthorizationCallback(req) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } // upstream handler should add the request token secret from the login step _, requestSecret, err := RequestTokenFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } accessToken, accessSecret, err := config.AccessToken(requestToken, requestSecret, verifier) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithAccessToken(ctx, accessToken, accessSecret) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
func TestCallbackHandler_AccessTokenError(t *testing.T) { requestSecret := "request_secret" _, server := testutils.NewErrorServer("OAuth1 Service Down", http.StatusInternalServerError) defer server.Close() config := &oauth1.Config{ Endpoint: oauth1.Endpoint{ AccessTokenURL: server.URL, }, } success := testutils.AssertSuccessNotCalled(t) failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { err := gologin.ErrorFromContext(ctx) if assert.NotNil(t, err) { assert.Equal(t, "oauth1: Response missing oauth_token or oauth_token_secret", err.Error()) } fmt.Fprintf(w, "failure handler called") } // CallbackHandler cannot get the OAuth1 access token, assert that: // - failure handler is called // - error about missing oauth_token and oauth_token_secret is added to the ctx callbackHandler := CallbackHandler(config, success, ctxh.ContextHandlerFunc(failure)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/?oauth_token=any_token&oauth_verifier=any_verifier", nil) ctx := WithRequestToken(context.Background(), "", requestSecret) callbackHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "failure handler called", w.Body.String()) }
func TestAuthRedirectHandler_AuthorizationURL(t *testing.T) { requestToken := "request_token" config := &oauth1.Config{ Endpoint: oauth1.Endpoint{ AuthorizeURL: "%gh&%ij", // always causes AuthorizationURL parse error }, } failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { err := gologin.ErrorFromContext(ctx) if assert.NotNil(t, err) { assert.Equal(t, "parse %gh&%ij: invalid URL escape \"%gh\"", err.Error()) } fmt.Fprintf(w, "failure handler called") } // AuthRedirectHandler cannot construct the AuthorizationURL, assert that: // - failure handler is called // - error about authorization URL is added to the ctx authRedirectHandler := AuthRedirectHandler(config, ctxh.ContextHandlerFunc(failure)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) ctx := WithRequestToken(context.Background(), requestToken, "") authRedirectHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "failure handler called", w.Body.String()) }
func TestCallbackHandler_ParseCallbackError(t *testing.T) { config := &oauth2.Config{} success := testutils.AssertSuccessNotCalled(t) failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { err := gologin.ErrorFromContext(ctx) if assert.NotNil(t, err) { assert.Equal(t, "oauth2: Request missing code or state", err.Error()) } fmt.Fprintf(w, "failure handler called") } // CallbackHandler called without code or state, assert that: // - failure handler is called // - error about missing code or state is added to the ctx callbackHandler := CallbackHandler(config, success, ctxh.ContextHandlerFunc(failure)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/?code=any_code", nil) callbackHandler.ServeHTTP(context.Background(), w, req) assert.Equal(t, "failure handler called", w.Body.String()) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/?state=any_state", nil) callbackHandler.ServeHTTP(context.Background(), w, req) assert.Equal(t, "failure handler called", w.Body.String()) }
func TestBitbucketHandler(t *testing.T) { jsonData := `{"username": "******", "display_name": "Atlas Ian"}` expectedUser := &User{Username: "******", DisplayName: "Atlas Ian"} proxyClient, server := newBitbucketTestServer(jsonData) defer server.Close() // oauth2 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth2.HTTPClient, proxyClient) anyToken := &oauth2.Token{AccessToken: "any-token"} ctx = oauth2Login.WithToken(ctx, anyToken) config := &oauth2.Config{} success := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { bitbucketUser, err := UserFromContext(ctx) assert.Nil(t, err) assert.Equal(t, expectedUser, bitbucketUser) fmt.Fprintf(w, "success handler called") } failure := testutils.AssertFailureNotCalled(t) // BitbucketHandler assert that: // - Token is read from the ctx and passed to the Bitbucket API // - bitbucket User is obtained from the Bitbucket API // - success handler is called // - bitbucket User is added to the ctx of the success handler bitbucketHandler := bitbucketHandler(config, ctxh.ContextHandlerFunc(success), failure) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) bitbucketHandler.ServeHTTP(ctx, w, req) assert.Equal(t, "success handler called", w.Body.String()) }
// EmptyTempHandler adds an empty request token secret to the ctx if none is // present to support OAuth1 providers which do not require temp secrets to // be kept between the login phase and callback phase. func EmptyTempHandler(success ctxh.ContextHandler) ctxh.ContextHandler { fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { _, _, err := RequestTokenFromContext(ctx) if err != nil { ctx = WithRequestToken(ctx, "", "") } success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// issueSession issues a cookie session after successful Twitter login func issueSession() ctxh.ContextHandler { fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { twitterUser, err := twitter.UserFromContext(ctx) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 2. Implement a success handler to issue some form of session session := sessionStore.New(sessionName) session.Values[sessionUserKey] = twitterUser.ID session.Save(w) http.Redirect(w, req, "/profile", http.StatusFound) } return ctxh.ContextHandlerFunc(fn) }
func checkSuccess(t *testing.T) ctxh.ContextHandler { fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { account, err := AccountFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testDigitsToken, account.AccessToken.Token) assert.Equal(t, testDigitsSecret, account.AccessToken.Secret) assert.Equal(t, "0123456789", account.PhoneNumber) endpoint, header, err := EchoFromContext(ctx) assert.Nil(t, err) assert.Equal(t, testAccountEndpoint, endpoint) assert.Equal(t, testAccountRequestHeader, header) } return ctxh.ContextHandlerFunc(fn) }
// LoginHandler handles OAuth2 login requests by reading the state value from // the ctx and redirecting requests to the AuthURL with that state value. func LoginHandler(config *oauth2.Config, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { state, err := StateFromContext(ctx) if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } authURL := config.AuthCodeURL(state) http.Redirect(w, req, authURL, http.StatusFound) } return ctxh.ContextHandlerFunc(fn) }
// LoginHandler handles OAuth1 login requests by obtaining a request token and // secret (temporary credentials) and adding it to the ctx. If successful, // handling delegates to the success handler, otherwise to the failure handler. // // Typically, the success handler is an AuthRedirectHandler or a handler which // stores the request token secret. func LoginHandler(config *oauth1.Config, success, failure ctxh.ContextHandler) ctxh.ContextHandler { if failure == nil { failure = gologin.DefaultFailureHandler } fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { requestToken, requestSecret, err := config.RequestToken() if err != nil { ctx = gologin.WithError(ctx, err) failure.ServeHTTP(ctx, w, req) return } ctx = WithRequestToken(ctx, requestToken, requestSecret) success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
// StateHandler checks for a state cookie. If found, the state value is read // and added to the ctx. Otherwise, a non-guessable value is added to the ctx // and to a (short-lived) state cookie issued to the requester. // // Implements OAuth 2 RFC 6749 10.12 CSRF Protection. If you wish to issue // state params differently, write a ContextHandler which sets the ctx state, // using oauth2 WithState(ctx, state) since it is required by LoginHandler // and CallbackHandler. func StateHandler(config gologin.CookieConfig, success ctxh.ContextHandler) ctxh.ContextHandler { fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { cookie, err := req.Cookie(config.Name) if err == nil { // add the cookie state to the ctx ctx = WithState(ctx, cookie.Value) } else { // add Cookie with a random state val := randomState() http.SetCookie(w, internal.NewCookie(config, val)) ctx = WithState(ctx, val) } success.ServeHTTP(ctx, w, req) } return ctxh.ContextHandlerFunc(fn) }
func TestTokenHandler_UnauthorizedPassesError(t *testing.T) { proxyClient, server := testutils.UnauthorizedTestServer() defer server.Close() // oauth1 Client will use the proxy client's base Transport ctx := context.WithValue(context.Background(), oauth1.HTTPClient, proxyClient) config := &oauth1.Config{} failure := func(ctx context.Context, w http.ResponseWriter, req *http.Request) { // assert that error passed through ctx err := gologin.ErrorFromContext(ctx) if assert.Error(t, err) { assert.Equal(t, err, ErrUnableToGetTwitterUser) } } handler := TokenHandler(config, assertSuccessNotCalled(t), ctxh.ContextHandlerFunc(failure)) ts := httptest.NewServer(ctxh.NewHandlerWithContext(ctx, handler)) http.PostForm(ts.URL, url.Values{accessTokenField: {testTwitterToken}, accessTokenSecretField: {testTwitterTokenSecret}}) }