⬆️ update dependencies, ✨ implement user store

This commit is contained in:
2020-10-09 18:35:44 +02:00
parent d2c0ebb043
commit 129df9d7f2
90 changed files with 6640 additions and 12741 deletions
+154 -104
View File
@@ -1,106 +1,166 @@
// Copyright (c) 2020, Andreas Schneider
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"errors"
"fmt"
"strings"
uuid "github.com/satori/go.uuid"
"github.com/tidwall/buntdb"
"golang.org/x/crypto/bcrypt"
"strings"
"time"
)
type UserStore interface {
AddUser(username string, role GlobalRole, password string) (err error)
AddUser(user User) (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)
GetUsers() ([]User, error)
Update(user User) error
RemoveUser(username string) (err error)
}
type ShareStore interface {
}
var _ UserStore = &DBStore{}
type GlobalRole string
const (
GlobalRoleUser GlobalRole = "user"
GlobalRoleManager GlobalRole = "manager"
GlobalRoleUser GlobalRole = "user"
GlobalRoleAdmin GlobalRole = "admin"
)
type ShareRole string
const (
ShareRoleReader ShareRole = "reader"
ShareRoleWriter ShareRole = "writer"
ShareRoleAdmin ShareRole = "admin"
ShareRoleAdmin ShareRole = "admin"
)
type User struct {
Username string
Role GlobalRole
Apps []App
}
type App struct {
Name string
CreatedAt time.Time
Password string
Role GlobalRole
}
type Share struct {
UUID uuid.UUID
Directory string
Users []ShareUser
Users []ShareUser
}
type ShareUser struct {
Username string
Role ShareRole
Role ShareRole
Logins []Login
}
type Login struct {
Loginname string
Password string
}
const usersPrefix = "users:"
type DBStore struct {
db *buntdb.DB
}
func (u User) key() string {
return usersPrefix + u.Username
}
func (u User) prefix() string {
return fmt.Sprintf("user:%s", u.Username)
}
func (u User) roleKey() string {
return u.prefix() + ":role"
}
func (u User) passwordKey() string {
return u.prefix() + ":password"
}
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
}
func (store *DBStore) Close() error {
return store.db.Close()
}
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, ":") {
func (store *DBStore) setUserValues(tx *buntdb.Tx, user User) (exists bool, err error) {
pwString, err := buildPassword(user.Username, user.Password)
if err != nil {
return false, fmt.Errorf("cannot hash password: %w", err)
}
if _, replaced, err := tx.Set(user.roleKey(), string(user.Role), nil); err != nil {
return false, err
} else if replaced {
exists = true
}
if _, replaced, err := tx.Set(user.passwordKey(), pwString, nil); err != nil {
return false, err
} else if replaced {
exists = true
}
return exists, nil
}
func (store *DBStore) AddUser(user User) (err error) {
if strings.Contains(user.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 {
if _, exists, err := tx.Set(user.key(), "", nil); err != nil {
return err
} else if replaced {
} else if exists {
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
}
if exists, err := store.setUserValues(tx, user); err != nil {
return err
} else if exists {
return ErrExists
}
return nil
}); err != nil {
return err
@@ -114,33 +174,20 @@ func (store *DBStore) GetUser(username string) (user User, err error) {
}
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 {
if val, err := tx.Get(user.roleKey()); err != nil && err != buntdb.ErrNotFound {
return err
} else if err == buntdb.ErrNotFound {
return ErrUserNotFound
} 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 {
if val, err := tx.Get(user.passwordKey()); err != nil && err != buntdb.ErrNotFound {
return err
} else if err == buntdb.ErrNotFound {
return ErrUserNotFound
} else {
user.Password = val
}
return nil
}); err != nil {
return user, err
@@ -148,56 +195,39 @@ func (store *DBStore) GetUser(username string) (user User, err error) {
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
}
func (store *DBStore) GetUsers() (users []User, err error) {
err = store.db.View(func(tx *buntdb.Tx) error {
if err := tx.AscendKeys(usersPrefix+"*", func(key, value string) bool {
var user User
user.Username = strings.TrimPrefix(key, usersPrefix)
users = append(users, user)
return true
}); err != nil {
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 {
for i := range users {
if roleString, err := tx.Get(users[i].roleKey()); 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
}
} else {
users[i].Role = GlobalRole(roleString)
}
}
return nil
})
return users, err
}
func (store *DBStore) RemoveLogin(username string, appName string) (err error) {
panic("implement me")
func (store *DBStore) Update(user User) error {
if strings.Contains(user.Username, ":") {
return ErrInvalidUsername
}
return store.db.Update(func(tx *buntdb.Tx) error {
_, err := store.setUserValues(tx, user)
return err
})
}
func (store *DBStore) RemoveUser(username string) (err error) {
@@ -205,9 +235,29 @@ func (store *DBStore) RemoveUser(username string) (err error) {
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
user := User{Username: username}
// Delete the main key first. This is a good indicator if the user generally exists.
if _, err := tx.Delete(user.key()); err == buntdb.ErrNotFound {
return ErrUserNotFound
} else if err != nil {
return err
}
// Now get all attributes and delete them as well. One by one.
var keys []string
if err := tx.AscendKeys(user.prefix()+":*", func(key, value string) bool {
keys = append(keys, key)
return true
}); err != nil {
return fmt.Errorf("cannot iterate keys: %w", err)
}
for _, key := range keys {
if _, err := tx.Delete(key); err != nil {
return fmt.Errorf("cannot remove key: %w", err)
}
}
return nil
})
}
@@ -217,4 +267,4 @@ func buildPassword(username string, password string) (string, error) {
return "", err
}
return fmt.Sprintf("%s:%s", username, hash), nil
}
}