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

goto / guardian / 12304289958

12 Dec 2024 08:30PM UTC coverage: 73.933% (-0.5%) from 74.39%
12304289958

push

github

Ayushi Sharma
fix: update oss client caching logic

10823 of 14639 relevant lines covered (73.93%)

4.8 hits per line

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

72.26
/core/provider/service.go
1
package provider
2

3
import (
4
        "context"
5
        "fmt"
6
        "reflect"
7
        "strings"
8
        "time"
9

10
        "github.com/goto/guardian/pkg/evaluator"
11

12
        "github.com/go-playground/validator/v10"
13
        "github.com/goto/guardian/domain"
14
        "github.com/goto/guardian/pkg/log"
15
        "github.com/goto/guardian/plugins/providers"
16
        "github.com/goto/guardian/utils"
17
        "github.com/goto/salt/audit"
18
)
19

20
const (
21
        AuditKeyCreate = "provider.create"
22
        AuditKeyUpdate = "provider.update"
23
        AuditKeyDelete = "provider.delete"
24
)
25

26
//go:generate mockery --name=repository --exported --with-expecter
27
type repository interface {
28
        Create(context.Context, *domain.Provider) error
29
        Update(context.Context, *domain.Provider) error
30
        Find(context.Context) ([]*domain.Provider, error)
31
        GetByID(ctx context.Context, id string) (*domain.Provider, error)
32
        GetTypes(context.Context) ([]domain.ProviderType, error)
33
        GetOne(ctx context.Context, pType, urn string) (*domain.Provider, error)
34
        Delete(ctx context.Context, id string) error
35
}
36

37
//go:generate mockery --name=Client --exported --with-expecter
38
type Client interface {
39
        providers.PermissionManager
40
        providers.Client
41
}
42

43
//go:generate mockery --name=activityManager --exported --with-expecter
44
type activityManager interface {
45
        GetActivities(context.Context, domain.Provider, domain.ListActivitiesFilter) ([]*domain.Activity, error)
46
}
47

48
//go:generate mockery --name=dormancyChecker --exported --with-expecter
49
type dormancyChecker interface {
50
        ListActivities(context.Context, domain.Provider, domain.ListActivitiesFilter) ([]*domain.Activity, error)
51
        CorrelateGrantActivities(context.Context, domain.Provider, []*domain.Grant, []*domain.Activity) error
52
}
53

54
//go:generate mockery --name=assignmentTyper --exported --with-expecter
55
type assignmentTyper interface {
56
        IsExclusiveRoleAssignment(context.Context) bool
57
}
58

59
type grantDependenciesResolver interface {
60
        GetDependencyGrants(context.Context, domain.Provider, domain.Grant) ([]*domain.Grant, error)
61
}
62

63
//go:generate mockery --name=resourceService --exported --with-expecter
64
type resourceService interface {
65
        Find(context.Context, domain.ListResourcesFilter) ([]*domain.Resource, error)
66
        BulkUpsert(context.Context, []*domain.Resource) error
67
        BatchDelete(context.Context, []string) error
68
}
69

70
//go:generate mockery --name=auditLogger --exported --with-expecter
71
type auditLogger interface {
72
        Log(ctx context.Context, action string, data interface{}) error
73
}
74

75
// Service handling the business logics
76
type Service struct {
77
        repository      repository
78
        resourceService resourceService
79
        clients         map[string]Client
80

81
        validator   *validator.Validate
82
        logger      log.Logger
83
        auditLogger auditLogger
84
}
85

86
type ServiceDeps struct {
87
        Repository      repository
88
        ResourceService resourceService
89
        Clients         []Client
90

91
        Validator   *validator.Validate
92
        Logger      log.Logger
93
        AuditLogger auditLogger
94
}
95

96
// NewService returns service struct
97
func NewService(deps ServiceDeps) *Service {
10✔
98
        mapProviderClients := make(map[string]Client)
10✔
99
        for _, c := range deps.Clients {
20✔
100
                mapProviderClients[c.GetType()] = c
10✔
101
        }
10✔
102

103
        return &Service{
10✔
104
                deps.Repository,
10✔
105
                deps.ResourceService,
10✔
106
                mapProviderClients,
10✔
107

10✔
108
                deps.Validator,
10✔
109
                deps.Logger,
10✔
110
                deps.AuditLogger,
10✔
111
        }
10✔
112
}
113

