{ "info": { "author": "Kevin L. Mitchell", "author_email": "klmitch@mit.edu", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Topic :: Security", "Topic :: Software Development :: Interpreters", "Topic :: Software Development :: Libraries" ], "description": "========\nPolicies\n========\n\nA package for interpretation and enforcement of access control\npolicies.\n\nIntroduction\n============\n\nIt is often necessary to separate code that performs an action from\nthe code that performs the access check. One reason for this is to\naccommodate different users with different access control\nrequirements. For instance, one user may be operating a system\ninternally, where all authenticated users should be able to perform\nall actions, whereas another user may need to lock down specific\noperations so they can only be executed by administrators.\n\nThe ``policies`` package is designed to accommodate these needs.\nAccess control policies can be expressed as strings, using a subset of\nPython; then, these policies can be loaded into a ``policies.Policy``\nobject. When an access determination needs to be made, a call to the\n``policies.Policy.evaluate()`` method will evaluate a named policy\nrule and return an ``Authorization`` object, which evaluates as either\n``True`` or ``False``.\n\nThe policy strings may be loaded from any source. They are simply\nstrings, written in a subset of the Python language, and allow much of\nthe expressive power of Python. The policy language has syntax for\nmaking function calls, including functions defined as entrypoints_;\nthis allows any desired access control policy to be implemented for\nany application using ``policies``.\n\n``policies`` for Developers\n===========================\n\nThe ``policies`` package is easy for developers to use; simply\ninstantiate a ``policies.Policy`` object with an optional entrypoint\ngroup and dictionary of built-in functions (defaults to select Python\nbuiltins, available as ``policies.Policy.builtins``), then add rules\nto the object. This can be done by assigning the rule text using the\ndictionary item setting syntax, like so::\n\n policy['rule_name'] = \"user.is_admin()\"\n\nAlternatively, the rule text can be passed to ``policies.Rule`` and\nset using the ``policies.Policy.set_rule()`` method, like so::\n\n rule = policies.Rule(\"rule_name\", \"user.is_admin()\")\n policy.set_rule(rule)\n\nThese two different methods allow for the rules to be loaded from any\ndesired source, such as a file or a database.\n\nEvaluation of a policy rule is as simple as calling the\n``policies.Policy.evaluate()`` function::\n\n authz = policy.evaluate(\"rule_name\", {'user': user})\n\nThe ``authz`` value can then be used to determine if the operation is\nallowed by the policy::\n\n if authz:\n # Perform the operation here\n\tpass\n else:\n # Tell the user he's unprivileged\n\tpass\n\nNote the dictionary passed as the second argument to\n``policy.evaluate()`` above; this allows variables to be passed in to\npolicy rules.\n\nAuthorization Attributes\n------------------------\n\nThe return value from ``policy.evaluate()`` is not a simple ``True``\nor ``False`` value; it is an instance of ``policies.Authorization``.\nThe reason for this is that the policy language allows for setting\n*authorization attributes*. To explain what this is about, let's\nassume that the operation we're writing a policy for is a user update\noperation. Obviously, we want the user to be able to update certain\nparts of their own record, but others--say, payment status--should\nonly be available to administrators. We can write this all in one\nrule in the policy language::\n\n user.is_admin() or user == target {{ payment=user.is_admin() }}\n\nWhen we evaluate this rule, the ``policies.Authorization`` object\nreturned will test ``True`` or ``False`` depending on the result of\nevaluating the first part of the rule, ``user.is_admin() or user ==\ntarget``. However, the ``authz`` object will now also have an\nattribute named ``payment``; this attribute will have the value\nobtained by computing ``user.is_admin()``.\n\nAuthorization attributes default to ``None`` if the policy language\ndoesn't set them. This default can be overridden by passing a\ndictionary of attribute defaults to the ``policies.Rule`` instance\nwhen it is created, or by declaring the rule using\n``policies.Policy.declare()``.\n\nNote that authorization attribute names CANNOT begin with an\nunderscore (\"_\").\n\nDeclaring Policy Rules\n----------------------\n\nSetting policy rules has been described above, but what about setting\nup defaults for the policy rules? This can be done using the\n``policies.Policy.declare()`` method::\n\n policy.declare(\"rule_name\", text=\"user.is_admin()\")\n\nThis can also be used to set defaults for authorization attributes, by\npassing a dictionary of those defaults as the ``attrs`` keyword\nargument.\n\nThe ``policy.declare()`` method also allows associating documentation\ntext with the rule and the authorization attributes, using the ``doc``\nand ``attr_docs`` keyword arguments; calling ``policy.declare()`` will\nresult in the creation of ``policies.RuleDoc`` objects to contain the\npassed-in documentation. These objects can be retrieved using the\n``policies.Policy.get_doc()`` and ``policies.Policy.get_docs()``\nmethods, and could be used to generate sample policy configuration\nfiles.\n\nVariable Resolution in Policy Rules\n-----------------------------------\n\nWhen a variable is encountered in a policy rule, it must be resolved\nto an actual value. The first place searched when resolving variables\nis the dictionary of variables that was passed to\n``policies.Policy.evaluate()``; values passed here override any other\nsource.\n\nIf the variable cannot be found in the dictionary passed to\n``policies.Policy.evaluate()``, then a dictionary of builtins is\nsearched; by default, these builtins are the ones in\n``policies.Policy.builtins``, and represent a subset of the Python\nbuiltins. These builtins can be overridden by passing a dictionary as\nthe ``builtins`` parameter of the ``policies.Policy`` constructor.\nNote that one special builtin exists which is not listed in\n``policies.Policy.builtins``, and which will be added to the builtins\npassed to the ``policies.Policy`` constructor: the ``rule()`` builtin\nallows for one rule to call another. It can be overridden, if\ndesired, by passing an alternate value for the \"rule\" key in the\n``builtins`` dictionary.\n\nIf the variable cannot be resolved from either of the sources above,\nit is next searched for using entrypoints_. The entrypoint group to\nsearch can be specified as the ``group`` argument to the\n``policies.Policy`` constructor. There is no default for the\nentrypoint group, so if left unset, no entrypoints will be resolved.\nAny entrypoints found will be cached for the lifetime of the\n``policies.Policy`` object. It is recommended that you set ``group``\nto be the name of your application, followed by a period, followed by\nthe name \"policies\"; e.g., if your application was called \"spam\", you\nwould use \"spam.policies\". Using an entrypoint group allows your\nusers to set up arbitrary functions for use in evaluating access\ncontrol policies, and thus allows them ultimate control over access.\n\nIf a variable cannot be resolved using any of the above sources, its\nvalue will be ``None``. This is as opposed to the standard Python\nbehavior of raising a ``NameError``. The ``policies`` package is\ndesigned to be as tolerant of user errors as possible.\n\n``policies`` for Users\n======================\n\nPolicy rules are written in a subset of the Python expression\nlanguage. The singleton values ``True``, ``False``, and ``None`` are\nrecognized, as are single- and double-quoted strings, integers, and\nfloats. The set literal syntax is also recognized, i.e., ``{1, 2,\n3}`` represents the value ``frozenset([1, 2, 3])``. Tuple literals,\nlist literals, dictionary literals, and comprehensions are not\nsupported, although the ``tuple()``, ``list()``, and ``dict()``\nbuiltins are available, as are ``set()`` and ``frozenset()``.\n\nIn addition to the literal values mentioned above, the policy language\nalso supports attribute reference, subscription (``x[index]``), and\nfunction calls. Note that \"slicing\" (``x[index:index]``) is not\nsupported, however. Finally, all arithmetic, logical, and comparison\noperators are supported, as is the Python \"trinary\" syntax (``a if b\nelse c``).\n\nAs an example, let's suppose that a particular rule is controlling\nupdate access to a user record. The ``user`` variable will be the\nuser requesting the operation, and ``target`` will be the user record\nthe operation is to act upon. The policy we want to implement is to\nallow a given user to update only their own record, but we want\nadministrators to be able to update any user record. We'll assume\nthat ``user`` has a boolean attribute named ``admin`` that is ``True``\nif the user is an administrator. Under these assumptions, the policy\nrule could be written as::\n\n user == target or user.admin\n\nIt is also possible to call methods on an object. Lets say that,\ninstead of a boolean attribute named ``admin`` that specifies whether\na user is an admin, we instead base administrator status on the\nmembers of a group. We assume that the ``user`` object has an\n``in_group()`` method. We could then write the rule as::\n\n user == target or user.in_group(\"administrators\")\n\nFinally, it is also possible to call functions. If the\n``policies.Policy()`` class was instantiated with an entrypoint group,\nyou can install a package with a function defined in that entrypoint\ngroup (see entrypoints_), which will then be available to policy\nrules. This allows ultimate control over access control. Note that\nonly positional arguments can be passed to functions; keyword\narguments are not available.\n\nNote that operator short-circuiting is implemented; that is, in an\nexpression like ``user == target or user.admin``, if the ``user ==\ntarget`` clause evaluates to ``True``, then ``user.admin`` will not be\nevaluated. This applies for the logical operators (``and`` and\n``or``), as well as in the \"trinary\" syntax. Constant folding is also\nimplemented, so rule text like ``5 + 23 > user.spam`` will only\ncompute the operation ``5 + 23`` once, during rule parsing.\n\nAuthorization Attributes\n------------------------\n\nLet us take the example from above and add one more requirement.\nLet's say that one of the things the user update operation can update\nis the current payment status on a user. Obviously, that is something\nthat we don't want a user to be able to update; only administrators\nshould be able to update the payment status. A developer can allow\nthis particular subset of functionality to be controlled separately\nusing an *authorization attribute*. For the example above, let's\nassume that the ``payment`` authorization attribute can control access\nto the update of the payment status. Now we can rewrite the policy\nrule as::\n\n user == target or user.admin {{ payment=user.admin }}\n\nMore than one authorization attribute can be computed by separating\nthem with commas. Let's assume that we have an authorization\nattribute ``name`` that allows updating the user's name, and we want\nto allow only the user to alter the name; we could write the rule as::\n\n user == target or user.admin {{ payment=user.admin,\n name=user==target }}\n\nEvaluating Other Rules\n----------------------\n\nEach rule has an associated name. It is possible to define an\narbitrary rule, and then evaluate it from another rule. Taking our\nexample from above, let's assume that an admin must not only be in the\n\"administrators\" group, but must also have ``admin`` set to ``True``\non their user record. (This could be the case if your policy requires\nadministrators to explicitly turn on their administrative privileges.)\nWe could create an \"is_admin\" rule that looks like this::\n\n user.in_group(\"administrators\") and user.admin\n\nWe could then write the rule controlling access to the user update\noperation as::\n\n user == target or rule(\"is_admin\")\n\nNote that any authorization attributes on the \"is_admin\" rule will be\nignored; to set an authorization attribute on the user update\noperation, they have to be explicitly declared::\n\n user == target or rule(\"is_admin\") {{ payment=rule(\"is_admin\"),\n name=user==target }}\n\nAvailable Builtins\n------------------\n\nThe following Python builtins are available:\n\n* ``abs()``\n* ``basestring()``\n* ``bin()``\n* ``bool()``\n* ``bytes()``\n* ``callable()``\n* ``chr()``\n* ``complex()``\n* ``dict()``\n* ``divmod()``\n* ``enumerate()``\n* ``float()``\n* ``format()``\n* ``frozenset()``\n* ``getattr()``\n* ``hasattr()``\n* ``hash()``\n* ``hex()``\n* ``id()``\n* ``int()``\n* ``isinstance()``\n* ``issubclass()``\n* ``iter()``\n* ``len()``\n* ``list()``\n* ``long()``\n* ``max()``\n* ``min()``\n* ``next()``\n* ``object()``\n* ``oct()``\n* ``ord()``\n* ``pow()``\n* ``range()``\n* ``repr()``\n* ``reversed()``\n* ``round()``\n* ``set()``\n* ``sorted()``\n* ``str()``\n* ``sum()``\n* ``tuple()``\n* ``type()``\n* ``unichr()``\n* ``unicode()``\n* ``xrange()``\n* ``zip()``\n\nAdvanced Function Calls\n=======================\n\nUnder normal circumstances, functions are called with only the\narguments passed in the rule text, and their return values are then\npushed onto the stack in place of those function arguments. However,\ncertain functions--such as the ``rule()`` function--need access to the\ncontext object (``policies.PolicyContext``). In the case of\n``rule()``, this allows it to keep a cache of rules that have been\nevaluated for the duration of the ``policies.Policy.evaluate()`` call,\nas well as looking up the rule to be evaluated.\n\nTo facilitate functions like ``rule()``, use the\n``@policies.want_context`` decorator. The ``policies.PolicyContext``\nobject will be passed as the first argument of the function, with\nremaining arguments passed after that. Note that all the arguments\nwill be popped off the stack, but the function's return value will\n*not* be pushed on the stack; a function decorated with\n``@policies.want_context`` must perform its own manipulation of the\nstack. For a function like this to push a return value on the stack,\nand assuming that the context argument is ``ctxt``, the relevant code\nwould be::\n\n ctxt.stack.append(\"value\")\n\nIn instances where you're using functions decorated with\n``@policies.want_context``, it may be necessary to perform some\napplication-specific initialization on the ``policies.PolicyContext``\nclass, such as initializing a context attribute. This may be done by\nchanging the ``policies.Policy.context_class`` setting. Ideally, this\nwould be on an instance of ``policies.Policy``, rather than altering\nthe class itself, i.e.::\n\n policy = policies.Policy(...)\n policy.context_class = MyPolicyContext\n\nBe very careful using ``@policies.want_context``. Failing to push a\nfunction return value onto the evaluation context stack could corrupt\nthe stack and cause a crash during rule evaluation.\n\n``policies`` Internals\n======================\n\nThis section intended for developers interested in developing the\n``policies`` package itself.\n\nRule Parsing\n------------\n\nThe policy rules work by parsing the rule text, using a parser built\nwith ``pyparsing``, into a sequence of *instructions*. The\ninstructions are stored in postfix order; that is, an expression like\n\"1+2\" would become a sequence of instructions that would first push\nthe value \"1\" onto a stack; then push the value \"2\" onto the stack;\nthen pop the top two values from the stack, add them, and push the\nresult onto the stack. The instructions are all defined in\n``instructions.py``, and the parser is defined in ``parser.py``. The\n``policies.Policy.evaluate()`` method simply constructs an evaluation\ncontext (a ``policies.policy.PolicyContext`` object), then executes\nthe instructions. Included in the instructions are instructions that\ncreate a ``policy.Authorization`` object and set up the authorization\nattributes (if any were defined); this authorization object is then\nreturned.\n\nCaching\n-------\n\nCaching is used wherever possible to achieve the highest possible\nefficiency. Policy rules are compiled the first time they are\nevaluated, and the instructions are then cached. The results of an\nentrypoint look-up are also cached, as are the results of calling\nrules--in the example above::\n\n user == target or rule(\"is_admin\") {{ payment=rule(\"is_admin\"),\n name=user==target }}\n\nThe \"is_admin\" rule will only be evaluated one time. This cache is\nstored in the ``policies.PolicyContext`` object, in the ``rule_cache``\nattribute.\n\n.. _entrypoints: http://pythonhosted.org/distribute/pkg_resources.html#entry-points", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/klmitch/policies", "keywords": null, "license": "GNU General Public License v3 or later (GPLv3+)", "maintainer": null, "maintainer_email": null, "name": "policies", "package_url": "https://pypi.org/project/policies/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/policies/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/klmitch/policies" }, "release_url": "https://pypi.org/project/policies/0.4.2/", "requires_dist": null, "requires_python": null, "summary": "An access policy language evaluator.", "version": "0.4.2" }, "last_serial": 1819565, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "2526ba2b9cbd72b2516921f21fce7d70", "sha256": "e8f18e6438110224db1efe4c55ff79298ed557eca3f1b03a46e1a06be6097c1c" }, "downloads": -1, "filename": "policies-0.1.tar.gz", "has_sig": false, "md5_digest": "2526ba2b9cbd72b2516921f21fce7d70", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44930, "upload_time": "2013-10-13T17:29:21", "url": "https://files.pythonhosted.org/packages/65/aa/9b68b86598c51b2324a44040c848b98565772b74fd949ea77e65e46a37e6/policies-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "9934fd52bb76f4f8b10328bd2ff28a6b", "sha256": "a820c04e43bd10edd0201bf5a6e51c2134efe005ac2f1353858bdaca8da7d194" }, "downloads": -1, "filename": "policies-0.2.tar.gz", "has_sig": false, "md5_digest": "9934fd52bb76f4f8b10328bd2ff28a6b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45056, "upload_time": "2013-10-13T18:41:46", "url": "https://files.pythonhosted.org/packages/f5/5e/e0f0718615487d194113c44b53a91a3b3626c32a1299d04e731700892565/policies-0.2.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "2006da3887024e4c50b934882783f00c", "sha256": "6cb3f77d022c5b2aa58b23baf5ec6e8b4819c34e7b13ae0eea5e8e2b35651cae" }, "downloads": -1, "filename": "policies-0.2.1.tar.gz", "has_sig": false, "md5_digest": "2006da3887024e4c50b934882783f00c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44962, "upload_time": "2013-10-13T18:42:54", "url": "https://files.pythonhosted.org/packages/a1/30/f0e3ab11e765402d86e603b5941b167cd82cbe03af99fd5ae9375ed00f3a/policies-0.2.1.tar.gz" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "2a4f3e3bbccc2ad6f931c8d0703bb1b4", "sha256": "d0afd755e8d0b88d8de78464cf2b273a54cc66d9b64a0aa2ffe38b777db631b0" }, "downloads": -1, "filename": "policies-0.2.2.tar.gz", "has_sig": false, "md5_digest": "2a4f3e3bbccc2ad6f931c8d0703bb1b4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45534, "upload_time": "2013-10-13T19:24:06", "url": "https://files.pythonhosted.org/packages/78/ad/1a5c6eb9743265db4a49ae92e92fce4d4d4d100f8b06e33295be218ed71a/policies-0.2.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "734f1d40d9ccc8a0de2f57f2b57bb178", "sha256": "bb5268aa6742c025a1060434502f3b4821500065d4869ca341e9ae284edc65b7" }, "downloads": -1, "filename": "policies-0.3.tar.gz", "has_sig": false, "md5_digest": "734f1d40d9ccc8a0de2f57f2b57bb178", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45886, "upload_time": "2013-10-13T19:53:38", "url": "https://files.pythonhosted.org/packages/51/03/c366bb97e34e879384ee8c42e0eb3127f77c755aa56b96623779aa32ec76/policies-0.3.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "4c1b22f32858c08c6125941329ebd9e2", "sha256": "566c49b85e307b732af760bbe4620ce5868981e443f37f89eccd84c56f8c2080" }, "downloads": -1, "filename": "policies-0.3.1.tar.gz", "has_sig": false, "md5_digest": "4c1b22f32858c08c6125941329ebd9e2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46124, "upload_time": "2013-10-17T00:56:47", "url": "https://files.pythonhosted.org/packages/d3/0b/31c3d25eb13222a323e3f12f12c682406133bb805f23359a1c5284468904/policies-0.3.1.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "e55c29911bd4b53ef427dad55d0610a1", "sha256": "b19f8092c21d998982733ce16feb470d86262bdea9dc8458d44f91fa23475a4a" }, "downloads": -1, "filename": "policies-0.4.tar.gz", "has_sig": false, "md5_digest": "e55c29911bd4b53ef427dad55d0610a1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50219, "upload_time": "2013-10-23T00:18:56", "url": "https://files.pythonhosted.org/packages/36/3c/5e018c188873c81e00750be37bf7c46db2aadf84bb9fb709a470755c9a39/policies-0.4.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "f43520c9077b519175b5b36c09215ffb", "sha256": "6a65f05c698101093fc42196183dbd78f36fb58e11ead1997ea38e446dbd2198" }, "downloads": -1, "filename": "policies-0.4.1.tar.gz", "has_sig": false, "md5_digest": "f43520c9077b519175b5b36c09215ffb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50164, "upload_time": "2015-11-13T18:45:51", "url": "https://files.pythonhosted.org/packages/51/7d/58018352f93590646ffc551b0126900d13c560e97931b97ea9c41ffb4962/policies-0.4.1.tar.gz" } ], "0.4.2": [ { "comment_text": "", "digests": { "md5": "10eac72d493ea6abfc7d67b51d75be8b", "sha256": "5dd797aae74d3fb6f42c1e9e283f1730ae92beb071081a116b4f6ea449a593de" }, "downloads": -1, "filename": "policies-0.4.2.tar.gz", "has_sig": false, "md5_digest": "10eac72d493ea6abfc7d67b51d75be8b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50541, "upload_time": "2015-11-16T23:32:49", "url": "https://files.pythonhosted.org/packages/5c/0c/1c5024e8814fb1ba3a29ec4304ca70245e152982333dbe436fa11a090152/policies-0.4.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "10eac72d493ea6abfc7d67b51d75be8b", "sha256": "5dd797aae74d3fb6f42c1e9e283f1730ae92beb071081a116b4f6ea449a593de" }, "downloads": -1, "filename": "policies-0.4.2.tar.gz", "has_sig": false, "md5_digest": "10eac72d493ea6abfc7d67b51d75be8b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50541, "upload_time": "2015-11-16T23:32:49", "url": "https://files.pythonhosted.org/packages/5c/0c/1c5024e8814fb1ba3a29ec4304ca70245e152982333dbe436fa11a090152/policies-0.4.2.tar.gz" } ] }