• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

goto / shield / 10787256047

10 Sep 2024 06:47AM UTC coverage: 51.953% (+0.01%) from 51.943%
10787256047

Pull #91

github

FemiNoviaLina
fix: core function name
Pull Request #91: feat: list resource of a user

66 of 143 new or added lines in 8 files covered. (46.15%)

1 existing line in 1 file now uncovered.

6809 of 13106 relevant lines covered (51.95%)

11.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/core/resource/service.go
1
package resource
2

3
import (
4
        "context"
5
        "fmt"
6
        "strings"
7

8
        "github.com/goto/salt/log"
9
        "github.com/goto/shield/core/action"
10
        "github.com/goto/shield/core/activity"
11
        "github.com/goto/shield/core/group"
12
        "github.com/goto/shield/core/namespace"
13
        "github.com/goto/shield/core/organization"
14
        "github.com/goto/shield/core/policy"
15
        "github.com/goto/shield/core/project"
16
        "github.com/goto/shield/core/relation"
17
        "github.com/goto/shield/core/user"
18
        "github.com/goto/shield/internal/schema"
19
        "github.com/goto/shield/pkg/db"
20
        "github.com/goto/shield/pkg/uuid"
21
)
22

23
const (
24
        auditKeyResourceCreate = "resource.create"
25
        auditKeyResourceUpdate = "resource.update"
26

27
        userNamespace = schema.UserPrincipal
28
)
29

30
type RelationService interface {
31
        Create(ctx context.Context, rel relation.RelationV2) (relation.RelationV2, error)
32
        Delete(ctx context.Context, rel relation.Relation) error
33
        CheckPermission(ctx context.Context, usr user.User, resourceNS namespace.Namespace, resourceIdxa string, action action.Action) (bool, error)
34
        BulkCheckPermission(ctx context.Context, rels []relation.Relation, acts []action.Action) ([]relation.Permission, error)
35
        DeleteSubjectRelations(ctx context.Context, resourceType, optionalResourceID string) error
36
        LookupResources(ctx context.Context, resourceType, permission, subjectType, subjectID string) ([]string, error)
37
}
38

39
type UserService interface {
40
        FetchCurrentUser(ctx context.Context) (user.User, error)
41
        Get(ctx context.Context, userID string) (user.User, error)
42
}
43

44
type ProjectService interface {
45
        Get(ctx context.Context, id string) (project.Project, error)
46
}
47

48
type OrganizationService interface {
49
        Get(ctx context.Context, id string) (organization.Organization, error)
50
}
51

52
type GroupService interface {
53
        GetBySlug(ctx context.Context, id string) (group.Group, error)
54
}
55

56
type ActivityService interface {
57
        Log(ctx context.Context, action string, actor activity.Actor, data any) error
58
}
59

60
type PolicyService interface {
61
        List(ctx context.Context, filter policy.Filters) ([]policy.Policy, error)
62
}
63

64
type NamespaceService interface {
65
        List(ctx context.Context) ([]namespace.Namespace, error)
66
}
67

68
type Service struct {
69
        logger              log.Logger
70
        repository          Repository
71
        configRepository    ConfigRepository
72
        relationService     RelationService
73
        userService         UserService
74
        projectService      ProjectService
75
        organizationService OrganizationService
76
        groupService        GroupService
77
        policyService       PolicyService
78
        namespaceService    NamespaceService
79
        activityService     ActivityService
80
}
81

NEW
82
func NewService(logger log.Logger, repository Repository, configRepository ConfigRepository, relationService RelationService, userService UserService, projectService ProjectService, organizationService OrganizationService, groupService GroupService, policyService PolicyService, namespaceService NamespaceService, activityService ActivityService) *Service {
×
83
        return &Service{
×
84
                logger:              logger,
×
85
                repository:          repository,
×
86
                configRepository:    configRepository,
×
87
                relationService:     relationService,
×
88
                userService:         userService,
×
89
                projectService:      projectService,
×
90
                organizationService: organizationService,
×
91
                groupService:        groupService,
×
NEW
92
                policyService:       policyService,
×
NEW
93
                namespaceService:    namespaceService,
×
94
                activityService:     activityService,
×
95
        }
×
96
}
×
97

