From e5a98d4c0c3d26b8c63d657be962ce36c9040729 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Wed, 22 Feb 2017 01:42:45 +0000 Subject: [PATCH] Improve websocket RPC API --- web/auth.go | 366 +++++++++++++++++++++++++++++----------------------- web/rpc.go | 25 +++- 2 files changed, 228 insertions(+), 163 deletions(-) diff --git a/web/auth.go b/web/auth.go index 0cb69ea0..c4755de8 100644 --- a/web/auth.go +++ b/web/auth.go @@ -43,16 +43,18 @@ func cidr(ip net.IP, networks []*net.IPNet) bool { } func auth() fibre.MiddlewareFunc { - - user := []byte(cnf.Settings.Auth.User) - pass := []byte(cnf.Settings.Auth.Pass) - return func(h fibre.HandlerFunc) fibre.HandlerFunc { return func(c *fibre.Context) (err error) { auth := &cnf.Auth{} c.Set("auth", auth) + // Ensure that the authentication data + // object is initiated at the beginning + // so it is present when serialized. + + auth.Data = make(map[string]interface{}) + // Start off with an authentication level // which prevents running any sql queries, // and denies access to all data. @@ -114,9 +116,12 @@ func auth() fibre.MiddlewareFunc { // which might contain authn information. if len(head) == 0 { - for _, val := range websocket.Subprotocols(c.Request().Request) { - if len(val) > 7 && val[0:7] == "bearer-" { - head = "Bearer " + val[7:] + for _, prot := range websocket.Subprotocols(c.Request().Request) { + if len(prot) > 7 && prot[0:7] == "bearer-" { + head = "Bearer " + prot[7:] + return checkBearer(c, prot[7:], func() error { + return h(c) + }) } } } @@ -126,22 +131,9 @@ func auth() fibre.MiddlewareFunc { // process this as root authentication. if len(head) > 0 && head[:5] == "Basic" { - - base, err := base64.StdEncoding.DecodeString(head[6:]) - - if err == nil && cidr(c.IP(), cnf.Settings.Auth.Nets) { - - cred := bytes.SplitN(base, []byte(":"), 2) - - if len(cred) == 2 && bytes.Equal(cred[0], user) && bytes.Equal(cred[1], pass) { - auth.Kind = sql.AuthKV - auth.Possible.NS = "*" - auth.Possible.DB = "*" - return h(c) - } - - } - + return checkMaster(c, head[6:], func() error { + return h(c) + }) } // Check whether the Authorization header @@ -149,146 +141,9 @@ func auth() fibre.MiddlewareFunc { // process this as default authentication. if len(head) > 0 && head[:6] == "Bearer" { - - var txn kvs.TX - var vars jwt.MapClaims - var nok, dok, sok, tok, uok bool - var nsv, dbv, scv, tkv, usv string - - // Start a new read transaction. - - if txn, err = db.Begin(false); err != nil { - return fibre.NewHTTPError(500) - } - - // Ensure the transaction closes. - - defer txn.Cancel() - - // Parse the specified JWT Token. - - token, err := jwt.Parse(head[7:], func(token *jwt.Token) (interface{}, error) { - - vars = token.Claims.(jwt.MapClaims) - - if err := vars.Valid(); err != nil { - return nil, err - } - - if val, ok := vars["auth"].(map[string]interface{}); ok { - auth.Data = val - } - - nsv, nok = vars["NS"].(string) // Namespace - dbv, dok = vars["DB"].(string) // Database - scv, sok = vars["SC"].(string) // Scope - tkv, tok = vars["TK"].(string) // Token - usv, uok = vars["US"].(string) // Login - - if tkv == "default" { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method") - } - } - - if nok && dok && sok && tok { - - scp, err := mem.New(txn).GetSC(nsv, dbv, scv) - if err != nil { - fmt.Errorf("Credentials failed") - } - - auth.Data["scope"] = scp.Name - - if tkv != "default" { - key, err := mem.New(txn).GetST(nsv, dbv, scv, tkv) - if err != nil { - fmt.Errorf("Credentials failed") - } - if token.Header["alg"] != key.Type { - return nil, fmt.Errorf("Unexpected signing method") - } - auth.Kind = sql.AuthSC - return key.Code, nil - } else { - auth.Kind = sql.AuthSC - return scp.Code, nil - } - - } else if nok && dok && tok { - - if tkv != "default" { - key, err := mem.New(txn).GetDT(nsv, dbv, tkv) - if err != nil { - fmt.Errorf("Credentials failed") - } - if token.Header["alg"] != key.Type { - return nil, fmt.Errorf("Unexpected signing method") - } - auth.Kind = sql.AuthDB - return key.Code, nil - } else if uok { - usr, err := mem.New(txn).GetDU(nsv, dbv, usv) - if err != nil { - fmt.Errorf("Credentials failed") - } - auth.Kind = sql.AuthDB - return usr.Code, nil - } - - } else if nok && tok { - - if tkv != "default" { - key, err := mem.New(txn).GetNT(nsv, tkv) - if err != nil { - fmt.Errorf("Credentials failed") - } - if token.Header["alg"] != key.Type { - return nil, fmt.Errorf("Unexpected signing method") - } - auth.Kind = sql.AuthNS - return key.Code, nil - } else if uok { - usr, err := mem.New(txn).GetNU(nsv, usv) - if err != nil { - fmt.Errorf("Credentials failed") - } - auth.Kind = sql.AuthNS - return usr.Code, nil - } - - } - - return nil, fmt.Errorf("No available token") - - }) - - if err == nil && token.Valid { - - if auth.Kind == sql.AuthNS { - auth.Possible.NS = nsv - auth.Selected.NS = nsv - auth.Possible.DB = "*" - } - - if auth.Kind == sql.AuthDB { - auth.Possible.NS = nsv - auth.Selected.NS = nsv - auth.Possible.DB = dbv - auth.Selected.DB = dbv - } - - if auth.Kind == sql.AuthSC { - auth.Possible.NS = nsv - auth.Selected.NS = nsv - auth.Possible.DB = dbv - auth.Selected.DB = dbv - } - + return checkBearer(c, head[6:], func() error { return h(c) - - } - + }) } return h(c) @@ -296,3 +151,190 @@ func auth() fibre.MiddlewareFunc { } } } + +func checkRoot(c *fibre.Context, user, pass string, callback func() error) (err error) { + + auth := c.Get("auth").(*cnf.Auth) + + if cidr(c.IP(), cnf.Settings.Auth.Nets) { + + if user == cnf.Settings.Auth.User && pass == cnf.Settings.Auth.Pass { + auth.Kind = sql.AuthKV + auth.Possible.NS = "*" + auth.Possible.DB = "*" + } + + } + + return callback() + +} + +func checkMaster(c *fibre.Context, info string, callback func() error) (err error) { + + auth := c.Get("auth").(*cnf.Auth) + user := []byte(cnf.Settings.Auth.User) + pass := []byte(cnf.Settings.Auth.Pass) + + base, err := base64.StdEncoding.DecodeString(info) + + if err == nil && cidr(c.IP(), cnf.Settings.Auth.Nets) { + + cred := bytes.SplitN(base, []byte(":"), 2) + + if len(cred) == 2 && bytes.Equal(cred[0], user) && bytes.Equal(cred[1], pass) { + auth.Kind = sql.AuthKV + auth.Possible.NS = "*" + auth.Possible.DB = "*" + } + + } + + return callback() + +} + +func checkBearer(c *fibre.Context, info string, callback func() error) (err error) { + + auth := c.Get("auth").(*cnf.Auth) + + var txn kvs.TX + var vars jwt.MapClaims + var nok, dok, sok, tok, uok bool + var nsv, dbv, scv, tkv, usv string + + // Start a new read transaction. + + if txn, err = db.Begin(false); err != nil { + return fibre.NewHTTPError(500) + } + + // Ensure the transaction closes. + + defer txn.Cancel() + + // Parse the specified JWT Token. + + token, err := jwt.Parse(info, func(token *jwt.Token) (interface{}, error) { + + vars = token.Claims.(jwt.MapClaims) + + if err := vars.Valid(); err != nil { + return nil, err + } + + if val, ok := vars["auth"].(map[string]interface{}); ok { + auth.Data = val + } + + nsv, nok = vars["NS"].(string) // Namespace + dbv, dok = vars["DB"].(string) // Database + scv, sok = vars["SC"].(string) // Scope + tkv, tok = vars["TK"].(string) // Token + usv, uok = vars["US"].(string) // Login + + if tkv == "default" { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method") + } + } + + if nok && dok && sok && tok { + + scp, err := mem.New(txn).GetSC(nsv, dbv, scv) + if err != nil { + return nil, fmt.Errorf("Credentials failed") + } + + auth.Data["scope"] = scp.Name + + if tkv != "default" { + key, err := mem.New(txn).GetST(nsv, dbv, scv, tkv) + if err != nil { + return nil, fmt.Errorf("Credentials failed") + } + if token.Header["alg"] != key.Type { + return nil, fmt.Errorf("Unexpected signing method") + } + auth.Kind = sql.AuthSC + return key.Code, nil + } else { + auth.Kind = sql.AuthSC + return scp.Code, nil + } + + } else if nok && dok && tok { + + if tkv != "default" { + key, err := mem.New(txn).GetDT(nsv, dbv, tkv) + if err != nil { + return nil, fmt.Errorf("Credentials failed") + } + if token.Header["alg"] != key.Type { + return nil, fmt.Errorf("Unexpected signing method") + } + auth.Kind = sql.AuthDB + return key.Code, nil + } else if uok { + usr, err := mem.New(txn).GetDU(nsv, dbv, usv) + if err != nil { + return nil, fmt.Errorf("Credentials failed") + } + auth.Kind = sql.AuthDB + return usr.Code, nil + } + + } else if nok && tok { + + if tkv != "default" { + key, err := mem.New(txn).GetNT(nsv, tkv) + if err != nil { + return nil, fmt.Errorf("Credentials failed") + } + if token.Header["alg"] != key.Type { + return nil, fmt.Errorf("Unexpected signing method") + } + auth.Kind = sql.AuthNS + return key.Code, nil + } else if uok { + usr, err := mem.New(txn).GetNU(nsv, usv) + if err != nil { + return nil, fmt.Errorf("Credentials failed") + } + auth.Kind = sql.AuthNS + return usr.Code, nil + } + + } + + return nil, fmt.Errorf("No available token") + + }) + + if err == nil && token.Valid { + + if auth.Kind == sql.AuthNS { + auth.Possible.NS = nsv + auth.Selected.NS = nsv + auth.Possible.DB = "*" + } + + if auth.Kind == sql.AuthDB { + auth.Possible.NS = nsv + auth.Selected.NS = nsv + auth.Possible.DB = dbv + auth.Selected.DB = dbv + } + + if auth.Kind == sql.AuthSC { + auth.Possible.NS = nsv + auth.Selected.NS = nsv + auth.Possible.DB = dbv + auth.Selected.DB = dbv + } + + } + + return callback() + +} diff --git a/web/rpc.go b/web/rpc.go index 27afa667..b55a090c 100644 --- a/web/rpc.go +++ b/web/rpc.go @@ -22,7 +22,15 @@ import ( type rpc struct{} -func (r *rpc) Sql(c *fibre.Context, sql string, vars map[string]interface{}) (interface{}, error) { +func (r *rpc) Info(c *fibre.Context) (interface{}, error) { + return c.Get("auth"), nil +} + +func (r *rpc) Auth(c *fibre.Context, auth string) (interface{}, error) { + return nil, checkBearer(c, auth, func() error { return nil }) +} + +func (r *rpc) Query(c *fibre.Context, sql string, vars map[string]interface{}) (interface{}, error) { return db.Execute(c, sql, vars) } @@ -69,6 +77,21 @@ func (r *rpc) Update(c *fibre.Context, class string, thing interface{}, data map } } +func (r *rpc) Change(c *fibre.Context, class string, thing interface{}, data map[string]interface{}) (interface{}, error) { + switch thing.(type) { + case *fibre.RPCNull: + return db.Execute(c, "UPDATE $class MERGE $data RETURN AFTER", map[string]interface{}{ + "class": sql.NewTable(class), + "data": data, + }) + default: + return db.Execute(c, "UPDATE $thing MERGE $data RETURN AFTER", map[string]interface{}{ + "thing": sql.NewThing(class, thing), + "data": data, + }) + } +} + func (r *rpc) Modify(c *fibre.Context, class string, thing interface{}, data map[string]interface{}) (interface{}, error) { switch thing.(type) { case *fibre.RPCNull: