From 9c442254a7b04db1d11ed13d0200679e1e400e0d Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Wed, 28 Aug 2013 23:05:28 -0400 Subject: [PATCH] Finish adding simple user authentication --- client/asink.go | 11 +++++++++++ client/net.go | 24 ++++++++++++++++++++++-- events.go | 2 +- server/database.go | 8 ++++---- server/server/longpolling.go | 8 ++++---- server/server/server.go | 20 ++++++++++---------- util/util.go | 8 ++++++++ 7 files changed, 60 insertions(+), 21 deletions(-) diff --git a/client/asink.go b/client/asink.go index 31fdc48..f98cd2a 100644 --- a/client/asink.go +++ b/client/asink.go @@ -22,6 +22,8 @@ type AsinkGlobals struct { storage Storage server string port int + username string + password string } var globals AsinkGlobals @@ -42,6 +44,12 @@ func init() { func main() { flag.Parse() + //make sure config file's permissions are read-write only for the current user + if !util.FileExistsAndHasPermissions(globals.configFileName, 384 /*0b110000000*/) { + fmt.Println("Error: Either the file at "+globals.configFileName+" doesn't exist, or it doesn't have permissions such that the current user is the only one allowed to read and write.") + return + } + config, err := conf.ReadConfigFile(globals.configFileName) if err != nil { fmt.Println(err) @@ -73,8 +81,11 @@ func main() { panic(err) } + //TODO check errors on server settings globals.server, err = config.GetString("server", "host") globals.port, err = config.GetInt("server", "port") + globals.username, err = config.GetString("server", "username") + globals.password, err = config.GetString("server", "password") globals.db, err = GetAndInitDB(config) if err != nil { diff --git a/client/net.go b/client/net.go index 7289bee..204f8fc 100644 --- a/client/net.go +++ b/client/net.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "net/http" "strconv" @@ -15,6 +16,25 @@ import ( const MIN_ERROR_WAIT = 100 // 1/10 of a second const MAX_ERROR_WAIT = 10000 // 10 seconds +func AuthenticatedRequest(method, url, bodyType string, body io.Reader, username, password string) (*http.Response, error) { + client := &http.Client{} + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + if bodyType != "" { + req.Header.Set("Content-Type", bodyType) + } + req.SetBasicAuth(username, password) + return client.Do(req) +} +func AuthenticatedGet(url string, username, password string) (*http.Response, error) { + return AuthenticatedRequest("GET", url, "", nil, username, password) +} +func AuthenticatedPost(url, bodyType string, body io.Reader, username, password string) (*http.Response, error) { + return AuthenticatedRequest("POST", url, bodyType, body, username, password) +} + func SendEvent(globals AsinkGlobals, event *asink.Event) error { url := "http://" + globals.server + ":" + strconv.Itoa(int(globals.port)) + "/events/" @@ -28,7 +48,7 @@ func SendEvent(globals AsinkGlobals, event *asink.Event) error { } //actually make the request - resp, err := http.Post(url, "application/json", bytes.NewReader(b)) + resp, err := AuthenticatedPost(url, "application/json", bytes.NewReader(b), globals.username, globals.password) if err != nil { return err } @@ -80,7 +100,7 @@ func GetEvents(globals AsinkGlobals, events chan *asink.Event) { } else { fullUrl = url + "0" } - resp, err := http.Get(fullUrl) + resp, err := AuthenticatedGet(fullUrl, globals.username, globals.password) //if error, perform exponential backoff (with maximum timeout) if err != nil { diff --git a/events.go b/events.go index 6485844..26fc5e6 100644 --- a/events.go +++ b/events.go @@ -29,7 +29,7 @@ type Event struct { Timestamp int64 Permissions os.FileMode Username string - Sharename string + Sharename string //TODO start differentiating between a users' different shares LocalStatus EventStatus `json:"-"` LocalId int64 `json:"-"` InDB bool `json:"-"` //defaults to false. Omitted from json marshalling. diff --git a/server/database.go b/server/database.go index bfd8e4e..2169a65 100644 --- a/server/database.go +++ b/server/database.go @@ -64,7 +64,7 @@ func GetAndInitDB() (*AsinkDB, error) { return ret, nil } -func (adb *AsinkDB) DatabaseAddEvent(e *asink.Event) (err error) { +func (adb *AsinkDB) DatabaseAddEvent(u *User, e *asink.Event) (err error) { adb.lock.Lock() tx, err := adb.db.Begin() if err != nil { @@ -79,7 +79,7 @@ func (adb *AsinkDB) DatabaseAddEvent(e *asink.Event) (err error) { adb.lock.Unlock() }() - result, err := tx.Exec("INSERT INTO events (userid, type, path, hash, predecessor, timestamp, permissions) VALUES (?,?,?,?,?,?,?,?);", e.Type, e.Path, e.Hash, e.Predecessor, e.Timestamp, e.Permissions) + result, err := tx.Exec("INSERT INTO events (userid, type, path, hash, predecessor, timestamp, permissions) VALUES (?,?,?,?,?,?,?);", u.Id, e.Type, e.Path, e.Hash, e.Predecessor, e.Timestamp, e.Permissions) if err != nil { return err } @@ -97,13 +97,13 @@ func (adb *AsinkDB) DatabaseAddEvent(e *asink.Event) (err error) { return nil } -func (adb *AsinkDB) DatabaseRetrieveEvents(firstId uint64, maxEvents uint) (events []*asink.Event, err error) { +func (adb *AsinkDB) DatabaseRetrieveEvents(firstId uint64, maxEvents uint, u *User) (events []*asink.Event, err error) { adb.lock.Lock() //make sure the database gets unlocked on return defer func() { adb.lock.Unlock() }() - rows, err := adb.db.Query("SELECT id, type, path, hash, predecessor, timestamp, permissions FROM events WHERE id >= ? ORDER BY id ASC LIMIT ?;", firstId, maxEvents) + rows, err := adb.db.Query("SELECT id, type, path, hash, predecessor, timestamp, permissions FROM events WHERE userid = ? AND id >= ? ORDER BY id ASC LIMIT ?;", u.Id, firstId, maxEvents) if err != nil { return nil, err } diff --git a/server/server/longpolling.go b/server/server/longpolling.go index bf174b9..a571381 100644 --- a/server/server/longpolling.go +++ b/server/server/longpolling.go @@ -13,17 +13,17 @@ type LongPollGroup struct { type PollingManager struct { lock sync.RWMutex - groups map[string]*LongPollGroup + groups map[int64]*LongPollGroup } var pm *PollingManager func init() { pm = new(PollingManager) - pm.groups = make(map[string]*LongPollGroup) + pm.groups = make(map[int64]*LongPollGroup) } -func addPoller(uid string, channel *chan *asink.Event) { +func addPoller(uid int64, channel *chan *asink.Event) { pm.lock.RLock() group := pm.groups[uid] @@ -57,7 +57,7 @@ func addPoller(uid string, channel *chan *asink.Event) { }) } -func broadcastToPollers(uid string, event *asink.Event) { +func broadcastToPollers(uid int64, event *asink.Event) { //store off the long polling group we're trying to send to and remove //it from PollingManager.groups pm.lock.Lock() diff --git a/server/server/server.go b/server/server/server.go index f0469a9..8a29864 100644 --- a/server/server/server.go +++ b/server/server/server.go @@ -62,7 +62,7 @@ func rootHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "You're probably looking for /events/") } -func getEvents(w http.ResponseWriter, r *http.Request, nextEvent uint64) { +func getEvents(w http.ResponseWriter, r *http.Request, user *server.User, nextEvent uint64) { var events []*asink.Event var error_message string = "" defer func() { @@ -86,7 +86,7 @@ func getEvents(w http.ResponseWriter, r *http.Request, nextEvent uint64) { w.Write(b) }() - events, err := adb.DatabaseRetrieveEvents(nextEvent, 50) + events, err := adb.DatabaseRetrieveEvents(nextEvent, 50, user) if err != nil { panic(err) error_message = err.Error() @@ -96,7 +96,7 @@ func getEvents(w http.ResponseWriter, r *http.Request, nextEvent uint64) { //long-poll if events is empty if len(events) == 0 { c := make(chan *asink.Event) - addPoller("aclindsa", &c) //TODO support more than one user + addPoller(user.Id, &c) //TODO support more than one share per user e, ok := <-c if ok { events = append(events, e) @@ -104,7 +104,7 @@ func getEvents(w http.ResponseWriter, r *http.Request, nextEvent uint64) { } } -func putEvents(w http.ResponseWriter, r *http.Request) { +func putEvents(w http.ResponseWriter, r *http.Request, user *server.User) { var events asink.EventList var error_message string = "" defer func() { @@ -138,7 +138,7 @@ func putEvents(w http.ResponseWriter, r *http.Request) { return } for _, event := range events.Events { - err = adb.DatabaseAddEvent(event) + err = adb.DatabaseAddEvent(user, event) if err != nil { //TODO should probably do this in a way that the caller knows how many of these have failed and doesn't re-try sending ones that succeeded //i.e. add this to the return codes or something @@ -148,7 +148,7 @@ func putEvents(w http.ResponseWriter, r *http.Request) { } } - broadcastToPollers("aclindsa", events.Events[0]) //TODO support more than one user + broadcastToPollers(user.Id, events.Events[0]) //TODO support more than one user } func eventHandler(w http.ResponseWriter, r *http.Request) { @@ -172,15 +172,15 @@ func eventHandler(w http.ResponseWriter, r *http.Request) { if err != nil { //TODO display error message here instead fmt.Printf("ERROR parsing " + sm[1] + "\n") - getEvents(w, r, 0) + getEvents(w, r, user, 0) } else { - getEvents(w, r, i) + getEvents(w, r, user, i) } } else { - getEvents(w, r, 0) + getEvents(w, r, user, 0) } } else if r.Method == "POST" { - putEvents(w, r) + putEvents(w, r, user) } else { apiresponse := asink.APIResponse{ Status: asink.ERROR, diff --git a/util/util.go b/util/util.go index 1907ee6..56dae7d 100644 --- a/util/util.go +++ b/util/util.go @@ -28,6 +28,14 @@ func EnsureDirExists(dir string) error { return nil } +func FileExistsAndHasPermissions(file string, mode os.FileMode) bool { + info, err := os.Stat(file) + if err != nil { + return false + } + return info.Mode().Perm() == mode +} + //TODO maybe this shouldn't fail silently? func RecursiveRemoveEmptyDirs(dir string) { var err error = nil