98
func (s Service) GetByURN(ctx context.Context, id string) (Resource, error) {
×
99
        return s.repository.GetByURN(ctx, id)
×
100
}
×
101

102
func (s Service) Get(ctx context.Context, id string) (Resource, error) {
×
103
        return s.repository.GetByID(ctx, id)
×
104
}
×
105

106
func (s Service) Upsert(ctx context.Context, res Resource) (Resource, error) {
×
107
        currentUser, err := s.userService.FetchCurrentUser(ctx)
×
108
        if err != nil {
×
109
                return Resource{}, err
×
110
        }
×
111

112
        urn := res.CreateURN()
×
113

×
114
        if err != nil {
×
115
                return Resource{}, err
×
116
        }
×
117

118
        fetchedProject, err := s.projectService.Get(ctx, res.ProjectID)
×
119
        if err != nil {
×
120
                return Resource{}, err
×
121
        }
×
122

123
        userId := res.UserID
×
124
        if strings.TrimSpace(userId) == "" {
×
125
                userId = currentUser.ID
×
126
        }
×
127

128
        newResource, err := s.repository.Upsert(ctx, Resource{
×
129
                URN:            urn,
×
130
                Name:           res.Name,
×
131
                OrganizationID: fetchedProject.Organization.ID,
×
132
                ProjectID:      fetchedProject.ID,
×
133
                NamespaceID:    res.NamespaceID,
×
134
                UserID:         userId,
×
135
        })
×
136
        if err != nil {
×
137
                return Resource{}, err
×
138
        }
×
139

140
        if err = s.relationService.DeleteSubjectRelations(ctx, newResource.NamespaceID, newResource.Idxa); err != nil {
×
141
                return Resource{}, err
×
142
        }
×
143

144
        if err = s.AddProjectToResource(ctx, project.Project{ID: res.ProjectID}, newResource); err != nil {
×
145
                return Resource{}, err
×
146
        }
×
147

148
        if err = s.AddOrgToResource(ctx, organization.Organization{ID: newResource.OrganizationID}, newResource); err != nil {
×
149
                return Resource{}, err
×
150
        }
×
151

152
        go func() {
×
153
                ctx := context.WithoutCancel(ctx)
×
154
                resourceLogData := newResource.ToLogData()
×
155
                actor := activity.Actor{ID: currentUser.ID, Email: currentUser.Email}
×
156
                if err := s.activityService.Log(ctx, auditKeyResourceCreate, actor, resourceLogData); err != nil {
×
157
                        s.logger.Error(fmt.Sprintf("%s: %s", ErrLogActivity.Error(), err.Error()))
×
158
                }
×
159
        }()
160

161
        return newResource, nil
×
162
}
163

164
func (s Service) Create(ctx context.Context, res Resource) (Resource, error) {
×
165
        currentUser, err := s.userService.FetchCurrentUser(ctx)
×
166
        if err != nil {
×
167
                return Resource{}, err
×
168
        }
×
169

170
        urn := res.CreateURN()
×
171

×
172
        if err != nil {
×
173
                return Resource{}, err
×
174
        }
×
175

176
        fetchedProject, err := s.projectService.Get(ctx, res.ProjectID)
×
177
        if err != nil {
×
178
                return Resource{}, err
×
179
        }
×
180

181
        userId := res.UserID
×
182
        if strings.TrimSpace(userId) == "" {
×
183
                userId = currentUser.ID
×
184
        }
×
185

186
        newResource, err := s.repository.Create(ctx, Resource{
×
187
                URN:            urn,
×
188
                Name:           res.Name,
×
189
                OrganizationID: fetchedProject.Organization.ID,
×
190
                ProjectID:      fetchedProject.ID,
×
191
                NamespaceID:    res.NamespaceID,
×
192
                UserID:         userId,
×
193
        })
×
194
        if err != nil {
×
195
                return Resource{}, err
×
196
        }
×
197

198
        if err = s.relationService.DeleteSubjectRelations(ctx, newResource.NamespaceID, newResource.Idxa); err != nil {
×
199
                return Resource{}, err
×
200
        }
×
201

202
        if err = s.AddProjectToResource(ctx, project.Project{ID: res.ProjectID}, newResource); err != nil {
×
203
                return Resource{}, err
×
204
        }
×
205

206
        if err = s.AddOrgToResource(ctx, organization.Organization{ID: newResource.OrganizationID}, newResource); err != nil {
×
207
                return Resource{}, err
×
208
        }
×
209

210
        go func() {
×
211
                ctx = db.WithoutTx(ctx)
×
212
                ctx = context.WithoutCancel(ctx)
×
213
                resourceLogData := newResource.ToLogData()
×
214
                actor := activity.Actor{ID: currentUser.ID, Email: currentUser.Email}
×
215
                if err := s.activityService.Log(ctx, auditKeyResourceCreate, actor, resourceLogData); err != nil {
×
216
                        s.logger.Error(fmt.Sprintf("%s: %s", ErrLogActivity.Error(), err.Error()))
×
217
                }
×
218
        }()
219

220
        return newResource, nil
×
221
}
222

