Finish core funtionality for server user-administration tool

This commit is contained in:
Aaron Lindsay 2013-08-27 00:03:38 -04:00
parent 4d7d82ed94
commit 219eecbfda
4 changed files with 166 additions and 37 deletions

View File

@ -7,7 +7,7 @@ import (
"fmt"
"os"
"strconv"
"reflect"
"net/rpc"
)
type boolIsSetFlag struct {
@ -66,10 +66,13 @@ func UserAdd(args []string) {
user.Username = flags.Arg(0)
user.PWHash = server.HashPassword(passwordOne)
fmt.Println(user)
i := 99
err = RPCCall("UserModifier.AddUser", user, &i)
if err != nil {
if _, ok := err.(rpc.ServerError); ok && err.Error() == server.DuplicateUsernameErr.Error() {
fmt.Println("Error: "+err.Error())
return
}
panic(err)
}
}
@ -83,10 +86,13 @@ func UserDel(args []string) {
user := new(server.User)
user.Username = args[0]
fmt.Println(user)
i := 99
err := RPCCall("UserModifier.RemoveUser", user, &i)
if err != nil {
if _, ok := err.(rpc.ServerError); ok && err.Error() == server.NoUserErr.Error() {
fmt.Println("Error: "+err.Error())
return
}
panic(err)
}
}
@ -106,8 +112,6 @@ func UserMod(args []string) {
flags.BoolVar(&rpcargs.UpdateLogin, "l", false, "Change the user's username (short version)")
flags.Parse(args)
//set the UpdateAdmin flag based on whether it was present on the command-line
if flags.NArg() != 1 {
fmt.Println("Error: please supply a username (and only one)")
os.Exit(1)
@ -136,6 +140,7 @@ func UserMod(args []string) {
rpcargs.Updated.PWHash = server.HashPassword(passwordOne)
}
//set the UpdateRole flag based on whether it was present on the command-line
rpcargs.UpdateRole = admin.IsSet
if admin.Value {
rpcargs.Updated.Role = server.ADMIN
@ -143,11 +148,18 @@ func UserMod(args []string) {
rpcargs.Updated.Role = server.NORMAL
}
fmt.Println(rpcargs)
if !rpcargs.UpdateRole && !rpcargs.UpdateLogin && !rpcargs.UpdatePassword {
fmt.Println("What exactly are you modifying again?")
return
}
i := 99
err := RPCCall("UserModifier.ModifyUser", rpcargs, &i)
if err != nil {
fmt.Println(reflect.TypeOf(err))
if _, ok := err.(rpc.ServerError); ok && err.Error() == server.NoUserErr.Error() {
fmt.Println("Error: "+err.Error())
return
}
panic(err)
}
}

View File

@ -1,13 +1,14 @@
package server
import (
"fmt"
"net"
"net/http"
"net/rpc"
)
type UserModifier int
type UserModifier struct {
adb *AsinkDB
}
type UserModifierArgs struct {
Current *User
@ -18,32 +19,58 @@ type UserModifierArgs struct {
}
func (u *UserModifier) AddUser(user *User, result *int) error {
fmt.Println("adding user: ", user)
ret := 0
result = &ret
return nil
err := u.adb.DatabaseAddUser(user)
if err != nil {
*result = 1
} else {
*result = 0
}
return err
}
func (u *UserModifier) ModifyUser(args *UserModifierArgs, result *int) error {
fmt.Println("modifying user: ", args)
fmt.Println("from: ", args.Current)
fmt.Println("to: ", args.Updated)
ret := 0
result = &ret
currentUser, err := u.adb.DatabaseGetUser(args.Current.Username)
if err != nil {
*result = 1
return err
}
if args.UpdateLogin {
currentUser.Username = args.Updated.Username
}
if args.UpdateRole {
currentUser.Role = args.Updated.Role
}
if args.UpdatePassword {
currentUser.PWHash = args.Updated.PWHash
}
err = u.adb.DatabaseUpdateUser(currentUser)
if err != nil {
*result = 1
return err
}
*result = 0
return nil
}
func (u *UserModifier) RemoveUser(user *User, result *int) error {
fmt.Println("removing user: ", user)
ret := 0
result = &ret
return nil
err := u.adb.DatabaseDeleteUser(user)
if err != nil {
*result = 1
} else {
*result = 0
}
return err
}
func StartRPC(townDown chan int) {
defer func() { townDown <- 0 }() //the main thread waits for this to ensure the socket is closed
func StartRPC(tornDown chan int, adb *AsinkDB) {
defer func() { tornDown <- 0 }() //the main thread waits for this to ensure the socket is closed
usermod := new(UserModifier)
usermod.adb = adb
rpc.Register(usermod)
rpc.HandleHTTP()
l, err := net.Listen("unix", "/tmp/asink.sock")

View File

@ -1,8 +1,8 @@
package main
package server
import (
"asink"
"asink/server"
"errors"
"database/sql"
_ "github.com/mattn/go-sqlite3"
"sync"
@ -13,6 +13,9 @@ type AsinkDB struct {
lock sync.Mutex
}
var DuplicateUsernameErr = errors.New("Username already exists")
var NoUserErr = errors.New("User doesn't exist")
func GetAndInitDB() (*AsinkDB, error) {
dbLocation := "asink-server.db" //TODO make me configurable
@ -46,7 +49,7 @@ func GetAndInitDB() (*AsinkDB, error) {
}
if !rows.Next() {
//if this is false, it means no rows were returned
tx.Exec("CREATE TABLE user (id INTEGER PRIMARY KEY ASC, username TEXT, pwhash TEXT, role INTEGER);")
tx.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY ASC, username TEXT, pwhash TEXT, role INTEGER);")
} else {
rows.Close()
}
@ -116,7 +119,7 @@ func (adb *AsinkDB) DatabaseRetrieveEvents(firstId uint64, maxEvents uint) (even
return events, nil
}
func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) {
func (adb *AsinkDB) DatabaseAddUser(u *User) (err error) {
adb.lock.Lock()
tx, err := adb.db.Begin()
if err != nil {
@ -131,11 +134,24 @@ func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) {
adb.lock.Unlock()
}()
result, err := tx.Exec("INSERT INTO users (username, pwhash, role) VALUES (?,?);", u.Username, u.PWHash, u.Role)
//make sure the username we're switching to doesn't already exist in the database
existingUsername := ""
row := tx.QueryRow("SELECT username FROM users WHERE username == ?;", u.Username)
err = row.Scan(&existingUsername)
switch {
case err == sql.ErrNoRows:
//keep going
case err != nil:
return err
default:
return DuplicateUsernameErr
}
result, err := tx.Exec("INSERT INTO users (username, pwhash, role) VALUES (?,?,?);", u.Username, u.PWHash, u.Role)
if err != nil {
return err
}
id, err := result.LastInsertId()
u.Id, err = result.LastInsertId()
if err != nil {
return err
}
@ -144,23 +160,63 @@ func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) {
return err
}
u.Id = id
return nil
}
func (adb *AsinkDB) DatabaseGetUser(username string) (user *server.User, err error) {
//set attributes for the user with the same Id as *u
func (adb *AsinkDB) DatabaseUpdateUser(u *User) (err error) {
adb.lock.Lock()
tx, err := adb.db.Begin()
if err != nil {
return err
}
//make sure the transaction gets rolled back on error, and the database gets unlocked
defer func() {
if err != nil {
tx.Rollback()
}
adb.lock.Unlock()
}()
//make sure the username we're switching to doesn't already exist in the database
existingUsername := ""
row := tx.QueryRow("SELECT username FROM users WHERE username == ? AND id != ?;", u.Username, u.Id)
err = row.Scan(&existingUsername)
switch {
case err == sql.ErrNoRows:
//keep going
case err != nil:
return err
default:
return DuplicateUsernameErr
}
_, err = tx.Exec("UPDATE users SET username=?, pwhash=?, role=? WHERE id=?;", u.Username, u.PWHash, u.Role, u.Id)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (adb *AsinkDB) DatabaseGetUser(username string) (user *User, err error) {
adb.lock.Lock()
//make sure the database gets unlocked
defer adb.lock.Unlock()
row := adb.db.QueryRow("SELECT id, username, pwhash, role FROM users WHERE username == ?;", username)
user = new(server.User)
user = new(User)
err = row.Scan(&user.Id, &user.Username, &user.PWHash, &user.Role)
switch {
case err == sql.ErrNoRows:
return nil, nil
return nil, NoUserErr
case err != nil:
return nil, err
default:
@ -168,4 +224,38 @@ func (adb *AsinkDB) DatabaseGetUser(username string) (user *server.User, err err
}
}
func (adb *AsinkDB) DatabaseDeleteUser(u *User) (err error) {
adb.lock.Lock()
tx, err := adb.db.Begin()
if err != nil {
return err
}
//make sure the transaction gets rolled back on error, and the database gets unlocked
defer func() {
if err != nil {
tx.Rollback()
}
adb.lock.Unlock()
}()
res, err := tx.Exec("DELETE FROM users WHERE username=?;", u.Username)
if err != nil {
return err
}
rows, err := res.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return NoUserErr
} else if rows > 1 {
return errors.New("Error: attempting to delete user by username, but more than row will be affected: " + u.Username)
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}

View File

@ -18,7 +18,7 @@ import (
//global variables
var eventsRegexp *regexp.Regexp
var port int = 8080
var adb *AsinkDB
var adb *server.AsinkDB
func init() {
var err error
@ -29,7 +29,7 @@ func init() {
eventsRegexp = regexp.MustCompile("^/events/([0-9]+)$")
adb, err = GetAndInitDB()
adb, err = server.GetAndInitDB()
if err != nil {
panic(err)
}
@ -39,7 +39,7 @@ func main() {
flag.Parse()
rpcTornDown := make(chan int)
go server.StartRPC(rpcTornDown)
go server.StartRPC(rpcTornDown, adb)
http.HandleFunc("/", rootHandler)
http.HandleFunc("/events", eventHandler)