├── .gitignore
├── .travis.yml
├── CHANGES.md
├── MANIFEST.in
├── Makefile
├── README.md
├── README.rst
├── miracle
├── __init__.py
└── acl.py
├── requirements-dev.txt
├── setup.cfg
├── setup.py
├── tests
└── acl_test.py
└── tox.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | # ===[ APP ]=== #
2 |
3 | # ===[ PYTHON PACKAGE ]=== #
4 | /build/
5 | /dist/
6 | /MANIFEST
7 | /*.egg/
8 | /*.egg-info/
9 |
10 | # ===[ OTHER ]=== #
11 |
12 | # IDE Projects
13 | .idea
14 | .nbproject
15 | .project
16 | *.sublime-project
17 |
18 | # Temps
19 | *~
20 | *.tmp
21 | *.bak
22 | *.swp
23 | *.kate-swp
24 | *.DS_Store
25 | Thumbs.db
26 |
27 | # Utils
28 | /.tox/
29 | .sass-cache/
30 | .coverage
31 |
32 | # Generated
33 | __pycache__
34 | *.py[cod]
35 | *.pot
36 | *.mo
37 |
38 | # Runtime
39 | /*.log
40 | /*.pid
41 |
42 | # ===[ EXCLUDES ]=== #
43 | !.gitkeep
44 | !.htaccess
45 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | sudo: false
3 | language: python
4 |
5 | matrix:
6 | include:
7 | - python: 2.7
8 | env: TOXENV=py
9 | - python: 3.4
10 | env: TOXENV=py
11 | - python: 3.5
12 | env: TOXENV=py
13 | - python: 3.6
14 | env: TOXENV=py
15 | - python: 3.7-dev
16 | env: TOXENV=py
17 | - python: pypy
18 | env: TOXENV=py
19 | - python: pypy3
20 | env: TOXENV=py
21 | install:
22 | - pip install tox
23 | cache:
24 | - pip
25 | script:
26 | - tox
27 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | v0.0.3, 2013.01.08
5 | ------------------
6 |
7 | New:
8 |
9 | * acl.revoke_all()
10 |
11 | v0.0.2, 2013.01.03
12 | ------------------
13 |
14 | New:
15 |
16 | * acl.add_roles() to add multiple roles
17 | * acl.clear() method
18 |
19 | Fixed:
20 |
21 | * acl.del_*() does proper clean-up
22 |
23 | v0.0.1, 2013.12.31
24 | ------------------
25 |
26 | Initial release
27 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.md *.rst
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 |
3 | SHELL := /bin/bash
4 |
5 | # Package
6 | .PHONY: clean
7 | clean:
8 | @rm -rf build/ dist/ *.egg-info/ README.md README.rst
9 | #README.md: $(shell find * -type f -name '*.py' -o -name '*.j2') $(wildcard misc/_doc/**)
10 | # @python misc/_doc/README.py | j2 --format=json -o README.md misc/_doc/README.md.j2
11 | README.rst: README.md
12 | @pandoc -f markdown -t rst -o README.rst README.md
13 |
14 | .PHONY: build publish-test publish
15 | build: README.rst
16 | @./setup.py build sdist bdist_wheel
17 | publish-test: README.rst
18 | @twine upload --repository pypitest dist/*
19 | publish: README.rst
20 | @twine upload dist/*
21 |
22 |
23 | .PHONY: test test-tox test-docker test-docker-2.6
24 | test:
25 | @nosetests
26 | test-tox:
27 | @tox
28 | test-docker:
29 | @docker run --rm -it -v `pwd`:/src themattrix/tox
30 | test-docker-2.6: # temporary, since `themattrix/tox` has faulty 2.6
31 | @docker run --rm -it -v $(realpath .):/app mrupgrade/deadsnakes:2.6 bash -c 'cd /app && pip install -e . && pip install nose argparse && nosetests'
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/kolypto/py-miracle)
2 | [](.travis.yml)
3 |
4 | Miracle
5 | =======
6 |
7 | Miracle is an ACL for Python that was designed to be well-structuted,
8 | simple yet exhaustive. It uses *permissions* defined on *resources*, and *roles* are granted with the access to them.
9 |
10 | To be a universal tool, it does not include any special cases,
11 | does not force you to persist and does not insist on any formats or conventions.
12 |
13 | Maximum flexibility and total control. Enjoy! :)
14 |
15 | Highlights:
16 |
17 | * Inspired by [miracle](https://github.com/kolypto/nodejs-miracle/) for NodeJS ;
18 | * Simple core
19 | * No restrictions on authorization entities
20 | * Unit-tested
21 |
22 |
23 |
24 |
25 |
26 |
27 | Table of Contents
28 | =================
29 |
30 | * Installation
31 | * Define The Structure
32 | * Acl
33 | * Create
34 | * add_role(role)
35 | * add_roles(roles)
36 | * add_resource(resource)
37 | * add_permission(resource, permission)
38 | * add(structure)
39 | * Remove
40 | * remove_role(role)
41 | * remove_resource(resource)
42 | * remove_permission(resource, permission)
43 | * clear()
44 | * Get
45 | * get_roles()
46 | * get_resources()
47 | * get_permissions(resource)
48 | * get()
49 | * Export and Import
50 | * Authorize
51 | * Grant Permissions
52 | * grant(role, resource, permission)
53 | * grants(grants)
54 | * revoke(role, resource, permission)
55 | * revoke_all(role[, resource])
56 | * Check Permissions
57 | * check(role, resource, permission)
58 | * check_any(roles, resource, permission)
59 | * check_all(roles, resource, permission)
60 | * Show Grants
61 | * which_permissions(role, resource)
62 | * which_permissions_any(roles, resource)
63 | * which_permissions_all(roles, resource)
64 | * which(role)
65 | * which_any(roles)
66 | * which_all(roles)
67 | * show()
68 |
69 |
70 |
71 |
72 |
73 |
74 | Installation
75 | ============
76 |
77 | With pip:
78 |
79 | ```bash
80 | $ pip install miracle-acl
81 | ```
82 |
83 | Define The Structure
84 | ====================
85 |
86 | Acl
87 | ---
88 | To start using miracle, instantiate the `Acl` object:
89 |
90 | ```python
91 | from miracle import Acl
92 | acl = Acl()
93 | ```
94 |
95 | The `Acl` object keeps track of your *resources* and *permissions* defined on them, handles *grants* over *roles* and
96 | provides utilities to manage them. When configured, you can check the access against the defined state.
97 |
98 | Create
99 | ------
100 |
101 | Methods from this section allow you to build the *structure*: list of roles, resources and permissions.
102 |
103 | It's not required that you have the structure defined before you start granting the access: the `grant()` method
104 | implicitly creates all resources and permissions that were not previously defined.
105 |
106 | Start with defining the *resources* and *permissions* on them, then you can grant a *role* with the access to some
107 | permissions on a resource.
108 |
109 | For roles, resources & permissions, any hashable objects will do.
110 |
111 | ### `add_role(role)`
112 | Define a role.
113 |
114 | * `role`: the role to define.
115 |
116 | The role will have no permissions granted, but will appear in `get_roles()`.
117 |
118 | ```python
119 | acl.add_role('admin')
120 | acl.get_roles() # -> {'admin'}
121 | ```
122 |
123 | ### `add_roles(roles)`
124 | Define multiple roles
125 |
126 | * `roles`: An iterable of roles
127 |
128 | ```python
129 | acl.add_roles(['admin', 'root'])
130 | acl.get_roles() # -> {'admin', 'root'}
131 | ```
132 |
133 | ### `add_resource(resource)`
134 | Define a resource.
135 |
136 | * `resources`: the resource to define.
137 |
138 | The resource will have no permissions defined but will appear in `get_resources()`.
139 |
140 | ```python
141 | acl.add_resource('blog')
142 | acl.get_resources() # -> {'blog'}
143 | ```
144 |
145 | ### `add_permission(resource, permission)`
146 | Define a permission on a resource.
147 |
148 | * `resource`: the resource to define the permission on.
149 | Is created if was not previously defined.
150 | * `permission`: the permission to define.
151 |
152 | The defined permission is not granted to anyone, but will appear in `get_permissions(resource)`.
153 |
154 | ```python
155 | acl.add_permission('blog', 'post')
156 | acl.get_permissions('blog') # -> {'post'}
157 | ```
158 |
159 | ### `add(structure)`
160 | Define the whole resource/permission structure with a single dict.
161 |
162 | * `structure`: a dict that maps resources to an iterable of permissions.
163 |
164 | ```python
165 | acl.add({
166 | 'blog': ['post'],
167 | 'page': {'create', 'read', 'update', 'delete'},
168 | })
169 | ```
170 |
171 | Remove
172 | ------
173 |
174 | ### `remove_role(role)`
175 | Remove the role and its grants.
176 |
177 | * `role`: the role to remove.
178 |
179 | ```python
180 | acl.remove_role('admin')
181 | ```
182 |
183 | ### `remove_resource(resource)`
184 | Remove the resource along with its grants and permissions.
185 |
186 | * `resource`: the resource to remove.
187 |
188 | ```python
189 | acl.remove_resource('blog')
190 | ```
191 |
192 | ### `remove_permission(resource, permission)`
193 | Remove the permission from a resource.
194 |
195 | * `resource`: the resource to remove the permission from.
196 | * `permission`: the permission to remove.
197 |
198 | The resource is not implicitly removed: it remains with an empty set of permissions.
199 |
200 | ```python
201 | acl.remove_permission('blog', 'post')
202 | ```
203 |
204 | ### `clear()`
205 | Remove all roles, resources, permissions and grants.
206 |
207 | Get
208 | ---
209 |
210 | ### `get_roles()`
211 | Get the set of defined roles.
212 |
213 | ```python
214 | acl.get_roles() # -> {'admin', 'anonymous', 'registered'}
215 | ```
216 |
217 | ### `get_resources()`
218 | Get the set of defined resources, including those with empty permissions set.
219 |
220 | ```python
221 | acl.get_resources() # -> {'blog', 'page', 'article'}
222 | ```
223 |
224 | ### `get_permissions(resource)`
225 | Get the set of permissions for a resource.
226 |
227 | * `resource`: the resource to get the permissions for.
228 |
229 | ```python
230 | acl.get_permissions('page') # -> {'create', 'read', 'update', 'delete'}
231 | ```
232 |
233 | ### `get()`
234 | Get the *structure*: hash of all resources mapped to their permissions.
235 |
236 | Returns a dict: `{ resource: set(permission,...), ... }`.
237 |
238 | ```python
239 | acl.get() # -> { blog: {'post'}, page: {'create', ...} }
240 | ```
241 |
242 |
243 |
244 | Export and Import
245 | -----------------
246 | The `Acl` class is picklable:
247 |
248 | ```python
249 | acl = miracle.Acl()
250 | save = acl.__getstate__()
251 |
252 | #...
253 |
254 | acl = miracle.Acl()
255 | acl.__setstate__(save)
256 | ```
257 |
258 |
259 |
260 |
261 |
262 | Authorize
263 | =========
264 |
265 | Grant Permissions
266 | -----------------
267 |
268 | ### `grant(role, resource, permission)`
269 | Grant a permission over resource to the specified role.
270 |
271 | * `role`: The role to grant the access to
272 | * `resource`: The resource to grant the access over
273 | * `permission`: The permission to grant with
274 |
275 | Roles, resources and permissions are implicitly created if missing.
276 |
277 | ```python
278 | acl.grant('admin', 'blog', 'delete')
279 | acl.grant('anonymous', 'page', 'view')
280 | ```
281 |
282 | ### `grants(grants)`
283 | Add a structure of grants to the Acl.
284 |
285 | * `grants`: A hash in the following form: `{ role: { resource: set(permission) } }`.
286 |
287 | ```python
288 | acl.grants({
289 | 'admin': {
290 | 'blog': ['post'],
291 | },
292 | 'anonymous': {
293 | 'page': ['view']
294 | }
295 | })
296 | ```
297 |
298 | ### `revoke(role, resource, permission)`
299 | Revoke a permission over a resource from the specified role.
300 |
301 | ```python
302 | acl.revoke('anonymous', 'page', 'view')
303 | acl.revoke('user', 'account', 'delete')
304 | ```
305 |
306 | ### `revoke_all(role[, resource])`
307 | Revoke all permissions from the specified role for all resources.
308 | If the optional `resource` argument is provided - removes all permissions from the specified resource.
309 |
310 | ```python
311 | acl.revoke_all('anonymous', 'page') # revoke all permissions from a single resource
312 | acl.revoke_all('anonymous') # revoke permissions from all resources
313 | ```
314 |
315 |
316 |
317 | Check Permissions
318 | -----------------
319 |
320 | ### `check(role, resource, permission)`
321 | Test whether the given role has access to the resource with the specified permission.
322 |
323 | * `role`: The role to check
324 | * `resource`: The protected resource
325 | * `permission`: The required permission
326 |
327 | Returns a boolean.
328 |
329 | ```python
330 | acl.check('admin', 'blog') # True
331 | acl.check('anonymous', 'page', 'delete') # -> False
332 | ```
333 |
334 | ### `check_any(roles, resource, permission)`
335 | Test whether *any* of the given roles have access to the resource with the specified permission.
336 |
337 | * `roles`: An iterable of roles.
338 |
339 | When no roles are provided, returns False.
340 |
341 | ### `check_all(roles, resource, permission)`
342 | Test whether *all* of the given roles have access to the resource with the specified permission.
343 |
344 | * `roles`: An iterable of roles.
345 |
346 | When no roles are provided, returns False.
347 |
348 |
349 |
350 | Show Grants
351 | -----------
352 |
353 | ### which_permissions(role, resource)
354 | List permissions that the provided role has over the resource:
355 |
356 | ```python
357 | acl.which_permissions('admin', 'blog') # -> {'post'}
358 | ```
359 |
360 | ### which_permissions_any(roles, resource)
361 | List permissions that any of the provided roles have over the resource:
362 |
363 | ```python
364 | acl.which_permissions_any(['anonymous', 'registered'], 'page') # -> {'view'}
365 | ```
366 |
367 | ### which_permissions_all(roles, resource)
368 | List permissions that all of the provided roles have over the resource:
369 |
370 | ```python
371 | acl.which_permissions_all(['anonymous', 'registered'], 'page') # -> {'view'}
372 | ```
373 |
374 |
375 |
376 | ### `which(role)`
377 | Collect grants that the provided role has:
378 |
379 | ```python
380 | acl.which('admin') # -> { blog: {'post'} }
381 | ```
382 |
383 | ### `which_any(roles)`
384 | Collect grants that any of the provided roles have (union).
385 |
386 | ```python
387 | acl.which(['anonymous', 'registered']) # -> { page: ['view'] }
388 | ```
389 |
390 | ### `which_all(roles)`
391 | Collect grants that all of the provided roles have (intersection):
392 |
393 | ```python
394 | acl.which(['anonymous', 'registered']) # -> { page: ['view'] }
395 | ```
396 |
397 |
398 |
399 | ### `show()`
400 | Get all current grants.
401 |
402 | Returns a dict `{ role: { resource: set(permission) } }`.
403 |
404 | ```python
405 | acl.show() # -> { admin: { blog: ['post'] } }
406 | ```
407 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | `Build Status `__
2 | `Pythons <.travis.yml>`__
3 |
4 | Miracle
5 | =======
6 |
7 | Miracle is an ACL for Python that was designed to be well-structuted,
8 | simple yet exhaustive. It uses *permissions* defined on *resources*, and
9 | *roles* are granted with the access to them.
10 |
11 | To be a universal tool, it does not include any special cases, does not
12 | force you to persist and does not insist on any formats or conventions.
13 |
14 | Maximum flexibility and total control. Enjoy! :)
15 |
16 | Highlights:
17 |
18 | - Inspired by `miracle `__
19 | for NodeJS ;
20 | - Simple core
21 | - No restrictions on authorization entities
22 | - Unit-tested
23 |
24 | Table of Contents
25 | =================
26 |
27 | - Define The Structure
28 |
29 | - Acl
30 | - Create
31 |
32 | - add_role(role)
33 | - add_roles(roles)
34 | - add_resource(resource)
35 | - add_permission(resource, permission)
36 | - add(structure)
37 |
38 | - Remove
39 |
40 | - remove_role(role)
41 | - remove_resource(resource)
42 | - remove_permission(resource, permission)
43 | - clear()
44 |
45 | - Get
46 |
47 | - get_roles()
48 | - get_resources()
49 | - get_permissions(resource)
50 | - get()
51 |
52 | - Export and Import
53 |
54 | - Authorize
55 |
56 | - Grant Permissions
57 |
58 | - grant(role, resource, permission)
59 | - grants(grants)
60 | - revoke(role, resource, permission)
61 | - revoke_all(role[, resource])
62 |
63 | - Check Permissions
64 |
65 | - check(role, resource, permission)
66 | - check_any(roles, resource, permission)
67 | - check_all(roles, resource, permission)
68 |
69 | - Show Grants
70 |
71 | - which_permissions(role, resource)
72 | - which_permissions_any(roles, resource)
73 | - which_permissions_all(roles, resource)
74 | - which(role)
75 | - which_any(roles)
76 | - which_all(roles)
77 | - show()
78 |
79 | Define The Structure
80 | ====================
81 |
82 | Acl
83 | ---
84 |
85 | To start using miracle, instantiate the ``Acl`` object:
86 |
87 | .. code:: python
88 |
89 | from miracle import Acl
90 | acl = Acl()
91 |
92 | The ``Acl`` object keeps track of your *resources* and *permissions*
93 | defined on them, handles *grants* over *roles* and provides utilities to
94 | manage them. When configured, you can check the access against the
95 | defined state.
96 |
97 | Create
98 | ------
99 |
100 | Methods from this section allow you to build the *structure*: list of
101 | roles, resources and permissions.
102 |
103 | It’s not required that you have the structure defined before you start
104 | granting the access: the ``grant()`` method implicitly creates all
105 | resources and permissions that were not previously defined.
106 |
107 | Start with defining the *resources* and *permissions* on them, then you
108 | can grant a *role* with the access to some permissions on a resource.
109 |
110 | For roles, resources & permissions, any hashable objects will do.
111 |
112 | ``add_role(role)``
113 | ~~~~~~~~~~~~~~~~~~
114 |
115 | Define a role.
116 |
117 | - ``role``: the role to define.
118 |
119 | The role will have no permissions granted, but will appear in
120 | ``get_roles()``.
121 |
122 | .. code:: python
123 |
124 | acl.add_role('admin')
125 | acl.get_roles() # -> {'admin'}
126 |
127 | ``add_roles(roles)``
128 | ~~~~~~~~~~~~~~~~~~~~
129 |
130 | Define multiple roles
131 |
132 | - ``roles``: An iterable of roles
133 |
134 | .. code:: python
135 |
136 | acl.add_roles(['admin', 'root'])
137 | acl.get_roles() # -> {'admin', 'root'}
138 |
139 | ``add_resource(resource)``
140 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
141 |
142 | Define a resource.
143 |
144 | - ``resources``: the resource to define.
145 |
146 | The resource will have no permissions defined but will appear in
147 | ``get_resources()``.
148 |
149 | .. code:: python
150 |
151 | acl.add_resource('blog')
152 | acl.get_resources() # -> {'blog'}
153 |
154 | ``add_permission(resource, permission)``
155 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
156 |
157 | Define a permission on a resource.
158 |
159 | - ``resource``: the resource to define the permission on. Is created if
160 | was not previously defined.
161 | - ``permission``: the permission to define.
162 |
163 | The defined permission is not granted to anyone, but will appear in
164 | ``get_permissions(resource)``.
165 |
166 | .. code:: python
167 |
168 | acl.add_permission('blog', 'post')
169 | acl.get_permissions('blog') # -> {'post'}
170 |
171 | ``add(structure)``
172 | ~~~~~~~~~~~~~~~~~~
173 |
174 | Define the whole resource/permission structure with a single dict.
175 |
176 | - ``structure``: a dict that maps resources to an iterable of
177 | permissions.
178 |
179 | .. code:: python
180 |
181 | acl.add({
182 | 'blog': ['post'],
183 | 'page': {'create', 'read', 'update', 'delete'},
184 | })
185 |
186 | Remove
187 | ------
188 |
189 | ``remove_role(role)``
190 | ~~~~~~~~~~~~~~~~~~~~~
191 |
192 | Remove the role and its grants.
193 |
194 | - ``role``: the role to remove.
195 |
196 | .. code:: python
197 |
198 | acl.remove_role('admin')
199 |
200 | ``remove_resource(resource)``
201 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
202 |
203 | Remove the resource along with its grants and permissions.
204 |
205 | - ``resource``: the resource to remove.
206 |
207 | .. code:: python
208 |
209 | acl.remove_resource('blog')
210 |
211 | ``remove_permission(resource, permission)``
212 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
213 |
214 | Remove the permission from a resource.
215 |
216 | - ``resource``: the resource to remove the permission from.
217 | - ``permission``: the permission to remove.
218 |
219 | The resource is not implicitly removed: it remains with an empty set of
220 | permissions.
221 |
222 | .. code:: python
223 |
224 | acl.remove_permission('blog', 'post')
225 |
226 | ``clear()``
227 | ~~~~~~~~~~~
228 |
229 | Remove all roles, resources, permissions and grants.
230 |
231 | Get
232 | ---
233 |
234 | ``get_roles()``
235 | ~~~~~~~~~~~~~~~
236 |
237 | Get the set of defined roles.
238 |
239 | .. code:: python
240 |
241 | acl.get_roles() # -> {'admin', 'anonymous', 'registered'}
242 |
243 | ``get_resources()``
244 | ~~~~~~~~~~~~~~~~~~~
245 |
246 | Get the set of defined resources, including those with empty permissions
247 | set.
248 |
249 | .. code:: python
250 |
251 | acl.get_resources() # -> {'blog', 'page', 'article'}
252 |
253 | ``get_permissions(resource)``
254 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
255 |
256 | Get the set of permissions for a resource.
257 |
258 | - ``resource``: the resource to get the permissions for.
259 |
260 | .. code:: python
261 |
262 | acl.get_permissions('page') # -> {'create', 'read', 'update', 'delete'}
263 |
264 | .. _get-1:
265 |
266 | ``get()``
267 | ~~~~~~~~~
268 |
269 | Get the *structure*: hash of all resources mapped to their permissions.
270 |
271 | Returns a dict: ``{ resource: set(permission,...), ... }``.
272 |
273 | .. code:: python
274 |
275 | acl.get() # -> { blog: {'post'}, page: {'create', ...} }
276 |
277 | Export and Import
278 | -----------------
279 |
280 | The ``Acl`` class is picklable:
281 |
282 | .. code:: python
283 |
284 | acl = miracle.Acl()
285 | save = acl.__getstate__()
286 |
287 | #...
288 |
289 | acl = miracle.Acl()
290 | acl.__setstate__(save)
291 |
292 | Authorize
293 | =========
294 |
295 | Grant Permissions
296 | -----------------
297 |
298 | ``grant(role, resource, permission)``
299 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
300 |
301 | Grant a permission over resource to the specified role.
302 |
303 | - ``role``: The role to grant the access to
304 | - ``resource``: The resource to grant the access over
305 | - ``permission``: The permission to grant with
306 |
307 | Roles, resources and permissions are implicitly created if missing.
308 |
309 | .. code:: python
310 |
311 | acl.grant('admin', 'blog', 'delete')
312 | acl.grant('anonymous', 'page', 'view')
313 |
314 | ``grants(grants)``
315 | ~~~~~~~~~~~~~~~~~~
316 |
317 | Add a structure of grants to the Acl.
318 |
319 | - ``grants``: A hash in the following form:
320 | ``{ role: { resource: set(permission) } }``.
321 |
322 | .. code:: python
323 |
324 | acl.grants({
325 | 'admin': {
326 | 'blog': ['post'],
327 | },
328 | 'anonymous': {
329 | 'page': ['view']
330 | }
331 | })
332 |
333 | ``revoke(role, resource, permission)``
334 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
335 |
336 | Revoke a permission over a resource from the specified role.
337 |
338 | .. code:: python
339 |
340 | acl.revoke('anonymous', 'page', 'view')
341 | acl.revoke('user', 'account', 'delete')
342 |
343 | ``revoke_all(role[, resource])``
344 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
345 |
346 | Revoke all permissions from the specified role for all resources. If the
347 | optional ``resource`` argument is provided - removes all permissions
348 | from the specified resource.
349 |
350 | .. code:: python
351 |
352 | acl.revoke_all('anonymous', 'page') # revoke all permissions from a single resource
353 | acl.revoke_all('anonymous') # revoke permissions from all resources
354 |
355 | Check Permissions
356 | -----------------
357 |
358 | ``check(role, resource, permission)``
359 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
360 |
361 | Test whether the given role has access to the resource with the
362 | specified permission.
363 |
364 | - ``role``: The role to check
365 | - ``resource``: The protected resource
366 | - ``permission``: The required permission
367 |
368 | Returns a boolean.
369 |
370 | .. code:: python
371 |
372 | acl.check('admin', 'blog') # True
373 | acl.check('anonymous', 'page', 'delete') # -> False
374 |
375 | ``check_any(roles, resource, permission)``
376 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
377 |
378 | Test whether *any* of the given roles have access to the resource with
379 | the specified permission.
380 |
381 | - ``roles``: An iterable of roles.
382 |
383 | When no roles are provided, returns False.
384 |
385 | ``check_all(roles, resource, permission)``
386 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
387 |
388 | Test whether *all* of the given roles have access to the resource with
389 | the specified permission.
390 |
391 | - ``roles``: An iterable of roles.
392 |
393 | When no roles are provided, returns False.
394 |
395 | Show Grants
396 | -----------
397 |
398 | which_permissions(role, resource)
399 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
400 |
401 | List permissions that the provided role has over the resource:
402 |
403 | .. code:: python
404 |
405 | acl.which_permissions('admin', 'blog') # -> {'post'}
406 |
407 | which_permissions_any(roles, resource)
408 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
409 |
410 | List permissions that any of the provided roles have over the resource:
411 |
412 | .. code:: python
413 |
414 | acl.which_permissions_any(['anonymous', 'registered'], 'page') # -> {'view'}
415 |
416 | which_permissions_all(roles, resource)
417 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
418 |
419 | List permissions that all of the provided roles have over the resource:
420 |
421 | .. code:: python
422 |
423 | acl.which_permissions_all(['anonymous', 'registered'], 'page') # -> {'view'}
424 |
425 | ``which(role)``
426 | ~~~~~~~~~~~~~~~
427 |
428 | Collect grants that the provided role has:
429 |
430 | .. code:: python
431 |
432 | acl.which('admin') # -> { blog: {'post'} }
433 |
434 | ``which_any(roles)``
435 | ~~~~~~~~~~~~~~~~~~~~
436 |
437 | Collect grants that any of the provided roles have (union).
438 |
439 | .. code:: python
440 |
441 | acl.which(['anonymous', 'registered']) # -> { page: ['view'] }
442 |
443 | ``which_all(roles)``
444 | ~~~~~~~~~~~~~~~~~~~~
445 |
446 | Collect grants that all of the provided roles have (intersection):
447 |
448 | .. code:: python
449 |
450 | acl.which(['anonymous', 'registered']) # -> { page: ['view'] }
451 |
452 | ``show()``
453 | ~~~~~~~~~~
454 |
455 | Get all current grants.
456 |
457 | Returns a dict ``{ role: { resource: set(permission) } }``.
458 |
459 | .. code:: python
460 |
461 | acl.show() # -> { admin: { blog: ['post'] } }
462 |
--------------------------------------------------------------------------------
/miracle/__init__.py:
--------------------------------------------------------------------------------
1 | from .acl import Acl
2 |
--------------------------------------------------------------------------------
/miracle/acl.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 |
3 |
4 | class Acl(object):
5 | def __init__(self):
6 | #: Set of defined roles
7 | self._roles = set()
8 |
9 | #: Resources & Permissions: { resource: set(permission) }
10 | self._structure = defaultdict(set)
11 |
12 | #: Grants: set( (role, resource, permission) )
13 | self._grants = set()
14 |
15 | #region Add
16 |
17 | def add_role(self, role):
18 | """ Define a role.
19 |
20 | Existing roles are not overwritten nor duplicated.
21 |
22 | :param role: Role to define.
23 | Any hashable object will do.
24 | :type role: str
25 | :rtype: Acl
26 | """
27 | self._roles.add(role)
28 | return self
29 |
30 | def add_roles(self, roles):
31 | """ Define multiple roles
32 |
33 | Existing roles are not overwritten nor duplicated.
34 |
35 | :param roles: The roles to define
36 | :type roles: list(str)
37 | :rtype: Acl
38 | """
39 | self._roles.update(set(roles))
40 | return self
41 |
42 | def add_resource(self, resource):
43 | """ Define a resource.
44 |
45 | Existing resources are not overwritten nor duplicated
46 |
47 | :param resource: Resource to define.
48 | For now, it will have an empty set of permissions
49 | :type resource: str
50 | :rtype: Acl
51 | """
52 | if resource not in self._structure:
53 | self._structure[resource] = set()
54 | return self
55 |
56 | def add_permission(self, resource, permission):
57 | """ Define permission on a resource
58 |
59 | The resource is created if missing.
60 | Existing permissions are not overwritten nor duplicated
61 |
62 | :param resource: Resource to define the permission on.
63 | :type resource: str
64 | :param permission: Permission to define.
65 | :type permission: str
66 | :rtype: Acl
67 | """
68 | self._structure[resource].add(permission)
69 | return self
70 |
71 | def add(self, structure):
72 | """ Define the whole structure of resources and permissions
73 |
74 | :param structure: A dict {resource: [permissions]}
75 | :type structure: dict(list(str))
76 | :rtype: Acl
77 | """
78 | for resource, permissions in structure.items():
79 | for permission in permissions:
80 | self._structure[resource].add(permission)
81 | return self
82 |
83 | #endregion
84 |
85 | #region Delete
86 |
87 | def clear(self):
88 | """ Clear the Acl completely
89 |
90 | This removes all roles, resources, permissions ans grants
91 |
92 | :rtype: Acl
93 | """
94 | self._roles.clear()
95 | self._structure.clear()
96 | self._grants.clear()
97 | return self
98 |
99 | def del_role(self, role):
100 | """ Remove a role. The grants remain.
101 |
102 | Undefined roles are silently ignored
103 |
104 | :param role: Role to remove
105 | :type role: str
106 | :rtype: Acl
107 | """
108 | self._roles.discard(role)
109 | self._grants = set([x for x in self._grants if x[0] != role])
110 | return self
111 |
112 | def del_resource(self, resource):
113 | """ Remove a resource and its permissions. The grants remain.
114 |
115 | Undefined resources are silently ignored
116 |
117 | :param resource: Resource to remove
118 | :type resource: str
119 | :rtype: Acl
120 | """
121 | if resource in self._structure:
122 | del self._structure[resource]
123 | self._grants = set([x for x in self._grants if x[1] != resource])
124 | return self
125 |
126 | def del_permission(self, resource, permission):
127 | """ Remove a permission from the resource. The grants remain.
128 |
129 | Undefined resources and permissions are silently ignored
130 |
131 | :param resource: The resource to remove the permission from
132 | :type resource: str
133 | :param permission: Permission to remove
134 | :type permission: str
135 | :rtype: Acl
136 | """
137 | if resource in self._structure:
138 | self._structure[resource].discard(permission)
139 | self._grants = set([x for x in self._grants if x[2] != permission])
140 | return self
141 |
142 | #endregion
143 |
144 | #region Get
145 |
146 | def get_roles(self):
147 | """ Get the set of roles.
148 |
149 | :rtype: set(str)
150 | """
151 | return set(self._roles)
152 |
153 | def get_resources(self):
154 | """ Get the set of resources
155 |
156 | :rtype: set(str)
157 | """
158 | return set(self._structure.keys())
159 |
160 | def get_permissions(self, resource):
161 | """ Get the set of permissions on a resource
162 |
163 | :param resource: The resource to list the permissions for
164 | :type resource: str
165 | :rtype: set(str)
166 | """
167 | if resource not in self._structure:
168 | return set()
169 | return set(self._structure[resource])
170 |
171 | def get(self):
172 | """ Get the whole structure of resources and permissions
173 |
174 | Returns { resource: set(permission) }
175 |
176 | :rtype: dict(set(str))
177 | """
178 | ret = {}
179 | for resource, permissions in self._structure.items():
180 | ret[resource] = set(permissions)
181 | return ret
182 |
183 | #endregion
184 |
185 | #region Grant Permissions
186 |
187 | def grant(self, role, resource, permission):
188 | """ Grant a permission over resource to the role.
189 |
190 | Missing entities are added to the structure
191 |
192 | :param role: The role to grant the access to
193 | :type role: str
194 | :param resource: The resource to grant the access over
195 | :type resource: str
196 | :param permission: The permission to grant with
197 | :type permission: str
198 | :rtype: Acl
199 | """
200 | self.add_role(role)
201 | self.add_resource(resource)
202 | self.add_permission(resource, permission)
203 | self._grants.add((role, resource, permission))
204 | return self
205 |
206 | def grants(self, grants):
207 | """ Add a structure of grants to the Acl
208 |
209 | Input: { role: { resource: set(permissions) } }
210 |
211 | :param grants: Grants structure to add
212 | :type grants: dict(dict(set(str)))
213 | :rtype: Acl
214 | """
215 | for role, gs in grants.items():
216 | self.add_role(role)
217 | for resource, permissions in gs.items():
218 | self.add_resource(resource)
219 | for permission in permissions:
220 | self.add_permission(resource, permission)
221 | self._grants.add((role, resource, permission))
222 | return self
223 |
224 | def revoke(self, role, resource, permission):
225 | """ Revoke a permission over a resource from the specified role.
226 |
227 | :param role: The role to modify
228 | :type role: str
229 | :param resource: The resource to modify
230 | :type resource: str
231 | :param permission: The permission to revoke
232 | :type permission: str
233 | :rtype: Acl
234 | """
235 | self._grants.discard((role, resource, permission))
236 | return self
237 |
238 | def revoke_all(self, role, resource=None):
239 | """ Revoke all permissions from the specified role [over the specified resource]
240 |
241 | :param role: The role to revoke all permissions from
242 | :type role: str
243 | :param resource: The resource to revoke the permissions from. Optional: revokes from all resources
244 | :type resource: str
245 | :rtype: Acl
246 | """
247 | self._grants = { g for g in self._grants if not (g[0] == role and (resource is None or g[1] == resource)) }
248 | return self
249 |
250 | #endregion
251 |
252 | #region Check
253 |
254 | def check(self, role, resource, permission):
255 | """ Test whether the given role has access to the resource with the specified permission.
256 |
257 | :param role: The role to check the access for
258 | :type role: str
259 | :param resource: The resource to check the access for
260 | :type resource: str
261 | :param permission: The permission to check the access with
262 | :type permission: str
263 | :rtype: bool
264 | """
265 | return (role, resource, permission) in self._grants
266 |
267 | def check_any(self, roles, resource, permission):
268 | """ Test whether ANY of the given roles have access to the resource with the specified permission.
269 |
270 | :param roles: Roles collection to check the access for
271 | :type roles: list(str)
272 | :param resource: The resource to check the access for
273 | :type resource: str
274 | :param permission: The permission to check the access with
275 | :type permission: str
276 | :rtype: bool
277 | """
278 | # No roles
279 | if not roles:
280 | return False
281 |
282 | # Any
283 | return any((role, resource, permission) in self._grants for role in roles)
284 |
285 | def check_all(self, roles, resource, permission):
286 | """ Test whether ALL of the given roles have access to the resource with the specified permission.
287 |
288 | :param roles: Roles collection to check the access for
289 | :type roles: list(str)
290 | :param resource: The resource to check the access for
291 | :type resource: str
292 | :param permission: The permission to check the access with
293 | :type permission: str
294 | :rtype: bool
295 | """
296 | # No roles
297 | if not roles:
298 | return False
299 |
300 | # all
301 | return all((role, resource, permission) in self._grants for role in roles)
302 |
303 | #endregion
304 |
305 | #region Show Grants
306 |
307 | def which_permissions(self, role, resource):
308 | """ List permissions that the provided role has over the resource
309 |
310 | :param role: The role to list permissions for
311 | :type role: str
312 | :rtype: set(str)
313 | """
314 | return {permission for r, res, permission in self._grants if r == role and res == resource}
315 |
316 | def which_permissions_any(self, roles, resource):
317 | """ List permissions that any of the provided roles have over the resource
318 |
319 | :param roles: Roles to list permissions for
320 | :type roles: list(str)
321 | :rtype: set(str)
322 | """
323 | if not roles:
324 | return {}
325 |
326 | # Collect permissions per role
327 | roles = set(roles)
328 | return {permission for r, res, permission in self._grants if r in roles and res == resource}
329 |
330 | def which_permissions_all(self, roles, resource):
331 | """ List permissions that all of the provided roles have over the resource
332 |
333 | :param roles: Roles to list permissions for
334 | :type roles: list(str)
335 | :rtype: set(str)
336 | """
337 | if not roles:
338 | return {}
339 | roles = set(roles)
340 |
341 | # Collect permissions per role
342 | ppr = {
343 | role: set(
344 | permission
345 | for r, res, permission in self._grants
346 | if r == role and res == resource)
347 | for role in roles
348 | }
349 |
350 | # Intersect them
351 | return set.intersection(*ppr.values())
352 |
353 | def which(self, role):
354 | """ Collect grants that the provided role has
355 |
356 | Returns: { resource: set(permission) }
357 |
358 | :param role: The role to show the grants for
359 | :type role: str
360 | :rtype: dict(set(str))
361 | """
362 | ret = defaultdict(set)
363 | for (r, resource, permission) in self._grants:
364 | if r == role:
365 | ret[resource].add(permission)
366 | return dict(ret)
367 |
368 | def which_any(self, roles):
369 | """ Collect grants that ANY of the provided roles have
370 |
371 | Returns: { resource: set(permission) }
372 |
373 | :param roles: The roles to show the grants for
374 | :type roles: list(str)
375 | :rtype: dict(set(str))
376 | """
377 | # No roles
378 | roles = set(roles)
379 | if not roles:
380 | return {}
381 |
382 | # Union
383 | ret = defaultdict(set)
384 | for (r, resource, permission) in self._grants:
385 | if r in roles:
386 | ret[resource].add(permission)
387 | return dict(ret)
388 |
389 | def which_all(self, roles):
390 | """ Collect grants that ALL of the provided roles have
391 |
392 | Returns: { resource: set(permission) }
393 |
394 | :param roles: The roles to show the grants for
395 | :type roles: list(str)
396 | :rtype: dict(set(str))
397 | """
398 | # No roles
399 | roles = set(roles)
400 | if not roles:
401 | return {}
402 |
403 | # Collect grants for each role
404 | grants = {role: self.which(role) for role in roles}
405 |
406 | # Start with the first one
407 | if not grants:
408 | return dict()
409 | ret = grants.popitem()[1]
410 |
411 | # Intersect them
412 | for role, gs in grants.items():
413 | for resource in list(ret.keys()):
414 | if resource in gs:
415 | ret[resource] &= gs[resource]
416 | else:
417 | del ret[resource]
418 |
419 | # Finish
420 | return dict(ret)
421 |
422 | def show(self):
423 | """ Show all current grants
424 |
425 | Returns: { role: { resource: set(permission) } }
426 |
427 | :rtype: dict(dict(set(str))
428 | """
429 | ret = defaultdict(lambda: defaultdict(set))
430 | for (role, resource, permission) in self._grants:
431 | ret[role][resource].add(permission)
432 | return dict(ret)
433 |
434 | #endregion
435 |
436 | #region Export & Import
437 |
438 | def __getstate__(self):
439 | return {
440 | 'roles': self.get_roles(),
441 | 'struct': self.get(),
442 | 'grants': self.show()
443 | }
444 |
445 | def __setstate__(self, state):
446 | self.add_roles(state['roles'])
447 | self.add(state['struct'])
448 | self.grants(state['grants'])
449 | return self
450 |
451 | #endregion
452 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | wheel
2 | nose
3 | j2cli
4 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal=1
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 | """ Flexible role-based authorization solution that is a pleasure to use """
3 |
4 | from setuptools import setup, find_packages
5 |
6 | setup(
7 | # http://pythonhosted.org/setuptools/setuptools.html
8 | name='miracle-acl',
9 | version='0.0.4-1',
10 | author='Mark Vartanyan',
11 | author_email='kolypto@gmail.com',
12 |
13 | url='https://github.com/kolypto/py-miracle',
14 | license='MIT',
15 | description=__doc__,
16 | long_description=open('README.md').read(),
17 | long_description_content_type='text/markdown',
18 | keywords=['acl', 'rbac', 'authorization'],
19 |
20 | packages=find_packages(),
21 | scripts=[],
22 | entry_points={},
23 |
24 | install_requires=[
25 | ],
26 | extras_require={},
27 | include_package_data=True,
28 | test_suite='nose.collector',
29 |
30 | platforms='any',
31 | classifiers=[
32 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers
33 | 'Development Status :: 5 - Production/Stable',
34 | 'Intended Audience :: Developers',
35 | 'Natural Language :: English',
36 | 'Programming Language :: Python :: 2',
37 | 'Programming Language :: Python :: 3',
38 | 'Operating System :: OS Independent'
39 | ],
40 | )
41 |
--------------------------------------------------------------------------------
/tests/acl_test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import miracle
3 |
4 |
5 | class TestAclStructure(unittest.TestCase):
6 | def test_roles(self):
7 | """ add_role(), add_roles(), list_roles(), del_role() """
8 | acl = miracle.Acl()
9 |
10 | # Add roles
11 | acl.add_role('root')
12 | acl.add_role('superadmin')
13 | acl.add_role('superadmin') # does not replace or fail
14 | acl.add_role('user')
15 | acl.add_role('poweruser')
16 | acl.add_roles(['n00b', 'poweruser'])
17 |
18 | # Test roles
19 | self.assertSetEqual(acl.get_roles(), {'root','superadmin','user','poweruser','n00b'})
20 |
21 | # Del roles
22 | acl.del_role('poweruser')
23 | acl.del_role('poweruser') # does not fail
24 | acl.del_role('n00b')
25 |
26 | # Test roles
27 | self.assertSetEqual(acl.get_roles(), {'root','superadmin','user'})
28 |
29 | def test_resources(self):
30 | """ add_resource(), list_resource(), del_resource() """
31 | acl = miracle.Acl()
32 |
33 | # Add resources
34 | acl.add_resource('user')
35 | acl.add_resource('page')
36 | acl.add_resource('page') # does not replace or fail
37 | acl.add_resource('news')
38 | acl.add_resource('blog')
39 |
40 | # Test resources
41 | self.assertSetEqual(acl.get_resources(), {'user', 'page', 'news', 'blog'})
42 |
43 | # Delete resources
44 | acl.del_resource('news')
45 | acl.del_resource('news') # does not fail
46 | acl.del_resource('blog')
47 |
48 | # Test resources
49 | self.assertSetEqual(acl.get_resources(), {'user', 'page'})
50 |
51 | def test_permissions(self):
52 | """ add_permission(), list_permissions(), del_permission() """
53 | acl = miracle.Acl()
54 |
55 | # Add permissions
56 | acl.add_permission('user', 'create') # silently creates a resource
57 | acl.add_permission('user', 'create') # does not replace or fail
58 | acl.add_permission('user', 'read')
59 | acl.add_permission('user', 'write')
60 | acl.add_permission('post', 'read')
61 | acl.add_permission('post', 'create')
62 | acl.add_permission('log', 'delete')
63 |
64 | # Test resources
65 | self.assertSetEqual(acl.get_resources(), {'user', 'post', 'log'})
66 |
67 | # Test permissions on resources
68 | self.assertSetEqual(acl.get_permissions('404'), set()) # empty ok
69 | self.assertSetEqual(acl.get_permissions('user'), {'create','read','write'})
70 | self.assertSetEqual(acl.get_permissions('post'), {'read', 'create'})
71 | self.assertSetEqual(acl.get_permissions('log'), {'delete'})
72 |
73 | # Del permissions
74 | acl.del_permission('user', 'write')
75 | acl.del_permission('post', 'create')
76 | acl.del_permission('post', 'create') # does not fail
77 |
78 | # Test resources
79 | self.assertSetEqual(acl.get_resources(), {'user', 'post', 'log'})
80 |
81 | # Test permissions on resources
82 | self.assertSetEqual(acl.get_permissions('404'), set())
83 | self.assertSetEqual(acl.get_permissions('user'), {'create', 'read'})
84 | self.assertSetEqual(acl.get_permissions('post'), {'read'})
85 | self.assertSetEqual(acl.get_permissions('log'), {'delete'})
86 |
87 | def test_structure(self):
88 | """ add(), list() """
89 | acl = miracle.Acl()
90 |
91 | # Add
92 | acl.add({
93 | '/article': {'create','edit'},
94 | '/profile': ['edit'],
95 | })
96 | acl.add({
97 | '/article': ['vote'],
98 | })
99 |
100 | # list()
101 | self.assertDictEqual(
102 | acl.get(),
103 | {
104 | '/article': {'create','edit','vote'},
105 | '/profile': {'edit'}
106 | }
107 | )
108 |
109 | # lit() must produce a copy
110 | l = acl.get()
111 | l['/lol'] = 'a'
112 | l['/article'].add('lol')
113 |
114 | # Test: should not be modified
115 | self.assertDictEqual(
116 | acl.get(),
117 | {
118 | '/article': {'create', 'edit', 'vote'},
119 | '/profile': {'edit'}
120 | }
121 | )
122 |
123 | # Test resources
124 | self.assertSetEqual(acl.get_resources(), {'/article', '/profile'})
125 |
126 | # Test permissions on resources
127 | self.assertSetEqual(acl.get_permissions('/article'), {'create', 'edit', 'vote'}) # empty ok
128 | self.assertSetEqual(acl.get_permissions('/profile'), {'edit'}) # empty ok
129 |
130 | def test_grant(self):
131 | """ grant(), grants(), revoke(), revoke_all(), show() """
132 | acl = miracle.Acl()
133 | acl.grant('root', '/admin', 'enter')
134 | acl.grant('root', '/admin', 'enter') # dupe
135 | acl.grant('root', '/article', 'edit')
136 | acl.grants({
137 | 'user': {
138 | '/article': ['view'],
139 | '/admin': ['kill']
140 | }
141 | })
142 | acl.revoke('user', '/admin', 'kill')
143 | acl.revoke('user', '/admin', 'kill') # dupe
144 |
145 | # Structure
146 | self.assertSetEqual(acl.get_roles(), {'root', 'user'})
147 | self.assertDictEqual( acl.get(), {
148 | '/admin': {'enter','kill'}, # 'kill' remains, though revoked
149 | '/article': {'view','edit'}
150 | })
151 |
152 | # Grants
153 | self.assertDictEqual(acl.show(), {
154 | 'root': {
155 | '/admin': {'enter'},
156 | '/article': {'edit'}
157 | },
158 | 'user': {
159 | '/article':{'view'}
160 | }
161 | })
162 |
163 | # revoke_all()
164 | acl.revoke_all('root', '/article')
165 | self.assertDictEqual(acl.show(), {
166 | 'root': {
167 | '/admin': {'enter'}
168 | },
169 | 'user': {
170 | '/article':{'view'}
171 | }
172 | })
173 |
174 | acl.revoke_all('root')
175 | self.assertDictEqual(acl.show(), {
176 | 'user': {
177 | '/article':{'view'}
178 | }
179 | })
180 |
181 | def test_check(self):
182 | """ check(), check_any(), check_all() ; which(), which_any(), which_all() """
183 | acl = miracle.Acl()
184 | acl.grant('root', '/admin', 'enter')
185 | acl.grant('admin', '/admin', 'enter')
186 | acl.grant('root', '/user', 'edit')
187 | acl.grant('root', '/user', 'delete')
188 | acl.grant('root', '/user', 'show')
189 | acl.grant('admin', '/user', 'show')
190 | acl.grant('admin', '/user', 'edit')
191 | acl.grant('user', '/user', 'show')
192 | acl.add_role('nobody')
193 |
194 | # which()
195 | self.assertDictEqual(acl.which('???'), {})
196 | self.assertDictEqual(acl.which('root'), {
197 | '/admin': {'enter'},
198 | '/user': {'show','edit','delete'}
199 | })
200 | self.assertDictEqual(acl.which('admin'), {
201 | '/admin': {'enter'},
202 | '/user': {'show','edit'}
203 | })
204 | self.assertDictEqual(acl.which('user'), {
205 | '/user': {'show'}
206 | })
207 | self.assertDictEqual(acl.which('nobody'), {})
208 |
209 | # which_any()
210 | self.assertDictEqual(acl.which_any([]), {})
211 | self.assertDictEqual(acl.which_any(['root', 'user']), acl.which('root'))
212 | self.assertDictEqual(acl.which_any(['admin', 'user']), acl.which('admin'))
213 | self.assertDictEqual(acl.which_any(['user']), acl.which('user'))
214 | self.assertDictEqual(acl.which_any(['user', 'nobody']), acl.which('user'))
215 |
216 | # which_all()
217 | self.assertDictEqual(acl.which_all([]), {})
218 | self.assertDictEqual(acl.which_all(['root', 'admin']), acl.which('admin'))
219 | self.assertDictEqual(acl.which_all(['admin', 'user']), acl.which('user'))
220 | self.assertDictEqual(acl.which_all(['root', 'nobody']), acl.which('nobody'))
221 | self.assertDictEqual(acl.which_all(['user', 'root', 'nobody']), acl.which('nobody'))
222 |
223 | # which_permissions()
224 | self.assertEqual(acl.which_permissions('???', '/admin'), set())
225 | self.assertEqual(acl.which_permissions('root', '/???'), set())
226 | self.assertEqual(acl.which_permissions('root', '/admin'), {'enter'})
227 | self.assertEqual(acl.which_permissions('root', '/user'), {'edit', 'delete', 'show'})
228 | self.assertEqual(acl.which_permissions('user', '/user'), {'show'})
229 |
230 | # which_permissions_any()
231 | self.assertEqual(acl.which_permissions_any([], '/admin'), {})
232 | self.assertEqual(acl.which_permissions_any(['root', 'user'], '/user'), acl.which_permissions('root', '/user'))
233 | self.assertEqual(acl.which_permissions_any(['admin', 'user'], '/user'), acl.which_permissions('admin', '/user'))
234 | self.assertEqual(acl.which_permissions_any(['user'], '/user'), acl.which_permissions('user', '/user'))
235 | self.assertEqual(acl.which_permissions_any(['user', 'nobody'], '/user'), acl.which_permissions('user', '/user'))
236 |
237 | # which_permissions_all()
238 | self.assertEqual(acl.which_permissions_all([], '/admin'), {})
239 | self.assertEqual(acl.which_permissions_all(['root', 'user'], '/user'), acl.which_permissions('user', '/user'))
240 | self.assertEqual(acl.which_permissions_all(['admin', 'user'], '/user'), acl.which_permissions('user', '/user'))
241 | self.assertEqual(acl.which_permissions_all(['user'], '/user'), acl.which_permissions('user', '/user'))
242 | self.assertEqual(acl.which_permissions_all(['user', 'nobody'], '/user'), acl.which_permissions('nobody', '/user'))
243 |
244 | # check()
245 | self.assertTrue(acl.check('root', '/admin', 'enter'))
246 | self.assertFalse(acl.check('???', '/admin', 'enter')) # unknown role
247 | self.assertFalse(acl.check('root', '/???', 'enter')) # unknown resource
248 | self.assertFalse(acl.check('root', '/admin', '???')) # unknown permission
249 | self.assertTrue(acl.check('user', '/user', 'show'))
250 |
251 | # check_any()
252 | self.assertFalse(acl.check_any([], '/user', 'show'))
253 | self.assertTrue(acl.check_any(['root'], '/user', 'show'))
254 | self.assertTrue(acl.check_any(['root','user'], '/user', 'show'))
255 | self.assertTrue(acl.check_any(['root','user'], '/admin', 'enter'))
256 | self.assertTrue(acl.check_any(['root','user'], '/user', 'delete'))
257 | self.assertFalse(acl.check_any(['admin','user'], '/user', 'delete'))
258 |
259 | # check_all()
260 | self.assertFalse(acl.check_all([], '/user', 'show'))
261 | self.assertTrue(acl.check_all(['root','user'], '/user', 'show'))
262 | self.assertFalse(acl.check_all(['root','user'], '/admin', 'enter'))
263 | self.assertFalse(acl.check_all(['root','user'], '/user', 'delete'))
264 | self.assertFalse(acl.check_all(['root','user'], '/user', 'delete'))
265 | self.assertFalse(acl.check_all(['root','admin'], '/user', 'delete'))
266 | self.assertTrue(acl.check_all(['root','admin'], '/user', 'edit'))
267 |
268 | def test_pickle(self):
269 | """ __getstate__(), __setstate__() """
270 | acl = miracle.Acl()
271 | acl.grant('root', '/admin', 'enter')
272 | acl.grant('user', '/user', 'show')
273 | acl.grant('author', '/article', 'post')
274 |
275 | self.assertDictEqual(acl.__getstate__(), {
276 | 'roles': {'root','user','author'},
277 | 'struct': {
278 | '/admin': {'enter'},
279 | '/user': {'show'},
280 | '/article': {'post'}
281 | },
282 | 'grants': {
283 | 'root': { '/admin': {'enter'} },
284 | 'user': { '/user': {'show'} },
285 | 'author': { '/article': {'post'} }
286 | }
287 | })
288 |
289 | acl2 = miracle.Acl()
290 | acl2.__setstate__(acl.__getstate__())
291 |
292 | self.assertDictEqual(
293 | acl .__getstate__(),
294 | acl2.__getstate__(),
295 | )
296 |
297 | def test_del(self):
298 | """ del_*() does not remove grants """
299 | acl = miracle.Acl()
300 | acl.grants({
301 | 'root': {
302 | 'a': ['anything'],
303 | 'b': ['everything']
304 | },
305 | 'admin': {
306 | 'b': ['something'],
307 | },
308 | 'nobody': {
309 | 'a': {'nothing'},
310 | 'c': {'nothing'}
311 | }
312 | })
313 |
314 | acl.del_permission('a', 'anything')
315 | acl.del_role('root')
316 | acl.del_resource('c')
317 |
318 | self.assertDictEqual(acl.show(), {
319 | 'admin': {
320 | 'b': {'something'},
321 | },
322 | 'nobody': {
323 | 'a': {'nothing'}
324 | }
325 | })
326 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist=py{27,34,35,36,37},pypy,pypy3
3 | skip_missing_interpreters=True
4 |
5 | [testenv]
6 | deps=-rrequirements-dev.txt
7 | commands=
8 | nosetests {posargs:tests/}
9 | whitelist_externals=make
10 |
11 | [testenv:dev]
12 | deps=-rrequirements-dev.txt
13 | usedevelop=True
14 |
--------------------------------------------------------------------------------