223
func (s Service) List(ctx context.Context, flt Filter) (PagedResources, error) {
×
224
        resources, err := s.repository.List(ctx, flt)
×
225
        if err != nil {
×
226
                return PagedResources{}, err
×
227
        }
×
228
        return PagedResources{
×
229
                Count:     int32(len(resources)),
×
230
                Resources: resources,
×
231
        }, nil
×
232
}
233

234
func (s Service) Update(ctx context.Context, id string, resource Resource) (Resource, error) {
×
235
        currentUser, err := s.userService.FetchCurrentUser(ctx)
×
236
        if err != nil {
×
237
                return Resource{}, err
×
238
        }
×
239

240
        // TODO there should be an update logic like create here
241
        updatedResource, err := s.repository.Update(ctx, id, resource)
×
242
        if err != nil {
×
243
                return Resource{}, err
×
244
        }
×
245

246
        go func() {
×
247
                ctx := context.WithoutCancel(ctx)
×
248
                resourceLogData := updatedResource.ToLogData()
×
249
                actor := activity.Actor{ID: currentUser.ID, Email: currentUser.Email}
×
250
                if err := s.activityService.Log(ctx, auditKeyResourceUpdate, actor, resourceLogData); err != nil {
×
251
                        s.logger.Error(fmt.Sprintf("%s: %s", ErrLogActivity.Error(), err.Error()))
×
252
                }
×
253
        }()
254

255
        return updatedResource, nil
×
256
}
257

258
func (s Service) AddProjectToResource(ctx context.Context, project project.Project, res Resource) error {
×
259
        rel := relation.RelationV2{
×
260
                Object: relation.Object{
×
261
                        ID:          res.Idxa,
×
262
                        NamespaceID: res.NamespaceID,
×
263
                },
×
264
                Subject: relation.Subject{
×
265
                        RoleID:    schema.ProjectRelationName,
×
266
                        ID:        project.ID,
×
267
                        Namespace: schema.ProjectNamespace,
×
268
                },
×
269
        }
×
270

×
271
        if _, err := s.relationService.Create(ctx, rel); err != nil {
×
272
                return err
×
273
        }
×
274

275
        return nil
×
276
}
277

278
func (s Service) AddOrgToResource(ctx context.Context, org organization.Organization, res Resource) error {
×
279
        rel := relation.RelationV2{
×
280
                Object: relation.Object{
×
281
                        ID:          res.Idxa,
×
282
                        NamespaceID: res.NamespaceID,
×
283
                },
×
284
                Subject: relation.Subject{
×
285
                        RoleID:    schema.OrganizationRelationName,
×
286
                        ID:        org.ID,
×
287
                        Namespace: schema.OrganizationNamespace,
×
288
                },
×
289
        }
×
290

×
291
        if _, err := s.relationService.Create(ctx, rel); err != nil {
×
292
                return err
×
293
        }
×
294
        return nil
×
295
}
296

297
func (s Service) GetAllConfigs(ctx context.Context) ([]YAML, error) {
×
298
        return s.configRepository.GetAll(ctx)
×
299
}
×
300

