{"id":23915613,"url":"https://github.com/jonhteper/minos","last_synced_at":"2025-06-26T16:33:49.297Z","repository":{"id":41128784,"uuid":"508429172","full_name":"jonhteper/minos","owner":"jonhteper","description":"Authorization library","archived":false,"fork":false,"pushed_at":"2025-06-12T22:07:06.000Z","size":370,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-12T23:23:29.436Z","etag":null,"topics":["authorization","crate","library","rust"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/minos","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonhteper.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-06-28T19:27:17.000Z","updated_at":"2025-06-12T22:07:11.000Z","dependencies_parsed_at":"2024-04-16T02:33:26.034Z","dependency_job_id":"0ada54d9-37d4-4514-b93d-9726cbc7a9c3","html_url":"https://github.com/jonhteper/minos","commit_stats":{"total_commits":109,"total_committers":2,"mean_commits":54.5,"dds":0.00917431192660545,"last_synced_commit":"42574172497b78b2bc2640301560cd6b548c58c3"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jonhteper/minos","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhteper%2Fminos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhteper%2Fminos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhteper%2Fminos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhteper%2Fminos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonhteper","download_url":"https://codeload.github.com/jonhteper/minos/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonhteper%2Fminos/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262102523,"owners_count":23259276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","crate","library","rust"],"created_at":"2025-01-05T11:44:50.562Z","updated_at":"2025-06-26T16:33:49.271Z","avatar_url":"https://github.com/jonhteper.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"***Warning ⚠️ ️: This project is active development***\n\n# minos\n\nAuthorization library for Minos authorization language. This repository contains the language definition, the parser and the authorization engine.\n\n## Minos authorization\n\n...\n\n## Minos Language\nThe Minos lang is a Domain-specific language, created for write authorization policies.\n\nTo describe the syntax we start with the next file:\n```minos\n/* in example.minos */\nsyntax = 0.16;\n\n/* Resource declaration */\nresource User {\n\t/* Environment declaration */\n    env DEFAULT {\n    \t/* policy declaration */ \n        policy {\n        \t/* Permissions granted if at least one rule is met */\n            allow = [\"create\", \"read\", \"update\", \"delete\"];\n\t\t\t\n\t\t\t/* Rule declaration */\n            rule {\n                actor.type = RootUser;\n            }\n\n            rule {\n                actor.type = resource.type;\n                actor.id = resource.id;\n            }\n        }\n    }\n}\n\n```\n\nFirst, comments in the minos files are written between the `/*` and `*/` characters, as in CSS.\n\n```minos\n/*in example.minos */\n```\n\n\n\nEach minos file needs to declare the version of the syntax used at the beginning of the document.\n\n```minos\nsyntax = 0.16;\n```\n\n...\n\nNow, we can comeback to the all file:\n\n```minos\nsyntax = 0.16;\n\nresource User {\n    env DEFAULT {\n        policy {\n            allow = [\"create\", \"read\", \"update\", \"delete\"];\n            rule {\n                actor.type = RootUser;\n            }\n            rule {\n                actor.type = resource.type;\n                actor.id = resource.id;\n            }\n        }\n    }\n}\n\n```\n\nThe block describes a resource named `User` with an only one environment named `DEFAULT`. Is important to indicate that if the `DEFAULT` block exist, will be apply in every authorization process.\n\n### Implicit environment\n\nBased on the above, we can rewrite the block without environments:\n\n```minos\nresource User {\n    policy {\n        allow = [\"create\", \"read\", \"update\", \"delete\"];\n        rule {\n            actor.type = RootUser;\n        }\n        rule {\n            actor.type = resource.type;\n            actor.id = resource.id;\n        }\n    }\n}\n```\n\nThe code will be parsed equals to previous example but in this case the `DEFAULT` block is implicit. This feature is named \"implicit default\".\n\n### Named environments\n\nThe environments provide encapsulation for policies. To exclude an \"default behavior\" pattern, is possible use named environments. We can modify the example, and define two environments: `Testing` and `Production`.\n\n```minos\nresource User {\n    env Testing {\n        policy {\n            allow = [\"create\", \"read\", \"update\", \"delete\"];\n            rule {\n                actor.type = RootUser;\n            }\n            rule {\n                actor.type = resource.type;\n                actor.id = resource.id;\n            }\n        }\n    }\n    env Production {\n        policy {\n            allow = [\"create\", \"read\", \"update\", \"delete\"];\n            rule {\n                actor.type = resource.type;\n                actor.id = resource.id;\n            }\n        }\n    }\n}\n```\n\nWe can see, that in the `Testing` environment, a `RootUser` can manipulate the `Users`, but in \n`Production` only the `User` can manipulate it self.\n\nIs important to mention that when exist two or more environment, use the implicit `DEFAULT` isn't possible. Since in this case the `DEFAULT` block not exists and is necessary to indicate the environment's name in the authorization process.\n\nNow, if we are sure that only need two environments, we can rewrite the code, using the `DEFAULT` environment.\n\n```minos\nresource User {\n    env DEFAULT {\n        policy {\n            allow = [\"create\", \"read\", \"update\", \"delete\"];\n            rule {\n                actor.type = resource.type;\n                actor.id = resource.id;\n            }\n        }\n    }\n    env Testing {\n        policy {\n            allow = [\"create\", \"read\", \"update\", \"delete\"];\n\n            rule {\n                actor.type = RootUser;\n            }\n        }\n    }\n}\n```\n\nThis pattern makes it clear that it is an edge case, and allow us to remove the `Testing` environment before deployment. But, we only do it because the rules are compatible and we **want** to use the `DEFAULT` environment.\n\n### Specification\n\nWhat if, we need a different behavior based not on environments but on the resources themselves? This is possible using the *specification* feature and the *attributed resources*.\n\nIn the following example, we can two different blocks:\n\n```minos\nresource File {\n    policy {\n        allow = [\"read\"];\n        rule {\n            actor.type = User;\n        }\n    }\n    policy {\n        allow = [\"write\", \"delete\"];\n\n        rule {\n            actor.roles *= [\"admin\"];\n        }\n    }\n}\n\nresource File {\n    id = \"confidential.john.data.file.id\";\n    policy {\n        allow = [\"read\"];\n\n        rule {\n            actor.id = \"john.user.Id\";\n        }\n    }\n}\n\n```\n\nThe first block defines a resource named `File`. Based on  the rules, the `Users` can read a `File` but only an actor with role \"admin\" can write or delete a `File`.\n\nIn the second block, we find another declaration for `File`. We can see some diferencies:\n* the resource have an attribute named `id`, and for this is an *attributed resource*;\n* the resource have a policy that contains a *conflictive* rule, because the permission \"read\" is granted with two policies in the same environment.\n\nHow the authorization process works in this case? If the resource is a `File` and its id is \"confidential.john.data.file.id\", the  rules for `File` will be ignored, and will only apply the policy with the matching id. The consequent of this, is that the `File` with id \"confidential.john.data.file.id\" can't be overwrited nor deleted.\n\nIt is simple, if we want to define special rules for an specific resource, we just use the *specification*.\n\n### Use of id attribute in rules\n\nBut If we want use the rules into `DEFAULT` environment instead of ignore it, we can extend the first blocks like this:\n\n```minos\nresource File {\n    policy {\n        allow = [\"read\"];\n        rule {\n            actor.type = User;\n        }\n        /* start of adition --- */\n        rule {\n        \tresource.id = \"confidential.john.data.file.id\";\n        \tactor.id = \"john.user.Id\";\n        }\n        /* --- end of adition */\n    }\n    policy {\n        allow = [\"write\", \"delete\"];\n\n        rule {\n            actor.roles *= [\"admin\"];\n        }\n    }\n}\n```\n\nThe above code will be parsed since *the actor with id \"john.user.Id\" can read the `File` with id \"confidential.john.data.file.id\"*. We can see that the if the actor is an `User`, the new rule is really useless. This is a problem with the use of `actor.id` and/or `resource.id` in the same environment: the restrictive rules can be skipped for more relaxed rules.\n\n### Attributes comparison\n\nThe use of id attribute in the last example can be useful only in case of the actor isn't an `User`. So, we can work with the next data, using the above code:\n\n```json\n{\n    \"actor\": {\n    \t\"id\": \"john.user.Id\",\n        \"type\": \"Employee\",\n        \"groups\": [\"employees\"],\n        \"roles\": [\"admin\"]\n    },\n    \"resource\": {\n        \"id\": \"confidential.john.data.file.id\",\n        \"type\": \"File\",\n        \"owner\": \"john.user.Id\"\n    },\n    \"permissions\": [\"read\"]\n}\n```\n\nThe authorization is granted, because the rules cover this case exactly. So, what if we have one confidential file for every employee? We must be to write one rule for every file-employee couple? Not really, we can use the `actor.owner` attribute, that's be convenient supported in currently minos lang version (v0.16).\n\n```minos\nresource File {\n    policy {\n        allow = [\"read\"];\n        rule {\n            actor.type = User;\n        }\n        /* start of change --- */\n        rule {\n        \tresource.owner = actor.id;\n        }\n        /* --- end of change */\n    }\n    policy {\n        allow = [\"write\", \"delete\"];\n\n        rule {\n            actor.roles *= [\"admin\"];\n        }\n    }\n}\n```\n\nAdditionally, is possible to enhance the rule, adding the `actor.type` requirement:\n\n```minos\n/*... */\nrule {\n    actor.type = Employee;\n\tresource.owner = actor.id;\n}\n/*...*/\n```\n\nThe attribute comparison is an excellent way to avoid the common places logic duplication. Unfortunately, in currently minos lang version (v0.16) isn't many supported attributes. But we hope to improve support for this feature in future releases.\n\n### Parsing rules\n\nAt this point it is important to explain how minos parser works with blocks with the same identifier. For example, the code of [this section](#Use of id attribute in rules) can be rewrite like this:\n\n```minos\nresource File {\n    policy {\n        allow = [\"read\"];\n        rule {\n            actor.type = User;\n        }\n        rule {\n        \tresource.id = \"confidential.john.data.file.id\";\n        \tactor.id = \"john.user.Id\";\n        }\n    }\n}\n\nresource File {\n    policy {\n        allow = [\"write\", \"delete\"];\n\n        rule {\n            actor.roles *= [\"admin\"];\n        }\n    }\n}\n```\n\nHow minos parses the above text?\n\n1. Search for resources.\n2. Add `File` resource.\n3. Search for environments.\n4. Add implicit `DEFAULT` environment.\n5. Search for policies.\n6. Add policy to allow permissions `[\"read\"]` and two rules.\n7. Search for more policies.\n8. Skip searching for more environments, because the block uses  implicit `DEFAULT`.\n9. Search for more resources.\n10. Find `File` resource.\n11. Search for environments.\n12. Find implicit DEFAULT environment.\n13. Search for policies.\n14. Add policy to allow permissions `[\"write\", \"delete\"]` and one rule.\n15. Search for more policies.\n16. Skip searching for more environments, because the block uses  implicit `DEFAULT`.\n17. Search for more resources ...\n\nThis behavior is the same with resources in other files. For example, we can rewrite the above code like this:\n\n```minos\n/* in file.minos */\nresource File {\n    policy {\n        allow = [\"read\"];\n        rule {\n            actor.type = User;\n        }\n    }\n}\n```\n\n```minos\n/* in file2.minos */\nresource File {\n    policy {\n        allow = [\"read\"];        \n        rule {\n        \tresource.id = \"confidential.john.data.file.id\";\n        \tactor.id = \"john.user.Id\";\n        }\n    }\n}\n```\n\n```minos\n/* in file3.minos */\nresource File {\n    policy {\n        allow = [\"write\", \"delete\"];\n\n        rule {\n            actor.roles *= [\"admin\"];\n        }\n    }\n}\n```\n\nThe final parsing, will be exactly the same that the first example in this section.\n\n### Macros\n\nMacros behave like abbreviations. And are zero cost in runtime, because are \"expanded\" during parsing.\n\n*WARNING*: The macro syntax are only available in versions its ends with `M` character. For example: `version = 0.16M;` can use macros, but `version = 0.16;` can't. Why? Because the files with macros needs special algorithms to expand its during parsing time and we can avoid this operations if us are sure that the files not use its.\n\nFor example, the next files will are equals, after parsed: \n\n```minos\nsyntax = 0.16M;\n\n#BASIC_USER_PERMISSIONS {\n    \"read_status\",\n    \"update_status\"\n}\n\n#ADVANCED_USER_PERMISSIONS {\n    \"create\",\n    \"delete\",\n    \"sudo\"\n}\n\n#BY_SELF_AUTH {\n    actor.id = resource.id;\n    actor.type = resource.type;\n    actor.status = Active;\n}\n\n#BY_ADMIN_AUTH {\n    actor.roles *= \"admin\";\n    actor.status = Active;\n}\n\nresource User {\n    env STD {\n        policy {\n            allow = [\n                #[BASIC_USER_PERMISSIONS]\n            ];\n\n            rule {\n                #[BY_SELF_AUTH]\n            }\n        }\n\n        policy {\n            allow = [\n                #[BASIC_USER_PERMISSIONS],\n                #[ADVANCED_USER_PERMISSIONS]\n            ];\n\n            rule {\n                #[BY_ADMIN_AUTH]\n            }\n        }\n    }\n\n    env ROOT {\n        policy {\n            allow = [\n                #[BASIC_USER_PERMISSIONS],\n                #[ADVANCED_USER_PERMISSIONS]\n            ];\n\n            rule {\n                actor.type = SuperUser;\n            }\n        }\n    }\n}\n```\n```minos\nsyntax = 0.16;\n\nresource User {\n    env STD {\n        policy {\n            allow = [\n                \"read_status\",\n    \t\t\t\"update_status\"\n            ];\n\n            rule {\n                actor.id = resource.id;\n    \t\t\tactor.type = resource.type;\n    \t\t\tactor.status = Active;\n            }\n        }\n\n        policy {\n            allow = [\n                \"read_status\",\n    \t\t\t\"update_status\",\n                \"create\",\n                \"delete\",\n                \"sudo\"\n            ];\n\n            rule {\n                actor.roles *= \"admin\";\n    \t\t\tactor.status = Active;\n            }\n        }\n    }\n\n    env ROOT {\n        policy {\n            allow = [\n                \"read_status\",\n    \t\t\t\"update_status\",\n                \"create\",\n                \"delete\",\n                \"sudo\"\n            ];\n\n            rule {\n                actor.type = SuperUser;\n            }\n        }\n    }\n}\n```\n\nInterestingly, the first file is larger than the file not using macros. So why use macros, anyway? For the same reasons we divide our code into small functions: code reuse and ease of functionality extension.\n\n#### Rules for macros\n\nCurrently only be two macro types: macros with permissions, and macros with requirements. It's have specific rules to write its.\n\n##### Macro definition\n\n1. The last permission within a macro cannot end in a comma (`,`).\n   ```minos\n   #BAD_MACRO_DEFINITION {\n       \"create\",\n       \"delete\",\n       \"sudo\", /* ❌ */\n   }\n   \n   #CORRECT_MACRO_DEFINITION {\n       \"create\",\n       \"delete\",\n       \"sudo\" /* ✅ */\n   }\n   ```\n\n   \n\n2. Every requirement within a macro must end with a colon (`;`).\n   ```minos\n   #BAD_MACRO_DEFINITION {\n       actor.status = Active /* ❌ */\n   }\n   \n   #CORRECT_MACRO_DEFINITION { /* ✅ */\n       actor.id = resource.id;\n       actor.type = resource.type;\n       actor.status = Active;\n   }\n   ```\n\n3. No macro can mix permissions and requirements nor add another minos structure inside it.\n   ```minos\n   #INVALID_MACRO {\n   \tactor.status = active; /* \u003c----- requirement ❌ */\n   \t\"create\"/* \u003c--- permisssion ❌ */\n   }\n   \n   #INVALID_MACRO_2 {\n   \tresource User { \u003c--- resource block ❌ */\n   \t\tpolicy {\n   \t\t\t/*...*/\n   \t\t}\n   \t}\n   }\n   \n   #INVALID_MACRO_3 {\n   \t#[INVALID_MACRO] /* \u003c--- macro call ❌ */\n   }\n   ```\n\n##### Macro call\n\n1. The macros only be called inside allow blocks or inside rule blocks.\n   ```minos\n   resource User {\n   \t#[DEFAULT_ENV_MACRO] /* \u003c---- bad macro calling ❌ */\n   \tenv TEST {\n   \t\tpolicy {\n   \t\t\tallow = [\"read\"];\n   \t\t\trule {\n   \t\t\t\tactor.type = SuperUser;\n   \t\t\t}\n   \t\t}\n   \t\t\n   \t\t#[POLICY_MACRO] /* \u003c---- bad macro calling ❌ */\n   \t}\n   }\n   ```\n\n2. Never use semicolon after macro call; but inside allow blocks is possible to use comma.\n   ```minos\n   policy {\n       allow = [\n           #[DEFAULT_PERMISSIONS]; /* \u003c---- Don't use semicolon! ❌ */\n       ];\n       \n       rule {\n       \t#[FOO] /* ✅ */\n       \t#[BAZZ] /* ✅ */\n       }\n   }\n   \n   policy {\n   \tallow = [\n   \t\t#[DEFAULT_PERMISSIONS], /* \u003c---- Valid macro call ✅ */\n   \t\t\"lock_data\",\n   \t\t#[ADVANCED_PERMISSIONS], /* \u003c---- Valid macro call ✅ */\n   \t]\n   }\n   \n   ```\n\n3. Never call macros with incompatible content.\n   ```minos\n   #[ALLOW_MACRO] {\n   \t\"read\",\n   \t\"update\",\n   }\n   \n   #[BY_SELF_AUTH] {\n   \tactor.type = resource.type;\n   \tactor.id = resource.id;\n   }\n   \n   resource File {\n   \tpolicy {\n   \t\tallow = [\n           #[BY_SELF_AUTH] /*\u003c--- invalid token found: \"Requirement\", expected: \"String\" ❌ */\n   \t\t]\n   \t\t\n   \t\trule {\n   \t\t    #[ALLOW_MACRO] /*\u003c--- invalid token found: \"String\", expected: \"Requirement\" ❌ */\n   \t\t}\n   \t}\n   }\n   ```\n\n   \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonhteper%2Fminos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonhteper%2Fminos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonhteper%2Fminos/lists"}