114
// Create record
115
func (s *Service) Create(ctx context.Context, p *domain.Provider) error {
7✔
116
        c := s.getClient(p.Type)
7✔
117
        if c == nil {
9✔
118
                return ErrInvalidProviderType
2✔
119
        }
2✔
120

121
        accountTypes := c.GetAccountTypes()
5✔
122
        if err := s.validateAccountTypes(p.Config, accountTypes); err != nil {
6✔
123
                s.logger.Error(ctx, "failed to validate account types", "type", p.Type, "provider_urn", p.URN, "error", err)
1✔
124
                return err
1✔
125
        }
1✔
126

127
        if p.Config.Appeal != nil {
4✔
128
                if err := s.validateAppealConfig(p.Config.Appeal); err != nil {
×
129
                        s.logger.Error(ctx, "failed to validate appeal config", "type", p.Type, "provider_urn", p.URN, "error", err)
×
130
                        return err
×
131
                }
×
132
        }
133

134
        if err := c.CreateConfig(p.Config); err != nil {
5✔
135
                return err
1✔
136
        }
1✔
137
        s.logger.Debug(ctx, "provider config created", "provider_urn", p.URN)
3✔
138

3✔
139
        dryRun := isDryRun(ctx)
3✔
140

3✔
141
        if !dryRun {
5✔
142
                if err := s.repository.Create(ctx, p); err != nil {
3✔
143
                        return err
1✔
144
                }
1✔
145

146
                go func() {
2✔
147
                        ctx := context.WithoutCancel(ctx)
1✔
148
                        if err := s.auditLogger.Log(ctx, AuditKeyCreate, p); err != nil {
1✔
149
                                s.logger.Error(ctx, "failed to record audit log", "error", err)
×
150
                        }
×
151
                }()
152
        } else {
1✔
153
                s.logger.Info(ctx, "dry run enabled, skipping provider creation", "provider_urn", p.URN)
1✔
154
        }
1✔
155

156
        go func() {
4✔
157
                s.logger.Info(ctx, "provider create fetching resources", "provider_urn", p.URN)
2✔
158
                ctx := audit.WithActor(context.Background(), domain.SystemActorName)
2✔
159
                resources, _, err := s.fetchNewResources(ctx, p)
2✔
160
                if err != nil {
2✔
161
                        s.logger.Error(ctx, "failed to fetch resources", "error", err)
×
162
                }
×
163
                s.logger.Debug(ctx, "provider create fetched resources", "provider_urn", p.URN, "count", len(resources))
2✔
164
                if !dryRun {
3✔
165
                        if err := s.resourceService.BulkUpsert(ctx, resources); err != nil {
1✔
166
                                s.logger.Error(ctx, "failed to insert resources to db", "error", err)
×
167
                        } else {
1✔
168
                                s.logger.Info(ctx, "resources added", "provider_urn", p.URN, "count", len(resources))
1✔
169
                        }
1✔
170
                }
171
        }()
172

173
        return nil
2✔
174
}
175

176
// Find records
177
func (s *Service) Find(ctx context.Context) ([]*domain.Provider, error) {
2✔
178
        providers, err := s.repository.Find(ctx)
2✔
179
        if err != nil {
3✔
180
                return nil, err
1✔
181
        }
1✔
182

183
        return providers, nil
1✔
184
}
185

186
func (s *Service) GetByID(ctx context.Context, id string) (*domain.Provider, error) {
×
187
        return s.repository.GetByID(ctx, id)
×
188
}
×
189

190
func (s *Service) GetTypes(ctx context.Context) ([]domain.ProviderType, error) {
×
191
        return s.repository.GetTypes(ctx)
×
192
}
×
193

194
func (s *Service) GetOne(ctx context.Context, pType, urn string) (*domain.Provider, error) {
8✔
195
        return s.repository.GetOne(ctx, pType, urn)
8✔
196
}
8✔
197

