1
0
mirror of https://github.com/volatiletech/authboss.git synced 2024-12-10 10:40:07 +02:00
authboss/oauth2/oauth2.go

244 lines
5.8 KiB
Go
Raw Normal View History

package oauth2
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"path"
"path/filepath"
"strings"
"golang.org/x/oauth2"
"gopkg.in/authboss.v0"
"gopkg.in/authboss.v0/internal/response"
)
var (
errOAuthStateValidation = errors.New("Could not validate oauth2 state param")
)
2015-03-16 23:42:45 +02:00
// OAuth2 module
2015-04-01 00:27:47 +02:00
type OAuth2 struct {
*authboss.Authboss
}
func init() {
authboss.RegisterModule("oauth2", &OAuth2{})
}
2015-03-16 23:42:45 +02:00
// Initialize module
2015-04-01 00:27:47 +02:00
func (o *OAuth2) Initialize(ab *authboss.Authboss) error {
o.Authboss = ab
if o.OAuth2Storer == nil {
return errors.New("oauth2: need an OAuth2Storer")
}
return nil
}
2015-03-16 23:42:45 +02:00
// Routes for module
func (o *OAuth2) Routes() authboss.RouteTable {
routes := make(authboss.RouteTable)
2015-04-01 00:27:47 +02:00
for prov, cfg := range o.OAuth2Providers {
prov = strings.ToLower(prov)
init := fmt.Sprintf("/oauth2/%s", prov)
callback := fmt.Sprintf("/oauth2/callback/%s", prov)
2015-04-01 00:27:47 +02:00
routes[init] = o.oauthInit
routes[callback] = o.oauthCallback
2015-04-01 00:27:47 +02:00
if len(o.MountPath) > 0 {
callback = path.Join(o.MountPath, callback)
}
2015-04-01 00:27:47 +02:00
cfg.OAuth2Config.RedirectURL = o.RootURL + callback
}
2015-04-01 00:27:47 +02:00
routes["/oauth2/logout"] = o.logout
2015-03-17 06:46:15 +02:00
return routes
}
2015-03-16 23:42:45 +02:00
// Storage requirements
func (o *OAuth2) Storage() authboss.StorageOptions {
return authboss.StorageOptions{
authboss.StoreEmail: authboss.String,
authboss.StoreOAuth2UID: authboss.String,
authboss.StoreOAuth2Provider: authboss.String,
authboss.StoreOAuth2Token: authboss.String,
authboss.StoreOAuth2Refresh: authboss.String,
authboss.StoreOAuth2Expiry: authboss.DateTime,
}
}
2015-04-01 00:27:47 +02:00
func (o *OAuth2) oauthInit(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
provider := strings.ToLower(filepath.Base(r.URL.Path))
2015-04-01 00:27:47 +02:00
cfg, ok := o.OAuth2Providers[provider]
if !ok {
return fmt.Errorf("OAuth2 provider %q not found", provider)
}
random := make([]byte, 32)
_, err := rand.Read(random)
if err != nil {
return err
}
state := base64.URLEncoding.EncodeToString(random)
ctx.SessionStorer.Put(authboss.SessionOAuth2State, state)
passAlongs := make(map[string]string)
for k, vals := range r.URL.Query() {
for _, val := range vals {
passAlongs[k] = val
}
}
if len(passAlongs) > 0 {
str, err := json.Marshal(passAlongs)
if err != nil {
return err
}
ctx.SessionStorer.Put(authboss.SessionOAuth2Params, string(str))
} else {
ctx.SessionStorer.Del(authboss.SessionOAuth2Params)
}
2015-04-01 00:27:47 +02:00
url := cfg.OAuth2Config.AuthCodeURL(state)
2015-04-01 00:27:47 +02:00
extraParams := cfg.AdditionalParams.Encode()
if len(extraParams) > 0 {
url = fmt.Sprintf("%s&%s", url, extraParams)
}
http.Redirect(w, r, url, http.StatusFound)
return nil
}
// for testing
var exchanger = (*oauth2.Config).Exchange
2015-04-01 00:27:47 +02:00
func (o *OAuth2) oauthCallback(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
provider := strings.ToLower(filepath.Base(r.URL.Path))
sessState, err := ctx.SessionStorer.GetErr(authboss.SessionOAuth2State)
ctx.SessionStorer.Del(authboss.SessionOAuth2State)
if err != nil {
return err
}
sessValues, ok := ctx.SessionStorer.Get(authboss.SessionOAuth2Params)
// Don't delete this value from session immediately, callbacks use this too
var values map[string]string
if ok {
if err := json.Unmarshal([]byte(sessValues), &values); err != nil {
return err
}
}
hasErr := r.FormValue("error")
if len(hasErr) > 0 {
2015-04-01 00:27:47 +02:00
if err := o.Callbacks.FireAfter(authboss.EventOAuthFail, ctx); err != nil {
return err
}
return authboss.ErrAndRedirect{
Err: errors.New(r.FormValue("error_reason")),
2015-04-01 00:27:47 +02:00
Location: o.AuthLoginFailPath,
FlashError: fmt.Sprintf("%s login cancelled or failed.", strings.Title(provider)),
}
}
2015-04-01 00:27:47 +02:00
cfg, ok := o.OAuth2Providers[provider]
if !ok {
return fmt.Errorf("OAuth2 provider %q not found", provider)
}
// Ensure request is genuine
state := r.FormValue(authboss.FormValueOAuth2State)
splState := strings.Split(state, ";")
if len(splState) == 0 || splState[0] != sessState {
return errOAuthStateValidation
}
// Get the code
code := r.FormValue("code")
2015-04-01 00:27:47 +02:00
token, err := exchanger(cfg.OAuth2Config, oauth2.NoContext, code)
if err != nil {
return fmt.Errorf("Could not validate oauth2 code: %v", err)
}
2015-04-01 00:27:47 +02:00
user, err := cfg.Callback(*cfg.OAuth2Config, token)
if err != nil {
return err
}
// OAuth2UID is required.
uid, err := user.StringErr(authboss.StoreOAuth2UID)
if err != nil {
return err
}
user[authboss.StoreOAuth2UID] = uid
user[authboss.StoreOAuth2Provider] = provider
user[authboss.StoreOAuth2Expiry] = token.Expiry
user[authboss.StoreOAuth2Token] = token.AccessToken
if len(token.RefreshToken) != 0 {
user[authboss.StoreOAuth2Refresh] = token.RefreshToken
}
2015-04-01 00:27:47 +02:00
if err = o.OAuth2Storer.PutOAuth(uid, provider, user); err != nil {
return err
}
// Fully log user in
ctx.SessionStorer.Put(authboss.SessionKey, fmt.Sprintf("%s;%s", uid, provider))
ctx.SessionStorer.Del(authboss.SessionHalfAuthKey)
2015-04-01 00:27:47 +02:00
if err = o.Callbacks.FireAfter(authboss.EventOAuth, ctx); err != nil {
return nil
}
ctx.SessionStorer.Del(authboss.SessionOAuth2Params)
2015-04-01 00:27:47 +02:00
redirect := o.AuthLoginOKPath
query := make(url.Values)
for k, v := range values {
switch k {
case authboss.CookieRemember:
case authboss.FormValueRedirect:
redirect = v
default:
query.Set(k, v)
}
}
if len(query) > 0 {
redirect = fmt.Sprintf("%s?%s", redirect, query.Encode())
}
sf := fmt.Sprintf("Logged in successfully with %s.", strings.Title(provider))
response.Redirect(ctx, w, r, redirect, sf, "", false)
return nil
}
2015-03-17 06:46:15 +02:00
2015-04-01 00:27:47 +02:00
func (o *OAuth2) logout(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
2015-03-17 06:46:15 +02:00
switch r.Method {
case "GET":
ctx.SessionStorer.Del(authboss.SessionKey)
ctx.CookieStorer.Del(authboss.CookieRemember)
ctx.SessionStorer.Del(authboss.SessionLastAction)
2015-04-01 00:27:47 +02:00
response.Redirect(ctx, w, r, o.AuthLogoutOKPath, "You have logged out", "", true)
2015-03-17 06:46:15 +02:00
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
return nil
}