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

goto / guardian / 17695507522

13 Sep 2025 10:47AM UTC coverage: 70.095% (-0.3%) from 70.379%
17695507522

Pull #222

github

rahmatrhd
chore: update proto
Pull Request #222: feat: introduce create resource API

0 of 65 new or added lines in 5 files covered. (0.0%)

19 existing lines in 3 files now uncovered.

11319 of 16148 relevant lines covered (70.1%)

4.63 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

64
type resourceValidator interface {
65
        ValidateResource(ctx context.Context, r *domain.Resource) error
66
}
67

68
//go:generate mockery --name=resourceService --exported --with-expecter
69
type resourceService interface {
70
        Create(context.Context, *domain.Resource) error
71
        Find(context.Context, domain.ListResourcesFilter) ([]*domain.Resource, error)
72
        BulkUpsert(context.Context, []*domain.Resource) error
73
        BatchDelete(context.Context, []string) error
74
}
75

76
//go:generate mockery --name=auditLogger --exported --with-expecter
77
type auditLogger interface {
78
        Log(ctx context.Context, action string, data interface{}) error
79
}
80

81
// Service handling the business logics
82
type Service struct {
83
        repository      repository
84
        resourceService resourceService
85
        clients         map[string]Client
86

87
        validator   *validator.Validate
88
        logger      log.Logger
89
        auditLogger auditLogger
90
}
91

92
type ServiceDeps struct {
93
        Repository      repository
94
        ResourceService resourceService
95
        Clients         []Client
96

97
        Validator   *validator.Validate
98
        Logger      log.Logger
99
        AuditLogger auditLogger
100
}
101

102
// NewService returns service struct
103
func NewService(deps ServiceDeps) *Service {
10✔
104
        mapProviderClients := make(map[string]Client)
10✔
105
        for _, c := range deps.Clients {
20✔
106
                mapProviderClients[c.GetType()] = c
10✔
107
        }
10✔
108

109
        return &Service{
10✔
110
                deps.Repository,
10✔
111
                deps.ResourceService,
10✔
112
                mapProviderClients,
10✔
113

10✔
114
                deps.Validator,
10✔
115
                deps.Logger,
10✔
116
                deps.AuditLogger,
10✔
117
        }
10✔
118
}
119