198
// Update updates the non-zero value(s) only
199
func (s *Service) Update(ctx context.Context, p *domain.Provider) error {
4✔
200
        c := s.getClient(p.Type)
4✔
201
        if c == nil {
4✔
202
                return ErrInvalidProviderType
×
203
        }
×
204

205
        accountTypes := c.GetAccountTypes()
4✔
206
        if err := s.validateAccountTypes(p.Config, accountTypes); err != nil {
5✔
207
                s.logger.Error(ctx, "failed to validate account types", "type", p.Type, "provider_urn", p.URN, "error", err)
1✔
208
                return err
1✔
209
        }
1✔
210

211
        if p.Config.Appeal != nil {
6✔
212
                if err := s.validateAppealConfig(p.Config.Appeal); err != nil {
4✔
213
                        s.logger.Error(ctx, "failed to validate appeal config", "type", p.Type, "provider_urn", p.URN, "error", err)
1✔
214
                        return err
1✔
215
                }
1✔
216
        }
217

218
        if err := c.CreateConfig(p.Config); err != nil {
2✔
219
                return err
×
220
        }
×
221
        s.logger.Debug(ctx, "provider config created", "provider_urn", p.URN)
2✔
222

2✔
223
        dryRun := isDryRun(ctx)
2✔
224

2✔
225
        if !dryRun {
3✔
226
                if err := s.repository.Update(ctx, p); err != nil {
1✔
227
                        return err
×
228
                }
×
229

230
                go func() {
2✔
231
                        ctx := context.WithoutCancel(ctx)
1✔
232
                        if err := s.auditLogger.Log(ctx, AuditKeyUpdate, p); err != nil {
1✔
233
                                s.logger.Error(ctx, "failed to record audit log", "error", err)
×
234
                        }
×
235
                }()
236
        } else {
1✔
237
                s.logger.Info(ctx, "dry run enabled, skipping provider update", "provider_urn", p.URN)
1✔
238
        }
1✔
239

240
        go func() {
4✔
241
                s.logger.Info(ctx, "provider update fetching resources", "provider_urn", p.URN)
2✔
242
                ctx := audit.WithActor(context.Background(), domain.SystemActorName)
2✔
243
                resources, _, err := s.fetchNewResources(ctx, p)
2✔
244
                if err != nil {
2✔
245
                        s.logger.Error(ctx, "failed to fetch resources", "error", err)
×
246
                }
×
247
                s.logger.Debug(ctx, "provider create fetched resources", "provider_urn", p.URN, "count", len(resources))
×
248

×
249
                if !dryRun {
×
250
                        if err := s.resourceService.BulkUpsert(ctx, resources); err != nil {
×
251
                                s.logger.Error(ctx, "failed to insert resources to db", "error", err)
×
252
                        } else {
×
253
                                s.logger.Info(ctx, "resources added", "provider_urn", p.URN, "count", len(resources))
×
254
                        }
×
255
                }
256
        }()
257

258
        return nil
2✔
259
}
260

261
// FetchResources fetches all resources for all registered providers
262
func (s *Service) FetchResources(ctx context.Context) error {
7✔
263
        providers, err := s.repository.Find(ctx)
7✔
264
        if err != nil {
8✔
265
                return err
1✔
266
        }
1✔
267
        failedProviders := map[string]error{}
6✔
268
        totalFetchedResourcesCount := 0
6✔
269
        updatedResourcesCount := 0
6✔
270
        for _, p := range providers {
12✔
271
                startTime := time.Now()
6✔
272
                s.logger.Info(ctx, "fetching resources", "provider_urn", p.URN)
6✔
273
                resources, fetchedResourcesCount, err := s.fetchNewResources(ctx, p)
6✔
274
                if err != nil {
6✔
275
                        s.logger.Error(ctx, "failed to get resources", "error", err)
×
276
                        continue
×
277
                }
278
                totalFetchedResourcesCount += fetchedResourcesCount
6✔
279
                updatedResourcesCount += len(resources)
6✔
280
                if len(resources) == 0 {
7✔
281
                        s.logger.Info(ctx, "no changes in this provider", "provider_urn", p.URN)
1✔
282
                        continue
1✔
283
                }
284
                s.logger.Info(ctx, "resources added", "provider_urn", p.URN, "count", len(flattenResources(resources)))
5✔
285
                if err := s.resourceService.BulkUpsert(ctx, resources); err != nil {
6✔
286
                        failedProviders[p.URN] = err
1✔
287
                        s.logger.Error(ctx, "failed to add resources", "provider_urn", p.URN, "error", err)
1✔
288
                }
1✔
289
                s.logger.Info(ctx, "fetching resources completed", "provider_urn", p.URN, "duration", time.Since(startTime))
5✔
290
        }
291
        s.logger.Info(ctx, "resources", "count", totalFetchedResourcesCount, "upserted", updatedResourcesCount)
6✔
292
        if len(failedProviders) > 0 {
7✔
293
                var urns []string
1✔
294
                for providerURN, err := range failedProviders {
2✔
295
                        s.logger.Error(ctx, "failed to add resources for provider", "provider_urn", providerURN, "error", err)
1✔
296
                        urns = append(urns, providerURN)
1✔
297
                }
1✔
298
                return fmt.Errorf("failed to add resources for providers: %v", urns)
1✔
299
        }
300
        return nil
5✔
301
}
302

