Filter data by permission
Return only the data a user is allowed to see, instead of a single allow or deny decision. This page is for backend developers who query lists of records, such as documents or projects, and must drop the records a user can't access. The shortest path is FilterObjects() in the Go SDK and filter_objects() in the Python SDK: pass the records you fetched, get back the allowed subset.
Prerequisites
- A Permit SDK client connected to a policy decision point (PDP) (Run the PDP)
- A policy with resource instances or attributes that decide which records a user can access
- Your environment API key (Get your API key)
Filter a list of objects with the SDK
FilterObjects() in Go and filter_objects() in Python take a user, an action, a request context, and the resources to filter. Both send one bulk check to the PDP and return the resources the user can perform the action on, in the order you passed them. Resources the policy denies are left out of the returned list, so the returned list is usually shorter than the list you passed.
In both samples, replace <YOUR_API_KEY> with your environment API key, and replace the document and folder resources with resources from your own policy.
- Go
- Python
package main
import (
"fmt"
)
import p "github.com/permitio/permit-golang/pkg/permit"
import "github.com/permitio/permit-golang/pkg/config"
import "github.com/permitio/permit-golang/pkg/enforcement"
func main() {
// Create permit client
permitConfig := config.NewConfigBuilder("<YOUR_API_KEY>").Build()
permit := p.NewPermit(permitConfig)
requestContext := map[string]string{
"source": "docs",
}
user := enforcement.UserBuilder("john@permit.io").Build()
var action enforcement.Action = "read"
resourcesToCheck := []enforcement.ResourceI{
enforcement.ResourceBuilder("document").WithID("document-1").WithTenant(enforcement.DefaultTenant),
enforcement.ResourceBuilder("folder").WithID("folder-1").WithTenant(enforcement.DefaultTenant),
enforcement.ResourceBuilder("document").WithID("document-2").WithTenant(enforcement.DefaultTenant),
enforcement.ResourceBuilder("document").WithID("document-3").WithTenant(enforcement.DefaultTenant),
}
var allowedResources []enforcement.ResourceI
var err error
// Filter the objects
allowedResources, err = permit.FilterObjects(user, action, requestContext, resourcesToCheck...)
if err != nil {
fmt.Printf("Error enforcing permissions: %s", err)
} else {
// allowedResources holds only the resources the policy allows, in the order they were passed
fmt.Printf("%d of %d resources allowed\n", len(allowedResources), len(resourcesToCheck))
for i, resource := range allowedResources {
fmt.Printf("%d. User '%s' is PERMITTED to '%s' a '%s' with id '%s'\n",
i, user.Key, action, resource.GetType(), resource.GetID(),
)
}
}
}
import asyncio
from permit import Permit
permit = Permit(
token="<YOUR_API_KEY>",
pdp="http://localhost:7766",
)
async def main():
user = {"key": "john@permit.io"}
resources = [
{"type": "document", "key": "document-1", "tenant": "default"},
{"type": "folder", "key": "folder-1", "tenant": "default"},
{"type": "document", "key": "document-2", "tenant": "default"},
{"type": "document", "key": "document-3", "tenant": "default"},
]
allowed = await permit.filter_objects(
user=user,
action="read",
context={"source": "docs"},
resources=resources,
)
print(f"{len(allowed)} of {len(resources)} resources allowed")
for i, resource in enumerate(allowed):
print(f"{i}. User '{user['key']}' is PERMITTED to 'read' a '{resource['type']}' with key '{resource['key']}'")
asyncio.run(main())
Verify the filter result
Both samples first print how many of the four resources the policy allows, then one line per allowed resource. With a policy that gives John read on document-1 and document-3 only, the output is:
2 of 4 resources allowed
0. User 'john@permit.io' is PERMITTED to 'read' a 'document' with id 'document-1'
1. User 'john@permit.io' is PERMITTED to 'read' a 'document' with id 'document-3'
0 of 4 resources allowed means the policy denies read on all four resources for John. Check that the user, the resource keys, and the tenant in the sample match facts that exist in your environment, and that the policy grants read on those instances.
Other data filtering approaches
Both FilterObjects() and filter_objects() send one check per record that your query already returned, so they suit result sets you page or limit. When the result set is too large to check record by record, keep unauthorized records out of the query itself with one of the following approaches.
| Approach | Where filtering happens | How it works | Permit functions |
|---|---|---|---|
| Application-level filtering | Your application, after the query | Fetch the records, send one bulk check for all of them, and keep the records whose result is true. | permit.bulkCheck() |
| Pre-filtering from the permissions graph | Your application, before the query | Ask the PDP which objects the user can access, and add the allowed keys to the query's WHERE clause. | getUserPermissions(), authorized_users() |
| Source-level filtering (partial evaluation) | The database | Ask Open Policy Agent (OPA) for the conditions a record must meet, translate the conditions into a query filter, and run the filtered query. | OPA's Compile API, through the PDP's exposed OPA port |
Data filtering answers which records a user may act on. A different question is whether a query may run at all: map the query to a resource and an action, then run a single permit.check(). For an example that parses a Couchbase N1QL query into resources and actions, see the Permit.io and Couchbase access control post.