/
perms.go
187 lines (174 loc) · 5.67 KB
/
perms.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// A simple authentication module that supports user and group based authentication methods, with users authenticated via e-mail addresses.
// In order to use it, you pass a http.Request. In return, you get the user's permissions.
// Permission files are stored in data/shared/(users|groups)/<username>/perms. Each user's group membership is stored in data/shared/users/<username>/groups.
// Currently, the format for the permissions file is something like this:
// r /
// rw /profiles/bob/
// rw /admin/
// Pattern matching is the same as in the HTTP library. A trailing / will match the given path and all subpaths. Longer paths are evaluated before shorter ones.
package perms
import (
"http"
"io/ioutil"
"bytes"
"fmt"
"sort"
// Local imports
"github.com/wcspromoteam/session"
)
// TODO: Do we want more permissions here?
type Permissions struct {
Read bool
Write bool
// Is the user authenticated at all? Aka 1) do they have a session cookie, and 2) have they logged in?
Authenticated bool
// Is the user recognized by the system? Do they have an account?
Recognized bool
// What path are these permissions for?
Path string
}
type User struct {
Email string
Groups []string
Perms []string
}
// Allows sorting the list of paths for matching.
type PermissionsList []Permissions
func (this PermissionsList) Len() int {
return len(this)
}
func (this PermissionsList) Less(i, j int) bool {
return len(this[i].Path) > len(this[j].Path)
}
func (this PermissionsList) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
}
// DEPRECATED! Exists only for compatibility reasons. Can be removed when modules are changed to use the new function.
func GetPerms(r *http.Request) (p *Permissions) {
return Get(r)
}
// Basic function that retrieves the permissions a user has based on the contents of their request, including cookies and request path. Designed to be a simple function for most uses. If you want more control, you can use the GetGroupPerms and GetUserPerms functions.
func Get(r *http.Request) (p *Permissions) {
p = new(Permissions)
s, e := session.GetExisting(r)
if e != nil {
p.Authenticated = false
return
}
p.Authenticated = true
// Current authentication is based on e-mail. Might change this?
uname := s.Get("openid-email")
fmt.Println("Getting permissions for", uname)
uperms := GetUserPerms(uname, r.URL.Path)
if uperms == nil {
p.Recognized = false
return
}
p.Write = uperms.Write
p.Read = uperms.Read
fmt.Println("Grabbed permissions for user")
groups := loadGroups(uname)
for _, group := range(groups) {
gperms := GetGroupPerms(group, r.URL.Path)
if gperms == nil {
continue
}
// Use the most permissive interpretation of the permissions. If a group is allowed to access something, so should all the users in the group.
if !uperms.Read {
if gperms.Read {
p.Read = true
}
}
if !uperms.Write {
if gperms.Write {
p.Write = true
}
}
}
return
}
// Retrieves the permissions for all members of a group with the given name.
func GetGroupPerms(name, path string) (p *Permissions) {
mperms := loadPerms("data/shared/groups/"+name+"/perms")
fmt.Println("Permissions for group", name, ":", mperms)
p = matchPerms(mperms, path)
fmt.Println("Matched permissions for group", name, ":", p)
return
}
// Retrieves the user permissions, and the user permissions ONLY, for a user with a given name. Does not take group membership into account, and is likely not that useful for this reason.
func GetUserPerms(name, path string) (p *Permissions) {
mperms := loadPerms("data/shared/users/"+name+"/perms")
fmt.Println("Permissions for user", name, ":", mperms)
p = matchPerms(mperms, path)
fmt.Println("Matched permissions for user", name, ":", p)
return
}
// Takes a list of permissions, likely garnered from a file, and returns the first found match. You should use sort.Sort to sort the list longest to shortest first, as this would allow the patterns to work as one would expect (more specific patterns override less specific ones, even if they have less permissions than than the more general ones).
func matchPerms(mperms PermissionsList, path string) (p *Permissions) {
for _, mperm := range(mperms) {
if pathMatch(mperm.Path, path) {
// TODO: Caching
fmt.Println("Found match for path", path, ":", mperm.Path)
p = &mperm
return
}
}
return
}
func loadGroups(name string) (gr []string) {
path := "data/shared/users/"+name+"/groups"
file, e := ioutil.ReadFile(path)
if e != nil {
fmt.Println("Could not get group list for", name, ":", e)
}
lines := bytes.Split(file, []byte{'\n'}, -1)
gr = make([]string, len(lines))
// Is this necessary? It's somewhat inefficient...
for i, line := range(lines) {
gr[i] = string(line)
}
return
}
func loadPerms(path string) (mperms PermissionsList) {
file, e := ioutil.ReadFile(path)
if e != nil {
fmt.Println("Could not get group permissions for", path, ":", e)
return
}
lines := bytes.Split(file, []byte("\n"), -1)
mperms = make(PermissionsList, len(lines))
for i, line := range(lines) {
parts := bytes.Split(line, []byte(" "), 2)
perms := mperms[i]
for _, perm := range(parts[0]) {
switch (perm) {
case 'r':
perms.Read = true
case 'w':
perms.Write = true
default:
fmt.Println("WARNING: Unrecognized permission", perm)
}
perms.Path = string(parts[1])
mperms[i] = perms
}
}
sort.Sort(mperms)
if !sort.IsSorted(mperms) {
fmt.Println("Failed to sort!")
}
return
}
// Does path match pattern?
// Stolen from HTTP library
func pathMatch(pattern, path string) bool {
if len(pattern) == 0 {
// should not happen
return false
}
n := len(pattern)
if pattern[n-1] != '/' {
return pattern == path
}
return len(path) >= n && path[0:n] == pattern
}