diff --git a/databunker.yaml b/databunker.yaml index 667e56e..805abb8 100644 --- a/databunker.yaml +++ b/databunker.yaml @@ -4,6 +4,8 @@ generic: create_user_without_access_token: true # use separate app tables, default false # use_separate_app_tables: true + # specify if API call to list users is available (default false) + # list_users: true selfservice: # specifies if user can remove himself withour Admin/DPO approval (default false) forget_me: false diff --git a/src/bunker.go b/src/bunker.go index 8d79e80..3a93b2a 100644 --- a/src/bunker.go +++ b/src/bunker.go @@ -46,6 +46,7 @@ type Config struct { UseSeparateAppTables bool `yaml:"use_separate_app_tables" default:"false"` UserRecordSchema string `yaml:"user_record_schema"` AdminEmail string `yaml:"admin_email" envconfig:"ADMIN_EMAIL"` + ListUsers bool `yaml:"list_users" default:"false"` } SelfService struct { ForgetMe bool `yaml:"forget_me" default:"false"` @@ -191,6 +192,7 @@ func (e mainEnv) setupRouter() *httprouter.Router { router.GET("/v1/sys/backup", e.backupDB) router.POST("/v1/user", e.userCreate) + router.POST("/v1/users", e.userList) router.GET("/v1/user/:mode/:identity", e.userGet) router.DELETE("/v1/user/:mode/:identity", e.userDelete) router.PUT("/v1/user/:mode/:identity", e.userChange) @@ -557,8 +559,9 @@ func main() { flag.Parse() var cfg Config - readConfFile(&cfg, confPtr) readEnv(&cfg) + readConfFile(&cfg, confPtr) + customRootToken := "" if *demoPtr { customRootToken = "DEMO" diff --git a/src/users_api.go b/src/users_api.go index 84e7828..a7c0c5b 100644 --- a/src/users_api.go +++ b/src/users_api.go @@ -164,6 +164,37 @@ func (e mainEnv) userGet(w http.ResponseWriter, r *http.Request, ps httprouter.P w.Write([]byte(finalJSON)) } +func (e mainEnv) userList(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + authResult := e.enforceAdmin(w, r) + if authResult == "" { + return + } + if e.conf.Generic.ListUsers == false { + returnError(w, r, "access denied", 403, nil, nil) + return + } + var offset int32 = 0 + var limit int32 = 10 + args := r.URL.Query() + if value, ok := args["offset"]; ok { + offset = atoi(value[0]) + } + if value, ok := args["limit"]; ok { + limit = atoi(value[0]) + } + resultJSON, counter, _ := e.db.getUsers(offset, limit) + fmt.Printf("Total count of events: %d\n", counter) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + if counter == 0 { + str := fmt.Sprintf(`{"status":"ok","total":%d,"rows":[]}`, counter) + w.Write([]byte(str)) + } else { + str := fmt.Sprintf(`{"status":"ok","total":%d,"rows":%s}`, counter, resultJSON) + w.Write([]byte(str)) + } +} + func (e mainEnv) userChange(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { identity := ps.ByName("identity") mode := ps.ByName("mode") diff --git a/src/users_db.go b/src/users_db.go index 0c10206..736d393 100644 --- a/src/users_db.go +++ b/src/users_db.go @@ -377,6 +377,58 @@ func (dbobj dbcon) getUser(userTOKEN string) ([]byte, bson.M, error) { return decrypted, userBson, err } +func (dbobj dbcon) getUsers(offset int32, limit int32) ([]byte, int64, error) { + count, err := dbobj.store.CountRecords0(storage.TblName.Users) + if err != nil { + return nil, 0, err + } + if count == 0 { + return nil, 0, err + } + var results []bson.M + records, err := dbobj.store.GetList0(storage.TblName.Users, offset, limit, "token") + if err != nil { + return nil, 0, err + } + for _, element := range records { + rec := make(map[string]interface{}) + userKey := element["key"].(string) + userKeyBinary, err := base64.StdEncoding.DecodeString(userKey) + if err != nil { + return nil, 0, err + } + userTOKEN := element["token"].(string) + encData0 := element["data"].(string) + if len(encData0) > 0 { + encData, err := base64.StdEncoding.DecodeString(encData0) + if err != nil { + return nil, 0, err + } + decrypted, err := decrypt(dbobj.masterKey, userKeyBinary, encData) + if err != nil { + return nil, 0, err + } + var raw2 map[string]interface{} + err = json.Unmarshal(decrypted, &raw2) + if err != nil { + return nil, 0, err + } + rec["private"] = raw2 + } + expstatus := getStringValue(element["expstatus"]) + if len(expstatus) > 0 { + rec["endtime"] = element["endtime"] + rec["expstatus"] = expstatus + } + rec["token"] = userTOKEN + results = append(results, rec) + } + resultJSON, err := json.Marshal(results) + if err != nil { + return nil, 0, err + } + return resultJSON, count, nil +} func (dbobj dbcon) dumpUserPII(email string, conf Config) (string, error) { fullJSON := ""