-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
permissions.go
199 lines (171 loc) · 6.27 KB
/
permissions.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
188
189
190
191
192
193
194
195
196
197
198
199
// Package permissions provides a way to keep track of users, login states and permissions.
package permissions
import (
"net/http"
"strings"
"github.com/xyproto/pinterface"
)
const (
// VersionString is the current version. The API is stable within major versions.
VersionString = "1.0.0"
)
// Permissions is a structure that keeps track of the permissions for various path prefixes
type Permissions struct {
state *UserState
denied http.HandlerFunc
adminPathPrefixes []string
userPathPrefixes []string
publicPathPrefixes []string
rootIsPublic bool
}
// New will initialize a Permissions struct with all the default settings.
// This will also connect to the redis host at localhost:6379.
func New() *Permissions {
return NewPermissions(NewUserStateSimple())
}
// New2 will initialize a Permissions struct with all the default settings.
// This will also connect to the redis host at localhost:6379.
func New2() (*Permissions, error) {
userstate, err := NewUserStateSimple2()
if err != nil {
return nil, err
}
return NewPermissions(userstate), nil
}
// NewWithRedisConf will initialize a Permissions struct with Redis DB index and host:port.
// Calls log.Fatal if something goes wrong.
func NewWithRedisConf(dbindex int, hostPort string) *Permissions {
return NewPermissions(NewUserState(dbindex, true, hostPort))
}
// NewWithRedisConf2 will initialize a Permissions struct with Redis DB index and host:port.
// Returns an error if something goes wrong.
func NewWithRedisConf2(dbindex int, hostPort string) (*Permissions, error) {
userstate, err := NewUserState2(dbindex, true, hostPort)
if err != nil {
return nil, err
}
return NewPermissions(userstate), nil
}
// NewPermissions will initialize a Permissions struct with the given UserState and
// a few default paths for admin/user/public path prefixes.
func NewPermissions(state *UserState) *Permissions {
// default permissions
return &Permissions{state,
PermissionDenied,
[]string{"/admin"}, // admin path prefixes
[]string{"/repo", "/data"}, // user path prefixes
[]string{"/",
"/login",
"/register",
"/favicon.ico",
"/style",
"/img",
"/js",
"/favicon.ico",
"/robots.txt",
"/sitemap_index.xml"}, // public
true} // root ("/") is public?
}
// SetDenyFunction can be used for specifying a http.HandlerFunc that will be used when the permissions are denied.
func (perm *Permissions) SetDenyFunction(f http.HandlerFunc) {
perm.denied = f
}
// DenyFunction returns the current http.HandlerFunc, for when permissions are denied.
func (perm *Permissions) DenyFunction() http.HandlerFunc {
return perm.denied
}
// UserState retrieves the UserState struct
func (perm *Permissions) UserState() pinterface.IUserState {
return perm.state
}
// Clear sets every URL path prefix permission to "public"
func (perm *Permissions) Clear() {
perm.adminPathPrefixes = []string{}
perm.userPathPrefixes = []string{}
}
// AddAdminPath registers a path prefix for URLs that shall only be reached by logged in administrators
func (perm *Permissions) AddAdminPath(prefix string) {
perm.adminPathPrefixes = append(perm.adminPathPrefixes, prefix)
}
// AddUserPath registers a path prefix for URLs that shall only be reached by logged in users
func (perm *Permissions) AddUserPath(prefix string) {
perm.userPathPrefixes = append(perm.userPathPrefixes, prefix)
}
// AddPublicPath registers a path prefix for URLs that can be reached by anyone
func (perm *Permissions) AddPublicPath(prefix string) {
perm.publicPathPrefixes = append(perm.publicPathPrefixes, prefix)
}
// SetAdminPath can be used for setting all URL path prefixes that are for the logged in administrator pages.
func (perm *Permissions) SetAdminPath(pathPrefixes []string) {
perm.adminPathPrefixes = pathPrefixes
}
// SetUserPath can be used for setting all URL path prefixes that are for the logged in user pages.
func (perm *Permissions) SetUserPath(pathPrefixes []string) {
perm.userPathPrefixes = pathPrefixes
}
// SetPublicPath can be used for setting all URL path prefixes that are for the public pages.
func (perm *Permissions) SetPublicPath(pathPrefixes []string) {
perm.publicPathPrefixes = pathPrefixes
}
// PermissionDenied is the default "permission denied" http handler.
func PermissionDenied(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "Permission denied.", http.StatusForbidden)
}
// Rejected checks if a given request should be rejected.
func (perm *Permissions) Rejected(_ http.ResponseWriter, req *http.Request) bool {
path := req.URL.Path // the path of the url that the user wish to visit
// If root is set to be public regardless of permissions and the path is "/", accept it
if perm.rootIsPublic && path == "/" {
return false // accept
}
// Reject if it is an admin page and user does not have admin permissions
for _, prefix := range perm.adminPathPrefixes {
if strings.HasPrefix(path, prefix) {
if !perm.state.AdminRights(req) {
return true // reject
}
}
}
// Reject if it's a user page and the user does not have user rights
for _, prefix := range perm.userPathPrefixes {
if strings.HasPrefix(path, prefix) {
if !perm.state.UserRights(req) {
return true // reject
}
}
}
// Don't reject if it's a public page
for _, prefix := range perm.publicPathPrefixes {
if strings.HasPrefix(path, prefix) {
return false // accept
}
}
// Otherwise
return true // reject
}
// Middleware handler (compatible with Negroni)
func (perm *Permissions) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
// Check if the user has the right admin/user rights
if perm.Rejected(w, req) {
// Get and call the Permission Denied function
perm.DenyFunction()(w, req)
// Reject the request by not calling the next handler below
return
}
// Call the next middleware handler
next(w, req)
}
// Middleware handler (compatible with Chi)
func (perm *Permissions) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Check if the user has the right admin/user rights
if perm.Rejected(w, req) {
// Get and call the Permission Denied function
perm.DenyFunction()(w, req)
// Reject the request by not calling the next handler below
return
}
// Call the next middleware handler
next.ServeHTTP(w, req)
})
}