mirror of
https://github.com/outbackdingo/databunker.git
synced 2026-01-27 18:18:43 +00:00
create admin request if user is changing admin records
This commit is contained in:
324
src/schema.go
324
src/schema.go
@@ -1,144 +1,180 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/paranoidguy/jsonschema"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
jptr "github.com/qri-io/jsonpointer"
|
||||
)
|
||||
|
||||
var userSchema *jsonschema.Schema
|
||||
|
||||
// our custom validator
|
||||
type IsLocked bool
|
||||
|
||||
func loadUserSchema(cfg Config, confFile *string) error {
|
||||
fileSchema := cfg.Generic.UserRecordSchema
|
||||
parentDir := ""
|
||||
if confFile != nil && len(*confFile) > 0 {
|
||||
parentDir = filepath.Base(*confFile)
|
||||
if parentDir != "." {
|
||||
parentDir = ""
|
||||
}
|
||||
}
|
||||
if len(fileSchema) == 0 {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(fileSchema, "./") {
|
||||
_, err := os.Stat(cfg.Generic.UserRecordSchema)
|
||||
if os.IsNotExist(err) && confFile != nil {
|
||||
fileSchema = parentDir + fileSchema[2:]
|
||||
}
|
||||
} else {
|
||||
fileSchema = parentDir + fileSchema
|
||||
}
|
||||
_, err := os.Stat(fileSchema)
|
||||
if os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
schemaData, err := ioutil.ReadFile(fileSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rs := &jsonschema.Schema{}
|
||||
jsonschema.LoadDraft2019_09()
|
||||
jsonschema.RegisterKeyword("locked", newIsLocked)
|
||||
err = rs.UnmarshalJSON(schemaData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userSchema = rs
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUserEnabled() bool {
|
||||
if userSchema == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ValidateUserRecord(record []byte) error {
|
||||
if userSchema == nil {
|
||||
return nil
|
||||
}
|
||||
var doc interface{}
|
||||
if err := json.Unmarshal(record, &doc); err != nil {
|
||||
return err
|
||||
}
|
||||
result := userSchema.Validate(nil, doc)
|
||||
if len(*result.Errs) > 0 {
|
||||
return (*result.Errs)[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUserRecordChange(oldRecord []byte, newRecord []byte) error {
|
||||
if userSchema == nil {
|
||||
return nil
|
||||
}
|
||||
var oldDoc interface{}
|
||||
var newDoc interface{}
|
||||
if err := json.Unmarshal(oldRecord, &oldDoc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(newRecord, &newDoc); err != nil {
|
||||
return err
|
||||
}
|
||||
result := userSchema.Validate(nil, newDoc)
|
||||
//if len(*result.Errs) > 0 {
|
||||
// return (*result.Errs)[0]
|
||||
//}
|
||||
result2 := userSchema.Validate(nil, oldDoc)
|
||||
if len(*result2.Errs) > 0 {
|
||||
return (*result.Errs)[0]
|
||||
}
|
||||
for _, r := range *result.ExtendedResults {
|
||||
fmt.Printf("path: %s key: %s data: %v\n", r.PropertyPath, r.Key, r.Value)
|
||||
if r.Key == "locked" {
|
||||
pointer, _ := jptr.Parse(r.PropertyPath)
|
||||
data1, _ := pointer.Eval(oldDoc)
|
||||
data1Binary, _ := json.Marshal(data1)
|
||||
data2, _ := pointer.Eval(newDoc)
|
||||
data2Binary, _ := json.Marshal(data2)
|
||||
if !jsonpatch.Equal(data1Binary, data2Binary) {
|
||||
fmt.Printf("Locked value changed. Old: %s New %s\n", data1Binary, data2Binary)
|
||||
return errors.New("User schema check error. Locked value changed: "+r.PropertyPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func newIsLocked() jsonschema.Keyword {
|
||||
return new(IsLocked)
|
||||
}
|
||||
|
||||
// Validate implements jsonschema.Keyword
|
||||
func (f *IsLocked) Validate(propPath string, data interface{}, errs *[]jsonschema.KeyError) {
|
||||
fmt.Printf("Validate: %s -> %v\n", propPath, data)
|
||||
}
|
||||
|
||||
// Register implements jsonschema.Keyword
|
||||
func (f *IsLocked) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
fmt.Printf("Register %s\n", uri)
|
||||
}
|
||||
|
||||
// Resolve implements jsonschema.Keyword
|
||||
func (f *IsLocked) Resolve(pointer jptr.Pointer, uri string) *jsonschema.Schema {
|
||||
fmt.Printf("Resolve %s\n", uri)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *IsLocked) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
|
||||
fmt.Printf("ValidateKeyword locked %s => %v\n", currentState.InstanceLocation.String(), data)
|
||||
currentState.AddExtendedResult("locked", data)
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/paranoidguy/jsonschema"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
jptr "github.com/qri-io/jsonpointer"
|
||||
)
|
||||
|
||||
var userSchema *jsonschema.Schema
|
||||
|
||||
// our custom validator
|
||||
type IsLocked bool
|
||||
type IsAdmin bool
|
||||
|
||||
func loadUserSchema(cfg Config, confFile *string) error {
|
||||
fileSchema := cfg.Generic.UserRecordSchema
|
||||
parentDir := ""
|
||||
if confFile != nil && len(*confFile) > 0 {
|
||||
parentDir = filepath.Base(*confFile)
|
||||
if parentDir != "." {
|
||||
parentDir = ""
|
||||
}
|
||||
}
|
||||
if len(fileSchema) == 0 {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(fileSchema, "./") {
|
||||
_, err := os.Stat(cfg.Generic.UserRecordSchema)
|
||||
if os.IsNotExist(err) && confFile != nil {
|
||||
fileSchema = parentDir + fileSchema[2:]
|
||||
}
|
||||
} else {
|
||||
fileSchema = parentDir + fileSchema
|
||||
}
|
||||
_, err := os.Stat(fileSchema)
|
||||
if os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
schemaData, err := ioutil.ReadFile(fileSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rs := &jsonschema.Schema{}
|
||||
jsonschema.LoadDraft2019_09()
|
||||
jsonschema.RegisterKeyword("locked", newIsLocked)
|
||||
jsonschema.RegisterKeyword("admin", newIsAdmin)
|
||||
err = rs.UnmarshalJSON(schemaData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userSchema = rs
|
||||
return nil
|
||||
}
|
||||
|
||||
func UserSchemaEnabled() bool {
|
||||
if userSchema == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ValidateUserRecord(record []byte) error {
|
||||
if userSchema == nil {
|
||||
return nil
|
||||
}
|
||||
var doc interface{}
|
||||
if err := json.Unmarshal(record, &doc); err != nil {
|
||||
return err
|
||||
}
|
||||
result := userSchema.Validate(nil, doc)
|
||||
if len(*result.Errs) > 0 {
|
||||
return (*result.Errs)[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUserRecordChange(oldRecord []byte, newRecord []byte, authResult string) (bool, error) {
|
||||
if userSchema == nil {
|
||||
return false, nil
|
||||
}
|
||||
var oldDoc interface{}
|
||||
var newDoc interface{}
|
||||
if err := json.Unmarshal(oldRecord, &oldDoc); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := json.Unmarshal(newRecord, &newDoc); err != nil {
|
||||
return false, err
|
||||
}
|
||||
result := userSchema.Validate(nil, newDoc)
|
||||
//if len(*result.Errs) > 0 {
|
||||
// return (*result.Errs)[0]
|
||||
//}
|
||||
result2 := userSchema.Validate(nil, oldDoc)
|
||||
if len(*result2.Errs) > 0 {
|
||||
return false, (*result.Errs)[0]
|
||||
}
|
||||
if result.ExtendedResults == nil {
|
||||
return false, nil
|
||||
}
|
||||
adminRecordChanged := false
|
||||
for _, r := range *result.ExtendedResults {
|
||||
fmt.Printf("path: %s key: %s data: %v\n", r.PropertyPath, r.Key, r.Value)
|
||||
if r.Key == "locked" || (r.Key == "admin" && authResult == "login") {
|
||||
pointer, _ := jptr.Parse(r.PropertyPath)
|
||||
data1, _ := pointer.Eval(oldDoc)
|
||||
data1Binary, _ := json.Marshal(data1)
|
||||
data2, _ := pointer.Eval(newDoc)
|
||||
data2Binary, _ := json.Marshal(data2)
|
||||
if !jsonpatch.Equal(data1Binary, data2Binary) {
|
||||
if r.Key == "locked" {
|
||||
fmt.Printf("Locked value changed. Old: %s New %s\n", data1Binary, data2Binary)
|
||||
return false, errors.New("User schema check error. Locked value changed: "+r.PropertyPath)
|
||||
} else {
|
||||
fmt.Printf("Admin value changed. Approval required. Old: %s New %s\n", data1Binary, data2Binary)
|
||||
adminRecordChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return adminRecordChanged, nil
|
||||
}
|
||||
|
||||
// Locked keyword - meaningin that value should never be changed after record created
|
||||
func newIsLocked() jsonschema.Keyword {
|
||||
return new(IsLocked)
|
||||
}
|
||||
|
||||
// Validate implements jsonschema.Keyword
|
||||
func (f *IsLocked) Validate(propPath string, data interface{}, errs *[]jsonschema.KeyError) {
|
||||
fmt.Printf("Validate: %s -> %v\n", propPath, data)
|
||||
}
|
||||
|
||||
// Register implements jsonschema.Keyword
|
||||
func (f *IsLocked) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
}
|
||||
|
||||
// Resolve implements jsonschema.Keyword
|
||||
func (f *IsLocked) Resolve(pointer jptr.Pointer, uri string) *jsonschema.Schema {
|
||||
fmt.Printf("Resolve %s\n", uri)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *IsLocked) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
|
||||
fmt.Printf("ValidateKeyword locked %s => %v\n", currentState.InstanceLocation.String(), data)
|
||||
currentState.AddExtendedResult("locked", data)
|
||||
}
|
||||
|
||||
// Admin keyword. Any change in this record requires admin approval.
|
||||
func newIsAdmin() jsonschema.Keyword {
|
||||
return new(IsAdmin)
|
||||
}
|
||||
|
||||
// Validate implements jsonschema.Keyword
|
||||
func (f *IsAdmin) Validate(propPath string, data interface{}, errs *[]jsonschema.KeyError) {
|
||||
fmt.Printf("Validate: %s -> %v\n", propPath, data)
|
||||
}
|
||||
|
||||
// Register implements jsonschema.Keyword
|
||||
func (f *IsAdmin) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
}
|
||||
|
||||
// Resolve implements jsonschema.Keyword
|
||||
func (f *IsAdmin) Resolve(pointer jptr.Pointer, uri string) *jsonschema.Schema {
|
||||
fmt.Printf("Resolve %s\n", uri)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *IsAdmin) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
|
||||
fmt.Printf("ValidateKeyword admin %s => %v\n", currentState.InstanceLocation.String(), data)
|
||||
currentState.AddExtendedResult("admin", data)
|
||||
}
|
||||
|
||||
@@ -189,8 +189,9 @@ func (e mainEnv) userChange(w http.ResponseWriter, r *http.Request, ps httproute
|
||||
if authResult == "" {
|
||||
return
|
||||
}
|
||||
if ValidateUserEnabled() {
|
||||
err = e.db.validateUserRecordChange(parsedData.jsonData, userTOKEN)
|
||||
adminRecordChanged := false
|
||||
if UserSchemaEnabled() {
|
||||
adminRecordChanged, err = e.db.validateUserRecordChange(parsedData.jsonData, userTOKEN, authResult)
|
||||
if err != nil {
|
||||
returnError(w, r, "schema validation error: " + err.Error(), 405, err, event)
|
||||
return
|
||||
@@ -198,7 +199,7 @@ func (e mainEnv) userChange(w http.ResponseWriter, r *http.Request, ps httproute
|
||||
}
|
||||
if authResult == "login" {
|
||||
event.Title = "user change-profile request"
|
||||
if e.conf.SelfService.UserRecordChange == false {
|
||||
if e.conf.SelfService.UserRecordChange == false || adminRecordChanged == true {
|
||||
rtoken, err := e.db.saveUserRequest("change-profile", userTOKEN, "", "", parsedData.jsonData)
|
||||
if err != nil {
|
||||
returnError(w, r, "internal error", 405, err, event)
|
||||
|
||||
@@ -88,40 +88,36 @@ func (dbobj dbcon) generateDemoLoginCode(userTOKEN string) int32 {
|
||||
return rnd
|
||||
}
|
||||
|
||||
func (dbobj dbcon) validateUserRecordChange(jsonDataPatch []byte, userTOKEN string) error {
|
||||
func (dbobj dbcon) validateUserRecordChange(jsonDataPatch []byte, userTOKEN string, authResult string) (bool, error) {
|
||||
oldUserBson, err := dbobj.lookupUserRecord(userTOKEN)
|
||||
if oldUserBson == nil || err != nil {
|
||||
// not found
|
||||
return errors.New("not found")
|
||||
return false, errors.New("not found")
|
||||
}
|
||||
// get user key
|
||||
userKey := oldUserBson["key"].(string)
|
||||
recordKey, err := base64.StdEncoding.DecodeString(userKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
encData0 := oldUserBson["data"].(string)
|
||||
encData, err := base64.StdEncoding.DecodeString(encData0)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
decrypted, err := decrypt(dbobj.masterKey, recordKey, encData)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
// prepare merge
|
||||
fmt.Printf("old json: %s\n", decrypted)
|
||||
fmt.Printf("json patch: %s\n", jsonDataPatch)
|
||||
newJSON, err := jsonpatch.MergePatch(decrypted, jsonDataPatch)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
fmt.Printf("result: %s\n", newJSON)
|
||||
err = ValidateUserRecordChange(decrypted, newJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ValidateUserRecordChange(decrypted, newJSON, authResult)
|
||||
}
|
||||
|
||||
func (dbobj dbcon) updateUserRecord(jsonDataPatch []byte, userTOKEN string, event *auditEvent, conf Config) ([]byte, []byte, bool, error) {
|
||||
|
||||
Reference in New Issue
Block a user