-
Notifications
You must be signed in to change notification settings - Fork 177
/
authorization_attributes.go
134 lines (115 loc) · 4.04 KB
/
authorization_attributes.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
package middlewares
import (
"context"
"errors"
"net/http"
"path"
"strings"
"github.com/gorilla/mux"
"github.com/sensu/sensu-go/backend/authentication/jwt"
"github.com/sensu/sensu-go/backend/authorization"
"github.com/sensu/sensu-go/types"
)
// AuthorizationAttributes is an HTTP middleware that populates a context for the
// request, to be used by other middlewares. You probably want this middleware
// to be executed early in the middleware stack.
type AuthorizationAttributes struct{}
// Then is the AuthorizationAttrs middleware's main logic, heavily inspired by
// Kubernetes' WithRequestInfo.
//
// The general, expected format of a path is one of the following:
// /apis/{group}/{version}/namespaces
// /apis/{group}/{version}/namespaces/{namespace}/{resource}
// /apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}
//
// This middleware tries to fill in as many fields as it can in the
// authorization.Attributes struct added to the context, but there is no
// guarantee of any fields being filled in.
func (a AuthorizationAttributes) Then(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
attrs := &authorization.Attributes{}
ctx = authorization.SetAttributes(ctx, attrs)
defer next.ServeHTTP(w, r.WithContext(ctx))
switch r.Method {
case "POST":
attrs.Verb = "create"
case "GET", "HEAD":
attrs.Verb = "get"
case "PUT":
attrs.Verb = "update"
case "DELETE":
attrs.Verb = "delete"
default:
attrs.Verb = ""
}
vars := mux.Vars(r)
attrs.APIGroup = vars["group"]
attrs.APIVersion = vars["version"]
attrs.Namespace = vars["namespace"]
attrs.Resource = vars["resource"]
attrs.ResourceName = vars["id"]
// TODO: we can probably get rid of this special case by reworking the
// cluster router.
if attrs.Resource == "cluster" {
attrs.Resource = "cluster-members"
}
// Most resource names are identified by a route variable named "id".
// Other resources have snowflake paths; see their corresponding router
// and the expected paths above.
switch attrs.Resource {
case "events":
attrs.ResourceName = path.Join(vars["entity"], vars["check"])
case "silenced":
if strings.Contains(r.URL.Path, "/silenced/checks") {
attrs.ResourceName = path.Join("checks", vars["check"])
} else if strings.Contains(r.URL.Path, "/silenced/subscriptions") {
attrs.ResourceName = path.Join("subscriptions", vars["subscription"])
}
}
if attrs.Verb == "get" && (attrs.ResourceName == "" || isListable(attrs.Resource, attrs.ResourceName)) {
attrs.Verb = "list"
}
// Add the user to the attributes
if err := getUser(ctx, attrs); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// Verify if the authenticated user is trying to access itself
if attrs.Resource == "users" && attrs.ResourceName == attrs.User.Username {
// Change the resource to LocalSelfUserResource if a user views itself
if attrs.Verb == "get" && vars["subresource"] == "" {
attrs.Resource = types.LocalSelfUserResource
}
// Change the resource to LocalSelfUserResource if a user tries to change
// its own password
if attrs.Verb == "update" && vars["subresource"] == "password" {
attrs.Resource = types.LocalSelfUserResource
}
}
})
}
func isListable(resourceType, name string) bool {
// For /events, if the resource name doesn't contain a '/', we're listing
// /events/{entity} as opposed to getting /events/{entity}/{check}
if resourceType == "events" && !strings.ContainsRune(name, '/') {
return true
}
if resourceType == "silenced" && (name == "checks" || name == "subscriptions") {
return true
}
return false
}
func getUser(ctx context.Context, attrs *authorization.Attributes) error {
// Get the claims from the request context
claims := jwt.GetClaimsFromContext(ctx)
if claims == nil {
return errors.New("no claims found in the request context")
}
// Add the user to our request info
attrs.User = types.User{
Username: claims.Subject,
Groups: claims.Groups,
}
return nil
}