-
Notifications
You must be signed in to change notification settings - Fork 1
/
ownership.go
352 lines (292 loc) · 11.1 KB
/
ownership.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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package wsHelpers
import (
"context"
"fmt"
"github.com/pixlise/core/v4/api/dbCollections"
"github.com/pixlise/core/v4/api/services"
"github.com/pixlise/core/v4/core/errorwithstatus"
"github.com/pixlise/core/v4/core/timestamper"
"github.com/pixlise/core/v4/core/utils"
protos "github.com/pixlise/core/v4/generated-protos"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func MakeOwnerForWrite(objectId string, objectType protos.ObjectType, creatorUserId string, createTimeUnixSec int64) *protos.OwnershipItem {
ownerItem := &protos.OwnershipItem{
Id: objectId,
ObjectType: objectType,
CreatedUnixSec: uint32(createTimeUnixSec),
CreatorUserId: "",
}
if len(creatorUserId) > 0 {
ownerItem.CreatorUserId = creatorUserId
ownerItem.Editors = &protos.UserGroupList{
UserIds: []string{creatorUserId},
}
}
return ownerItem
}
// Checks object access - if requireEdit is true, it checks for edit access
// otherwise just checks for view access. Returns an error if it failed to determine
// or if access is not granted, returns error formed with MakeUnauthorisedError
func CheckObjectAccess(requireEdit bool, objectId string, objectType protos.ObjectType, hctx HandlerContext) (*protos.OwnershipItem, error) {
return CheckObjectAccessForUser(requireEdit, objectId, objectType, hctx.SessUser.User.Id, hctx.SessUser.MemberOfGroupIds, hctx.SessUser.ViewerOfGroupIds, hctx.Svcs.MongoDB)
}
func CheckObjectAccessForUser(requireEdit bool, objectId string, objectType protos.ObjectType, userId string, memberOfGroupIds []string, viewerOfGroupIds []string, db *mongo.Database) (*protos.OwnershipItem, error) {
ownerCollectionId := objectId
result := db.Collection(dbCollections.OwnershipName).FindOne(context.TODO(), bson.M{"_id": ownerCollectionId})
if result.Err() != nil {
// If the error is due to the item not existing, this isn't a permissions error, but likely the object doesn't
// exist at all. No point going further, report this as a bad request right here
if result.Err() == mongo.ErrNoDocuments {
return nil, errorwithstatus.MakeNotFoundError(objectId)
}
return nil, fmt.Errorf("Failed to determine object access permissions for id: %v, type: %v. Error was: %v", objectId, objectType.String(), result.Err())
}
// Read it in & check
ownership := &protos.OwnershipItem{}
err := result.Decode(ownership)
if err != nil {
return nil, fmt.Errorf("Failed to decode object access data for id: %v, type: %v. Error was: %v", objectId, objectType.String(), result.Err())
}
// Now check permissions
// For editing, we only look at the editor list
accessType := "Edit"
toCheck := []*protos.UserGroupList{ownership.Editors}
if !requireEdit {
// If we're interested in view permissions, editors have implicit view permissions
// so we just add the viewer list here
toCheck = append(toCheck, ownership.Viewers)
accessType = "View"
}
for _, toCheckItem := range toCheck {
if toCheckItem == nil {
continue
}
// First check user id
if toCheckItem.UserIds != nil && utils.ItemInSlice(userId, toCheckItem.UserIds) {
return ownership, nil // User has access
} else {
// Check groups
if toCheckItem.GroupIds != nil {
for _, groupId := range memberOfGroupIds {
if utils.ItemInSlice(groupId, toCheckItem.GroupIds) {
return ownership, nil // User has access via group it belongs to
}
}
if !requireEdit {
// If we don't require editing, check if the user is a viewer of any of the groups too
for _, groupId := range viewerOfGroupIds {
if utils.ItemInSlice(groupId, toCheckItem.GroupIds) {
return ownership, nil // User has access via group it belongs to
}
}
}
}
}
}
// Access denied
return nil, errorwithstatus.MakeUnauthorisedError(fmt.Errorf("%v access denied for: %v (%v)", accessType, objectType.String(), objectId))
}
// Gets all object IDs which the user has access to - if requireEdit is true, it checks for edit access
// otherwise just checks for view access
// Returns a map of object id->creator user id
func ListAccessibleIDs(requireEdit bool, objectType protos.ObjectType, svcs *services.APIServices, requestorSession SessionUser) (map[string]*protos.OwnershipItem, error) {
idLookups := []interface{}{
bson.D{{Key: "editors.userids", Value: requestorSession.User.Id}},
}
if !requireEdit {
idLookups = append(idLookups, bson.D{{Key: "viewers.userids", Value: requestorSession.User.Id}})
}
// If we don't require editing, then we just care if the uesr can see the ID
if !requireEdit {
allGroupsUserIsIn := append(requestorSession.MemberOfGroupIds, requestorSession.ViewerOfGroupIds...)
for _, groupId := range allGroupsUserIsIn {
idLookups = append(idLookups, bson.D{{Key: "editors.groupids", Value: groupId}})
idLookups = append(idLookups, bson.D{{Key: "viewers.groupids", Value: groupId}})
}
} else {
// If we require editing, then we only care if the user can edit the ID
for _, groupId := range requestorSession.MemberOfGroupIds {
idLookups = append(idLookups, bson.D{{Key: "editors.groupids", Value: groupId}})
}
}
filter := bson.D{
{
Key: "$and", Value: []interface{}{
bson.D{{Key: "objecttype", Value: objectType}},
bson.M{"$or": idLookups},
},
},
}
result := map[string]*protos.OwnershipItem{}
opts := options.Find() //.SetProjection(bson.D{{"_id", true}})
cursor, err := svcs.MongoDB.Collection(dbCollections.OwnershipName).Find(context.TODO(), filter, opts)
if err != nil {
return nil, err
}
//err = cursor.All(context.TODO(), &resultIds)
items := []*protos.OwnershipItem{}
err = cursor.All(context.TODO(), &items)
if err != nil {
return nil, err
}
for _, item := range items {
result[item.Id] = item
}
return result, nil
}
func ListGroupAccessibleIDs(requireEdit bool, objectType protos.ObjectType, groupID string, mongoDB *mongo.Database) (map[string]*protos.OwnershipItem, error) {
idLookups := []interface{}{
bson.D{{Key: "editors.groupids", Value: groupID}},
}
if !requireEdit {
idLookups = append(idLookups, bson.D{{Key: "viewers.groupids", Value: groupID}})
}
filter := bson.D{
{
Key: "$and", Value: []interface{}{
bson.D{{Key: "objecttype", Value: objectType}},
bson.M{"$or": idLookups},
},
},
}
result := map[string]*protos.OwnershipItem{}
opts := options.Find() //.SetProjection(bson.D{{"_id", true}})
cursor, err := mongoDB.Collection(dbCollections.OwnershipName).Find(context.TODO(), filter, opts)
if err != nil {
return nil, err
}
items := []*protos.OwnershipItem{}
err = cursor.All(context.TODO(), &items)
if err != nil {
return nil, err
}
for _, item := range items {
result[item.Id] = item
}
return result, nil
}
func FetchOwnershipSummary(ownership *protos.OwnershipItem, sessionUser SessionUser, db *mongo.Database, ts timestamper.ITimeStamper, fullDetails bool) *protos.OwnershipSummary {
user, err := getUserInfo(ownership.CreatorUserId, db, ts)
result := &protos.OwnershipSummary{
CreatedUnixSec: ownership.CreatedUnixSec,
CanEdit: false,
}
if err == nil {
result.CreatorUser = user
} else {
result.CreatorUser = &protos.UserInfo{
Id: ownership.CreatorUserId,
}
}
if ownership.Viewers != nil {
result.ViewerUserCount = uint32(len(ownership.Viewers.UserIds))
// NOTE: if we're a viewer, subtract one!
if utils.ItemInSlice(ownership.CreatorUserId, ownership.Viewers.UserIds) && result.ViewerUserCount > 0 {
result.ViewerUserCount--
}
result.ViewerGroupCount = uint32(len(ownership.Viewers.GroupIds))
}
if ownership.Editors != nil {
result.EditorUserCount = uint32(len(ownership.Editors.UserIds))
// NOTE: if we're an editor, subtract one!
if utils.ItemInSlice(ownership.CreatorUserId, ownership.Editors.UserIds) && result.EditorUserCount > 0 {
result.EditorUserCount--
}
result.EditorGroupCount = uint32(len(ownership.Editors.GroupIds))
if !result.CanEdit && ownership.Editors.UserIds != nil {
result.CanEdit = utils.ItemInSlice(sessionUser.User.Id, ownership.Editors.UserIds)
}
if !result.CanEdit && ownership.Editors.GroupIds != nil {
for _, groupId := range sessionUser.MemberOfGroupIds {
if utils.ItemInSlice(groupId, ownership.Editors.GroupIds) {
result.CanEdit = true
break
}
}
}
}
result.SharedWithOthers = result.ViewerUserCount > 0 || result.ViewerGroupCount > 0 || result.EditorUserCount > 0 || result.EditorGroupCount > 0
// Hide more data intensive fields if we don't care
if !fullDetails {
result.CreatorUser.IconURL = ""
}
return result
}
func MakeOwnerSummary(ownership *protos.OwnershipItem, sessionUser SessionUser, db *mongo.Database, ts timestamper.ITimeStamper) *protos.OwnershipSummary {
return FetchOwnershipSummary(ownership, sessionUser, db, ts, false)
}
func MakeFullOwnerSummary(ownership *protos.OwnershipItem, sessionUser SessionUser, db *mongo.Database, ts timestamper.ITimeStamper) *protos.OwnershipSummary {
return FetchOwnershipSummary(ownership, sessionUser, db, ts, true)
}
func FindUserIdsFor(objectId string, mongoDB *mongo.Database) ([]string, error) {
filter := bson.M{"_id": objectId}
opts := options.FindOne()
ownership := mongoDB.Collection(dbCollections.OwnershipName).FindOne(context.TODO(), filter, opts)
if ownership.Err() != nil {
if ownership.Err() == mongo.ErrNoDocuments {
return []string{}, errorwithstatus.MakeNotFoundError(objectId)
}
return []string{}, ownership.Err()
}
ownershipItem := protos.OwnershipItem{}
err := ownership.Decode(&ownershipItem)
if err != nil {
return []string{}, err
}
userIds := []string{}
groupIds := []string{}
// Gather up all the user ids for the groups selected
if ownershipItem.Viewers != nil {
userIds = append(userIds, ownershipItem.Viewers.UserIds...)
groupIds = append(groupIds, ownershipItem.Viewers.GroupIds...)
}
if ownershipItem.Editors != nil {
userIds = append(userIds, ownershipItem.Editors.UserIds...)
groupIds = append(groupIds, ownershipItem.Editors.GroupIds...)
}
usersForGroups, err := GetUserIdsForGroup(groupIds, mongoDB)
if err != nil {
return []string{}, err
}
return append(userIds, usersForGroups...), nil
}
func GetUserIdsForGroup(groupIds []string, mongoDB *mongo.Database) ([]string, error) {
filter := bson.M{"_id": bson.M{"$in": groupIds}}
opts := options.Find()
cursor, err := mongoDB.Collection(dbCollections.UserGroupsName).Find(context.TODO(), filter, opts)
if err != nil {
return []string{}, err
}
groups := []*protos.UserGroupDB{}
err = cursor.All(context.TODO(), &groups)
if err != nil {
return []string{}, err
}
userIds := []string{}
for _, group := range groups {
// Pull in the users
for _, userId := range group.Viewers.UserIds {
userIds = append(userIds, userId)
}
for _, userId := range group.Members.UserIds {
userIds = append(userIds, userId)
}
// Recurse into groups
groupIds := []string{}
for _, groupId := range group.Viewers.GroupIds {
groupIds = append(groupIds, groupId)
}
for _, groupId := range group.Members.GroupIds {
groupIds = append(groupIds, groupId)
}
usersForGroup, err := GetUserIdsForGroup(groupIds, mongoDB)
if err != nil {
return []string{}, err
}
userIds = append(userIds, usersForGroup...)
}
return userIds, nil
}