Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion assets/example-shapes/banking-system.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,18 @@ scenarios:
data:
amount: 3000
assertions:
withdraw: false
withdraw: false
- name: "Account 2 Owner Withdrawal with Scenario-Specific Attributes"
description: "Tests 'steven' can withdraw from 'account:2' using scenario-specific balance attribute."
attributes:
- account:2$balance|integer:6000
checks:
- entity: "account:2"
subject: "user:steven"
context:
tuples: []
attributes: []
data:
amount: 2000
assertions:
withdraw: true
9 changes: 5 additions & 4 deletions assets/example-shapes/custom-roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@ relationships:
- dashboard:project-progress#view@role:admin#assignee
- dashboard:project-progress#view@role:member#assignee
- dashboard:project-progress#edit@role:admin#assignee
- task:website-design-review#view@role:admin#assignee
- task:website-design-review#view@role:member#assignee
- task:website-design-review#edit@role:admin#assignee
- role:member#assignee@user:1

attributes:
attributes:

scenarios:
- name: "User Dashboard View Permissions for project-progress"
Expand All @@ -41,6 +38,10 @@ scenarios:
view: true
- name: "Role-Based Permissions for 'website-design-review' Task"
description: "Evaluates the access rights for 'website-design-review' task based on roles. The admin role should have both view and edit permissions, whereas the member role should only have view permission."
relationships:
- task:website-design-review#view@role:admin#assignee
- task:website-design-review#view@role:member#assignee
- task:website-design-review#edit@role:admin#assignee
checks:
- entity: "task:website-design-review"
subject: "role:admin#assignee"
Expand Down
11 changes: 11 additions & 0 deletions assets/example-shapes/organizations-hierarchies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ attributes:

scenarios:
- name: admin_access_test
description: "Verifies admin user can edit but not delete a repository they don't own."
checks:
- entity: repository:1234
subject: user:5678
Expand All @@ -45,3 +46,13 @@ scenarios:
delete: false
entity_filters: []
subject_filters: []
- name: owner_access_test
description: "Verifies repository owner has full permissions using scenario-specific relationships."
relationships:
- "repository:1234#owner@user:9999"
checks:
- entity: repository:1234
subject: user:9999
assertions:
edit: true
delete: true
28 changes: 28 additions & 0 deletions assets/example-shapes/user-groups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,36 @@ schema: |-
}

relationships:
- "organization:1#admin@user:1"
- "team:1#owner@user:1"
- "team:1#org@organization:1"

attributes:

scenarios:
- name: "Team Owner Permissions"
description: "Verifies that team owner (user:1) has edit, delete, and remove_user permissions on the team."
checks:
- entity: "team:1"
subject: "user:1"
assertions:
edit: true
delete: true
remove_user: true
- name: "Team Member Project Access"
description: "Verifies project access for a team member added via scenario-specific relationships."
relationships:
- "team:1#member@user:2"
- "project:1#team@team:1"
- "project:1#org@organization:1"
checks:
- entity: "project:1"
subject: "user:2"
assertions:
view: true
edit: true
delete: true
- entity: "project:1"
subject: "user:1"
assertions:
view: true
233 changes: 155 additions & 78 deletions pkg/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,133 @@ func (l *ErrList) Print() {
color.Danger.Println("FAILED")
}

// writeRelationships validates and writes relationship tuples to the datastore.
// Returns the written tuples (for cleanup) and whether any errors occurred.
func writeRelationships(
ctx context.Context,
dev *development.Development,
relationships []string,
version string,
list *ErrList,
indent string,
) []*base.Tuple {
var written []*base.Tuple
for _, t := range relationships {
tup, err := tuple.Tuple(t)
if err != nil {
list.Add(err.Error())
color.Danger.Printf("%sfail: %s\n", indent, validationError(err.Error()))
continue
}

definition, _, err := dev.Container.SR.ReadEntityDefinition(ctx, "t1", tup.GetEntity().GetType(), version)
if err != nil {
list.Add(err.Error())
color.Danger.Printf("%sfail: %s\n", indent, validationError(err.Error()))
continue
}

if err = serverValidation.ValidateTuple(definition, tup); err != nil {
list.Add(err.Error())
color.Danger.Printf("%sfail: %s\n", indent, validationError(err.Error()))
continue
}

if _, err = dev.Container.DW.Write(ctx, "t1", database.NewTupleCollection(tup), database.NewAttributeCollection()); err != nil {
list.Add(fmt.Sprintf("%s failed %s", t, err.Error()))
color.Danger.Println(fmt.Sprintf("%sfail: %s failed %s", indent, t, validationError(err.Error())))
continue
}

color.Success.Println(fmt.Sprintf("%ssuccess: %s ", indent, t))
written = append(written, tup)
}
return written
}

