create admin request if user is changing admin records

This commit is contained in:
Yuli
2020-06-14 13:55:37 +03:00
parent 3f280377ed
commit f640d40a79
4 changed files with 195 additions and 160 deletions

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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) {

View File

@@ -23,11 +23,13 @@
"email": {
"type": "string",
"minLength": 1,
"format": "email"
"format": "email",
"admin": true
},
"phone": {
"type": "string",
"minLength": 1
"minLength": 1,
"admin": true
},
"status": {
"type": "string",