303
func (s *Service) GetRoles(ctx context.Context, id string, resourceType string) ([]*domain.Role, error) {
×
304
        p, err := s.GetByID(ctx, id)
×
305
        if err != nil {
×
306
                return nil, err
×
307
        }
×
308

309
        c := s.getClient(p.Type)
×
310
        return c.GetRoles(p.Config, resourceType)
×
311
}
312

313
func (s *Service) GetPermissions(_ context.Context, pc *domain.ProviderConfig, resourceType, role string) ([]interface{}, error) {
×
314
        c := s.getClient(pc.Type)
×
315
        return c.GetPermissions(pc, resourceType, role)
×
316
}
×
317

318
func (s *Service) ValidateAppeal(ctx context.Context, a *domain.Appeal, p *domain.Provider, policy *domain.Policy) error {
12✔
319
        if err := s.validateAppealParam(a); err != nil {
14✔
320
                return err
2✔
321
        }
2✔
322

323
        resourceType := a.Resource.Type
10✔
324
        c := s.getClient(p.Type)
10✔
325
        if c == nil {
11✔
326
                return ErrInvalidProviderType
1✔
327
        }
1✔
328

329
        if !utils.ContainsString(p.Config.AllowedAccountTypes, a.AccountType) {
10✔
330
                allowedAccountTypesStr := strings.Join(p.Config.AllowedAccountTypes, ", ")
1✔
331
                return fmt.Errorf("%w: %q. allowed account types: %v", ErrAppealValidationInvalidAccountType, a.AccountType, allowedAccountTypesStr)
1✔
332
        }
1✔
333

334
        roles, err := c.GetRoles(p.Config, resourceType)
8✔
335
        if err != nil {
9✔
336
                return err
1✔
337
        }
1✔
338

339
        isRoleExists := len(roles) == 0
7✔
340
        for _, role := range roles {
14✔
341
                if a.Role == role.ID {
13✔
342
                        isRoleExists = true
6✔
343
                        break
6✔
344
                }
345
        }
346

347
        if !isRoleExists {
8✔
348
                return fmt.Errorf("%w: %q", ErrAppealValidationInvalidRole, a.Role)
1✔
349
        }
1✔
350

351
        // Default to use provider config if policy config is not set
352
        AllowPermanentAccess := false
6✔
353
        if p.Config.Appeal != nil {
12✔
354
                AllowPermanentAccess = p.Config.Appeal.AllowPermanentAccess
6✔
355
        }
6✔
356

357
        if policy != nil && policy.AppealConfig != nil {
8✔
358
                AllowPermanentAccess = policy.AppealConfig.AllowPermanentAccess
2✔
359
        }
2✔
360

361
        if !AllowPermanentAccess {
12✔
362
                if a.Options == nil {
7✔
363
                        return ErrAppealValidationDurationNotSpecified
1✔
364
                }
1✔
365

366
                if a.Options.Duration == "" {
6✔
367
                        return ErrAppealValidationEmptyDuration
1✔
368
                }
1✔
369

370
                if err := validateDuration(a.Options.Duration); err != nil {
5✔
371
                        return fmt.Errorf("%w: %q", ErrAppealValidationInvalidDurationValue, a.Options.Duration)
1✔
372
                }
1✔
373
        }
374

375
        if err = s.validateQuestionsAndParameters(a, p, policy); err != nil {
5✔
376
                return err
2✔
377
        }
2✔
378

379
        return nil
1✔
380
}
381