120
// Create record
121
func (s *Service) Create(ctx context.Context, p *domain.Provider) error {
7✔
122
        c := s.getClient(p.Type)
7✔
123
        if c == nil {
9✔
124
                return ErrInvalidProviderType
2✔
125
        }
2✔
126

127
        accountTypes := c.GetAccountTypes()
5✔
128
        if err := s.validateAccountTypes(p.Config, accountTypes); err != nil {
6✔
129
                s.logger.Error(ctx, "failed to validate account types", "type", p.Type, "provider_urn", p.URN, "error", err)
1✔
130
                return err
1✔
131
        }
1✔
132

133
        if p.Config.Appeal != nil {
4✔
134
                if err := s.validateAppealConfig(p.Config.Appeal); err != nil {
×
135
                        s.logger.Error(ctx, "failed to validate appeal config", "type", p.Type, "provider_urn", p.URN, "error", err)
×
136
                        return err
×
137
                }
×
138
        }
139

140
        if err := c.CreateConfig(p.Config); err != nil {
5✔
141
                return err
1✔
142
        }
1✔
143
        s.logger.Debug(ctx, "provider config created", "provider_urn", p.URN)
3✔
144

3✔
145
        dryRun := isDryRun(ctx)
3✔
146

3✔
147
        if !dryRun {
5✔
148
                if err := s.repository.Create(ctx, p); err != nil {
3✔
149
                        return err
1✔
150
                }
1✔
151

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

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

179
        return nil
2✔
180
}
181

182
// Find records
183
func (s *Service) Find(ctx context.Context) ([]*domain.Provider, error) {
2✔
184
        providers, err := s.repository.Find(ctx)
2✔
185
        if err != nil {
3✔
186
                return nil, err
1✔
187
        }
1✔
188

189
        return providers, nil
1✔
190
}
191

192
func (s *Service) GetByID(ctx context.Context, id string) (*domain.Provider, error) {
×
193
        return s.repository.GetByID(ctx, id)
×
194
}
×
195

196
func (s *Service) GetTypes(ctx context.Context) ([]domain.ProviderType, error) {
×
197
        return s.repository.GetTypes(ctx)
×
198
}
×
199

200
func (s *Service) GetOne(ctx context.Context, pType, urn string) (*domain.Provider, error) {
8✔
201
        return s.repository.GetOne(ctx, pType, urn)
8✔
202
}
8✔
203

204
// Update updates the non-zero value(s) only
205
func (s *Service) Update(ctx context.Context, p *domain.Provider) error {
4✔
206
        c := s.getClient(p.Type)
4✔
207
        if c == nil {
4✔
208
                return ErrInvalidProviderType
×
209
        }
×
210

211
        accountTypes := c.GetAccountTypes()
4✔
212
        if err := s.validateAccountTypes(p.Config, accountTypes); err != nil {
5✔
213
                s.logger.Error(ctx, "failed to validate account types", "type", p.Type, "provider_urn", p.URN, "error", err)
1✔
214
                return err
1✔
215
        }
1✔
216

217
        if p.Config.Appeal != nil {
6✔
218
                if err := s.validateAppealConfig(p.Config.Appeal); err != nil {
4✔
219
                        s.logger.Error(ctx, "failed to validate appeal config", "type", p.Type, "provider_urn", p.URN, "error", err)
1✔
220
                        return err
1✔
221
                }
1✔
222
        }
223

224
        if err := c.CreateConfig(p.Config); err != nil {
2✔
225
                return err
×
226
        }
×
227
        s.logger.Debug(ctx, "provider config created", "provider_urn", p.URN)
2✔
228

2✔
229
        dryRun := isDryRun(ctx)
2✔
230

2✔
231
        if !dryRun {
3✔
232
                if err := s.repository.Update(ctx, p); err != nil {
1✔
233
                        return err
×
234
                }
×
235

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

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

2✔
255
                if !dryRun {
3✔
256
                        if err := s.resourceService.BulkUpsert(ctx, resources); err != nil {
1✔
257
                                s.logger.Error(ctx, "failed to insert resources to db", "error", err)
×
258
                        } else {
1✔
259
                                s.logger.Info(ctx, "resources added", "provider_urn", p.URN, "count", len(resources))
1✔
260
                        }
1✔
261
                }
262
        }()
263

264
        return nil
2✔
265
}
266

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

NEW
309
func (s *Service) CreateResource(ctx context.Context, r *domain.Resource) error {
×
NEW
310
        p, err := s.repository.GetOne(ctx, r.ProviderType, r.ProviderURN)
×
NEW
311
        if err != nil {
×
NEW
312
                return err
×
NEW
313
        }
×
314

NEW
315
        c := s.getClient(r.ProviderType)
×
NEW
316

×
NEW
317
        if !slices.Contains(p.Config.GetResourceTypes(), r.Type) {
×
NEW
318
                return fmt.Errorf("%w: %q", ErrInvalidResourceType, r.Type)
×
NEW
319
        }
×
320

NEW
321
        if v, ok := c.(resourceValidator); ok {
×
NEW
322
                if err := v.ValidateResource(ctx, r); err != nil {
×
NEW
323
                        return fmt.Errorf("%w: %v", ErrInvalidResource, err)
×
NEW
324
                }
×
325
        }
326

NEW
327
        return s.resourceService.Create(ctx, r)
×
328
}
329

330
func (s *Service) GetRoles(ctx context.Context, id string, resourceType string) ([]*domain.Role, error) {
×
331
        p, err := s.GetByID(ctx, id)
×
332
        if err != nil {
×
333
                return nil, err
×
334
        }
×
335

336
        c := s.getClient(p.Type)
×
337
        return c.GetRoles(p.Config, resourceType)
×
338
}
339

340
func (s *Service) GetPermissions(_ context.Context, pc *domain.ProviderConfig, resourceType, role string) ([]interface{}, error) {
×
341
        c := s.getClient(pc.Type)
×
342
        return c.GetPermissions(pc, resourceType, role)
×
343
}
×
344

345
func (s *Service) ValidateAppeal(ctx context.Context, a *domain.Appeal, p *domain.Provider, policy *domain.Policy) error {
12✔
346
        if err := s.validateAppealParam(a); err != nil {
14✔
347
                return err
2✔
348
        }
2✔
349

350
        resourceType := a.Resource.Type
10✔
351
        c := s.getClient(p.Type)
10✔
352
        if c == nil {
11✔
353
                return ErrInvalidProviderType
1✔
354
        }
1✔
355

356
        if !utils.ContainsString(p.Config.AllowedAccountTypes, a.AccountType) {
10✔
357
                allowedAccountTypesStr := strings.Join(p.Config.AllowedAccountTypes, ", ")
1✔
358
                return fmt.Errorf("%w: %q. allowed account types: %v", ErrAppealValidationInvalidAccountType, a.AccountType, allowedAccountTypesStr)
1✔
359
        }
1✔
360

361
        roles, err := c.GetRoles(p.Config, resourceType)
8✔
362
        if err != nil {
9✔
363
                return err
1✔
364
        }
1✔
365

366
        isRoleExists := len(roles) == 0
7✔
367
        for _, role := range roles {
14✔
368
                if a.Role == role.ID {
13✔
369
                        isRoleExists = true
6✔
370
                        break
6✔
371
                }
372
        }
373

374
        if !isRoleExists {
8✔
375
                return fmt.Errorf("%w: %q", ErrAppealValidationInvalidRole, a.Role)
1✔
376
        }
1✔
377

378
        // Default to use provider config if policy config is not set
379
        AllowPermanentAccess := false
6✔
380
        if p.Config.Appeal != nil {
12✔
381
                AllowPermanentAccess = p.Config.Appeal.AllowPermanentAccess
6✔
382
        }
6✔
383

384
        if policy != nil && policy.AppealConfig != nil {
8✔
385
                AllowPermanentAccess = policy.AppealConfig.AllowPermanentAccess
2✔
386
        }
2✔
387

388
        if !AllowPermanentAccess {
12✔
389
                if a.Options == nil {
7✔
390
                        return ErrAppealValidationDurationNotSpecified
1✔
391
                }
1✔
392

393
                if a.Options.Duration == "" {
6✔
394
                        return ErrAppealValidationEmptyDuration
1✔
395
                }
1✔
396

397
                if err := validateDuration(a.Options.Duration); err != nil {
5✔
398
                        return fmt.Errorf("%w: %q", ErrAppealValidationInvalidDurationValue, a.Options.Duration)
1✔
399
                }
1✔
400
        }
401

402
        if err = s.validateQuestionsAndParameters(a, p, policy); err != nil {
5✔
403
                return err
2✔
404
        }
2✔
405

406
        return nil
1✔
407
}
408

409
func (*Service) validateQuestionsAndParameters(a *domain.Appeal, p *domain.Provider, policy *domain.Policy) error {
3✔
410
        parameterKeys := getFilledKeys(a, domain.ReservedDetailsKeyProviderParameters)
3✔
411
        questionKeys := getFilledKeys(a, domain.ReservedDetailsKeyPolicyQuestions)
3✔
412

3✔
413
        if p != nil && p.Config.Parameters != nil {
5✔
414
                for _, param := range p.Config.Parameters {
4✔
415
                        if param.Required && !utils.ContainsString(parameterKeys, param.Key) {
3✔
416
                                return fmt.Errorf("%w: %q", ErrAppealValidationMissingRequiredParameter, fmt.Sprintf("details.%s.%s", domain.ReservedDetailsKeyProviderParameters, param.Key))
1✔
417
                        }
1✔
418
                }
419
        }
420

421
        // TODO: do validation outside of provider.ValidateAppeal
422
        if policy != nil && policy.AppealConfig != nil && len(policy.AppealConfig.Questions) > 0 {
4✔
423
                for _, question := range policy.AppealConfig.Questions {
4✔
424
                        if question.Required && !utils.ContainsString(questionKeys, question.Key) {
3✔
425
                                return fmt.Errorf("%w: %q", ErrAppealValidationMissingRequiredQuestion, fmt.Sprintf("details.%s.%s", domain.ReservedDetailsKeyPolicyQuestions, question.Key))
1✔
426
                        }
1✔
427
                }
428
        }
429

430
        return nil
1✔
431
}
432

433
func getFilledKeys(a *domain.Appeal, key string) (filledKeys []string) {
6✔
434
        if a == nil {
6✔
435
                return
×
436
        }
×
437

438
        if parameters, ok := a.Details[key].(map[string]interface{}); ok {
9✔
439
                for k, v := range parameters {
6✔
440
                        if val, ok := v.(string); ok && val != "" {
5✔
441
                                filledKeys = append(filledKeys, k)
2✔
442
                        }
2✔
443
                }
444
        }
445
        return
6✔
446
}
447

448
func (s *Service) GrantAccess(ctx context.Context, a domain.Grant) error {
6✔
449
        if err := s.validateAccessParam(a); err != nil {
7✔
450
                return err
1✔
451
        }
1✔
452

453
        c := s.getClient(a.Resource.ProviderType)
5✔
454
        if c == nil {
6✔
455
                return ErrInvalidProviderType
1✔
456
        }
1✔
457

458
        p, err := s.getProviderConfig(ctx, a.Resource.ProviderType, a.Resource.ProviderURN)
4✔
459
        if err != nil {
6✔
460
                return err
2✔
461
        }
2✔
462

463
        return c.GrantAccess(ctx, p.Config, a)
2✔
464
}
465

466
func (s *Service) RevokeAccess(ctx context.Context, a domain.Grant) error {
6✔
467
        if err := s.validateAccessParam(a); err != nil {
7✔
468
                return err
1✔
469
        }
1✔
470

471
        c := s.getClient(a.Resource.ProviderType)
5✔
472
        if c == nil {
6✔
473
                return ErrInvalidProviderType
1✔
474
        }
1✔
475

476
        p, err := s.getProviderConfig(ctx, a.Resource.ProviderType, a.Resource.ProviderURN)
4✔
477
        if err != nil {
6✔
478
                return err
2✔
479
        }
2✔
480

481
        return c.RevokeAccess(ctx, p.Config, a)
2✔
482
}
483

484
func (s *Service) Delete(ctx context.Context, id string) error {
5✔
485
        p, err := s.repository.GetByID(ctx, id)
5✔
486
        if err != nil {
6✔
487
                return fmt.Errorf("getting provider details: %w", err)
1✔
488
        }
1✔
489

490
        s.logger.Info(ctx, "retrieving related resources", "provider", id)
4✔
491
        resources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
4✔
492
                ProviderType: p.Type,
4✔
493
                ProviderURN:  p.URN,
4✔
494
        })
