package main import ( "crypto/md5" "encoding/base64" "encoding/json" "errors" "strings" jsonpatch "github.com/evanphx/json-patch" "github.com/securitybunker/databunker/src/audit" "github.com/securitybunker/databunker/src/storage" "github.com/securitybunker/databunker/src/utils" "go.mongodb.org/mongo-driver/bson" ) func (dbobj dbcon) getUserApp(userTOKEN string, userBSON map[string]interface{}, appName string, conf Config) ([]byte, error) { appNameFull := "app_" + appName var record bson.M var err error if conf.Generic.UseSeparateAppTables == true { record, err = dbobj.store.GetRecordFromTable(appNameFull, "token", userTOKEN) } else { record, err = dbobj.store.GetRecord2(storage.TblName.Userapps, "token", userTOKEN, "appname", appName) } if err != nil { return nil, err } if record == nil { return nil, nil } encData0 := record["data"].(string) return dbobj.userDecrypt(userBSON, encData0) } func (dbobj dbcon) deleteUserApp(userTOKEN string, appName string, conf Config) { appNameFull := "app_" + appName if conf.Generic.UseSeparateAppTables == true { dbobj.store.DeleteRecordInTable(appNameFull, "token", userTOKEN) } else { dbobj.store.DeleteRecord2(storage.TblName.Userapps, "token", userTOKEN, "appname", appName) } } func (dbobj dbcon) deleteUserApps(userTOKEN string, conf Config) { if conf.Generic.UseSeparateAppTables == true { userApps, _ := dbobj.listAllAppsOnly(conf) // delete all user app records for _, appName := range userApps { appNameFull := "app_" + appName dbobj.store.DeleteRecordInTable(appNameFull, "token", userTOKEN) } } else { dbobj.store.DeleteRecord(storage.TblName.Userapps, "token", userTOKEN) } } func (dbobj dbcon) createAppRecord(jsonData []byte, userTOKEN string, userBSON map[string]interface{}, appName string, event *audit.AuditEvent, conf Config) (string, error) { appNameFull := "app_" + appName //log.Printf("Going to create app record: %s\n", appName) encodedStr, err := dbobj.userEncrypt(userBSON, jsonData) if err != nil { return userTOKEN, err } if conf.Generic.UseSeparateAppTables == true { dbobj.store.CreateNewAppTable(appNameFull) } //var bdoc interface{} bdoc := bson.M{} bdoc["data"] = encodedStr //it is ok to use md5 here, it is only for data sanity md5Hash := md5.Sum([]byte(encodedStr)) bdoc["md5"] = base64.StdEncoding.EncodeToString(md5Hash[:]) bdoc["token"] = userTOKEN if event != nil { event.After = encodedStr event.App = appName event.Record = userTOKEN } //fmt.Println("creating new app") if conf.Generic.UseSeparateAppTables == true { record, err := dbobj.store.GetRecordFromTable(appNameFull, "token", userTOKEN) if err != nil { return userTOKEN, err } if record != nil { _, err = dbobj.store.UpdateRecordInTable(appNameFull, "token", userTOKEN, &bdoc) } else { _, err = dbobj.store.CreateRecordInTable(appNameFull, bdoc) } } else { record, err := dbobj.store.GetRecord2(storage.TblName.Userapps, "token", userTOKEN, "appname", appName) if err != nil { return userTOKEN, err } if record != nil { _, err = dbobj.store.UpdateRecord2(storage.TblName.Userapps, "token", userTOKEN, "appname", appName, &bdoc, nil) } else { bdoc["appname"] = appName _, err = dbobj.store.CreateRecord(storage.TblName.Userapps, &bdoc) } } return userTOKEN, err } func (dbobj dbcon) updateAppRecord(jsonDataPatch []byte, userTOKEN string, userBSON map[string]interface{}, appName string, event *audit.AuditEvent, conf Config) (string, error) { //_, err = collection.InsertOne(context.TODO(), bson.M{"name": "The Go Language2", "genre": "Coding", "authorId": "4"}) appNameFull := "app_" + appName // get user key userKey := userBSON["key"].(string) recordKey, err := base64.StdEncoding.DecodeString(userKey) if err != nil { return userTOKEN, err } var record bson.M if conf.Generic.UseSeparateAppTables == true { record, err = dbobj.store.GetRecordFromTable(appNameFull, "token", userTOKEN) } else { record, err = dbobj.store.GetRecord2(storage.TblName.Userapps, "token", userTOKEN, "appname", appName) } if err != nil { return userTOKEN, err } if record == nil { return userTOKEN, errors.New("user app record not found") } sig := record["md5"].(string) encData0 := record["data"].(string) encData, err := base64.StdEncoding.DecodeString(encData0) if err != nil { return userTOKEN, err } decrypted, err := utils.Decrypt(dbobj.masterKey, recordKey, encData) if err != nil { return userTOKEN, err } // merge //fmt.Printf("old json: %s\n", decrypted) //fmt.Printf("json patch: %s\n", jsonDataPatch) var newJSON []byte if jsonDataPatch[0] == '{' { newJSON, err = jsonpatch.MergePatch(decrypted, jsonDataPatch) } else { patch, err := jsonpatch.DecodePatch(jsonDataPatch) if err != nil { return userTOKEN, err } newJSON, err = patch.Apply(decrypted) } if err != nil { return userTOKEN, err } //fmt.Printf("result: %s\n", newJSON) bdoc := bson.M{} encoded, err := utils.Encrypt(dbobj.masterKey, recordKey, newJSON) if err != nil { return userTOKEN, err } encodedStr := base64.StdEncoding.EncodeToString(encoded) bdoc["data"] = encodedStr //it is ok to use md5 here, it is only for data sanity md5Hash := md5.Sum([]byte(encodedStr)) bdoc["md5"] = base64.StdEncoding.EncodeToString(md5Hash[:]) bdoc["token"] = userTOKEN // here I add md5 of the original record to filter // to make sure this record was not change by other thread result := int64(0) if conf.Generic.UseSeparateAppTables == true { result, err = dbobj.store.UpdateRecordInTable2(appNameFull, "token", userTOKEN, "md5", sig, &bdoc, nil) } else { result, err = dbobj.store.UpdateRecord2(storage.TblName.Userapps, "token", userTOKEN, "appname", appName, &bdoc, nil) } if err != nil { return userTOKEN, err } if event != nil { event.Before = encData0 event.After = encodedStr if result > 0 { event.Status = "ok" } else { event.Status = "failed" event.Msg = "failed to update" } } return userTOKEN, nil } // go over app collections and check if we have user record inside func (dbobj dbcon) listUserApps(userTOKEN string, conf Config) ([]byte, error) { //_, err = collection.InsertOne(context.TODO(), bson.M{"name": "The Go Language2", "genre": "Coding", "authorId": "4"}) var result []string if conf.Generic.UseSeparateAppTables == true { allCollections, err := dbobj.store.GetAllTables() if err != nil { return nil, err } var result []string for _, colName := range allCollections { if strings.HasPrefix(colName, "app_") { record, err := dbobj.store.GetRecordFromTable(colName, "token", userTOKEN) if err != nil { return nil, err } if record != nil { result = append(result, colName[4:]) } } } } else { records, err := dbobj.store.GetList(storage.TblName.Userapps, "token", userTOKEN, 0, 0, "appname") if err != nil { return nil, err } count := len(records) if count == 0 { return []byte("[]"), nil } for _, rec := range records { appname := rec["appname"].(string) result = append(result, appname) } } if len(result) == 0 { return []byte("[]"), nil } resultJSON, err := json.Marshal(result) return resultJSON, err } func (dbobj dbcon) dumpUserApps(userTOKEN string, conf Config) ([]byte, error) { results := make(map[string]interface{}) if conf.Generic.UseSeparateAppTables == true { allCollections, err := dbobj.store.GetAllTables() if err != nil { return nil, err } for _, colName := range allCollections { if strings.HasPrefix(colName, "app_") { record, err := dbobj.store.GetRecordFromTable(colName, "token", userTOKEN) if err != nil { return nil, err } if record != nil { results[colName[4:]] = record } } } } else { records, err := dbobj.store.GetList(storage.TblName.Userapps, "token", userTOKEN, 0, 0, "appname") if err != nil { return nil, err } count := len(records) if count == 0 { return []byte("[]"), nil } for _, rec := range records { appname := rec["appname"].(string) delete(rec, "appname") results[appname] = rec } } if len(results) == 0 { return nil, nil } return json.Marshal(results) } func (dbobj dbcon) listAllAppsOnly(conf Config) ([]string, error) { var result []string if conf.Generic.UseSeparateAppTables == true { allCollections, err := dbobj.store.GetAllTables() if err != nil { return nil, err } for _, colName := range allCollections { if strings.HasPrefix(colName, "app_") { result = append(result, colName[4:]) } } } else { records, err := dbobj.store.GetUniqueList(storage.TblName.Userapps, "appname") if err != nil { return result, err } for _, rec := range records { appname := rec["appname"].(string) result = append(result, appname) } } return result, nil } func (dbobj dbcon) listAllApps(conf Config) ([]byte, error) { result, err := dbobj.listAllAppsOnly(conf) if err != nil { return nil, err } return json.Marshal(result) }