forked from rancher/rancher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validator_multiclusterapp.go
151 lines (142 loc) · 5.59 KB
/
validator_multiclusterapp.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
package multiclusterapp
import (
"fmt"
"net/http"
"reflect"
"sort"
"strings"
"time"
"github.com/rancher/norman/api/access"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/set"
"github.com/rancher/norman/types/values"
gaccess "github.com/rancher/rancher/pkg/api/customization/globalnamespaceaccess"
"github.com/rancher/types/apis/management.cattle.io/v3"
managementschema "github.com/rancher/types/apis/management.cattle.io/v3/schema"
"github.com/rancher/types/client/management/v3"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/meta"
)
type Wrapper struct {
MultiClusterApps v3.MultiClusterAppInterface
MultiClusterAppLister v3.MultiClusterAppLister
MultiClusterAppRevisionLister v3.MultiClusterAppRevisionLister
PrtbLister v3.ProjectRoleTemplateBindingLister
CrtbLister v3.ClusterRoleTemplateBindingLister
RoleTemplateLister v3.RoleTemplateLister
Users v3.UserInterface
}
const (
mcAppLabel = "io.cattle.field/multiClusterAppId"
creatorIDAnn = "field.cattle.io/creatorId"
globalScopeAnswersKey = "global"
)
func (w Wrapper) LinkHandler(apiContext *types.APIContext, next types.RequestHandler) error {
switch apiContext.Link {
case "revisions":
var app client.MultiClusterApp
if err := access.ByID(apiContext, &managementschema.Version, client.MultiClusterAppType, apiContext.ID, &app); err != nil {
return err
}
var appRevisions, filtered []map[string]interface{}
if err := access.List(apiContext, &managementschema.Version, client.MultiClusterAppRevisionType, &types.QueryOptions{}, &appRevisions); err != nil {
return err
}
for _, revision := range appRevisions {
labels := convert.ToMapInterface(revision["labels"])
if reflect.DeepEqual(labels[mcAppLabel], app.Name) {
filtered = append(filtered, revision)
}
}
sort.SliceStable(filtered, func(i, j int) bool {
val1, err := time.Parse(time.RFC3339, convert.ToString(values.GetValueN(filtered[i], "created")))
if err != nil {
logrus.Infof("error parsing time %v", err)
}
val2, err := time.Parse(time.RFC3339, convert.ToString(values.GetValueN(filtered[j], "created")))
if err != nil {
logrus.Infof("error parsing time %v", err)
}
return val1.After(val2)
})
apiContext.Type = client.MultiClusterAppRevisionType
apiContext.WriteResponse(http.StatusOK, filtered)
return nil
}
return nil
}
func (w Wrapper) Validator(request *types.APIContext, schema *types.Schema, data map[string]interface{}) error {
if request.Method != http.MethodPut && request.Method != http.MethodPost {
return nil
}
ma := gaccess.MemberAccess{
Users: w.Users,
PrtbLister: w.PrtbLister,
CrtbLister: w.CrtbLister,
RoleTemplateLister: w.RoleTemplateLister,
}
var targetProjects []string
callerID := request.Request.Header.Get(gaccess.ImpersonateUserHeader)
if request.Method == http.MethodPost {
// create request, caller is owner/creator
// Request is POST, hence multiclusterapp is being created.
// check if creator has the given roles in all target projects
targets, _ := values.GetSlice(data, client.MultiClusterAppFieldTargets)
for _, t := range targets {
targetProjects = append(targetProjects, convert.ToString(t[client.TargetFieldProjectID]))
}
roleTemplates := convert.ToStringSlice(data[client.MultiClusterAppFieldRoles])
return ma.EnsureRoleInTargets(targetProjects, roleTemplates, callerID)
}
// edit request, only editing members, roles and answers is allowed through this
split := strings.SplitN(request.ID, ":", 2)
if len(split) != 2 {
return fmt.Errorf("incorrect multiclusterapp ID %v", request.ID)
}
mcapp, err := w.MultiClusterAppLister.Get(split[0], split[1])
if err != nil {
return err
}
metaAccessor, err := meta.Accessor(mcapp)
if err != nil {
return err
}
creatorID, ok := metaAccessor.GetAnnotations()[creatorIDAnn]
if !ok {
return fmt.Errorf("MultiClusterApp %v has no creatorId annotation", metaAccessor.GetName())
}
accessType, err := ma.GetAccessTypeOfCaller(callerID, creatorID, mcapp.Name, mcapp.Spec.Members)
if err != nil {
return err
}
if accessType == gaccess.ReadonlyAccess {
return fmt.Errorf("read-only members cannot update multiclusterapp")
}
ownerAccess := accessType == gaccess.OwnerAccess
// only members and roles list, and templateversion/answers can be edited through PUT, for updating target projects, we need to use actions only
// that's why target projects field has been made non updatable in rancher/types
if err := gaccess.CheckAccessToUpdateMembers(mcapp.Spec.Members, data, ownerAccess); err != nil {
return err
}
// check whether roles are being edited, if yes then only owner should be allowed to, and should have this role in all projects
roles := convert.ToStringSlice(data[client.MultiClusterAppFieldRoles])
newRoles := make(map[string]bool)
for _, r := range roles {
newRoles[r] = true
}
originalRoles := make(map[string]bool)
for _, r := range mcapp.Spec.Roles {
originalRoles[r] = true
}
// get difference between new and original roles
rolesToAdd, rolesToRemove, _ := set.Diff(newRoles, originalRoles)
if (len(rolesToAdd) != 0 || len(rolesToRemove) != 0) && !ownerAccess {
return fmt.Errorf("user %v is not an owner of multiclusterapp %v, cannot edit roles", callerID, mcapp.Name)
}
// check if modifier has all roles listed in toAdd in all projects
for _, t := range mcapp.Spec.Targets {
targetProjects = append(targetProjects, t.ProjectName)
}
return ma.EnsureRoleInTargets(targetProjects, rolesToAdd, callerID)
}