4✔
495
        if err != nil {
5✔
496
                return fmt.Errorf("retrieving related resources: %w", err)
1✔
497
        }
1✔
498
        var resourceIds []string
3✔
499
        for _, r := range resources {
5✔
500
                resourceIds = append(resourceIds, r.ID)
2✔
501
        }
2✔
502
        s.logger.Info(ctx, "deleting resources", "provider", id, "count", len(resourceIds))
3✔
503

3✔
504
        // TODO: execute in transaction
3✔
505
        if err := s.resourceService.BatchDelete(ctx, resourceIds); err != nil {
4✔
506
                return fmt.Errorf("batch deleting resources: %w", err)
1✔
507
        }
1✔
508

509
        if err := s.repository.Delete(ctx, id); err != nil {
3✔
510
                return err
1✔
511
        }
1✔
512
        s.logger.Info(ctx, "provider deleted", "provider", id)
1✔
513

1✔
514
        go func() {
2✔
515
                ctx := context.WithoutCancel(ctx)
1✔
516
                if err := s.auditLogger.Log(ctx, AuditKeyDelete, p); err != nil {
1✔
517
                        s.logger.Error(ctx, "failed to record audit log", "error", err)
×
518
                }
×
519
        }()
520

521
        return nil
1✔
522
}
523

