-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpathauthz.go
More file actions
105 lines (87 loc) · 3.17 KB
/
pathauthz.go
File metadata and controls
105 lines (87 loc) · 3.17 KB
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
package pathauthz
import (
"context"
"net/http"
"strings"
)
// Config holds the plugin configuration.
type Config struct {
BasePath string `json:"basePath,omitempty"` // Configurable base path (e.g., "/v2")
UserHeader string `json:"userHeader,omitempty"` // Name of the header that contains the authenticated user
SuperUsers []string `json:superUsers,omitempty"` // List of users who can write to all paths
ReadOnlyUsers []string `json:readonlyUsers,omitempty"` // List of users can only read
}
// CreateConfig creates the default plugin configuration.
func CreateConfig() *Config {
return &Config{
BasePath: "/v2", // Default value for the base path
UserHeader: "X-Authenticated-User", // Default header for user identification
}
}
// RestrictMethodMiddleware is a middleware that restricts certain HTTP methods on specific paths.
type RestrictMethodMiddleware struct {
next http.Handler
basePath string
userHeader string
superUsers map[string]struct{}
readOnlyUsers map[string]struct{}
}
// New creates a new RestrictMethodMiddleware.
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
superUsers := make(map[string]struct{})
readOnlyUsers := make(map[string]struct{})
for _, user := range config.SuperUsers {
superUsers[user] = struct{}{}
}
for _, user := range config.ReadOnlyUsers {
readOnlyUsers[user] = struct{}{}
}
return &RestrictMethodMiddleware{
next: next,
basePath: config.BasePath,
userHeader: config.UserHeader,
superUsers: superUsers,
readOnlyUsers: readOnlyUsers,
}, nil
}
func isWriteReq(method string) bool {
return method == http.MethodPut || method == http.MethodPost || method == http.MethodDelete || method == http.MethodPatch
}
func (m *RestrictMethodMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
reqPath := strings.TrimRight(req.URL.Path, "/")
basePath := strings.TrimRight(m.basePath, "/")
// If not base path, move on
if reqPath != basePath && !strings.HasPrefix(reqPath, basePath+"/") {
m.next.ServeHTTP(rw, req)
return
}
// Extract the username from the specified header
user := req.Header.Get(m.userHeader)
// If super user, allow & move on
if _, isSuperUser := m.superUsers[user]; isSuperUser {
m.next.ServeHTTP(rw, req)
return
}
// Determine if it is a write request
isWriteReq := isWriteReq(req.Method)
// If a write request, and a read-only user, deny access
if _, isReadOnlyUser := m.readOnlyUsers[user]; isReadOnlyUser && isWriteReq {
http.Error(rw, "Forbidden", http.StatusForbidden)
return
}
// If request exactly matches base path, then restrict writes
if isWriteReq && reqPath == basePath {
http.Error(rw, "Forbidden", http.StatusForbidden)
return
}
// Build the restricted path
restrictedPath := basePath + "/" + user
restrictedPathPrefix := basePath + "/" + user + "/"
// If path doesn't match auth'd username, deny access
if isWriteReq && reqPath != restrictedPath && !strings.HasPrefix(reqPath, restrictedPathPrefix) {
http.Error(rw, "Forbidden", http.StatusForbidden)
return
}
// Continue to the next handler
m.next.ServeHTTP(rw, req)
}