mirror of
https://codeberg.org/scip/ephemerup.git
synced 2025-12-17 12:40:57 +01:00
added authorization checks
This commit is contained in:
@@ -20,6 +20,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/tlinden/up/upd/cfg"
|
"github.com/tlinden/up/upd/cfg"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -154,3 +155,20 @@ func Untaint(input string, wanted *regexp.Regexp) (string, error) {
|
|||||||
|
|
||||||
return untainted, nil
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/tlinden/up/upd/cfg"
|
||||||
//"github.com/alecthomas/repr"
|
//"github.com/alecthomas/repr"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
@@ -29,11 +30,12 @@ const Bucket string = "uploads"
|
|||||||
// wrapper for bolt db
|
// wrapper for bolt db
|
||||||
type Db struct {
|
type Db struct {
|
||||||
bolt *bolt.DB
|
bolt *bolt.DB
|
||||||
|
cfg *cfg.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDb(file string) (*Db, error) {
|
func NewDb(c *cfg.Config) (*Db, error) {
|
||||||
b, err := bolt.Open(file, 0600, nil)
|
b, err := bolt.Open(c.DbFile, 0600, nil)
|
||||||
db := Db{bolt: b}
|
db := Db{bolt: b, cfg: c}
|
||||||
return &db, err
|
return &db, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,38 +72,7 @@ func (db *Db) Insert(id string, entry *Upload) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Db) Lookup(id string) (Upload, error) {
|
func (db *Db) Delete(apicontext string, id string) 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 {
|
|
||||||
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
err := db.bolt.Update(func(tx *bolt.Tx) error {
|
||||||
bucket := tx.Bucket([]byte(Bucket))
|
bucket := tx.Bucket([]byte(Bucket))
|
||||||
|
|
||||||
@@ -115,8 +86,16 @@ func (db *Db) Delete(id string) error {
|
|||||||
return fmt.Errorf("id %s not found", id)
|
return fmt.Errorf("id %s not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bucket.Delete([]byte(id))
|
upload := &Upload{}
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
@@ -141,11 +120,13 @@ func (db *Db) List(apicontext string) (*Uploads, error) {
|
|||||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
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 {
|
if apicontext == upload.Context {
|
||||||
uploads.Entries = append(uploads.Entries, upload)
|
uploads.Entries = append(uploads.Entries, upload)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// return all, because there are no contexts or current==super
|
||||||
uploads.Entries = append(uploads.Entries, upload)
|
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
|
// 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{}
|
uploads := &Uploads{}
|
||||||
|
|
||||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
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)
|
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 nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return uploads, err
|
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
|
// init upload obj
|
||||||
entry := &Upload{Id: id, Uploaded: Timestamp{Time: time.Now()}}
|
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
|
// retrieve files, if any
|
||||||
files := form.File["upload[]"]
|
files := form.File["upload[]"]
|
||||||
members, err := SaveFormFiles(c, cfg, files, id)
|
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
|
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("Now serving %s from %s/%s", returnUrl, cfg.StorageDir, id)
|
||||||
Log("Expire set to: %s", entry.Expire)
|
Log("Expire set to: %s", entry.Expire)
|
||||||
Log("Uploaded with API-Context %s", entry.Context)
|
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!")
|
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 {
|
if err != nil {
|
||||||
// non existent db entry with that id, or other db error, see logs
|
// 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!")
|
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 {
|
if _, err := os.Stat(filename); err != nil {
|
||||||
// db entry is there, but file isn't (anymore?)
|
// 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!")
|
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
|
// check if we need to delete the file now and do it in the background
|
||||||
if upload.Expire == "asap" {
|
if upload.Expire == "asap" {
|
||||||
cleanup(filepath.Join(cfg.StorageDir, id))
|
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!")
|
"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 {
|
if err != nil {
|
||||||
// non existent db entry with that id, or other db error, see logs
|
// non existent db entry with that id, or other db error, see logs
|
||||||
return JsonStatus(c, fiber.StatusForbidden,
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
"No upload with that id could be found!")
|
"No upload with that id could be found!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanup(filepath.Join(cfg.StorageDir, id))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +228,14 @@ func Describe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
|||||||
"Invalid id provided!")
|
"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 {
|
if err != nil {
|
||||||
return JsonStatus(c, fiber.StatusForbidden,
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
"No upload with that id could be found!")
|
"No upload with that id could be found!")
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func Runserver(conf *cfg.Config, args []string) error {
|
|||||||
Sessionstore = session.New()
|
Sessionstore = session.New()
|
||||||
|
|
||||||
// bbolt db setup
|
// bbolt db setup
|
||||||
db, err := NewDb(conf.DbFile)
|
db, err := NewDb(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type Config struct {
|
|||||||
StorageDir string `koanf:"storagedir"` // db and uploads go there
|
StorageDir string `koanf:"storagedir"` // db and uploads go there
|
||||||
Url string `koanf:"url"` // public visible url, might be different from Listen
|
Url string `koanf:"url"` // public visible url, might be different from Listen
|
||||||
DbFile string `koanf:"dbfile"`
|
DbFile string `koanf:"dbfile"`
|
||||||
|
Super string `koanf:"super"` // the apicontext which has all permissions
|
||||||
|
|
||||||
// fiber settings, see:
|
// fiber settings, see:
|
||||||
// https://docs.gofiber.io/api/fiber/#config
|
// 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.ApiPrefix, "apiprefix", "a", "/api", "API endpoint path")
|
||||||
f.StringVarP(&conf.Url, "url", "u", "", "HTTP endpoint w/o 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.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
|
// server settings
|
||||||
f.BoolVarP(&conf.V4only, "ipv4", "4", false, "Only listen on ipv4")
|
f.BoolVarP(&conf.V4only, "ipv4", "4", false, "Only listen on ipv4")
|
||||||
|
|||||||
Reference in New Issue
Block a user