301
// TODO(krkvrm): Separate Authz for Resources & System Namespaces
302
func (s Service) CheckAuthz(ctx context.Context, res Resource, act action.Action) (bool, error) {
×
303
        currentUser, err := s.userService.FetchCurrentUser(ctx)
×
304
        if err != nil {
×
305
                return false, err
×
306
        }
×
307

308
        isSystemNS := namespace.IsSystemNamespaceID(res.NamespaceID)
×
309
        fetchedResource := res
×
310

×
311
        if isSystemNS {
×
312
                if !uuid.IsValid(res.Name) {
×
313
                        switch res.NamespaceID {
×
314
                        case namespace.DefinitionProject.ID:
×
315
                                project, err := s.projectService.Get(ctx, res.Name)
×
316
                                if err != nil {
×
317
                                        return false, err
×
318
                                }
×
319
                                res.Name = project.ID
×
320
                        case namespace.DefinitionOrg.ID:
×
321
                                organization, err := s.organizationService.Get(ctx, res.Name)
×
322
                                if err != nil {
×
323
                                        return false, err
×
324
                                }
×
325
                                res.Name = organization.ID
×
326
                        case namespace.DefinitionTeam.ID:
×
327
                                group, err := s.groupService.GetBySlug(ctx, res.Name)
×
328
                                if err != nil {
×
329
                                        return false, err
×
330
                                }
×
331
                                res.Name = group.ID
×
332
                        }
333
                }
334
                fetchedResource.Idxa = res.Name
×
335
        } else {
×
336
                fetchedResource, err = s.repository.GetByNamespace(ctx, res.Name, res.NamespaceID)
×
337
                if err != nil {
×
338
                        fetchedResource, err = s.repository.GetByID(ctx, res.Name)
×
339
                        if err != nil {
×
340
                                return false, ErrNotExist
×
341
                        }
×
342
                }
343
        }
344
        fetchedResourceNS := namespace.Namespace{ID: fetchedResource.NamespaceID}
×
345
        return s.relationService.CheckPermission(ctx, currentUser, fetchedResourceNS, fetchedResource.Idxa, act)
×
346
}
347

348
func (s Service) BulkCheckAuthz(ctx context.Context, resources []Resource, actions []action.Action) ([]relation.Permission, error) {
×
349
        currentUser, err := s.userService.FetchCurrentUser(ctx)
×
350
        if err != nil {
×
351
                return []relation.Permission{}, err
×
352
        }
×
353

354
        var relations []relation.Relation
×
355
        for _, res := range resources {
×
356
                isSystemNS := namespace.IsSystemNamespaceID(res.NamespaceID)
×
357
                fetchedResource := res
×
358

×
359
                if isSystemNS {
×
360
                        if !uuid.IsValid(res.Name) {
×
361
                                switch res.NamespaceID {
×
362
                                case namespace.DefinitionProject.ID:
×
363
                                        project, err := s.projectService.Get(ctx, res.Name)
×
364
                                        if err != nil {
×
365
                                                return []relation.Permission{}, err
×
366
                                        }
×
367
                                        res.Name = project.ID
×
368
                                case namespace.DefinitionOrg.ID:
×
369
                                        organization, err := s.organizationService.Get(ctx, res.Name)
×
370
                                        if err != nil {
×
371
                                                return []relation.Permission{}, err
×
372
                                        }
×
373
                                        res.Name = organization.ID
×
374
                                case namespace.DefinitionTeam.ID:
×
375
                                        group, err := s.groupService.GetBySlug(ctx, res.Name)
×
376
                                        if err != nil {
×
377
                                                return []relation.Permission{}, err
×
378
                                        }
×
379
                                        res.Name = group.ID
×
380
                                }
381
                        }
382
                        fetchedResource.Idxa = res.Name
×
383
                } else {
×
384
                        fetchedResource, err = s.repository.GetByNamespace(ctx, res.Name, res.NamespaceID)
×
385
                        if err != nil {
×
386
                                fetchedResource, err = s.repository.GetByID(ctx, res.Name)
×
387
                                if err != nil {
×
388
                                        return []relation.Permission{}, ErrNotExist
×
389
                                }
×
390
                        }
391
                }
392
                fetchedResourceNS := namespace.Namespace{ID: fetchedResource.NamespaceID}
×
393

×
394
                relations = append(relations, relation.Relation{
×
395
                        SubjectID:        currentUser.ID,
×
396
                        SubjectNamespace: namespace.DefinitionUser,
×
397
                        ObjectID:         fetchedResource.Idxa,
×
398
                        ObjectNamespace:  fetchedResourceNS,
×
399
                })
×
400
        }
401
        return s.relationService.BulkCheckPermission(ctx, relations, actions)
×
402
}
403

