added authorization checks

This commit is contained in:
2023-03-16 15:04:03 +01:00
parent 0bd5587ce2
commit 77d6c02d4d
6 changed files with 97 additions and 61 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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!")

View File

@@ -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
}

View File

@@ -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

View File

@@ -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")