Add supprt for new Rails 5.2 aes-256-gcm cookies

This commit is contained in:
Vikram Rangnekar
2019-04-10 01:38:48 -04:00
parent 96adec81bd
commit 512647156e
18 changed files with 571 additions and 265 deletions

View File

@ -2,62 +2,47 @@ package serv
import (
"context"
"errors"
"net/http"
)
const (
salt = "encrypted cookie"
signSalt = "signed encrypted cookie"
emptySecret = ""
authHeader = "Authorization"
"strings"
)
var (
userIDProviderKey = struct{}{}
userIDKey = struct{}{}
errSessionData = errors.New("error decoding session data")
)
func headerHandler(next http.HandlerFunc) http.HandlerFunc {
fn := conf.Auth.Header
if len(fn) == 0 {
panic(errors.New("no auth.header defined"))
func headerAuth(r *http.Request, c *config) *http.Request {
if len(c.Auth.Header) == 0 {
return nil
}
return func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get(fn)
if len(userID) == 0 {
next.ServeHTTP(w, r)
return
}
userID := r.Header.Get(c.Auth.Header)
if len(userID) != 0 {
ctx := context.WithValue(r.Context(), userIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
return r.WithContext(ctx)
}
return nil
}
func withAuth(next http.HandlerFunc) http.HandlerFunc {
at := conf.Auth.Type
ru := conf.Auth.Rails.URL
switch at {
case "header":
return headerHandler(next)
case "rails":
if strings.HasPrefix(ru, "memcache:") {
return railsMemcacheHandler(next)
}
if strings.HasPrefix(ru, "redis:") {
return railsRedisHandler(next)
}
case "rails_cookie":
return railsCookieHandler(next)
case "rails_memcache":
return railsMemcacheHandler(next)
case "rails_redis":
return railsRedisHandler(next)
case "jwt":
return jwtHandler(next)
default:
return next
}
return next

View File

@ -10,7 +10,8 @@ import (
)
const (
jwtBase int = iota
authHeader = "Authorization"
jwtBase int = iota
jwtAuth0
)
@ -57,6 +58,11 @@ func jwtHandler(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var tok string
if rn := headerAuth(r, conf); rn != nil {
next.ServeHTTP(w, rn)
return
}
if len(cookie) != 0 {
ck, err := r.Cookie(cookie)
if err != nil {

View File

@ -2,14 +2,14 @@ package serv
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"github.com/adjust/gorails/marshal"
"github.com/adjust/gorails/session"
"github.com/bradfitz/gomemcache/memcache"
"github.com/dosco/super-graph/rails"
"github.com/garyburd/redigo/redis"
)
@ -19,21 +19,20 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
panic(errors.New("no auth.cookie defined"))
}
authURL := conf.Auth.RailsRedis.URL
if len(authURL) == 0 {
panic(errors.New("no auth.rails_redis.url defined"))
if len(conf.Auth.Rails.URL) == 0 {
log.Fatal(errors.New("no auth.rails.url defined"))
}
rp := &redis.Pool{
MaxIdle: conf.Auth.RailsRedis.MaxIdle,
MaxActive: conf.Auth.RailsRedis.MaxActive,
MaxIdle: conf.Auth.Rails.MaxIdle,
MaxActive: conf.Auth.Rails.MaxActive,
Dial: func() (redis.Conn, error) {
c, err := redis.DialURL(authURL)
c, err := redis.DialURL(conf.Auth.Rails.URL)
if err != nil {
panic(err)
}
pwd := conf.Auth.RailsRedis.Password
pwd := conf.Auth.Rails.Password
if len(pwd) != 0 {
if _, err := c.Do("AUTH", pwd); err != nil {
panic(err)
@ -44,6 +43,11 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
}
return func(w http.ResponseWriter, r *http.Request) {
if rn := headerAuth(r, conf); rn != nil {
next.ServeHTTP(w, rn)
return
}
ck, err := r.Cookie(cookie)
if err != nil {
next.ServeHTTP(w, r)
@ -57,7 +61,7 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
return
}
userID, err := railsAuth(string(sessionData), emptySecret)
userID, err := rails.ParseCookie(string(sessionData))
if err != nil {
next.ServeHTTP(w, r)
return
@ -74,14 +78,23 @@ func railsMemcacheHandler(next http.HandlerFunc) http.HandlerFunc {
panic(errors.New("no auth.cookie defined"))
}
host := conf.Auth.RailsMemcache.Host
if len(host) == 0 {
panic(errors.New("no auth.rails_memcache.host defined"))
if len(conf.Auth.Rails.URL) == 0 {
log.Fatal(errors.New("no auth.rails.url defined"))
}
mc := memcache.New(host)
rURL, err := url.Parse(conf.Auth.Rails.URL)
if err != nil {
log.Fatal(err)
}
mc := memcache.New(rURL.Host)
return func(w http.ResponseWriter, r *http.Request) {
if rn := headerAuth(r, conf); rn != nil {
next.ServeHTTP(w, rn)
return
}
ck, err := r.Cookie(cookie)
if err != nil {
next.ServeHTTP(w, r)
@ -95,7 +108,7 @@ func railsMemcacheHandler(next http.HandlerFunc) http.HandlerFunc {
return
}
userID, err := railsAuth(string(item.Value), emptySecret)
userID, err := rails.ParseCookie(string(item.Value))
if err != nil {
next.ServeHTTP(w, r)
return
@ -112,11 +125,17 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc {
panic(errors.New("no auth.cookie defined"))
}
secret := conf.Auth.RailsCookie.SecretKeyBase
if len(secret) == 0 {
panic(errors.New("no auth.rails_cookie.secret_key_base defined"))
ra, err := railsAuth(conf)
if err != nil {
log.Fatal(err)
}
return func(w http.ResponseWriter, r *http.Request) {
if rn := headerAuth(r, conf); rn != nil {
next.ServeHTTP(w, rn)
return
}
ck, err := r.Cookie(cookie)
if err != nil {
logger.Error(err)
@ -124,7 +143,7 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc {
return
}
userID, err := railsAuth(ck.Value, secret)
userID, err := ra.ParseCookie(ck.Value)
if err != nil {
logger.Error(err)
next.ServeHTTP(w, r)
@ -136,98 +155,33 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc {
}
}
func railsAuth(cookie, secret string) (userID string, err error) {
var dcookie []byte
func railsAuth(c *config) (*rails.Auth, error) {
secret := c.Auth.Rails.SecretKeyBase
if len(secret) == 0 {
return nil, errors.New("no auth.rails.secret_key_base defined")
}
dcookie, err = session.DecryptSignedCookie(cookie, secret, salt, signSalt)
version := c.Auth.Rails.Version
if len(version) == 0 {
return nil, errors.New("no auth.rails.version defined")
}
ra, err := rails.NewAuth(version, secret)
if err != nil {
return
return nil, err
}
if dcookie[0] != '{' {
userID, err = getUserId4(dcookie)
} else {
userID, err = getUserId(dcookie)
if len(c.Auth.Rails.Salt) != 0 {
ra.Salt = c.Auth.Rails.Salt
}
return
}
func getUserId(data []byte) (userID string, err error) {
var sessionData map[string]interface{}
err = json.Unmarshal(data, &sessionData)
if err != nil {
return
}
userKey, ok := sessionData["warden.user.user.key"]
if !ok {
err = errors.New("key 'warden.user.user.key' not found in session data")
}
items, ok := userKey.([]interface{})
if !ok {
err = errSessionData
return
}
if len(items) != 2 {
err = errSessionData
return
}
uids, ok := items[0].([]interface{})
if !ok {
err = errSessionData
return
}
uid, ok := uids[0].(float64)
if !ok {
err = errSessionData
return
}
userID = fmt.Sprintf("%d", int64(uid))
return
}
func getUserId4(data []byte) (userID string, err error) {
sessionData, err := marshal.CreateMarshalledObject(data).GetAsMap()
if err != nil {
return
}
wardenData, ok := sessionData["warden.user.user.key"]
if !ok {
err = errSessionData
return
}
wardenUserKey, err := wardenData.GetAsArray()
if err != nil {
return
}
if len(wardenUserKey) < 1 {
err = errSessionData
return
}
userData, err := wardenUserKey[0].GetAsArray()
if err != nil {
return
}
if len(userData) < 1 {
err = errSessionData
return
}
uid, err := userData[0].GetAsInteger()
if err != nil {
return
}
userID = fmt.Sprintf("%d", uid)
return
if len(conf.Auth.Rails.SignSalt) != 0 {
ra.SignSalt = c.Auth.Rails.SignSalt
}
if len(conf.Auth.Rails.AuthSalt) != 0 {
ra.AuthSalt = c.Auth.Rails.AuthSalt
}
return ra, nil
}

View File

@ -1,49 +0,0 @@
package serv
import (
"testing"
)
func TestRailsEncryptedSession(t *testing.T) {
cookie := "dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09--75d8323b0f0e41cf4d5aabee1b229b1be76b83b6"
secret := "development_secret"
userID, err := railsAuth(cookie, secret)
if err != nil {
t.Error(err)
return
}
if userID != "1" {
t.Errorf("Expecting userID 1 got %s", userID)
}
}
func TestRailsJsonSession(t *testing.T) {
sessionData := `{"warden.user.user.key":[[1],"secret"]}`
userID, err := getUserId([]byte(sessionData))
if err != nil {
t.Error(err)
return
}
if userID != "1" {
t.Errorf("Expecting userID 1 got %s", userID)
}
}
func TestRailsMarshaledSession(t *testing.T) {
sessionData := "\x04\b{\bI\"\x15member_return_to\x06:\x06ETI\"\x06/\x06;\x00TI\"\x19warden.user.user.key\x06;\x00T[\a[\x06i\aI\"\"$2a$11$6SgXdvO9hld82kQAvpEY3e\x06;\x00TI\"\x10_csrf_token\x06;\x00FI\"17lqwj1UsTTgbXBQKH4ipCNW32uLusvfSPds1txppMec=\x06;\x00F"
userID, err := getUserId4([]byte(sessionData))
if err != nil {
t.Error(err)
return
}
if userID != "2" {
t.Errorf("Expecting userID 2 got %s", userID)
}
}

View File

@ -48,20 +48,17 @@ type config struct {
Cookie string
Header string
RailsCookie struct {
Rails struct {
Version string
SecretKeyBase string `mapstructure:"secret_key_base"`
} `mapstructure:"rails_cookie"`
RailsMemcache struct {
Host string
} `mapstructure:"rails_memcache"`
RailsRedis struct {
URL string
Password string
MaxIdle int `mapstructure:"max_idle"`
MaxActive int `mapstructure:"max_active"`
} `mapstructure:"rails_redis"`
URL string
Password string
MaxIdle int `mapstructure:"max_idle"`
MaxActive int `mapstructure:"max_active"`
Salt string
SignSalt string `mapstructure:"sign_salt"`
AuthSalt string `mapstructure:"auth_salt"`
}
JWT struct {
Provider string