Permitta is an intuitive go library, which aims to help handle any kind of permission/access control in a simple and easy to understand way, even for beginners .
The fact that its intuitive doesn't take away how powerful it is to handle very complex and frequently used permission scenarios in different types of projects .
Almost everything we do in computing these days needs permissions . There are hundreds of permission/access control systems and methods, many of them are not intuitive .
I wanted to create a system that would cover most popular use cases, where you can get started in minutes, whether you are a novice or very experienced programmer, yet still powerful enough to handle complex permissions .
I took inspiration from the linux permission system e.g rwxr--r-x, but took it some steps further and made it more intuitive
I wanted to be able to handle permissions/access control in a SaaS (or any application), with multiple access levels and users
I wanted to be able to control access for actions including Create, Read, Update, Delete, Execute (remember CRUDE - more on this later), I wanted to be able to control how much of each of those actions can be carried out by each user, and how frequently within a specific period of time they can carry out those actions .
I wanted to create a permission/access control system with no dependencies, except the go standard library
I wanted to enjoy writing and reading permissions/access control
I wanted to be able to write extremely complex permissions/access control for a user/org/entity on a single line
I did not want to have to write complex DB queries to verify permission and resource usage, which can get very tedious as an application gets more complex.
go get -u github.com/limitlessDonald/permitta
file2.mp4 belonging to user ; eagleusername, filename, permissionsaveFilePermission, the package currently doesn't save permissions for you, you have to implement that whichever way you deem fit in your applicationgetFilePermissionpackage main
import (
"fmt"
"github.com/limitlessDonald/permitta"
permittaConstants "github.com/limitlessDonald/permitta/constants"
)
func main() {
// There are two ways to set permission , we can make use of notation(shorthand) and using structs using permitta.Permission{}
// if we want to set a very simple permission for the eagle user we could do this :
username := "eagle"
filename := "file2.mp4"
// Now we set the permission using notation
// This permission means `eagle` would be able to Create(c) , Read(r), Update(u), Delete(d), and Execute(e) the `file2.mp4` file
// If we want to remove/disallow permission to a certain operation, we replace it with a hypen , so "cru-e" , would mean that delete operations are not permitted
notation := "crude"
// we can now save it to db for use / access control later
isFilePermissionSaved := saveFilePermission(username, filename, notation)
if isFilePermissionSaved == false {
fmt.Println("There was an issue saving file permission")
}
//If eagle tries to execute the file (an execution operation), we want to check if eagle has the permission to do so, here is how we do it
// assume getFilePermission returns the saved permission
currentUserPermission := getFilePermission(username, filename)
// Now let's verify if user has permission to execute file
// since the permission is saved as notation string, we want to convert it to the type permitta.Permission{}
permissions := permitta.NotationToPermission(currentUserPermission)
isUserPermitted := permitta.IsEntityOperationPermitted(permittaConstants.OperationExecute,permissions)
if isUserPermitted==true {
fmt.Println("Running program ....")
}
}
The power of permitta may not be evident from the simple example above, lets create a more complex permission/access control system to reveal more of its power
Let's take some assumptions and facts into consideration to make the example easier to understand
organization, one of them is a farm called Blue Acres Farmdomains or more precisely Blue Acres Farm has multiple branches , one in the US and another in the France. The US and France branches are domainsgroups , or more precisely departments in each of the domains , like Accounting , Marketing, Sales, IT, Engineering e.t.croles in each of the groups , take for example the Accounting group/department , could have roles like Budgeting Manager, Auditor and the Engineering could have Maintenance Engineer and Electrical Engineerusers for each of these roles , e.g there could be multiple Budgeting Managers , like users Anna and Pierreentities . So the user , Anna is an entity, just like her role Budgeting Manager is an entityNow that we have all the above cleared up, before we go into a full code example, let's do a brief "anatomy" of what a very complex permission notation for an entity looks like and explain it
notation:="cr-d-|start=1735693200000|end=1767229200000|q=5|c=batch:2,all:100,minute:3,hour:103,day:7,week:20,fortnight:30|r=all:100000,quarter:80000|u=year:10000,month:5000,custom:[per_32_seconds_67 & per_9_weeks_1200]"
```
**Explanation:**
- The notation is divided into sections using the separator `|`
- The first section `cr-d-` means : `c` Create operation allowed, `r` read allowed, `-` update NOT allowed, `d` delete allowed, `-` execute not allowed
-`start=1735693200000` means the entity won't be permitted for anything, if a permission request is made before the unix time `1735693200000`. In other words permission starts at this time
- `end=1767229200000` means permission ends at this time `1767229200000`
- `q=5` means Quota=5 , this is useful when you store resource/operation usage/count in a DB . if `q=5` for videos for example for the Engineering department/`group`, at any given time, they can't have more than 5 videos stored
- Any section starting with `c=`,`r=`,`u=`,`d=`,`e=` is for defining limits for specific operation where `c=` is for `Create` operation limits and so on.
- See [Operation Limits](#operation-limits) for all the available limits and what they mean
- The next section `c=batch:2,all:100,minute:3,hour:103,day:7,week:20,fortnight:30`
1. As we already highlighted above `c=` means "The following limits are for `create` operations"
2. `batch=2` means the entity can't create more than two resources at a time
3. `all:100` means the all-time limit of resources that can be created by the entity is `100`, not to be confused with `Quota`, See [Operation Limits](#operation-limits) to understand the difference
4. `minute:3` means only `3` resources can be created by the entity every minute. Permission would be denied if the entity tries to create a fourth resource within a minute
5. `hour:100`,`day:7`,`week:20`,`fortnight:30` are similar to the explanation for the `minute` limit
- I believe the remaining sections should be self-explanatory , except where we have `custom:[per_32_seconds_67 & per_9_weeks_1200]` **NOTE THAT CUSTOM DURATION IS STILL UNDER DEVELOPMENT** . However, it simply means we have a list of custom durations :
1. `per_32_seconds_67` means the entity is allowed to perform `67` `update` operations `every 32 seconds`
2. `&` is the separator for the list of custom duration limits
3. `per_9_weeks_1200` means the entity is allowed to perform `1200` `update` operations `every 9 weeks`
- If limits for any operation is left out from the notation, the default for all the limits would be unlimited, except `batch` which is always `1` by default
- **NOTE** : For limits to work, it has to be paired with `usages` that you have stored in your preferred DB, Permitta provides a self-explanatory struct to help store usages and a function to easily update usage
Let's proceed with the example
```go
// Assume we have `usage` column in each db table for org,branches(domain),departments(group),roles,users
// The column can be named anything, Permitta NEVER directly interacts with your DB
// if the usage has not being previously saved by you, we could initialize it with
jsonBytes, err := json.Marshal(permitta.PermissionUsage{})
if err != nil {
fmt.Println(err)
return
}
// save usage as json string to the `usage` column
// saveOrgUsage,saveBranchUsage,saveDepartmentUsage,saveRoleUsage,saveUserUsage are assumed functions in your code to save the usage values, they don't exist in Permitta. Permitta doesn't save to our DB for you
saveOrgUsage(orgID,string(jsonBytes))
saveBranchUsage(branchID,string(jsonBytes))
saveDepartmentUsage(departmentID,string(jsonBytes))
saveRoleUsage(roleID,string(jsonBytes))
saveUserUsage(userID,string(jsonBytes))
```go
// Let's assume we fetch notation for each entity from the DB, and convert it to permissions orgPermission:=permitta.NotationToPermission("crude") // unlimited access / permissions franceBranchPermission:=permitta.NotationToPermission("cru-e|q=9000|c=batch:50") //domain permissions financeDepartmentPermission := permitta.NotationToPermission("cru--|c=hour:30") //group permissions auditInternPermission:=permitta.NotationToPermission("-r---|r=hour:500") // role permissions
// there is a new intern in the france branch called Adesewa, she is in the finance department, she has a role of Audit intern, let's create her permission adesewaPermission:=permitta.NotationToPermission("crude|r=hour:10|start=1735693200000|end=1767229200000") // user permissions
// Let's check permission if Adesewa tries to delete a file since her permission allows her to delete a resource
// to check permission we have to check this against her usage, the usage and permission of her role, the usage and permission of her department, the usage and permission of her branch and org // let's get all saved usage json and convert it to permitta.PermissionUsage orgUsageJson:=getOrgUsage(orgID) branchUsageJson:=getBranchUsage(branchID) departmentUsageJson:=getDepartmentUsage(departmentID) roleUsageJson:=getRoleUsage(roleID) userUsageJson:=getUserUsage(userID)
// Ideally you can write a function to just do this for all the usage so you don't repeat yourself unnecessarily . OR include the conversion in your get-Entity-Usage functions like getOrgUsage // I am intentionally making this as clear as possible, so beginners can understand what is going on in details var orgUsage permitta.PermissionUsage var branchUsage permitta.PermissionUsage var departmentUsage permitta.PermissionUsage var roleUsage permitta.PermissionUsage var userUsage permitta.PermissionUsage
// convert json string to permitta.PermissionUsage orgUsageErr := json.Unmarshal([]byte(orgUsageJson), &orgUsage)
if orgUsageErr != nil { fmt.Println(orgUsageErr) return "AccessDenied" }
branchUsageErr := json.Unmarshal([]byte(branchUsageJson), &branchUsage)
if branchUsageErr != nil { fmt.Println(branchUsageErr) return "AccessDenied" }
departmentUsageErr := json.Unmarshal([]byte(departmentUsageJson), &departmentUsage)
if departmentUsageErr != nil { fmt.Println(departmentUsageErr) return "AccessDenied" }
roleUsageErr := json.Unmarshal([]byte(roleUsageJson), &roleUsage)
if roleUsageErr != nil { fmt.Println(roleUsageErr) return "AccessDenied" }
userUsageErr := json.Unmarshal([]byte(userUsageJson), &userUsage)
if userUsageErr != nil { fmt.Println(userUsageErr) return "AccessDenied" }
// Now it's time to check permission // The EntityPermissionOrder is the "flow" or hierarchy in which the permissions should be respected // This org->domain->group->role->user , means org permissions and usage are taken into consideration first to check if the operation is permitted, if it isn't the permission check stops there and returns FALSE, if its permission is ok, it moves to the next entity domain, and so on // if EntityPermissionOrder is not provided , the default is org->domain->group->role->user // You don't have to use all the entities in the permission order, to ignore anyone, just leave it out . // this means role->user is also valid // How you order things is totally up to you and how your application is designed permissionRequestData := permitta.PermissionWithUsageRequestData{ PermissionRequestData: PermissionRequestData{ Operation: permittaConstants.OperationDelete, UserEntityPermissions: adesewaPermission, RoleEntityPermissions: auditInternPermission, G
$ claude mcp add permitta \
-- python -m otcore.mcp_server <graph>