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

View File

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

View File

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

View File

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

View File

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

View File

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