382
func (*Service) validateQuestionsAndParameters(a *domain.Appeal, p *domain.Provider, policy *domain.Policy) error {
3✔
383
        parameterKeys := getFilledKeys(a, domain.ReservedDetailsKeyProviderParameters)
3✔
384
        questionKeys := getFilledKeys(a, domain.ReservedDetailsKeyPolicyQuestions)
3✔
385

3✔
386
        if p != nil && p.Config.Parameters != nil {
5✔
387
                for _, param := range p.Config.Parameters {
4✔
388
                        if param.Required && !utils.ContainsString(parameterKeys, param.Key) {
3✔
389
                                return fmt.Errorf("%w: %q", ErrAppealValidationMissingRequiredParameter, fmt.Sprintf("details.%s.%s", domain.ReservedDetailsKeyProviderParameters, param.Key))
1✔
390
                        }
1✔
391
                }
392
        }
393

394
        // TODO: do validation outside of provider.ValidateAppeal
395
        if policy != nil && policy.AppealConfig != nil && len(policy.AppealConfig.Questions) > 0 {
4✔
396
                for _, question := range policy.AppealConfig.Questions {
4✔
397
                        if question.Required && !utils.ContainsString(questionKeys, question.Key) {
3✔
398
                                return fmt.Errorf("%w: %q", ErrAppealValidationMissingRequiredQuestion, fmt.Sprintf("details.%s.%s", domain.ReservedDetailsKeyPolicyQuestions, question.Key))
1✔
399
                        }
1✔
400
                }
401
        }
402

403
        return nil
1✔
404
}
405

406
func getFilledKeys(a *domain.Appeal, key string) (filledKeys []string) {
6✔
407
        if a == nil {
6✔
408
                return
×
409
        }
×
410

411
        if parameters, ok := a.Details[key].(map[string]interface{}); ok {
9✔
412
                for k, v := range parameters {
6✔
413
                        if val, ok := v.(string); ok && val != "" {
5✔
414
                                filledKeys = append(filledKeys, k)
2✔
415
                        }
2✔
416
                }
417
        }
418
        return
6✔
419
}
420

421
func (s *Service) GrantAccess(ctx context.Context, a domain.Grant) error {
6✔
422
        if err := s.validateAccessParam(a); err != nil {
7✔
423
                return err
1✔
424
        }
1✔
425

426
        c := s.getClient(a.Resource.ProviderType)
5✔
427
        if c == nil {
6✔
428
                return ErrInvalidProviderType
1✔
429
        }
1✔
430

431
        p, err := s.getProviderConfig(ctx, a.Resource.ProviderType, a.Resource.ProviderURN)
4✔
432
        if err != nil {
6✔
433
                return err
2✔
434
        }
2✔
435

436
        return c.GrantAccess(ctx, p.Config, a)
2✔
437
}
438

439
func (s *Service) RevokeAccess(ctx context.Context, a domain.Grant) error {
6✔
440
        if err := s.validateAccessParam(a); err != nil {
7✔
441
                return err
1✔
442
        }
1✔
443

444
        c := s.getClient(a.Resource.ProviderType)
5✔
445
        if c == nil {
6✔
446
                return ErrInvalidProviderType
1✔
447
        }
1✔
448

449
        p, err := s.getProviderConfig(ctx, a.Resource.ProviderType, a.Resource.ProviderURN)
4✔
450
        if err != nil {
6✔
451
                return err
2✔
452
        }
2✔
453

454
        return c.RevokeAccess(ctx, p.Config, a)
2✔
455
}
456

457
func (s *Service) Delete(ctx context.Context, id string) error {
5✔
458
        p, err := s.repository.GetByID(ctx, id)
5✔
459
        if err != nil {
6✔
460
                return fmt.Errorf("getting provider details: %w", err)
1✔
461
        }
1✔
462

463
        s.logger.Info(ctx, "retrieving related resources", "provider", id)
4✔
464
        resources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
4✔
465
                ProviderType: p.Type,
4✔
466
                ProviderURN:  p.URN,
4✔
467
        })
4✔
468
        if err != nil {
5✔
469
                return fmt.Errorf("retrieving related resources: %w", err)
1✔
470
        }
1✔
471
        var resourceIds []string
3✔
472
        for _, r := range resources {
5✔
473
                resourceIds = append(resourceIds, r.ID)
2✔
474
        }
2✔
475
        s.logger.Info(ctx, "deleting resources", "provider", id, "count", len(resourceIds))
3✔
476

3✔
477
        // TODO: execute in transaction
3✔
478
        if err := s.resourceService.BatchDelete(ctx, resourceIds); err != nil {
4✔
479
                return fmt.Errorf("batch deleting resources: %w", err)
1✔
480
        }
1✔
481

482
        if err := s.repository.Delete(ctx, id); err != nil {
3✔
483
                return err
1✔
484
        }
1✔
485
        s.logger.Info(ctx, "provider deleted", "provider", id)
1✔
486

1✔
487
        go func() {
2✔
488
                ctx := context.WithoutCancel(ctx)
1✔
489
                if err := s.auditLogger.Log(ctx, AuditKeyDelete, p); err != nil {
1✔
490
                        s.logger.Error(ctx, "failed to record audit log", "error", err)
×
491
                }
×
492
        }()
