1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-01-24 05:17:10 +02:00
authboss/oauth2/oauth2_test.go
Aaron L 06edd2e615 Make OAuth2 implementation less shoddy.
- Add a new storer specifically for OAuth2 to enable clients to choose
  regular database storing OR Oauth2 but not have to have both.
- Stop storing OAuth2 credentials in a combined form inside username.
- Add new events to capture OAuth events just like auth.
- Have pass-through parameters for OAuth init urls, this allows us to
  pass additional behavior options (redirects and remember me) as well
  as other things that should be present on the page that is redirected
  to.
- Context.LoadUser is now OAuth aware.
- Remember's callbacks now include an OAuth check to see if a horribly
  packed state variable contains a flag to say that we want to be
  remembered.
- Change the OAuth2 Callback to use Attributes instead of that custom
  struct to allow people to append whatever attributes they want into
  the user that will be saved.
2015-03-13 16:29:58 -07:00

261 lines
6.7 KiB
Go

package oauth2
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"path"
"sort"
"strings"
"testing"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"gopkg.in/authboss.v0"
"gopkg.in/authboss.v0/internal/mocks"
)
var testProviders = map[string]authboss.OAuthProvider{
"google": authboss.OAuthProvider{
OAuth2Config: &oauth2.Config{
ClientID: `jazz`,
ClientSecret: `hands`,
Scopes: []string{`profile`, `email`},
Endpoint: GoogleEndpoint,
},
Callback: Google,
AdditionalParams: url.Values{"include_requested_scopes": []string{"true"}},
},
}
func TestInitialize(t *testing.T) {
authboss.Cfg = authboss.NewConfig()
authboss.Cfg.OAuth2Storer = mocks.NewMockStorer()
o := OAuth2{}
if err := o.Initialize(); err != nil {
t.Error(err)
}
}
func TestRoutes(t *testing.T) {
root := "https://localhost:8080"
mount := "/auth"
authboss.Cfg = authboss.NewConfig()
authboss.Cfg.RootURL = root
authboss.Cfg.MountPath = mount
authboss.Cfg.OAuth2Providers = testProviders
googleCfg := authboss.Cfg.OAuth2Providers["google"].OAuth2Config
if 0 != len(googleCfg.RedirectURL) {
t.Error("RedirectURL should not be set")
}
o := OAuth2{}
routes := o.Routes()
authURL := path.Join(mount, "oauth2", "google")
tokenURL := path.Join(mount, "oauth2", "callback", "google")
redir := root + path.Join(mount, "oauth2", "callback", "google")
if _, ok := routes[authURL]; !ok {
t.Error("Expected an auth url route:", authURL)
}
if _, ok := routes[tokenURL]; !ok {
t.Error("Expected a token url route:", tokenURL)
}
if googleCfg.RedirectURL != redir {
t.Error("The redirect URL should have been set:", googleCfg.RedirectURL)
}
}
func TestOAuth2Init(t *testing.T) {
cfg := authboss.NewConfig()
session := mocks.NewMockClientStorer()
cfg.OAuth2Providers = testProviders
authboss.Cfg = cfg
r, _ := http.NewRequest("GET", "/oauth2/google?r=/my/redirect&rm=true", nil)
w := httptest.NewRecorder()
ctx := authboss.NewContext()
ctx.SessionStorer = session
oauthInit(ctx, w, r)
if w.Code != http.StatusFound {
t.Error("Code was wrong:", w.Code)
}
loc := w.Header().Get("Location")
parsed, err := url.Parse(loc)
if err != nil {
t.Error(err)
}
if !strings.Contains(loc, GoogleEndpoint.AuthURL) {
t.Error("Redirected to wrong url:", loc)
}
query := parsed.Query()
if query["include_requested_scopes"][0] != "true" {
t.Error("Missing extra parameters:", loc)
}
state := query[authboss.FormValueOAuth2State][0]
if len(state) == 0 {
t.Error("It should have had some state:", loc)
}
splits := strings.Split(state, ";")
if len(splits[0]) != 44 {
t.Error("The xsrf token was wrong size:", len(splits[0]), splits[0])
}
// Maps are fun
sort.Strings(splits[1:])
if v, err := url.QueryUnescape(splits[1]); err != nil {
t.Error(err)
} else if v != "r=/my/redirect" {
t.Error("Redirect parameter not saved:", splits[1])
}
if v, err := url.QueryUnescape(splits[2]); err != nil {
t.Error(err)
} else if v != "rm=true" {
t.Error("Remember parameter not saved:", splits[2])
}
}
func TestOAuthSuccess(t *testing.T) {
cfg := authboss.NewConfig()
expiry := time.Now().UTC().Add(3600 * time.Second)
fakeToken := &oauth2.Token{
AccessToken: "token",
TokenType: "Bearer",
RefreshToken: "refresh",
Expiry: expiry,
}
fakeCallback := func(_ oauth2.Config, _ *oauth2.Token) (authboss.Attributes, error) {
return authboss.Attributes{
authboss.StoreOAuth2UID: "uid",
authboss.StoreEmail: "email",
}, nil
}
saveExchange := exchanger
defer func() {
exchanger = saveExchange
}()
exchanger = func(_ *oauth2.Config, _ context.Context, _ string) (*oauth2.Token, error) {
return fakeToken, nil
}
cfg.OAuth2Providers = map[string]authboss.OAuthProvider{
"fake": authboss.OAuthProvider{
OAuth2Config: &oauth2.Config{
ClientID: `jazz`,
ClientSecret: `hands`,
Scopes: []string{`profile`, `email`},
Endpoint: oauth2.Endpoint{"fakeauth", "faketoken"},
},
Callback: fakeCallback,
AdditionalParams: url.Values{"include_requested_scopes": []string{"true"}},
},
}
authboss.Cfg = cfg
url := fmt.Sprintf("/oauth2/fake?code=code&state=%s", url.QueryEscape("state;redir=/myurl;rm=true;myparam=5"))
r, _ := http.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
ctx := authboss.NewContext()
session := mocks.NewMockClientStorer()
session.Put(authboss.SessionOAuth2State, authboss.FormValueOAuth2State)
storer := mocks.NewMockStorer()
ctx.SessionStorer = session
cfg.OAuth2Storer = storer
cfg.AuthLoginOKPath = "/fakeloginok"
if err := oauthCallback(ctx, w, r); err != nil {
t.Error(err)
}
key := "uidfake"
user, ok := storer.Users[key]
if !ok {
t.Error("Couldn't find user.")
}
if val, _ := user.String(authboss.StoreEmail); val != "email" {
t.Error("Email was wrong:", val)
}
if val, _ := user.String(authboss.StoreOAuth2Token); val != "token" {
t.Error("Token was wrong:", val)
}
if val, _ := user.String(authboss.StoreOAuth2Refresh); val != "refresh" {
t.Error("Refresh was wrong:", val)
}
if val, _ := user.DateTime(authboss.StoreOAuth2Expiry); !val.Equal(expiry) {
t.Error("Expiry was wrong:", val)
}
if val, _ := session.Get(authboss.SessionKey); val != "uid;fake" {
t.Error("User was not logged in:", val)
}
if w.Code != http.StatusFound {
t.Error("It should redirect")
} else if loc := w.Header().Get("Location"); loc != "/myurl?myparam=5" {
t.Error("Redirect is wrong:", loc)
}
}
func TestOAuthXSRFFailure(t *testing.T) {
cfg := authboss.NewConfig()
session := mocks.NewMockClientStorer()
session.Put(authboss.SessionOAuth2State, authboss.FormValueOAuth2State)
cfg.OAuth2Providers = testProviders
authboss.Cfg = cfg
values := url.Values{}
values.Set(authboss.FormValueOAuth2State, "notstate")
values.Set("code", "code")
r, _ := http.NewRequest("GET", "/oauth2/google?"+values.Encode(), nil)
ctx := authboss.NewContext()
ctx.SessionStorer = session
err := oauthCallback(ctx, nil, r)
if err != errOAuthStateValidation {
t.Error("Should have gotten an error about state validation:", err)
}
}
func TestOAuthFailure(t *testing.T) {
cfg := authboss.NewConfig()
cfg.OAuth2Providers = testProviders
authboss.Cfg = cfg
values := url.Values{}
values.Set("error", "something")
values.Set("error_reason", "auth_failure")
values.Set("error_description", "Failed to auth.")
r, _ := http.NewRequest("GET", "/oauth2/google?"+values.Encode(), nil)
err := oauthCallback(nil, nil, r)
if red, ok := err.(authboss.ErrAndRedirect); !ok {
t.Error("Should be a redirect error")
} else if len(red.FlashError) == 0 {
t.Error("Should have a flash error.")
} else if red.Err.Error() != "auth_failure" {
t.Error("It should record the failure.")
}
}