ShareDAV/store.go

220 lines
5.5 KiB
Go
Raw Normal View History

package main
import (
"errors"
"fmt"
"github.com/tidwall/buntdb"
"golang.org/x/crypto/bcrypt"
"strings"
"time"
)
type UserStore interface {
AddUser(username string, role GlobalRole, password string) (err error)
GetUser(username string) (user User, err error)
GetLogin(username string, password string) (appName string, err error)
SetLogin(username string, appName string, password string) (err error)
RemoveLogin(username string, appName string) (err error)
RemoveUser(username string) (err error)
}
type ShareStore interface {
}
type GlobalRole string
const (
GlobalRoleUser GlobalRole = "user"
GlobalRoleManager GlobalRole = "manager"
GlobalRoleAdmin GlobalRole = "admin"
)
type ShareRole string
const (
ShareRoleReader ShareRole = "reader"
ShareRoleWriter ShareRole = "writer"
ShareRoleAdmin ShareRole = "admin"
)
type User struct {
Username string
Role GlobalRole
Apps []App
}
type App struct {
Name string
CreatedAt time.Time
}
type Share struct {
Directory string
Users []ShareUser
}
type ShareUser struct {
Username string
Role ShareRole
}
type DBStore struct {
db *buntdb.DB
}
func NewDBStore(filename string) (*DBStore, error) {
db, err := buntdb.Open(filename)
if err != nil {
return nil, err
}
if err := db.CreateIndex("login", "user:*:login*", buntdb.IndexBinary); err != nil && err != buntdb.ErrIndexExists {
return nil, errors.New("cannot create login index: " + err.Error())
}
return &DBStore{db}, nil
}
var ErrExists = errors.New("key already exists")
var ErrUserNotFound = errors.New("user not found")
var ErrInvalidUsername = errors.New("invalid username")
func (store *DBStore) AddUser(username string, role GlobalRole, password string) (err error) {
if strings.Contains(username, ":") {
return ErrInvalidUsername
}
pwString, err := buildPassword(username, password)
if err != nil {
return err
}
if err = store.db.Update(func(tx *buntdb.Tx) error {
if _, replaced, err := tx.Set(fmt.Sprintf("user:%s:role", username), string(role), nil); err != nil {
return err
} else if replaced {
return ErrExists
}
if _, replaced, err := tx.Set(fmt.Sprintf("user:%s:login", username), pwString, nil); err != nil {
return err
} else if replaced {
return ErrExists
}
// just in case ...
if _, err := tx.Delete(fmt.Sprintf("user:%s:login:*", username)); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func (store *DBStore) GetUser(username string) (user User, err error) {
if strings.Contains(username, ":") {
return user, ErrInvalidUsername
}
user.Username = username
if err := store.db.View(func(tx *buntdb.Tx) error {
if val, err := tx.Get(fmt.Sprintf("user:%s:role", username)); err != nil && err != buntdb.ErrNotFound {
return err
} else {
user.Role = GlobalRole(val)
}
loginPrefix := fmt.Sprintf("user:%s:login:", username)
if err := tx.AscendKeys(loginPrefix + "*", func(key, value string) bool {
app := App{Name: strings.TrimPrefix(key, loginPrefix)}
creationTimeValue, err := tx.Get(fmt.Sprintf("user:%s:app:%s:created_at", username, app.Name))
if err != nil {
// invalid app ... move along
return true
}
creationTime, err := time.Parse(time.RFC3339Nano, creationTimeValue)
if err != nil {
// invalid creation time ... invalid app ... move along.
return true
}
app.CreatedAt = creationTime
user.Apps = append(user.Apps, app)
return true
}); err != nil {
return err
}
return nil
}); err != nil {
return user, err
}
return user, nil
}
func (store *DBStore) GetLogin(username string, password string) (appName string, err error) {
panic("implement me")
}
func (store *DBStore) SetLogin(username string, appName string, password string) (err error) {
if strings.Contains(username, ":") {
return ErrInvalidUsername
}
pwString, err := buildPassword(username, password)
if err != nil {
return err
}
return store.db.Update(func(tx *buntdb.Tx) error {
mainLoginKey := fmt.Sprintf("user:%s:login", username)
// check if we actually know the user. the current login is a good indicator
if _, err := tx.Get(mainLoginKey); err != nil {
if err == buntdb.ErrNotFound {
return ErrUserNotFound
} else {
return err
}
}
saneAppName := strings.TrimSpace(appName)
// if no appname is given, we apparently have to update the password of the whole account.
if saneAppName == "" {
if _, _, err := tx.Set(mainLoginKey, pwString, nil); err != nil {
return err
}
} else {
if _, replaced, err := tx.Set(fmt.Sprintf("%s:%s", mainLoginKey, saneAppName), pwString, nil); err != nil {
return err
} else if !replaced {
// o,h so this is a new key. set the timestamp
creationTime := time.Now().Format(time.RFC3339Nano)
if _, _, err := tx.Set(fmt.Sprintf("user:%s:app:%s:created_at", username, saneAppName), creationTime, nil); err != nil {
return err
}
}
}
return nil
})
}
func (store *DBStore) RemoveLogin(username string, appName string) (err error) {
panic("implement me")
}
func (store *DBStore) RemoveUser(username string) (err error) {
if strings.Contains(username, ":") {
return ErrInvalidUsername
}
return store.db.Update(func(tx *buntdb.Tx) error {
// TODO won't work ... collect keys first, then delete them one by one
_, err := tx.Delete(fmt.Sprintf("user:%s:*", username))
return err
})
}
func buildPassword(username string, password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 0)
if err != nil {
return "", err
}
return fmt.Sprintf("%s:%s", username, hash), nil
}