Skip to content

Commit 556c396

Browse files
committed
Add entitlements for graph exploration.
1 parent 4345068 commit 556c396

13 files changed

Lines changed: 131 additions & 17 deletions

api/handle_graph_walk.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,12 @@ import (
1212
"github.com/checkmarble/marble-backend/dto"
1313
"github.com/checkmarble/marble-backend/models"
1414
"github.com/checkmarble/marble-backend/usecases"
15-
"github.com/checkmarble/marble-backend/utils"
1615
)
1716

1817
func handleGraphWalk(uc usecases.Usecases) func(c *gin.Context) {
1918
return func(c *gin.Context) {
2019
ctx := c.Request.Context()
2120

22-
organizationId, err := utils.OrganizationIdFromRequest(c.Request)
23-
if presentError(ctx, c, err) {
24-
return
25-
}
26-
2721
nodeType := c.Param("node_type")
2822
nodeId := c.Param("node_id")
2923

@@ -40,7 +34,7 @@ func handleGraphWalk(uc usecases.Usecases) func(c *gin.Context) {
4034
}
4135

4236
usecase := usecasesWithCreds(ctx, uc).NewGraphWalkUsecase()
43-
result, err := usecase.WalkGraph(ctx, organizationId, nodeType, nodeId, opts)
37+
result, err := usecase.WalkGraph(ctx, nodeType, nodeId, opts)
4438

4539
if presentError(ctx, c, err) {
4640
return

dto/license_dto.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type LicenseEntitlements struct {
2525
ContinuousScreening bool `json:"continuous_screening"`
2626
UserScoring bool `json:"user_scoring"`
2727
LexisNexis bool `json:"lexisnexis"` //nolint:tagliatelle
28+
GraphExploration bool `json:"graph_exploration"`
2829
}
2930

3031
func AdaptLicenseEntitlements(licenseEntitlements models.LicenseEntitlements) LicenseEntitlements {
@@ -43,6 +44,7 @@ func AdaptLicenseEntitlements(licenseEntitlements models.LicenseEntitlements) Li
4344
ContinuousScreening: licenseEntitlements.ContinuousScreening,
4445
UserScoring: licenseEntitlements.UserScoring,
4546
LexisNexis: licenseEntitlements.LexisNexis,
47+
GraphExploration: licenseEntitlements.GraphExploration,
4648
}
4749
}
4850

models/license.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type LicenseEntitlements struct {
6565
ContinuousScreening bool
6666
UserScoring bool
6767
LexisNexis bool
68+
GraphExploration bool
6869
}
6970

7071
type LicenseValidation struct {
@@ -91,6 +92,7 @@ func NewFullLicense() LicenseValidation {
9192
ContinuousScreening: true,
9293
UserScoring: true,
9394
LexisNexis: true,
95+
GraphExploration: true,
9496
},
9597
}
9698
}

models/organization_feature_access.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type OrganizationFeatureAccess struct {
2323
AiRuleBuilding FeatureAccess `redis:"ai_rule_building"`
2424
UserScoring FeatureAccess `redis:"user_scoring"`
2525
LexisNexis FeatureAccess `redis:"lexisnexis"`
26+
GraphExploration FeatureAccess `redis:"graph_exploration"`
2627
CreatedAt time.Time
2728
UpdatedAt time.Time
2829

@@ -41,6 +42,7 @@ type DbStoredOrganizationFeatureAccess struct {
4142
AiRuleBuilding FeatureAccess
4243
UserScoring FeatureAccess
4344
LexisNexis FeatureAccess
45+
GraphExploration FeatureAccess
4446
CreatedAt time.Time
4547
UpdatedAt time.Time
4648
}
@@ -55,6 +57,7 @@ type UpdateOrganizationFeatureAccessInput struct {
5557
AiRuleBuilding *FeatureAccess
5658
UserScoring *FeatureAccess
5759
LexisNexis *FeatureAccess
60+
GraphExploration *FeatureAccess
5861
}
5962

6063
type FeaturesConfiguration struct {
@@ -80,6 +83,7 @@ func (f DbStoredOrganizationFeatureAccess) MergeWithLicenseEntitlement(
8083
AiRuleBuilding: f.AiRuleBuilding,
8184
UserScoring: f.UserScoring,
8285
LexisNexis: f.LexisNexis,
86+
GraphExploration: f.GraphExploration,
8387
CreatedAt: f.CreatedAt,
8488
UpdatedAt: f.UpdatedAt,
8589
}
@@ -124,6 +128,10 @@ func (f DbStoredOrganizationFeatureAccess) MergeWithLicenseEntitlement(
124128
if !l.LexisNexis {
125129
o.LexisNexis = Restricted
126130
}
131+
// TODO: remove for actual entitlement validation
132+
if false && !l.GraphExploration {
133+
o.GraphExploration = Restricted
134+
}
127135

128136
// remove the feature accesses that are not allowed by the configuration
129137
if o.Analytics.IsAllowed() && !c.Analytics {

repositories/dbmodels/db_license.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type DBLicense struct {
3131
ContinuousScreening bool `db:"continuous_screening"`
3232
UserScoring bool `db:"user_scoring"`
3333
LexisNexis bool `db:"lexisnexis"`
34+
GraphExploration bool `db:"graph_exploration"`
3435
}
3536

3637
const TABLE_LICENSES = "licenses"
@@ -61,6 +62,7 @@ func AdaptLicense(db DBLicense) (models.License, error) {
6162
ContinuousScreening: db.ContinuousScreening,
6263
UserScoring: db.UserScoring,
6364
LexisNexis: db.LexisNexis,
65+
GraphExploration: db.GraphExploration,
6466
},
6567
}, nil
6668
}

repositories/dbmodels/db_organization_feature_access.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type DBOrganizationFeatureAccess struct {
2323
AiRuleBuilding string `db:"ai_rule_building"`
2424
UserScoring string `db:"user_scoring"`
2525
LexisNexis string `db:"lexisnexis"`
26+
GraphExploration string `db:"graph_exploration"`
2627
CreatedAt time.Time `db:"created_at"`
2728
UpdatedAt time.Time `db:"updated_at"`
2829
}
@@ -39,6 +40,7 @@ func AdaptOrganizationFeatureAccess(db DBOrganizationFeatureAccess) (models.DbSt
3940
AiRuleBuilding: models.FeatureAccessFrom(db.AiRuleBuilding),
4041
UserScoring: models.FeatureAccessFrom(db.UserScoring),
4142
LexisNexis: models.FeatureAccessFrom(db.LexisNexis),
43+
GraphExploration: models.FeatureAccessFrom(db.GraphExploration),
4244
CreatedAt: db.CreatedAt,
4345
UpdatedAt: db.UpdatedAt,
4446
}, nil

repositories/migrations/20260810081900_graph_walking.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
-- +goose Up
22

3+
alter table licenses
4+
add column if not exists graph_exploration boolean default false not null;
5+
6+
alter table organization_feature_access
7+
add column if not exists graph_exploration text default 'allowed' not null;
8+
39
create table graph_relations (
410
id uuid primary key default gen_random_uuid(),
511
org_id uuid not null,
@@ -20,4 +26,10 @@ create index idx_graph_relations_org_id on graph_relations (org_id);
2026

2127
-- +goose Down
2228

29+
alter table licenses
30+
drop column graph_exploration;
31+
32+
alter table organization_feature_access
33+
drop column graph_exploration;
34+
2335
drop table graph_relations;

usecases/graph_relation_usecase.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@ import (
1616
type GraphRelationUsecase struct {
1717
enforceSecurity security.EnforceSecurityOrganization
1818
executorFactory executor_factory.ExecutorFactory
19+
featureAccessReader OrganizationUsecaseFeatureAccessReader
1920
dataModelRepository repositories.DataModelRepository
2021
graphRelationRepository repositories.GraphRelationRepository
2122
}
2223

2324
func (uc GraphRelationUsecase) ListGraphRelations(ctx context.Context) ([]models.GraphRelation, error) {
25+
fa, err := uc.featureAccessReader.GetOrganizationFeatureAccess(ctx, uc.enforceSecurity.OrgId(), nil)
26+
if err != nil {
27+
return []models.GraphRelation{}, err
28+
}
29+
30+
if !fa.GraphExploration.IsAllowed() {
31+
return []models.GraphRelation{}, errors.Wrap(models.ForbiddenError,
32+
"organization not allowed to use the graph exploration feature")
33+
}
2434
if err := uc.enforceSecurity.ReadDataModel(); err != nil {
2535
return nil, err
2636
}
@@ -32,6 +42,15 @@ func (uc GraphRelationUsecase) ListGraphRelations(ctx context.Context) ([]models
3242
}
3343

3444
func (uc GraphRelationUsecase) CreateGraphRelation(ctx context.Context, input models.CreateGraphRelation) (models.GraphRelation, error) {
45+
fa, err := uc.featureAccessReader.GetOrganizationFeatureAccess(ctx, uc.enforceSecurity.OrgId(), nil)
46+
if err != nil {
47+
return models.GraphRelation{}, err
48+
}
49+
50+
if !fa.GraphExploration.IsAllowed() {
51+
return models.GraphRelation{}, errors.Wrap(models.ForbiddenError,
52+
"organization not allowed to use the graph exploration feature")
53+
}
3554
if err := uc.enforceSecurity.WriteDataModel(uc.enforceSecurity.OrgId()); err != nil {
3655
return models.GraphRelation{}, err
3756
}
@@ -87,6 +106,15 @@ func (uc GraphRelationUsecase) CreateGraphRelation(ctx context.Context, input mo
87106
}
88107

89108
func (uc GraphRelationUsecase) DeleteGraphRelation(ctx context.Context, relationId uuid.UUID) error {
109+
fa, err := uc.featureAccessReader.GetOrganizationFeatureAccess(ctx, uc.enforceSecurity.OrgId(), nil)
110+
if err != nil {
111+
return err
112+
}
113+
114+
if !fa.GraphExploration.IsAllowed() {
115+
return errors.Wrap(models.ForbiddenError,
116+
"organization not allowed to use the graph exploration feature")
117+
}
90118
if err := uc.enforceSecurity.WriteDataModel(uc.enforceSecurity.OrgId()); err != nil {
91119
return err
92120
}

usecases/graph_relation_usecase_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ func graphRelationUsecase(repo *mocks.GraphRelationRepository, orgId uuid.UUID)
2929
dataModelRepository.On("GetDataModel", mock.Anything, mock.Anything, mock.Anything, false, true).
3030
Return(amlDataModel(), nil)
3131

32+
featureAccessReader := new(mocks.FeatureAccessReader)
33+
featureAccessReader.On("GetOrganizationFeatureAccess", mock.Anything, mock.Anything, (*models.UserId)(nil)).
34+
Return(models.OrganizationFeatureAccess{GraphExploration: models.Allowed}, nil)
35+
3236
return GraphRelationUsecase{
3337
enforceSecurity: enforceSecurity,
3438
executorFactory: executor_factory.NewExecutorFactoryStub(),
39+
featureAccessReader: featureAccessReader,
3540
dataModelRepository: dataModelRepository,
3641
graphRelationRepository: repo,
3742
}

usecases/graph_walk_usecase.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ const (
142142
type GraphWalkUsecase struct {
143143
enforceSecurity security.EnforceSecurity
144144
executorFactory executor_factory.ExecutorFactory
145+
featureAccessReader OrganizationUsecaseFeatureAccessReader
145146
dataModelRepository repositories.DataModelRepository
146147
graphRepository repositories.GraphRepository
147148
graphRelationRepository repositories.GraphRelationRepository
@@ -159,15 +160,25 @@ type GraphWalkUsecase struct {
159160
// matches reached. Everything newly found feeds the next degree.
160161
func (uc GraphWalkUsecase) WalkGraph(
161162
ctx context.Context,
162-
organizationId uuid.UUID,
163163
startType, startId string,
164164
opts models.GraphWalkOptions,
165165
) (models.GraphResult, error) {
166-
if err := uc.enforceSecurity.ReadOrganization(organizationId); err != nil {
166+
orgId := uc.enforceSecurity.OrgId()
167+
168+
fa, err := uc.featureAccessReader.GetOrganizationFeatureAccess(ctx, orgId, nil)
169+
if err != nil {
170+
return models.GraphResult{}, err
171+
}
172+
173+
if !fa.GraphExploration.IsAllowed() {
174+
return models.GraphResult{}, errors.Wrap(models.ForbiddenError,
175+
"organization not allowed to use the graph exploration feature")
176+
}
177+
if err := uc.enforceSecurity.ReadOrganization(orgId); err != nil {
167178
return models.GraphResult{}, err
168179
}
169180

170-
dataModel, err := uc.dataModelRepository.GetDataModel(ctx, uc.executorFactory.NewExecutor(), organizationId, false, true)
181+
dataModel, err := uc.dataModelRepository.GetDataModel(ctx, uc.executorFactory.NewExecutor(), orgId, false, true)
171182
if err != nil {
172183
return models.GraphResult{}, err
173184
}
@@ -184,12 +195,12 @@ func (uc GraphWalkUsecase) WalkGraph(
184195

185196
// An organization declares its own shared-attribute relations against its own data model. An
186197
// organization that has declared none still gets a walk: it just follows links only.
187-
relations, err := uc.graphRelationRepository.ListGraphRelations(ctx, uc.executorFactory.NewExecutor(), organizationId)
198+
relations, err := uc.graphRelationRepository.ListGraphRelations(ctx, uc.executorFactory.NewExecutor(), orgId)
188199
if err != nil {
189200
return models.GraphResult{}, err
190201
}
191202

192-
exec, err := uc.executorFactory.NewClientDbExecutor(ctx, organizationId)
203+
exec, err := uc.executorFactory.NewClientDbExecutor(ctx, orgId)
193204
if err != nil {
194205
return models.GraphResult{}, err
195206
}
@@ -220,7 +231,7 @@ func (uc GraphWalkUsecase) WalkGraph(
220231
return models.GraphResult{}, err
221232
}
222233

223-
if err := uc.enrichGraph(ctx, organizationId, exec, dataModel, graph); err != nil {
234+
if err := uc.enrichGraph(ctx, orgId, exec, dataModel, graph); err != nil {
224235
return models.GraphResult{}, err
225236
}
226237

0 commit comments

Comments
 (0)