524
func (s *Service) ListAccess(ctx context.Context, p domain.Provider, resources []*domain.Resource) (domain.MapResourceAccess, error) {
1✔
525
        c := s.getClient(p.Type)
1✔
526
        providerAccesses, err := c.ListAccess(ctx, *p.Config, resources)
1✔
527
        if err != nil {
1✔
528
                return nil, err
×
529
        }
×
530

531
        for resourceURN, accessEntries := range providerAccesses {
2✔
532
                var filteredAccessEntries []domain.AccessEntry
1✔
533
                for _, ae := range accessEntries {
3✔
534
                        if utils.ContainsString(p.Config.AllowedAccountTypes, ae.AccountType) {
3✔
535
                                filteredAccessEntries = append(filteredAccessEntries, ae)
1✔
536
                        }
1✔
537
                }
538
                providerAccesses[resourceURN] = filteredAccessEntries
1✔
539
        }
540

541
        return providerAccesses, nil
1✔
542
}
543

544
func (s *Service) ImportActivities(ctx context.Context, filter domain.ListActivitiesFilter) ([]*domain.Activity, error) {
×
545
        p, err := s.GetByID(ctx, filter.ProviderID)
×
546
        if err != nil {
×
547
                return nil, fmt.Errorf("getting provider details: %w", err)
×
548
        }
×
549

550
        client := s.getClient(p.Type)
×
551
        activityClient, ok := client.(activityManager)
×
552
        if !ok {
×
553
                return nil, fmt.Errorf("%w: %s", ErrImportActivitiesMethodNotSupported, p.Type)
×
554
        }
×
555

556
        resources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
×
557
                IDs: filter.ResourceIDs,
×
558
        })
