diff --git a/go.mod b/go.mod index e2ffb7c..995478b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/bouncepaw/mycorrhiza go 1.14 -require github.com/valyala/quicktemplate v1.6.3 +require ( + github.com/adrg/xdg v0.2.2 + github.com/valyala/quicktemplate v1.6.3 +) diff --git a/go.sum b/go.sum index a9a43a1..dbbc51b 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ +github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= +github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= @@ -13,3 +19,5 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/user/fs.go b/user/fs.go new file mode 100644 index 0000000..f47c423 --- /dev/null +++ b/user/fs.go @@ -0,0 +1,69 @@ +package user + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + + "github.com/adrg/xdg" + "github.com/bouncepaw/mycorrhiza/util" +) + +func PopulateFixedUserStorage() { + contents, err := ioutil.ReadFile(util.FixedCredentialsPath) + if err != nil { + log.Fatal(err) + } + err = json.Unmarshal(contents, &UserStorage.Users) + if err != nil { + log.Fatal(err) + } + for _, user := range UserStorage.Users { + user.Group = groupFromString(user.GroupString) + } + log.Println("Found", len(UserStorage.Users), "fixed users") + + contents, err = ioutil.ReadFile(tokenStoragePath()) + if os.IsNotExist(err) { + return + } + if err != nil { + log.Fatal(err) + } + var tmp map[string]string + err = json.Unmarshal(contents, &tmp) + if err != nil { + log.Fatal(err) + } + for token, username := range tmp { + user := UserStorage.userByName(username) + UserStorage.Tokens[token] = user + } + log.Println("Found", len(tmp), "active sessions") +} + +func dumpTokens() { + tmp := make(map[string]string) + for token, user := range UserStorage.Tokens { + tmp[token] = user.Name + } + blob, err := json.Marshal(tmp) + if err != nil { + log.Println(err) + } else { + ioutil.WriteFile(tokenStoragePath(), blob, 0644) + } +} + +// Return path to tokens.json. +func tokenStoragePath() string { + dir, err := xdg.DataFile("mycorrhiza/tokens.json") + if err != nil { + // Yes, it is unix-only, but function above rarely fails, so this block is probably never reached. + path := "/home/" + os.Getenv("HOME") + "/.local/share/mycorrhiza/tokens.json" + os.MkdirAll(path, 0777) + return path + } + return dir +} diff --git a/user/group.go b/user/group.go new file mode 100644 index 0000000..8ff7f1f --- /dev/null +++ b/user/group.go @@ -0,0 +1,61 @@ +package user + +import ( + "log" +) + +func groupFromString(s string) UserGroup { + switch s { + case "admin": + return UserAdmin + case "moderator": + return UserModerator + case "trusted": + return UserTrusted + case "editor": + return UserEditor + default: + log.Fatal("Unknown user group", s) + return UserAnon + } +} + +// UserGroup represents a group that a user is part of. +type UserGroup int + +const ( + // UserAnon is the default user group which all unauthorized visitors have. + UserAnon UserGroup = iota + // UserEditor is a user who can edit and upload stuff. + UserEditor + // UserTrusted is a trusted editor who can also rename stuff. + UserTrusted + // UserModerator is a moderator who can also delete stuff. + UserModerator + // UserAdmin can do everything. + UserAdmin +) + +var minimalRights = map[string]UserGroup{ + "edit": UserEditor, + "upload-binary": UserEditor, + "upload-text": UserEditor, + "rename-ask": UserTrusted, + "rename-confirm": UserTrusted, + "delete-ask": UserModerator, + "delete-confirm": UserModerator, + "reindex": UserAdmin, +} + +func (ug UserGroup) CanAccessRoute(route string) bool { + if !AuthUsed { + return true + } + if minimalRight, ok := minimalRights[route]; ok { + if ug >= minimalRight { + return true + } + return false + } + return true +} diff --git a/user/user.go b/user/user.go index b69268d..ed7ec42 100644 --- a/user/user.go +++ b/user/user.go @@ -1,8 +1,6 @@ package user import ( - "encoding/json" - "io/ioutil" "log" "net/http" "time" @@ -25,6 +23,15 @@ func (us *FixedUserStorage) userByToken(token string) *User { return nil } +func (us *FixedUserStorage) userByName(username string) *User { + for _, user := range us.Users { + if user.Name == username { + return user + } + } + return nil +} + func FromRequest(rq *http.Request) *User { cookie, err := rq.Cookie("mycorrhiza_token") if err != nil { @@ -62,6 +69,7 @@ func AddSession(username string) (string, error) { for _, user := range UserStorage.Users { if user.Name == username { UserStorage.Tokens[token] = user + go dumpTokens() } } log.Println("New token for", username, "is", token) @@ -71,6 +79,7 @@ func AddSession(username string) (string, error) { func terminateSession(token string) { delete(UserStorage.Tokens, token) + go dumpTokens() } func HasUsername(username string) bool { @@ -98,21 +107,6 @@ type FixedUserStorage struct { var UserStorage = FixedUserStorage{Tokens: make(map[string]*User)} -func PopulateFixedUserStorage() { - contents, err := ioutil.ReadFile(util.FixedCredentialsPath) - if err != nil { - log.Fatal(err) - } - err = json.Unmarshal(contents, &UserStorage.Users) - if err != nil { - log.Fatal(err) - } - for _, user := range UserStorage.Users { - user.Group = groupFromString(user.GroupString) - } - log.Println("Found", len(UserStorage.Users), "fixed users") -} - // AuthUsed shows if a method of authentication is used. You should set it by yourself. var AuthUsed bool @@ -126,62 +120,6 @@ type User struct { Password string `json:"password"` } -func groupFromString(s string) UserGroup { - switch s { - case "admin": - return UserAdmin - case "moderator": - return UserModerator - case "trusted": - return UserTrusted - case "editor": - return UserEditor - default: - log.Fatal("Unknown user group", s) - return UserAnon - } -} - -// UserGroup represents a group that a user is part of. -type UserGroup int - -const ( - // UserAnon is the default user group which all unauthorized visitors have. - UserAnon UserGroup = iota - // UserEditor is a user who can edit and upload stuff. - UserEditor - // UserTrusted is a trusted editor who can also rename stuff. - UserTrusted - // UserModerator is a moderator who can also delete stuff. - UserModerator - // UserAdmin can do everything. - UserAdmin -) - -var minimalRights = map[string]UserGroup{ - "edit": UserEditor, - "upload-binary": UserEditor, - "upload-text": UserEditor, - "rename-ask": UserTrusted, - "rename-confirm": UserTrusted, - "delete-ask": UserModerator, - "delete-confirm": UserModerator, - "reindex": UserAdmin, -} - -func (ug UserGroup) CanAccessRoute(route string) bool { - if !AuthUsed { - return true - } - if minimalRight, ok := minimalRights[route]; ok { - if ug >= minimalRight { - return true - } - return false - } - return true -} - // A handy cookie constructor func cookie(name_suffix, val string, t time.Time) *http.Cookie { return &http.Cookie{