493

494
        return nil
1✔
495
}
496

497
func (s *Service) ListAccess(ctx context.Context, p domain.Provider, resources []*domain.Resource) (domain.MapResourceAccess, error) {
1✔
498
        c := s.getClient(p.Type)
1✔
499
        providerAccesses, err := c.ListAccess(ctx, *p.Config, resources)
1✔
500
        if err != nil {
1✔
501
                return nil, err
×
502
        }
×
503

504
        for resourceURN, accessEntries := range providerAccesses {
2✔
505
                var filteredAccessEntries []domain.AccessEntry
1✔
506
                for _, ae := range accessEntries {
3✔
507
                        if utils.ContainsString(p.Config.AllowedAccountTypes, ae.AccountType) {
3✔
508
                                filteredAccessEntries = append(filteredAccessEntries, ae)
1✔
509
                        }
1✔
510
                }
511
                providerAccesses[resourceURN] = filteredAccessEntries
1✔
512
        }
513

514
        return providerAccesses, nil
1✔
515
}
516

517
func (s *Service) ImportActivities(ctx context.Context, filter domain.ListActivitiesFilter) ([]*domain.Activity, error) {
×
518
        p, err := s.GetByID(ctx, filter.ProviderID)
×
519
        if err != nil {
×
520
                return nil, fmt.Errorf("getting provider details: %w", err)
×
521
        }
×
522

523
        client := s.getClient(p.Type)
×
524
        activityClient, ok := client.(activityManager)
×
525
        if !ok {
×
526
                return nil, fmt.Errorf("%w: %s", ErrImportActivitiesMethodNotSupported, p.Type)
×
527
        }
×
528

529
        resources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
×
530
                IDs: filter.ResourceIDs,
×
531
        })
×
532
        if err != nil {
×
533
                return nil, fmt.Errorf("retrieving specified resources: %w", err)
×
534
        }
×
535
        if err := filter.PopulateResources(domain.Resources(resources).ToMap()); err != nil {
×
536
                return nil, fmt.Errorf("populating resources: %w", err)
×
537
        }
×
538

539
        activities, err := activityClient.GetActivities(ctx, *p, filter)
×
540
        if err != nil {
×
541
                return nil, fmt.Errorf("getting activities: %w", err)
×
542
        }
×
543

544
        return activities, nil
×
545
}
546

547
func (s *Service) ListActivities(ctx context.Context, p domain.Provider, filter domain.ListActivitiesFilter) ([]*domain.Activity, error) {
×
548
        c := s.getClient(p.Type)
×
549
        activityClient, ok := c.(dormancyChecker)
×
550
        if !ok {
×
551
                return nil, fmt.Errorf("%w: %s", ErrGetActivityMethodNotSupported, p.Type)
×
552
        }
×
553

554
        return activityClient.ListActivities(ctx, p, filter)
×
555
}
556

557
func (s *Service) CorrelateGrantActivities(ctx context.Context, p domain.Provider, grants []*domain.Grant, activities []*domain.Activity) error {
×
558
        c := s.getClient(p.Type)
×
559
        activityClient, ok := c.(dormancyChecker)
×
560
        if !ok {
×
561
                return fmt.Errorf("%w: %s", ErrGetActivityMethodNotSupported, p.Type)
×
562
        }
×
563
        return activityClient.CorrelateGrantActivities(ctx, p, grants, activities)
×
564
}
565

566
// IsExclusiveRoleAssignment returns true if the provider only supports exclusive role assignment
567
// i.e. a user can only have one role per resource
568
func (s *Service) IsExclusiveRoleAssignment(ctx context.Context, providerType, resourceType string) bool {
×
569
        client := s.getClient(providerType)
×
570
        if c, ok := client.(assignmentTyper); ok {
×
571
                return c.IsExclusiveRoleAssignment(ctx)
×
572
        }
×
573
        return false
×
574
}
575