// writeAttributes validates and writes attributes to the datastore.
// Returns the written attributes (for cleanup) and whether any errors occurred.
func writeAttributes(
ctx context.Context,
dev *development.Development,
attributes []string,
version string,
list *ErrList,
indent string,
) []*base.Attribute {
var written []*base.Attribute
for _, a := range attributes {
attr, err := attribute.Attribute(a)
if err != nil {
list.Add(err.Error())
color.Danger.Printf("%sfail: %s\n", indent, validationError(err.Error()))
continue
}

definition, _, err := dev.Container.SR.ReadEntityDefinition(ctx, "t1", attr.GetEntity().GetType(), version)
if err != nil {
list.Add(err.Error())
color.Danger.Printf("%sfail: %s\n", indent, validationError(err.Error()))
continue
}

if err = serverValidation.ValidateAttribute(definition, attr); err != nil {
list.Add(err.Error())
color.Danger.Printf("%sfail: %s\n", indent, validationError(err.Error()))
continue
}

if _, err = dev.Container.DW.Write(ctx, "t1", database.NewTupleCollection(), database.NewAttributeCollection(attr)); err != nil {
list.Add(fmt.Sprintf("%s failed %s", a, err.Error()))
color.Danger.Println(fmt.Sprintf("%sfail: %s failed %s", indent, a, validationError(err.Error())))
continue
}

color.Success.Println(fmt.Sprintf("%ssuccess: %s ", indent, a))
written = append(written, attr)
}
return written
}

// deleteRelationships removes previously written relationship tuples from the datastore.
func deleteRelationships(ctx context.Context, dev *development.Development, tuples []*base.Tuple) error {
for _, tup := range tuples {
_, err := dev.Container.DW.Delete(ctx, "t1", &base.TupleFilter{
Entity: &base.EntityFilter{
Type: tup.GetEntity().GetType(),
Ids: []string{tup.GetEntity().GetId()},
},
Relation: tup.GetRelation(),
Subject: &base.SubjectFilter{
Type: tup.GetSubject().GetType(),
Ids: []string{tup.GetSubject().GetId()},
Relation: tup.GetSubject().GetRelation(),
},
}, &base.AttributeFilter{})
if err != nil {
return err
}
}
return nil
}

// deleteAttributes removes previously written attributes from the datastore.
func deleteAttributes(ctx context.Context, dev *development.Development, attrs []*base.Attribute) error {
for _, attr := range attrs {
_, err := dev.Container.DW.Delete(ctx, "t1", &base.TupleFilter{}, &base.AttributeFilter{
Entity: &base.EntityFilter{
Type: attr.GetEntity().GetType(),
Ids: []string{attr.GetEntity().GetId()},
},
Attributes: []string{attr.GetAttribute()},
})
if err != nil {
return err
}
}
return nil
}

// validate returns a function that validates authorization model with assertions
func validate() func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -144,87 +271,13 @@ func validate() func(cmd *cobra.Command, args []string) error {
color.Success.Println(" success")
}

// if debug is true, print relationships are creating with color blue
// Write global relationships
color.Notice.Println("relationships are creating... 🚀")
writeRelationships(ctx, dev, s.Relationships, version, list, " ")

