expr icon indicating copy to clipboard operation
expr copied to clipboard

Dynamically reloading input expressions

Open jon-whit opened this issue 1 year ago • 1 comments

I am evaluating the usage of expr for a work project. We are experimenting with usage of expr to filter resources based on certain content policy definitions. For example, we may have some content policies that express the following:

  • Any user should be blocked from viewing a resource if the resource's GeoCode restriction aligns with the user's' GeoCode.
  • Any user should be blocked from viewing a resource if the resource requires age verification and the user is not age verified.

I have an Env and policy definition that looks like this:

type User struct {
	Id      string
	GeoCode string
        AgeVerified bool
}

type Resource struct {
	Id          string

        // A slice of geo codes this resource is restricted in.
	GeoRestrictions []string

        // A map of consumer content privacy classifiers such as "profanity", "drugs", "political"
        ContentPrivacyClassifications map[string]struct{}
}

type Tweet struct {
	Resource
        UserId string 
}

type Comment struct {
	Resource
	UserId string
}

type Env struct {
	User      User
	Resources []any
}

func main() {
	policy := `filter(Resources,  User.GeoCode not in .Resource?.GeoCodes)`

	program, err := expr.Compile(policy, expr.Env(Env{}))
	if err != nil {
		panic(err)
	}

	output, err := expr.Run(program, Env{
		User: User{Id: "cool_dude", GeoCode: "us", AgeVerified: false},
		Resources: []any{
			Tweet{
				Resource: Resource{
					Id:          "tweet:1",
					GeoRestrictions: []string{"ru"}},
                                         ContentPrivacyClassifications: map[string]struct{}{
                                             "profanity": {},
                                         },
				},
				UserId: "other_dude",
			},
			Comment{Resource: Resource{Id: "comment:x"}},
		},
	})
	if err != nil {
		panic(err)
	}

	fmt.Println(output)
}

Today the content filtering policy only depends on the User.GeoCode not in .Resource?.GeoCodes, but tomorrow we may want to change the policy so that a user with additional content privacy restrictions doesn't see resources with those classification codes. For example, we would want to change the policy to something like:

// any resource with content classified as "profanity" or "drugs" should not be viewable by non age verified users

filter(Resources,  User.GeoCode not in .Resource?.LegalBlocks?.GeoCodes && (any(keys(.Resource.ContentPrivacyClassifications), # in ["profanity", "drugs"]) && User.AgeVerified) || all(keys(.Resource.ContentPrivacyClassifications), # not in ["profanity", "drugs"]))

But we don't want to change the code itself. In other words, we want more dynamic resource filtering.

Are there any good projects out there doing something like this today? If not, would you have any protips/recommendations on how to orchestrate this pattern with the API in expr today?

jon-whit avatar Oct 09 '24 05:10 jon-whit

Are there any good projects out there doing something like this today?

There are a few open source project which uses Expr, what you can take a look at:

  • https://www.crowdsec.net/
  • https://coredns.io/

would you have any protips/recommendations on how to orchestrate this pattern with the API in expr today?

If I got your problem correctly, I'd say I go with a rule per resource instead.

So instead of writing a filtering inside filter(Resources, User.GeoCode not in .Resource?.GeoCodes), I'll go with a loop over resources and use filtering per resource:

User.GeoCode not in Resource?.GeoCodes

For your more complex example as well. Additionally we are in the process of developing if-else expressions which can simplify your complex rules.

antonmedv avatar Oct 09 '24 14:10 antonmedv