×
559
        if err != nil {
×
560
                return nil, fmt.Errorf("retrieving specified resources: %w", err)
×
561
        }
×
562
        if err := filter.PopulateResources(domain.Resources(resources).ToMap()); err != nil {
×
563
                return nil, fmt.Errorf("populating resources: %w", err)
×
564
        }
×
565

566
        activities, err := activityClient.GetActivities(ctx, *p, filter)
×
567
        if err != nil {
×
568
                return nil, fmt.Errorf("getting activities: %w", err)
×
569
        }
×
570

571
        return activities, nil
×
572
}
573

574
func (s *Service) ListActivities(ctx context.Context, p domain.Provider, filter domain.ListActivitiesFilter) ([]*domain.Activity, error) {
×
575
        c := s.getClient(p.Type)
×
576
        activityClient, ok := c.(dormancyChecker)
×
577
        if !ok {
×
578
                return nil, fmt.Errorf("%w: %s", ErrGetActivityMethodNotSupported, p.Type)
×
579
        }
×
580

581
        return activityClient.ListActivities(ctx, p, filter)
×
582
}
583

584
func (s *Service) CorrelateGrantActivities(ctx context.Context, p domain.Provider, grants []*domain.Grant, activities []*domain.Activity) error {
×
585
        c := s.getClient(p.Type)
×
586
        activityClient, ok := c.(dormancyChecker)
×
587
        if !ok {
×
588
                return fmt.Errorf("%w: %s", ErrGetActivityMethodNotSupported, p.Type)
×
589
        }
×
590
        return activityClient.CorrelateGrantActivities(ctx, p, grants, activities)
×
591
}
592

593
// IsExclusiveRoleAssignment returns true if the provider only supports exclusive role assignment
594
// i.e. a user can only have one role per resource
595
func (s *Service) IsExclusiveRoleAssignment(ctx context.Context, providerType, resourceType string) bool {
×
596
        client := s.getClient(providerType)
×
597
        if c, ok := client.(assignmentTyper); ok {
×
598
                return c.IsExclusiveRoleAssignment(ctx)
×
599
        }
×
600
        return false
×
601
}
602

603
func (s *Service) GetDependencyGrants(ctx context.Context, g domain.Grant) ([]*domain.Grant, error) {
×
604
        client := s.getClient(g.Resource.ProviderType)
×
605
        if client == nil {
×
606
                return nil, ErrInvalidProviderType
×
607
        }
×
608

609
        c, ok := client.(grantDependenciesResolver)
×
610
        if !ok {
×
611
                return nil, nil
×
612
        }
×
613

614
        p, err := s.getProviderConfig(ctx, g.Resource.ProviderType, g.Resource.ProviderURN)
×
615
        if err != nil {
×
616
                return nil, err
×
617
        }
×
618

619
        dependencies, err := c.GetDependencyGrants(ctx, *p, g)
×
620
        if err != nil {
×
621
                return nil, err
×
622
        }
×
623

624
        for _, d := range dependencies {
×
625
                resources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
×
626
                        ProviderType: d.Resource.ProviderType,
×
627
                        ProviderURN:  d.Resource.ProviderURN,
×
628
                        ResourceType: d.Resource.Type,
×
629
                        ResourceURN:  d.Resource.URN,
×
630
                        Size:         1,
×
631
                })
