-
Notifications
You must be signed in to change notification settings - Fork 0
/
casbin.go
202 lines (169 loc) · 4.84 KB
/
casbin.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
200
201
202
package casbin
import (
"net/http"
"strings"
"github.com/casbin/casbin/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Config struct {
// Skipper defines a function to skip middleware.
Skipper middleware.Skipper
// Enforce defines the enforcer used for
// authorization enforcement and policy management.
// Required.
Enforcer *casbin.Enforcer
// ContextKey defines the key that will be used to
// read the roles on the echo.Context for enforcing.
// Optional. Defaults to "roles".
ContextKey string
// DefaultRoles defines
// Optional. Defaults to "any".
DefaultRole string
// EnableRolesHeader enables the RolesHeader.
// Optional. Defaults to false.
EnableRolesHeader bool
// RolesHeader defines the header that will be used to
// read in the roles if EnableRolesHeader is set to true.
// Roles should be separated by commas. E.g. "role1,role2".
// Optional. Defaults to false.
RolesHeader string
// RolesHeaderFunc defines the function that will validate that
// a client is allowed to the use roles they passed via the RolesHeader.
// The RolesHeader value will be passed unmodified, so you will need
// to parse it in this function yourself. The DefaultRole will be passed
// if the RolesHeader is empty. The roles that you want to have
// enforced will need to be returned in a slice: []string{"role1, "role2"}.
// Optional.
RolesHeaderFunc func(string) ([]string, error)
// RolesFunc defines the function that will retrieve the roles
// to be passed to the Enforcer.
// Takes precedence over ContextKey and RolesHeader if they're defined.
// Optional.
RolesFunc func(echo.Context) ([]string, error)
// ForbiddenMessage defines the message that will be
// returned when authorization fails.
// Optional. Defaults to "Access to this resource has been restricted".
ForbiddenMessage string
// SuccessFunc defines the function that will run
// when authorization succeeds.
// Optional.
SuccessFunc func(string, string, string)
// FailureFunc defines the function that will run
// when authorization fails.
// Optional.
FailureFunc func([]string, string, string)
}
var DefaultConfig = Config{
Skipper: middleware.DefaultSkipper,
ContextKey: "roles",
DefaultRole: "any",
RolesHeader: "X-Roles",
ForbiddenMessage: "Access to this resource has been restricted",
}
func Casbin(ce *casbin.Enforcer) echo.MiddlewareFunc {
c := DefaultConfig
c.Enforcer = ce
return CasbinWithConfig(c)
}
func CasbinWithConfig(config Config) echo.MiddlewareFunc {
if config.Skipper == nil {
config.Skipper = DefaultConfig.Skipper
}
if config.Enforcer == nil {
panic("enforcer is required")
}
if config.ContextKey == "" {
config.ContextKey = DefaultConfig.ContextKey
}
if config.DefaultRole == "" {
config.DefaultRole = DefaultConfig.DefaultRole
}
if config.RolesHeader == "" {
config.RolesHeader = DefaultConfig.RolesHeader
}
if config.ForbiddenMessage == "" {
config.ForbiddenMessage = DefaultConfig.ForbiddenMessage
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
var roles []string
if config.RolesFunc != nil {
var err error
roles, err = config.RolesFunc(c)
if err != nil {
return err
}
} else {
var ok bool
k := c.Get(config.ContextKey)
if k != nil {
switch k.(type) {
case []string:
roles = k.([]string)
ok = true
case []interface{}:
for _, role := range k.([]interface{}) {
_, str := role.(string)
if str {
roles = append(roles, role.(string))
}
}
ok = true
}
}
if !ok {
roles = []string{}
}
if len(roles) < 1 && config.EnableRolesHeader {
rolesHeader := c.Request().Header.Get(config.RolesHeader)
if rolesHeader == "" {
rolesHeader = config.DefaultRole
}
if config.RolesHeaderFunc != nil {
var err error
roles, err = config.RolesHeaderFunc(rolesHeader)
if err != nil {
return err
}
} else {
for _, role := range strings.Split(rolesHeader, ",") {
role = strings.TrimSpace(role)
roles = append(roles, role)
}
}
}
}
if len(roles) < 1 {
roles = append(roles, config.DefaultRole)
}
obj := c.Path()
act := c.Request().Method
var authorized bool
for _, role := range roles {
pass, err := config.Enforcer.Enforce(role, obj, act)
if err != nil {
return err
}
if pass {
authorized = true
if config.SuccessFunc != nil {
config.SuccessFunc(role, obj, act)
}
break
}
}
if !authorized {
if config.FailureFunc != nil {
config.FailureFunc(roles, obj, act)
}
err := echo.NewHTTPError(http.StatusForbidden, config.ForbiddenMessage)
return err
}
return next(c)
}
}
}