forked from xyproto/permissionbolt
/
permissionbolt.go
174 lines (147 loc) · 5.08 KB
/
permissionbolt.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
// Package permissionbolt provides middleware for keeping track of users, login states and permissions.
package permissionbolt
import (
"github.com/xyproto/pinterface"
"net/http"
"strings"
)
// The Permissions structure keeps track of the permissions for various path prefixes
type Permissions struct {
state *UserState
adminPathPrefixes []string
userPathPrefixes []string
publicPathPrefixes []string
rootIsPublic bool
denied http.HandlerFunc
}
const (
// Version number. Stable API within major version numbers.
Version = 2.0
)
// New initializes a Permissions struct with all the default settings.
func New() (*Permissions, error) {
state, err := NewUserStateSimple()
if err != nil {
return nil, err
}
return NewPermissions(state), nil
}
// NewWithConf initializes a Permissions struct with a database filename
func NewWithConf(filename string) (*Permissions, error) {
state, err := NewUserState(filename, true)
if err != nil {
return nil, err
}
return NewPermissions(state), nil
}
// NewPermissions initializes 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,
[]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,
PermissionDenied}
}
// SetDenyFunction specifies a http.HandlerFunc for when the permissions are denied
func (perm *Permissions) SetDenyFunction(f http.HandlerFunc) {
perm.denied = f
}
// DenyFunction returns the currently configured 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 permission to public
func (perm *Permissions) Clear() {
perm.adminPathPrefixes = []string{}
perm.userPathPrefixes = []string{}
}
// AddAdminPath adds an URL path prefix for pages that are only accessible for logged in administrators
func (perm *Permissions) AddAdminPath(prefix string) {
perm.adminPathPrefixes = append(perm.adminPathPrefixes, prefix)
}
// AddUserPath adds an URL path prefix for pages that are only accessible for logged in users
func (perm *Permissions) AddUserPath(prefix string) {
perm.userPathPrefixes = append(perm.userPathPrefixes, prefix)
}
// AddPublicPath adds an URL path prefix for pages that are public
func (perm *Permissions) AddPublicPath(prefix string) {
perm.publicPathPrefixes = append(perm.publicPathPrefixes, prefix)
}
// SetAdminPath sets all URL path prefixes for pages that are only accessible for logged in administrators
func (perm *Permissions) SetAdminPath(pathPrefixes []string) {
perm.adminPathPrefixes = pathPrefixes
}
// SetUserPath sets all URL path prefixes for pages that are only accessible for logged in users
func (perm *Permissions) SetUserPath(pathPrefixes []string) {
perm.userPathPrefixes = pathPrefixes
}
// SetPublicPath sets all URL path prefixes for pages that are public
func (perm *Permissions) SetPublicPath(pathPrefixes []string) {
perm.publicPathPrefixes = pathPrefixes
}
// PermissionDenied is the default "permission denied" handler function
func PermissionDenied(w http.ResponseWriter, req *http.Request) {
http.Error(w, "Permission denied.", http.StatusForbidden)
}
// Rejected checks if a given http request should be rejected
func (perm *Permissions) Rejected(w http.ResponseWriter, req *http.Request) bool {
reject := false
path := req.URL.Path // the path of the url that the user wish to visit
// If it's not "/" and set to be public regardless of permissions
if !(perm.rootIsPublic && path == "/") {
// 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) {
reject = true
break
}
}
}
if !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) {
reject = true
break
}
}
}
}
if !reject {
// Reject if it's not a public page
found := false
for _, prefix := range perm.publicPathPrefixes {
if strings.HasPrefix(path, prefix) {
found = true
break
}
}
if !found {
reject = true
}
}
}
return 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)
}