×
632
                if err != nil {
×
633
                        return nil, fmt.Errorf("unable to resolve resource %q for grant dependency: %w", d.Resource.URN, err)
×
634
                }
×
635
                if len(resources) == 0 {
×
636
                        return nil, fmt.Errorf("unable to resolve resource %q for grant dependency: not found", d.Resource.URN)
×
637
                }
×
638

639
                d.ResourceID = resources[0].ID
×
640
                d.Resource = resources[0]
×
641
        }
642

643
        return dependencies, nil
×
644
}
645

646
func (s *Service) fetchNewResources(ctx context.Context, p *domain.Provider) ([]*domain.Resource, int, error) {
10✔
647
        c := s.getClient(p.Type)
10✔
648
        if c == nil {
10✔
649
                return nil, 0, fmt.Errorf("%w: %v", ErrInvalidProviderType, p.Type)
×
650
        }
×
651

652
        existingResources, err := s.resourceService.Find(ctx, domain.ListResourcesFilter{
10✔
653
                ProviderType: p.Type,
10✔
654
                ProviderURN:  p.URN,
10✔
655
        })
10✔
656
        if err != nil {
10✔
657
                return nil, 0, err
×
658
        }
×
659
        mapExistingResources := make(map[string]*domain.Resource, len(existingResources))
10✔
660
        for _, existing := range existingResources {
15✔
661
                mapExistingResources[existing.GlobalURN] = existing
5✔
662
        }
5✔
663

664
        newResourcesWithChildren, err := c.GetResources(ctx, p.Config)
10✔
665
        if err != nil {
10✔
666
                return nil, 0, fmt.Errorf("error fetching resources for %v: %w", p.ID, err)
×
667
        }
×
668
        resourceTypeFilterMap := make(map[string]string)
10✔
669
        for _, rc := range p.Config.Resources {
12✔
670
                if len(rc.Filter) > 0 {
4✔
671
                        resourceTypeFilterMap[rc.Type] = rc.Filter
2✔
672
                }
2✔
673
        }
674
        filteredResources := make([]*domain.Resource, 0)
10✔
675
        for _, r := range newResourcesWithChildren {
20✔
676
                if filterExpression, ok := resourceTypeFilterMap[r.Type]; ok {
14✔
677
                        v, err := evaluator.Expression(filterExpression).EvaluateWithStruct(r)
4✔
678
                        if err != nil {
4✔
679
                                return nil, 0, err
×
680
                        }
×
681
                        if !reflect.ValueOf(v).IsZero() {
6✔
682
                                filteredResources = append(filteredResources, r)
2✔
683
                        }
2✔
684
                } else {
6✔
685
                        filteredResources = append(filteredResources, r)
6✔
686
                }
6✔
687
        }
688

689
        newAndUpdatedResources := s.compareResources(ctx, mapExistingResources, filteredResources)
10✔
690
        if len(newAndUpdatedResources) == 0 {
15✔
691
                return []*domain.Resource{}, 0, nil
5✔
692
        }
5✔
693
        for _, deletedResource := range mapExistingResources {
5✔
694
                deletedResource.IsDeleted = true
×
695
                newAndUpdatedResources = append(newAndUpdatedResources, deletedResource)
×
696
                s.logger.Info(ctx, "resource deleted", "resource", deletedResource.GlobalURN)
×
697
        }
×
698

699
        return newAndUpdatedResources, len(newResourcesWithChildren), nil
5✔
700
}
701

