mirror of
https://codeberg.org/scip/ephemerup.git
synced 2025-12-16 20:20:58 +01:00
added authorization checks
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user