576
func (s *Service) GetDependencyGrants(ctx context.Context, g domain.Grant) ([]*domain.Grant, error) {
×
577
        client := s.getClient(g.Resource.ProviderType)
×
578
        if client == nil {
×
579
                return nil, ErrInvalidProviderType
×
580
        }
×
581

582
        c, ok := client.(grantDependenciesResolver)
×
583
        if !ok {
×
584
                return nil, nil
×
585
        }
×
586

587
        p, err := s.getProviderConfig(ctx, g.Resource.ProviderType, g.Resource.ProviderURN)
×
588
        if err != nil {
×
589
                return nil, err
×
590
        }
×
591

592
        dependencies, err := c.GetDependencyGrants(ctx, *p, g)
×
593
        if err != nil {
×
594
                return nil, err
×
595
        }
×
596

597
        for _, d := range dependencies {
×
598
                resources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
×
599
                        ProviderType: d.Resource.ProviderType,
×
600
                        ProviderURN:  d.Resource.ProviderURN,
×
601
                        ResourceType: d.Resource.Type,
×
602
                        ResourceURN:  d.Resource.URN,
×
603
                        Size:         1,
×
604
                })
×
605
                if err != nil {
×
606
                        return nil, fmt.Errorf("unable to resolve resource %q for grant dependency: %w", d.Resource.URN, err)
×
607
                }
×
608
                if len(resources) == 0 {
×
609
                        return nil, fmt.Errorf("unable to resolve resource %q for grant dependency: not found", d.Resource.URN)
×
610
                }
×
611

612
                d.ResourceID = resources[0].ID
×
613
                d.Resource = resources[0]
×
614
        }
615

616
        return dependencies, nil
×
617
}
618

619
func (s *Service) fetchNewResources(ctx context.Context, p *domain.Provider) ([]*domain.Resource, int, error) {
10✔
620
        c := s.getClient(p.Type)
10✔
621
        if c == nil {
10✔
622
                return nil, 0, fmt.Errorf("%w: %v", ErrInvalidProviderType, p.Type)
×
623
        }
×
624

625
        existingResources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
10✔
626
                ProviderType: p.Type,
10✔
627
                ProviderURN:  p.URN,
10✔
628
        })
10✔
629
        if err != nil {
10✔
630
                return nil, 0, err
×
631
        }
×
632
        mapExistingResources := make(map[string]*domain.Resource, len(existingResources))
10✔
633
        for _, existing := range existingResources {
15✔
634
                mapExistingResources[existing.GlobalURN] = existing
5✔
635
        }
5✔
636

637
        newResourcesWithChildren, err := c.GetResources(ctx, p.Config)
10✔
638
        if err != nil {
10✔
639
                return nil, 0, fmt.Errorf("error fetching resources for %v: %w", p.ID, err)
×
640
        }
×
641
        resourceTypeFilterMap := make(map[string]string)
8✔
642
        for _, rc := range p.Config.Resources {
10✔
643
                if len(rc.Filter) > 0 {
4✔
644
                        resourceTypeFilterMap[rc.Type] = rc.Filter
2✔
645
                }
2✔
646
        }
647
        filteredResources := make([]*domain.Resource, 0)
8✔
648
        for _, r := range newResourcesWithChildren {
18✔
649
                if filterExpression, ok := resourceTypeFilterMap[r.Type]; ok {
14✔
650
                        v, err := evaluator.Expression(filterExpression).EvaluateWithStruct(r)
4✔
651
                        if err != nil {
4✔
652
                                return nil, 0, err
×
653
                        }
×
654
                        if !reflect.ValueOf(v).IsZero() {
6✔
655
                                filteredResources = append(filteredResources, r)
2✔
656
                        }
2✔
657
                } else {
6✔
658
                        filteredResources = append(filteredResources, r)
6✔
659
                }
6✔
660
        }
661

662
        newAndUpdatedResources := s.compareResources(ctx, mapExistingResources, filteredResources)
8✔
663
        if len(newAndUpdatedResources) == 0 {
11✔
664
                return []*domain.Resource{}, 0, nil
3✔
665
        }
3✔
666
        for _, deletedResource := range mapExistingResources {
5✔
667
                deletedResource.IsDeleted = true
×
668
                newAndUpdatedResources = append(newAndUpdatedResources, deletedResource)
×
669
                s.logger.Info(ctx, "resource deleted", "resource", deletedResource.GlobalURN)
×
670
        }
×
671

672
        return newAndUpdatedResources, len(newResourcesWithChildren), nil
5✔
673
}
674

