{ "info": { "author": "Counsyl Platform Team", "author_email": "platform@counsyl.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5" ], "description": "# Baya\n\nBaya is a library for using nested LDAP Groups for authorization. It lets you\nput URLs, methods, CBVs, and Admin views behind access control that uses nested\nLDAP groupOfNames.\n\nThis also includes an example Django site in the `testsite` directory.\n\nThe Baya Weaver is a species of bird that weaves complex nests.\n\n\n\nImage courtesy [J.M.Garg](http://commons.wikimedia.org/wiki/User:J.M.Garg)\non [Wikipedia](http://commons.wikimedia.org/wiki/File:Baya_Weaver_%28Ploceus_philippinus%29-_Male_W_IMG_0732.jpg).\nThis file is licensed under the [Creative Commons Attribution 3.0 Unported](http://creativecommons.org/licenses/by/3.0/deed.en)\nlicense.\n\n# Installation\n\n```sh\npip install baya\n```\n\nIf you wish to use `mockldap` during your development process with Baya, you can install it as part of the optional development dependencies by using:\n\n```sh\npip install baya[development]\n```\n\n```python\nINSTALLED_APPS = (\n ...\n 'baya',\n ...\n)\n\n# SessionMiddleware must be active\n\nAUTHENTICATION_BACKENDS = (\n 'baya.backend.NestedLDAPGroupsBackend',\n # If you're already using django_auth_ldap.backend.LDAPBackend, delete it\n 'django.contrib.auth.backends.ModelBackend',\n)\n```\n\n\n# LDAP Prerequisites\n\nNote that you must be using `GroupOfNames` or `GroupOfUniqueNames` and not\n`PosixGroup`. You should also emable the `memberOf` overlay (but with some\neffort we can eliminate that requirement).\n\n# Configuration\n\nYou need to set up your ldap settings. If you already have a working version\nof `django_auth_ldap` then you're nearly finished. Make sure the following\nsettings are configured:\n\n```python\nimport ldap\nfrom django_auth_ldap.config import LDAPSearch\nfrom django_auth_ldap.config import NestedGroupOfNamesType\n\nAUTH_LDAP_GROUP_TYPE = NestedGroupOfNamesType()\nAUTH_LDAP_FIND_GROUP_PERMS = True\nAUTH_LDAP_MIRROR_GROUPS = True\n\nAUTH_LDAP_BIND_DN = 'cn=auth,dc=example,dc=com'\nAUTH_LDAP_BIND_PASSWORD = 'password'\nAUTH_LDAP_SERVER_URI = 'ldaps://ldap'\n\nAUTH_LDAP_USER_SEARCH = LDAPSearch(\n 'ou=People,dc=example,dc=com',\n ldap.SCOPE_SUBTREE,\n '(uid=%(user)s)')\n\nAUTH_LDAP_GROUP_SEARCH = LDAPSearch(\n 'ou=Access,dc=example,dc=com',\n ldap.SCOPE_SUBTREE,\n \"(objectClass=groupOfNames)\"\n)\n\n# Change this to True when testing locally to disable permissions checking\nBAYA_ALLOW_ALL = False\n\n# If you have a custom, internal-only login url, you can set this:\n# (If you don't set this, baya defaults to LOGIN_URL)\nBAYA_LOGIN_URL = \"/internal/login/url\"\n```\n\nOf course, change the values to match your actual setup.\n\n## LDAP client configuration\n\nBy default, Baya uses `ldap.initialize` to make the LDAP connection. To use the\n`ReconnectLDAPObject` provided by the `ldap` package, configure this setting:\n\n```\nBAYA_USE_RECONNECTING_CLIENT = True\n```\n\n## Admin configuration\n\nThe django admin requires that users logging in have the `is_staff` flag set.\nYou should add this config to your settings if you use the django admin.\nSee also [admin](#admin).\n\n```python\nAUTH_LDAP_USER_FLAGS_BY_GROUP = {\n 'is_staff': ['cn=myapp_admin,ou=Access,dc=example,dc=com'],\n}\n```\n\n## Testing access permissions\n\nYou will not always have a connection to your production LDAP server, so Baya\nsupports a couple ways to develop locally and test your views.\n\n### Disable all permissions checking\n\nBe sure to never deploy this setting to production!\n\nThe easiest way to test your app locally is to just disable Baya entirely.\nYou can do this by enabling this setting:\n\n```python\n# settings.py\nBAYA_ALLOW_ALL = True\n```\n\nThis will allow all requests to your protected views and is useful if you're\njust testing that your view works, but don't currently care about the\naccess restrictions.\n\nOne drawback to this is that you cannot test admin views, due to the way\ndjango admin interacts with django-auth-ldap. It just has to have an LDAP\ndirectory to read from. If your views are protected admin views, then go to\nthe next section \"Use python-mock-ldap\".\n\n### Use python-mock-ldap\n\nIf you want to test a few views you can use\n[mockldap](http://pythonhosted.org//mockldap/). Place the following lines in\nyour urls.py file so it runs on django startup.\n\n```python\n# urls.py\n\npatterns = (...)\n\nfrom baya.mock_ldap_helpers import mock_ldap_setup\n\nmock_ldap_instance = mock_ldap_setup(\n extra_users=[\n ('my_user', 'group_1'),\n ('my_user', 'group_b'),\n ('other_user', 'group_a'),\n ],\n group_lineage=[\n ('group_a', 'group_b'), # group_b is a child of group_a\n ]\n)\nmock_ldap_instance.start()\n\n# And you must update the ldap bind password to use the fake one\nfrom django.conf import settings\nsettings.AUTH_LDAP_BIND_PASSWORD = 'password'\n```\n\nKeep in mind that if you are manually setting groups in your test fixtures\ndjango-auth-ldap will overwrite all of that user's groups with the groups in\nLDAP. If your tests suddenly break due to group permissions problems, that's\na likely cause. For this reason I recommend you create new test users for\neach app.\n\nFor a more complete example, see [baya.tests.directory](baya/tests/directory.py).\n\n# Usage\n\n## Syntax\n\n### baya.permissions.requires([groups, get, post])\n\n* `groups`: A `baya.membership.BaseNode` child which all `GET` and `POST`\n requests must pass.\n* `get`: A `baya.membership.BaseNode` child which all `GET` requests must pass.\n AND-ed with the `groups` parameter.\n* `post`: A `baya.membership.BaseNode` child which all `POST` requests must\n pass. AND-ed with the `groups` parameter.\n\nNote that if you specify `groups` and `get` or `post`, then the roles are `&`-ed\ntogether. This lets you specify roles common to both `GET` and `POST` requests,\nas well as further restrict each method accordingly.\n\n`requires` returns a function which takes your view or urlpattern as its only\nargument.\n\n```python\nfrom baya.permissions import requires\nfrom baya.membership import RolesNode as g\n\nadmin = g('admin')\nbilling_ro = g('billing_ro')\ncustomer_service = g('customer_service')\n\n# Only an admin may access this view\nrequires(admin)(view_or_url)\n\n# Anyone with 'billing_ro' permissions may access this view, but only an admin\n# may post. These two declarations result in the same behavior:\nrequires(billing_ro, post=admin)(view_or_url)\nrequires(get=billing_ro, post=(billing_ro & admin))\n\n# Customer service or anyone with billing_ro role may access the view, but only\n# an admin or customer service may post\nrequires(get=(customer_service | billing_ro), post=(admin | customer_service))(view_or_url)\n```\n\n### DENY_ALL\n\nFor convenience, there's a `DENY_ALL` permissions node which you can use to\ncompletely disable access to a view using a given class of HTTP verbs.\n\n```python\nfrom baya.permissions import requires\nfrom baya.permissions import DENY_ALL\nfrom baya.membership import RolesNode as g\n\n@requires(get=g('billing'), post=DENY_ALL)\ndef my_view(request):\n ...\n```\n\n## urls.py\n\nYou can protect URLs individually or an entire import:\n\nDecorating the URLs is the preferred usage, since decorating the view methods\nthemselves makes you hunt around for the permissions.\n\n```python\nfrom django.conf.urls import url\nfrom django.views.generic import ListView\n\nfrom baya import requires\nfrom baya import RolesNode as g\n\nfrom .models import Blag\n\n\nurlpatterns = [\n # Protect a single view\n url(r'^$', requires(g('group1'), post=g('group2'))(ListView.as_view(model=Blag))),\n # Protect an entire URL module include\n url(r'^billing/', requires(get=g('billing_ro'), post=g('billing'))(include('my_app.billing.urls'))),\n]\n```\n\n**Note** Typing the same `g('my_group')` over and over is tedious, verbose,\nand prone to typos. A better pattern is to define the groups you'll be using\nas constants at the module level:\n\n```python\nfrom django.conf.urls import url\nfrom django.views.generic import ListView\n\nfrom baya import requires\nfrom baya import RolesNode as g\n\nfrom .models import Blag\nfrom .models import Entry\n\nGROUP1 = g('group1')\nGROUP2 = g('group2')\nBILLING = g('billing')\nBILLING_RO = g('billing_ro')\n\nSUPER_GROUP = GROUP1 & GROUP2\n\nurlpatterns = [\n # Protect a single view\n url(r'^$', requires(GROUP1, post=GROUP2)(ListView.as_view(model=Blag))),\n url(r'^super/$', requires(SUPER_GROUP)(ListView.as_view(model=Entry))),\n # Protect an entire URL module include\n url(r'^billing/', requires(get=BILLING_RO, post=BILLING)(include('my_app.billing.urls'))),\n]\n```\n\n\n## views.py\n\nDecorate regular method-based views. Avoid this if possible, preferring url\ndecoration.\n\n```python\nfrom django.http import HttpResponse\n\nfrom baya import requires\nfrom baya import RolesNode as g\n\n\n@requires(g('A'))\ndef my_simple_view(request):\n return HttpResponse(\"my_simple_view response\")\n\n@requires(g('Aaa'), get=g('A_RO') | g('B_RO'), post=g('A') | g('B'))\ndef my_view(request):\n return HttpResponse(\"my_view response\")\n```\n\n## admin\n\nThe admin site takes a little more work. Rather than use\n`django.contrib.admin.site` you will instead have to instantiate\n`baya.admin.sites.NestedGroupsAdminSite`. You will also have to use the\n`baya.admin.BayaModelAdmin` in your `ModelAdmin` classes.\n\nYou have a couple more options with django admin option classes than you\nnormally do with regular views. You can specify different permissions for\nthe django admin's create, read, update, and delete views.\n\nNote that you can also decorate the `ModelAdmins` individually or wrap them\nin `requires` at `site.register` time if you want a given permission to apply\nto every request, and not just a particular CRUD verb.\n\n```python\nfrom django.contrib import admin\nfrom django.conf.urls import url\nfrom django.shortcuts import render\n\nfrom baya import requires\nfrom baya import RolesNode as g\nfrom baya.admin import BayaModelAdmin\nfrom baya.admin.sites import NestedGroupsAdminSite\nfrom baya.permissions import DENY_ALL\n\nfrom testsite.example.models import Blag\nfrom testsite.example.models import Entry\n\n\n@requires(get=g('Aaa'), post=g('A'))\nclass BlagOptions(BayaModelAdmin):\n fields = list_display = ['name']\n\n def get_urls(self):\n urls = super(BlagOptions, self).get_urls()\n\n # This inner url ends up protected like this:\n # requires(get=\"Aaa\", post=\"a\")(requires(\"B\")(self.inner))\n urls += [\n url(r'inner_admin_view',\n requires(g('B'))(self.inner),\n name='inner')\n ]\n return urls\n\n def inner(self, request):\n return render(...)\n\n\nclass EntryOptions(BayaModelAdmin):\n DELETE = DENY_ALL\n\n\nclass CommentOptions(BayaModelAdmin):\n CREATE = g('A')\n READ = g('Aaa')\n UPDATE = g('Aa')\n DELETE = DENY_ALL\n\n\nsite = NestedGroupsAdminSite(name='example')\nsite.register(Blag, BlagOptions)\nsite.register(Entry, requires(g('Aa'))(EntryOptions))\nsite.register(Comment, CommentOptions)\n```\n\nNote that all of the urls returned by your model admin's `get_urls` method\nwill be protected with the appropriate permissions. You can further restrict\nadmin inner urls by using the `requires` decorator there.\n\nYou must also add configuration for the `is_staff` flag. See\n[admin configuration](#admin-configuration).\n\n# Development Set Up\n\n## Django\n\nFirst check this out and install the requirements:\n\n```sh\nmake setup\ncd testsite\n./manage.py syncdb\n```\n\nIf installation of python-ldap fails on Mac OSX with `fatal error: 'sasl.h' file not found` or similar missing header files then run the following to manualy install `python-ldap`.\n\n```sh\nsource .venv/bin/activate\npip install python-ldap \\\n --global-option=build_ext \\\n --global-option=\"-I$(xcrun --show-sdk-path)/usr/include/sasl\"\n```\n\nNow `make setup` should work.\n\n# Run\n\n```sh\n# in testsite/\n./manage.py runserver\n```\n\nRunserver and log in to http://localhost:8000/example/ using one of the mock\nusers in the directory module. Play around with the `@requires` decorator in\n`tests.testsite.example.admin` to see the ldap authorization working.\n\n# Testing\n```sh\nmake test\n```\n\nbuild_revision: 4456bc42d4c19c043b2854c3353c7f5c40a04094\nbuild_pipeline: baya-oss-build\nbuild_label: 14\n", "description_content_type": "", "docs_url": null, "download_url": "https://github.com/counsyl/baya/tarball/v1.2.0", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/counsyl/baya", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "baya", "package_url": "https://pypi.org/project/baya/", "platform": "", "project_url": "https://pypi.org/project/baya/", "project_urls": { "Download": "https://github.com/counsyl/baya/tarball/v1.2.0", "Homepage": "https://github.com/counsyl/baya" }, "release_url": "https://pypi.org/project/baya/1.2.0/", "requires_dist": null, "requires_python": "", "summary": "Nested LDAP Groups authorization.", "version": "1.2.0" }, "last_serial": 4642083, "releases": { "0.1.17": [ { "comment_text": "", "digests": { "md5": "b30fc113cdf38bd435e9e3123c5875b8", "sha256": "2ed3113d4e5526142a34325a4f2bbf566e514e5868903247e608cd17dbbc010b" }, "downloads": -1, "filename": "baya-0.1.17.tar.gz", "has_sig": false, "md5_digest": "b30fc113cdf38bd435e9e3123c5875b8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21288, "upload_time": "2016-05-21T00:01:26", "url": "https://files.pythonhosted.org/packages/08/90/3218930eeb46f441018c1e2a321b2bdc0b826fad09b1bbd8d067a0e82037/baya-0.1.17.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "10d283dd4fbb92a19917948d191d728c", "sha256": "ea7921fe4014ee89cc17632cd02943f68d9158097d796298e4f1a8bf6b2f2710" }, "downloads": -1, "filename": "baya-1.0.0.tar.gz", "has_sig": false, "md5_digest": "10d283dd4fbb92a19917948d191d728c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21318, "upload_time": "2016-09-26T19:06:09", "url": "https://files.pythonhosted.org/packages/b0/4a/230be788f7be698956d4a391d1a18e773401d23083977a96c68fe51c1ad2/baya-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "e5396b8c93f4fd484adc4c4943738faf", "sha256": "927a91ddbfd550bbfa0194157cf07a14e103097b36237422082320a338f5e878" }, "downloads": -1, "filename": "baya-1.0.1-py2-none-any.whl", "has_sig": false, "md5_digest": "e5396b8c93f4fd484adc4c4943738faf", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 93106, "upload_time": "2017-11-11T17:53:25", "url": "https://files.pythonhosted.org/packages/ee/fc/dfa54d67e861035e44768f7edaea5536e8c6065e5276addd427c38d8bec4/baya-1.0.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "26a3ca2d81d72b5867c6e825aad2f8c3", "sha256": "eed73b78e720a7b536655e2ef28c17d589e62b3312bf999e34af194f70126fb5" }, "downloads": -1, "filename": "baya-1.0.1.tar.gz", "has_sig": false, "md5_digest": "26a3ca2d81d72b5867c6e825aad2f8c3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21354, "upload_time": "2017-11-11T17:53:28", "url": "https://files.pythonhosted.org/packages/b0/f3/1268f94f9758d691cced992f60e3b6b43a6abcc394b80982df9c4bbccb0d/baya-1.0.1.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "6165d09319ab465e75e273aa27bea2d9", "sha256": "a748fdc33f36a2a29da25bf5c4040232af267eb6f834503b0d55087904dd1702" }, "downloads": -1, "filename": "baya-1.1.0-py2-none-any.whl", "has_sig": false, "md5_digest": "6165d09319ab465e75e273aa27bea2d9", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 95924, "upload_time": "2017-12-21T02:27:24", "url": "https://files.pythonhosted.org/packages/2d/66/767fcee38184ab883d02e04ffc1de35599deeb9bf5f120a38a43f38ee3ae/baya-1.1.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ba88e8c46a4d0286739fb4c0145c38ff", "sha256": "1f2914838e9e61ee75126625d53bf2cea0638bfea15eef158f82ed43e23c612a" }, "downloads": -1, "filename": "baya-1.1.0.tar.gz", "has_sig": false, "md5_digest": "ba88e8c46a4d0286739fb4c0145c38ff", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22066, "upload_time": "2017-12-21T02:27:26", "url": "https://files.pythonhosted.org/packages/d4/57/bf03dbdd2567ff31c39accd1ac2744a64725b8078f8d0ecbd723b4a7b84c/baya-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "e73cfd754dd04202180453c58b1f08be", "sha256": "4c1e682fdf71acc9cc2e264f28703d65dc0e2b07582494bd268de0a0ac1b36aa" }, "downloads": -1, "filename": "baya-1.1.1-py2-none-any.whl", "has_sig": false, "md5_digest": "e73cfd754dd04202180453c58b1f08be", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 95978, "upload_time": "2017-12-23T02:17:44", "url": "https://files.pythonhosted.org/packages/be/22/59a751f9faee36f9b53a786c2cf2cf46fe7da772287e8d3596f28f6b4d9e/baya-1.1.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "130f031a2da720442a9bb54ed1f913fd", "sha256": "c7983fd1ee395174e5810edd1b6d86f510c14152c468ae2e0bf4566eb7288dd3" }, "downloads": -1, "filename": "baya-1.1.1.tar.gz", "has_sig": false, "md5_digest": "130f031a2da720442a9bb54ed1f913fd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22068, "upload_time": "2017-12-23T02:17:46", "url": "https://files.pythonhosted.org/packages/f8/0b/373791875e04dc2aca49f0f551a5b0db5312eda96b07633362e47b79a117/baya-1.1.1.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "60a3b897f284c44eb953d61301c49298", "sha256": "0e796c899b13eb3493ace990ff332a660a1d7fcce3d997e0002bff3426be5179" }, "downloads": -1, "filename": "baya-1.2.0-py2-none-any.whl", "has_sig": false, "md5_digest": "60a3b897f284c44eb953d61301c49298", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 91232, "upload_time": "2018-12-28T19:25:53", "url": "https://files.pythonhosted.org/packages/27/2f/2047ecba90b698cd6e7b73c7ccf2b0a471164cca48a51b37ffa006dee924/baya-1.2.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5a99ead6c580de94b45e693f4a253cf2", "sha256": "2f4f162ceed6ddee34179d397aae934a7bd059261388d687a066f53558ad32d2" }, "downloads": -1, "filename": "baya-1.2.0.tar.gz", "has_sig": false, "md5_digest": "5a99ead6c580de94b45e693f4a253cf2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22056, "upload_time": "2018-12-28T19:25:55", "url": "https://files.pythonhosted.org/packages/6e/56/0c0dd9fa0d3a80653ea8544596d3e3f3a363d57b50aa86a743264e0727b6/baya-1.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "60a3b897f284c44eb953d61301c49298", "sha256": "0e796c899b13eb3493ace990ff332a660a1d7fcce3d997e0002bff3426be5179" }, "downloads": -1, "filename": "baya-1.2.0-py2-none-any.whl", "has_sig": false, "md5_digest": "60a3b897f284c44eb953d61301c49298", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 91232, "upload_time": "2018-12-28T19:25:53", "url": "https://files.pythonhosted.org/packages/27/2f/2047ecba90b698cd6e7b73c7ccf2b0a471164cca48a51b37ffa006dee924/baya-1.2.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5a99ead6c580de94b45e693f4a253cf2", "sha256": "2f4f162ceed6ddee34179d397aae934a7bd059261388d687a066f53558ad32d2" }, "downloads": -1, "filename": "baya-1.2.0.tar.gz", "has_sig": false, "md5_digest": "5a99ead6c580de94b45e693f4a253cf2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22056, "upload_time": "2018-12-28T19:25:55", "url": "https://files.pythonhosted.org/packages/6e/56/0c0dd9fa0d3a80653ea8544596d3e3f3a363d57b50aa86a743264e0727b6/baya-1.2.0.tar.gz" } ] }