702
func (s *Service) compareResources(ctx context.Context, existingResources map[string]*domain.Resource, newResources []*domain.Resource) []*domain.Resource {
20✔
703
        var res []*domain.Resource
20✔
704
        for _, new := range newResources {
30✔
705
                new.Children = s.compareResources(ctx, existingResources, new.Children)
10✔
706

10✔
707
                existing, exist := existingResources[new.GlobalURN]
10✔
708
                if !exist {
15✔
709
                        // new resource
5✔
710
                        res = append(res, new)
5✔
711
                        continue
5✔
712
                }
713
                delete(existingResources, new.GlobalURN)
5✔
714
                if existingDetails := existing.Details; existingDetails != nil {
8✔
715
                        if new.Details != nil {
6✔
716
                                for key, value := range existingDetails {
9✔
717
                                        if _, ok := new.Details[key]; !ok {
9✔
718
                                                new.Details[key] = value
3✔
719
                                        }
3✔
720
                                }
721
                        } else {
×
722
                                new.Details = existingDetails
×
723
                        }
×
724
                }
725
                if len(new.Children) == 0 {
9✔
726
                        isUpdated, diff := compareResource(*existing, *new)
4✔
727
                        if !isUpdated {
7✔
728
                                continue
3✔
729
                        }
730
                        s.logger.Debug(ctx, "diff", "resources", diff)
1✔
731
                        s.logger.Info(ctx, "resources is updated", "resource", new.URN)
1✔
732
                }
733

734
                res = append(res, new)
2✔
735
        }
736

737
        return res
20✔
738
}
739

740
func (s *Service) validateAppealParam(a *domain.Appeal) error {
12✔
741
        if a == nil {
13✔
742
                return ErrNilAppeal
1✔
743
        }
1✔
744
        if a.Resource == nil {
12✔
745
                return ErrNilResource
1✔
746
        }
1✔
747
        //TO-DO
748
        //Make sure the user and role is required
749
        return nil
10✔
750
}
751

752
func (s *Service) validateAccessParam(a domain.Grant) error {
12✔
753
        if a.Resource == nil {
14✔
754
                return ErrNilResource
2✔
755
        }
2✔
756
        return nil
10✔
757
}
758

759
func (s *Service) getClient(pType string) Client {
42✔
760
        return s.clients[pType]
42✔
761
}
42✔
762

763
func (s *Service) getProviderConfig(ctx context.Context, pType, urn string) (*domain.Provider, error) {
8✔
764
        p, err := s.GetOne(ctx, pType, urn)
8✔
765
        if err != nil {
12✔
766
                return nil, err
4✔
767
        }
4✔
768
        return p, nil
4✔
769
}
770

771
func (s *Service) validateAccountTypes(pc *domain.ProviderConfig, accountTypes []string) error {
9✔
772
        if pc.AllowedAccountTypes == nil {
11✔
773
                pc.AllowedAccountTypes = accountTypes
2✔
774
        } else {
9✔
775
                if err := s.validator.Var(pc.AllowedAccountTypes, "min=1,unique"); err != nil {
7✔
776
                        return err
×
777
                }
×
778

779
                for _, at := range pc.AllowedAccountTypes {
14✔
780
                        accountTypesStr := strings.Join(accountTypes, " ")
7✔
781
                        if err := s.validator.Var(at, fmt.Sprintf("oneof=%v", accountTypesStr)); err != nil {
9✔
782
                                return err
2✔
783
                        }
2✔
784
                }
785
        }
786

787
        return nil
7✔
788
}
789

790
func (s *Service) validateAppealConfig(a *domain.AppealConfig) error {
3✔
791
        if a.AllowActiveAccessExtensionIn != "" {
6✔
792
                if err := validateDuration(a.AllowActiveAccessExtensionIn); err != nil {
4✔
793
                        return fmt.Errorf("invalid appeal extension policy: %v", err)
1✔
794
                }
1✔
795
        }
796

797
        return nil
2✔
798
}
799

800
func validateDuration(d string) error {
7✔
801
        _, err := time.ParseDuration(d)
7✔
802
        return err
7✔
803
}
7✔
804

805
func flattenResources(resources []*domain.Resource) []*domain.Resource {
5✔
806
        flattenedResources := []*domain.Resource{}
5✔
807
        for _, r := range resources {
11✔
808
                flattenedResources = append(flattenedResources, r.GetFlattened()...)
6✔
809
        }
6✔
810
        return flattenedResources
5✔
811
}
812

813
type isDryRunKey string
814

815
func WithDryRun(ctx context.Context) context.Context {
2✔
816
        return context.WithValue(ctx, isDryRunKey("dry_run"), true)
2✔
817
}
2✔
818

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