675
func (s *Service) compareResources(ctx context.Context, existingResources map[string]*domain.Resource, newResources []*domain.Resource) []*domain.Resource {
18✔
676
        var res []*domain.Resource
18✔
677
        for _, new := range newResources {
28✔
678
                new.Children = s.compareResources(ctx, existingResources, new.Children)
10✔
679

10✔
680
                existing, exist := existingResources[new.GlobalURN]
10✔
681
                if !exist {
15✔
682
                        // new resource
5✔
683
                        res = append(res, new)
5✔
684
                        continue
5✔
685
                }
686
                delete(existingResources, new.GlobalURN)
5✔
687
                if existingDetails := existing.Details; existingDetails != nil {
8✔
688
                        if new.Details != nil {
6✔
689
                                for key, value := range existingDetails {
9✔
690
                                        if _, ok := new.Details[key]; !ok {
9✔
691
                                                new.Details[key] = value
3✔
692
                                        }
3✔
693
                                }
694
                        } else {
×
695
                                new.Details = existingDetails
×
696
                        }
×
697
                }
698
                if len(new.Children) == 0 {
9✔
699
                        isUpdated, diff := compareResource(*existing, *new)
4✔
700
                        if !isUpdated {
7✔
701
                                continue
3✔
702
                        }
703
                        s.logger.Debug(ctx, "diff", "resources", diff)
1✔
704
                        s.logger.Info(ctx, "resources is updated", "resource", new.URN)
1✔
705
                }
706

707
                res = append(res, new)
2✔
708
        }
709

710
        return res
18✔
711
}
712

713
func (s *Service) validateAppealParam(a *domain.Appeal) error {
12✔
714
        if a == nil {
13✔
715
                return ErrNilAppeal
1✔
716
        }
1✔
717
        if a.Resource == nil {
12✔
718
                return ErrNilResource
1✔
719
        }
1✔
720
        //TO-DO
721
        //Make sure the user and role is required
722
        return nil
10✔
723
}
724

725
func (s *Service) validateAccessParam(a domain.Grant) error {
12✔
726
        if a.Resource == nil {
14✔
727
                return ErrNilResource
2✔
728
        }
2✔
729
        return nil
10✔
730
}
731

732
func (s *Service) getClient(pType string) Client {
42✔
733
        return s.clients[pType]
42✔
734
}
42✔
735

736
func (s *Service) getProviderConfig(ctx context.Context, pType, urn string) (*domain.Provider, error) {
8✔
737
        p, err := s.GetOne(ctx, pType, urn)
8✔
738
        if err != nil {
12✔
739
                return nil, err
4✔
740
        }
4✔
741
        return p, nil
4✔
742
}
743

744
func (s *Service) validateAccountTypes(pc *domain.ProviderConfig, accountTypes []string) error {
9✔
745
        if pc.AllowedAccountTypes == nil {
11✔
746
                pc.AllowedAccountTypes = accountTypes
2✔
747
        } else {
9✔
748
                if err := s.validator.Var(pc.AllowedAccountTypes, "min=1,unique"); err != nil {
7✔
749
                        return err
×
750
                }
×
751

752
                for _, at := range pc.AllowedAccountTypes {
14✔
753
                        accountTypesStr := strings.Join(accountTypes, " ")
7✔
754
                        if err := s.validator.Var(at, fmt.Sprintf("oneof=%v", accountTypesStr)); err != nil {
9✔
755
                                return err
2✔
756
                        }
2✔
757
                }
758
        }
759

760
        return nil
7✔
761
}
762

763
func (s *Service) validateAppealConfig(a *domain.AppealConfig) error {
3✔
764
        if a.AllowActiveAccessExtensionIn != "" {
6✔
765
                if err := validateDuration(a.AllowActiveAccessExtensionIn); err != nil {
4✔
766
                        return fmt.Errorf("invalid appeal extension policy: %v", err)
1✔
767
                }
1✔
768
        }
769

770
        return nil
2✔
771
}
772

773
func validateDuration(d string) error {
7✔
774
        _, err := time.ParseDuration(d)
7✔
775
        return err
7✔
776
}
7✔
777

778
func flattenResources(resources []*domain.Resource) []*domain.Resource {
5✔
779
        flattenedResources := []*domain.Resource{}
5✔
780
        for _, r := range resources {
11✔
781
                flattenedResources = append(flattenedResources, r.GetFlattened()...)
6✔
782
        }
6✔
783
        return flattenedResources
5✔
784
}
785

786
type isDryRunKey string
787

788
func WithDryRun(ctx context.Context) context.Context {
2✔
789
        return context.WithValue(ctx, isDryRunKey("dry_run"), true)
2✔
790
}
2✔
791

792
func isDryRun(ctx context.Context) bool {
5✔
793
        return ctx.Value(isDryRunKey("dry_run")) != nil
5✔
794
}
5✔
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