NEW
404
func (s Service) ListUserResourcesByType(ctx context.Context, userID string, resourceType string) (ResourcePermission, error) {
×
NEW
405
        user, err := s.userService.Get(ctx, userID)
×
NEW
406
        if err != nil {
×
NEW
407
                return ResourcePermission{}, err
×
NEW
408
        }
×
409

NEW
410
        res, err := s.listUserResources(ctx, resourceType, user)
×
NEW
411
        if err != nil {
×
NEW
412
                return ResourcePermission{}, err
×
NEW
413
        }
×
414

NEW
415
        return res, nil
×
416
}
417

NEW
418
func (s Service) ListAllUserResources(ctx context.Context, userID string, resourceType []string) (map[string]ResourcePermission, error) {
×
NEW
419
        user, err := s.userService.Get(ctx, userID)
×
NEW
420
        if err != nil {
×
NEW
421
                return map[string]ResourcePermission{}, err
×
NEW
422
        }
×
423

NEW
424
        if len(resourceType) == 0 {
×
NEW
425
                namespaces, err := s.namespaceService.List(ctx)
×
NEW
426
                if err != nil {
×
NEW
427
                        return map[string]ResourcePermission{}, err
×
NEW
428
                }
×
429

NEW
430
                for _, ns := range namespaces {
×
NEW
431
                        if namespace.IsSystemNamespaceID(ns.ID) {
×
NEW
432
                                continue
×
433
                        }
NEW
434
                        resourceType = append(resourceType, ns.ID)
×
435
                }
436
        }
437

NEW
438
        result := make(map[string]ResourcePermission)
×
NEW
439
        for _, res := range resourceType {
×
NEW
440
                if _, ok := result[res]; !ok {
×
NEW
441
                        list, err := s.listUserResources(ctx, res, user)
×
NEW
442
                        if err != nil {
×
NEW
443
                                return map[string]ResourcePermission{}, err
×
NEW
444
                        }
×
NEW
445
                        if len(list) != 0 {
×
NEW
446
                                result[res] = list
×
NEW
447
                        }
×
448
                }
449
        }
450

NEW
451
        return result, nil
×
452
}
453

NEW
454
func (s Service) listUserResources(ctx context.Context, resourceType string, user user.User) (ResourcePermission, error) {
×
NEW
455
        policies, err := s.policyService.List(ctx, policy.Filters{NamespaceID: resourceType})
×
NEW
456
        if err != nil {
×
NEW
457
                return ResourcePermission{}, err
×
NEW
458
        }
×
459

NEW
460
        res := make(ResourcePermission)
×
NEW
461
        actSet := make(map[string]bool)
×
NEW
462
        for _, policy := range policies {
×
NEW
463
                action := strings.Split(policy.ActionID, ".")[0]
×
NEW
464
                if _, ok := actSet[action]; !ok {
×
NEW
465
                        actSet[action] = true
×
NEW
466
                } else {
×
NEW
467
                        continue
×
468
                }
NEW
469
                resources, err := s.relationService.LookupResources(ctx, resourceType, action, userNamespace, user.ID)
×
NEW
470
                if err != nil {
×
NEW
471
                        return ResourcePermission{}, err
×
NEW
472
                }
×
473

NEW
474
                for _, r := range resources {
×
NEW
475
                        if _, ok := res[r]; !ok {
×
NEW
476
                                res[r] = []string{action}
×
NEW
477
                        } else {
×
NEW
478
                                res[r] = append(res[r], action)
×
NEW
479
                        }
×
480
                }
481
        }
482

NEW
483
        return res, nil
×
484
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc