diff --git a/upd/api/common.go b/upd/api/common.go index f66865d..d81cd2b 100644 --- a/upd/api/common.go +++ b/upd/api/common.go @@ -20,6 +20,7 @@ package api import ( "errors" "fmt" + "github.com/gofiber/fiber/v2" "github.com/tlinden/up/upd/cfg" "regexp" "strconv" @@ -154,3 +155,20 @@ func Untaint(input string, wanted *regexp.Regexp) (string, error) { return untainted, nil } + +/* + Retrieve the API Context name from the session, assuming is has + been successfully authenticated. However, if there are no api + contexts defined, we'll use 'default' (set in + auth.validateAPIKey()). + + If there's no apicontext in the session, assume unauth user, return "" +*/ +func GetApicontext(c *fiber.Ctx) (string, error) { + sess, err := Sessionstore.Get(c) + if err != nil { + return "", fmt.Errorf("Unable to initialize session store from context: " + err.Error()) + } + + return sess.Get("apicontext").(string), nil +} diff --git a/upd/api/db.go b/upd/api/db.go index 8cb5109..8b4fbcb 100644 --- a/upd/api/db.go +++ b/upd/api/db.go @@ -20,6 +20,7 @@ package api import ( "encoding/json" "fmt" + "github.com/tlinden/up/upd/cfg" //"github.com/alecthomas/repr" bolt "go.etcd.io/bbolt" ) @@ -29,11 +30,12 @@ const Bucket string = "uploads" // wrapper for bolt db type Db struct { bolt *bolt.DB + cfg *cfg.Config } -func NewDb(file string) (*Db, error) { - b, err := bolt.Open(file, 0600, nil) - db := Db{bolt: b} +func NewDb(c *cfg.Config) (*Db, error) { + b, err := bolt.Open(c.DbFile, 0600, nil) + db := Db{bolt: b, cfg: c} return &db, err } @@ -70,38 +72,7 @@ func (db *Db) Insert(id string, entry *Upload) error { return err } -func (db *Db) Lookup(id string) (Upload, error) { - var upload Upload - - err := db.bolt.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(Bucket)) - - if bucket == nil { - return fmt.Errorf("id %s not found", id) - } - - j := bucket.Get([]byte(id)) - - if len(j) == 0 { - return fmt.Errorf("id %s not found", id) - } - - if err := json.Unmarshal(j, &upload); err != nil { - return fmt.Errorf("unable to unmarshal json: %s", err) - } - - return nil - }) - - if err != nil { - Log("DB error: %s", err.Error()) - return upload, err - } - - return upload, nil -} - -func (db *Db) Delete(id string) error { +func (db *Db) Delete(apicontext string, id string) error { err := db.bolt.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(Bucket)) @@ -115,8 +86,16 @@ func (db *Db) Delete(id string) error { return fmt.Errorf("id %s not found", id) } - err := bucket.Delete([]byte(id)) - return err + upload := &Upload{} + if err := json.Unmarshal(j, &upload); err != nil { + return fmt.Errorf("unable to unmarshal json: %s", err) + } + + if (apicontext != "" && (db.cfg.Super == apicontext || upload.Context == apicontext)) || apicontext == "" { + return bucket.Delete([]byte(id)) + } + + return nil }) if err != nil { @@ -141,11 +120,13 @@ func (db *Db) List(apicontext string) (*Uploads, error) { return fmt.Errorf("unable to unmarshal json: %s", err) } - if apicontext != "" { + if apicontext != "" && db.cfg.Super != apicontext { + // only return the uploads for this context if apicontext == upload.Context { uploads.Entries = append(uploads.Entries, upload) } } else { + // return all, because there are no contexts or current==super uploads.Entries = append(uploads.Entries, upload) } @@ -159,7 +140,7 @@ func (db *Db) List(apicontext string) (*Uploads, error) { } // we only return one obj here, but could return more later -func (db *Db) Get(id string) (*Uploads, error) { +func (db *Db) Get(apicontext string, id string) (*Uploads, error) { uploads := &Uploads{} err := db.bolt.View(func(tx *bolt.Tx) error { @@ -178,10 +159,30 @@ func (db *Db) Get(id string) (*Uploads, error) { return fmt.Errorf("unable to unmarshal json: %s", err) } - uploads.Entries = append(uploads.Entries, upload) + if (apicontext != "" && (db.cfg.Super == apicontext || upload.Context == apicontext)) || apicontext == "" { + // allowed if no context (public or download) + // or if context matches or if context==super + uploads.Entries = append(uploads.Entries, upload) + } return nil }) return uploads, err } + +// a wrapper around Lookup() which extracts the 1st upload, if any +func (db *Db) Lookup(apicontext string, id string) (*Upload, error) { + uploads, err := db.Get(apicontext, id) + + if err != nil { + // non existent db entry with that id, or other db error, see logs + return &Upload{}, fmt.Errorf("No upload object found with id %s", id) + } + + if len(uploads.Entries) == 0 { + return &Upload{}, fmt.Errorf("No upload object found with id %s", id) + } + + return uploads.Entries[0], nil +} diff --git a/upd/api/handlers.go b/upd/api/handlers.go index f2d5800..4504874 100644 --- a/upd/api/handlers.go +++ b/upd/api/handlers.go @@ -58,6 +58,14 @@ func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { // init upload obj entry := &Upload{Id: id, Uploaded: Timestamp{Time: time.Now()}} + // retrieve the API Context name from the session + apicontext, err := GetApicontext(c) + if err != nil { + return JsonStatus(c, fiber.StatusInternalServerError, + "Unable to initialize session store from context: "+err.Error()) + } + entry.Context = apicontext + // retrieve files, if any files := form.File["upload[]"] members, err := SaveFormFiles(c, cfg, files, id) @@ -93,20 +101,6 @@ func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { } entry.File = Newfilename - // retrieve the API Context name from the session, assuming is has - // been successfully authenticated. However, if there are no api - // contexts defined, we'll use 'default' (set in - // auth.validateAPIKey()) - sess, err := Sessionstore.Get(c) - if err != nil { - return JsonStatus(c, fiber.StatusInternalServerError, - "Unable to initialize session store from context: "+err.Error()) - } - apicontext := sess.Get("apicontext") - if apicontext != nil { - entry.Context = apicontext.(string) - } - Log("Now serving %s from %s/%s", returnUrl, cfg.StorageDir, id) Log("Expire set to: %s", entry.Expire) Log("Uploaded with API-Context %s", entry.Context) @@ -132,7 +126,14 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error { return fiber.NewError(403, "Invalid id provided!") } - upload, err := db.Lookup(id) + // retrieve the API Context name from the session + apicontext, err := GetApicontext(c) + if err != nil { + return JsonStatus(c, fiber.StatusInternalServerError, + "Unable to initialize session store from context: "+err.Error()) + } + + upload, err := db.Lookup(apicontext, id) if err != nil { // non existent db entry with that id, or other db error, see logs return fiber.NewError(404, "No download with that id could be found!") @@ -143,7 +144,7 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error { if _, err := os.Stat(filename); err != nil { // db entry is there, but file isn't (anymore?) - go db.Delete(id) + go db.Delete(apicontext, id) return fiber.NewError(404, "No download with that id could be found!") } @@ -156,7 +157,7 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error { // check if we need to delete the file now and do it in the background if upload.Expire == "asap" { cleanup(filepath.Join(cfg.StorageDir, id)) - db.Delete(id) + db.Delete(apicontext, id) } }() } @@ -179,15 +180,22 @@ func DeleteUpload(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { "No id specified!") } - cleanup(filepath.Join(cfg.StorageDir, id)) + // retrieve the API Context name from the session + apicontext, err := GetApicontext(c) + if err != nil { + return JsonStatus(c, fiber.StatusInternalServerError, + "Unable to initialize session store from context: "+err.Error()) + } - err = db.Delete(id) + err = db.Delete(apicontext, id) if err != nil { // non existent db entry with that id, or other db error, see logs return JsonStatus(c, fiber.StatusForbidden, "No upload with that id could be found!") } + cleanup(filepath.Join(cfg.StorageDir, id)) + return nil } @@ -220,7 +228,14 @@ func Describe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { "Invalid id provided!") } - uploads, err := db.Get(id) + // retrieve the API Context name from the session + apicontext, err := GetApicontext(c) + if err != nil { + return JsonStatus(c, fiber.StatusInternalServerError, + "Unable to initialize session store from context: "+err.Error()) + } + + uploads, err := db.Get(apicontext, id) if err != nil { return JsonStatus(c, fiber.StatusForbidden, "No upload with that id could be found!") diff --git a/upd/api/server.go b/upd/api/server.go index 69994f3..bc28d94 100644 --- a/upd/api/server.go +++ b/upd/api/server.go @@ -39,7 +39,7 @@ func Runserver(conf *cfg.Config, args []string) error { Sessionstore = session.New() // bbolt db setup - db, err := NewDb(conf.DbFile) + db, err := NewDb(conf) if err != nil { return err } diff --git a/upd/cfg/config.go b/upd/cfg/config.go index 324ec0a..1856e9d 100644 --- a/upd/cfg/config.go +++ b/upd/cfg/config.go @@ -40,6 +40,7 @@ type Config struct { StorageDir string `koanf:"storagedir"` // db and uploads go there Url string `koanf:"url"` // public visible url, might be different from Listen DbFile string `koanf:"dbfile"` + Super string `koanf:"super"` // the apicontext which has all permissions // fiber settings, see: // https://docs.gofiber.io/api/fiber/#config diff --git a/upd/cmd/root.go b/upd/cmd/root.go index cc33edd..c9025ac 100644 --- a/upd/cmd/root.go +++ b/upd/cmd/root.go @@ -61,6 +61,7 @@ func Execute() error { f.StringVarP(&conf.ApiPrefix, "apiprefix", "a", "/api", "API endpoint path") f.StringVarP(&conf.Url, "url", "u", "", "HTTP endpoint w/o path") f.StringVarP(&conf.DbFile, "dbfile", "D", "/tmp/uploads.db", "Bold database file to use") + f.StringVarP(&conf.Super, "super", "", "", "The API Context which has permissions on all contexts") // server settings f.BoolVarP(&conf.V4only, "ipv4", "4", false, "Only listen on ipv4")