// Iterate over all relationships in the subject
for _, t := range s.Relationships {
// Convert each relationship to a Tuple
var tup *base.Tuple
tup, err = tuple.Tuple(t)
// If an error occurs during the conversion, add the error message to the list and continue to the next iteration
if err != nil {
list.Add(err.Error())
continue
}

// Retrieve the entity definition associated with the tuple's entity type
definition, _, err := dev.Container.SR.ReadEntityDefinition(ctx, "t1", tup.GetEntity().GetType(), version)
// If an error occurs while reading the entity definition, return the error
if err != nil {
return err
}

// Validate the tuple using the entity definition
err = serverValidation.ValidateTuple(definition, tup)
// If an error occurs during validation, return the error
if err != nil {
return err
}

// Write the validated tuple to the database
_, err = dev.Container.DW.Write(ctx, "t1", database.NewTupleCollection(tup), database.NewAttributeCollection())
// If an error occurs while writing to the database, add an error message to the list, log the error and continue to the next iteration
if err != nil {
list.Add(fmt.Sprintf("%s failed %s", t, err.Error()))
color.Danger.Println(fmt.Sprintf("fail: %s failed %s", t, validationError(err.Error())))
continue
}

// If the tuple was successfully written to the database, log a success message
color.Success.Println(fmt.Sprintf(" success: %s ", t))
}

// if debug is true, print attributes are creating with color blue
// Write global attributes
color.Notice.Println("attributes are creating... 🚀")

// Iterate over all attributes in the subject
for _, a := range s.Attributes {
// Convert each attribute to an Attribute
var attr *base.Attribute
attr, err = attribute.Attribute(a)
// If an error occurs during the conversion, add the error message to the list and continue to the next iteration
if err != nil {
list.Add(err.Error())
continue
}

// Retrieve the entity definition associated with the attribute's entity type
definition, _, err := dev.Container.SR.ReadEntityDefinition(ctx, "t1", attr.GetEntity().GetType(), version)
// If an error occurs while reading the entity definition, return the error
if err != nil {
return err
}

// Validate the attribute using the entity definition
err = serverValidation.ValidateAttribute(definition, attr)
// If an error occurs during validation, return the error
if err != nil {
return err
}

// Write the validated attribute to the database
_, err = dev.Container.DW.Write(ctx, "t1", database.NewTupleCollection(), database.NewAttributeCollection(attr))
// If an error occurs while writing to the database, add an error message to the list, log the error and continue to the next iteration
if err != nil {
list.Add(fmt.Sprintf("%s failed %s", a, err.Error()))
color.Danger.Println(fmt.Sprintf("fail: %s failed %s", a, validationError(err.Error())))
continue
}

// If the attribute was successfully written to the database, log a success message
color.Success.Println(fmt.Sprintf(" success: %s ", a))
}
writeAttributes(ctx, dev, s.Attributes, version, list, " ")

// if debug is true, print checking assertions with color blue
color.Notice.Println("checking scenarios... 🚀")
Expand All @@ -233,6 +286,20 @@ func validate() func(cmd *cobra.Command, args []string) error {
for sn, scenario := range s.Scenarios {
color.Notice.Printf("%v.scenario: %s - %s\n", sn+1, scenario.Name, scenario.Description)

// Write scenario-specific relationships if any are defined
var writtenTuples []*base.Tuple
if len(scenario.Relationships) > 0 {
color.Notice.Println(" scenario relationships:")
writtenTuples = writeRelationships(ctx, dev, scenario.Relationships, version, list, " ")
}

// Write scenario-specific attributes if any are defined
var writtenAttrs []*base.Attribute
if len(scenario.Attributes) > 0 {
color.Notice.Println(" scenario attributes:")
writtenAttrs = writeAttributes(ctx, dev, scenario.Attributes, version, list, " ")
}

// Start log output for checks
color.Notice.Println(" checks:")

Expand Down Expand Up @@ -469,6 +536,16 @@ func validate() func(cmd *cobra.Command, args []string) error {
}
}
}

// Clean up scenario-specific data so it doesn't pollute subsequent scenarios
if err := deleteRelationships(ctx, dev, writtenTuples); err != nil {
list.Add(fmt.Sprintf("scenario %d cleanup relationships: %s", sn+1, err.Error()))
color.Danger.Printf(" fail: cleanup relationships: %s\n", validationError(err.Error()))
}
if err := deleteAttributes(ctx, dev, writtenAttrs); err != nil {
list.Add(fmt.Sprintf("scenario %d cleanup attributes: %s", sn+1, err.Error()))
color.Danger.Printf(" fail: cleanup attributes: %s\n", validationError(err.Error()))
}
}

// If the error list is not empty, there were some errors during processing.
Expand Down
Loading
Loading