| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | """ |
|---|
| 3 | zine.privileges |
|---|
| 4 | ~~~~~~~~~~~~~~~ |
|---|
| 5 | |
|---|
| 6 | This module contains a list of builtin privileges. |
|---|
| 7 | |
|---|
| 8 | :copyright: (c) 2010 by the Zine Team, see AUTHORS for more details. |
|---|
| 9 | :license: BSD, see LICENSE for more details. |
|---|
| 10 | """ |
|---|
| 11 | from werkzeug.exceptions import Forbidden |
|---|
| 12 | |
|---|
| 13 | from zine.database import db |
|---|
| 14 | from zine.application import get_application, get_request |
|---|
| 15 | from zine.i18n import lazy_gettext |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | __all__ = ['DEFAULT_PRIVILEGES', 'Privilege'] |
|---|
| 19 | |
|---|
| 20 | DEFAULT_PRIVILEGES = {} |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | class _Expr(object): |
|---|
| 24 | |
|---|
| 25 | def iter_privileges(self, cache=None): |
|---|
| 26 | raise NotImplementedError() |
|---|
| 27 | |
|---|
| 28 | def __and__(self, other): |
|---|
| 29 | return _And(self, other) |
|---|
| 30 | |
|---|
| 31 | def __or__(self, other): |
|---|
| 32 | return _Or(self, other) |
|---|
| 33 | |
|---|
| 34 | def __call__(self, privileges): |
|---|
| 35 | return False |
|---|
| 36 | |
|---|
| 37 | |
|---|
| 38 | class _Bin(_Expr): |
|---|
| 39 | joiner = 'BIN' |
|---|
| 40 | |
|---|
| 41 | def __init__(self, a, b): |
|---|
| 42 | self.a = a |
|---|
| 43 | self.b = b |
|---|
| 44 | |
|---|
| 45 | def iter_privileges(self, cache=None): |
|---|
| 46 | if cache is None: |
|---|
| 47 | cache = set() |
|---|
| 48 | for op in self.a, self.b: |
|---|
| 49 | for item in op.iter_privileges(cache): |
|---|
| 50 | yield item |
|---|
| 51 | |
|---|
| 52 | def __repr__(self): |
|---|
| 53 | return '(%r %s %r)' % (self.a, self.joiner, self.b) |
|---|
| 54 | |
|---|
| 55 | |
|---|
| 56 | class _And(_Bin): |
|---|
| 57 | joiner = '&' |
|---|
| 58 | |
|---|
| 59 | def __call__(self, privileges): |
|---|
| 60 | return self.a(privileges) and self.b(privileges) |
|---|
| 61 | |
|---|
| 62 | |
|---|
| 63 | class _Or(_Bin): |
|---|
| 64 | joiner = '|' |
|---|
| 65 | |
|---|
| 66 | def __call__(self, privileges): |
|---|
| 67 | return self.a(privileges) or self.b(privileges) |
|---|
| 68 | |
|---|
| 69 | |
|---|
| 70 | class _Privilege(object): |
|---|
| 71 | """Internal throw-away class used for the association proxy.""" |
|---|
| 72 | |
|---|
| 73 | def __init__(self, name): |
|---|
| 74 | self.name = name |
|---|
| 75 | |
|---|
| 76 | @property |
|---|
| 77 | def privilege(self): |
|---|
| 78 | return get_application().privileges.get(self.name) |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | class Privilege(_Expr): |
|---|
| 82 | |
|---|
| 83 | def __init__(self, name, explanation, privilege_dependencies): |
|---|
| 84 | self.name = name |
|---|
| 85 | self.explanation = explanation |
|---|
| 86 | self.dependencies = privilege_dependencies |
|---|
| 87 | |
|---|
| 88 | def iter_privileges(self, cache=None): |
|---|
| 89 | if cache is None: |
|---|
| 90 | cache = set([self]) |
|---|
| 91 | yield self |
|---|
| 92 | elif self not in cache: |
|---|
| 93 | cache.add(self) |
|---|
| 94 | yield self |
|---|
| 95 | |
|---|
| 96 | def __call__(self, privileges): |
|---|
| 97 | return self in privileges |
|---|
| 98 | |
|---|
| 99 | def __repr__(self): |
|---|
| 100 | return self.name |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | def add_admin_privilege(privilege): |
|---|
| 104 | """If privilege is none, BLOG_ADMIN is returned, otherwise BLOG_ADMIN |
|---|
| 105 | is "or"ed to the expression. |
|---|
| 106 | """ |
|---|
| 107 | if privilege is None: |
|---|
| 108 | privilege = BLOG_ADMIN |
|---|
| 109 | elif privilege != BLOG_ADMIN: |
|---|
| 110 | privilege = BLOG_ADMIN | privilege |
|---|
| 111 | return privilege |
|---|
| 112 | |
|---|
| 113 | |
|---|
| 114 | def bind_privileges(container, privileges, user=None): |
|---|
| 115 | """Binds the privileges to the container. The privileges can be a list |
|---|
| 116 | of privilege names, the container must be a set. This is called for |
|---|
| 117 | the HTTP round-trip in the form validation. |
|---|
| 118 | """ |
|---|
| 119 | if not user: |
|---|
| 120 | user = get_request().user |
|---|
| 121 | app = get_application() |
|---|
| 122 | notification_types = app.notification_manager.notification_types |
|---|
| 123 | current_map = dict((x.name, x) for x in container) |
|---|
| 124 | currently_attached = set(x.name for x in container) |
|---|
| 125 | new_privileges = set(privileges) |
|---|
| 126 | |
|---|
| 127 | # remove out-dated privileges |
|---|
| 128 | for name in currently_attached.difference(new_privileges): |
|---|
| 129 | container.remove(current_map[name]) |
|---|
| 130 | # remove any privilege dependencies that are not attached to other |
|---|
| 131 | # privileges |
|---|
| 132 | if current_map[name].dependencies: |
|---|
| 133 | for privilege in current_map[name].dependencies.iter_privileges(): |
|---|
| 134 | try: |
|---|
| 135 | container.remove(privilege) |
|---|
| 136 | except KeyError: |
|---|
| 137 | # privilege probably already removed |
|---|
| 138 | pass |
|---|
| 139 | |
|---|
| 140 | # remove notification subscriptions that required the privilege |
|---|
| 141 | # being deleted. |
|---|
| 142 | for notification in user.notification_subscriptions: |
|---|
| 143 | privs = notification_types[notification.notification_id].privileges |
|---|
| 144 | if current_map[name] in privs.iter_privileges(): |
|---|
| 145 | db.session.delete(notification) |
|---|
| 146 | break |
|---|
| 147 | for privilege in current_map[name].dependencies: |
|---|
| 148 | if privilege in privs.iter_privileges(): |
|---|
| 149 | db.session.delete(notification) |
|---|
| 150 | |
|---|
| 151 | # add new privileges |
|---|
| 152 | for name in new_privileges.difference(currently_attached): |
|---|
| 153 | privilege = app.privileges[name] |
|---|
| 154 | container.add(privilege) |
|---|
| 155 | # add dependable privileges |
|---|
| 156 | if privilege.dependencies: |
|---|
| 157 | for privilege in privilege.dependencies.iter_privileges(): |
|---|
| 158 | container.add(privilege) |
|---|
| 159 | |
|---|
| 160 | |
|---|
| 161 | def require_privilege(expr): |
|---|
| 162 | """Requires BLOG_ADMIN privilege or one of the given.""" |
|---|
| 163 | def wrapped(f): |
|---|
| 164 | def decorated(request, *args, **kwargs): |
|---|
| 165 | if request.user.has_privilege(expr): |
|---|
| 166 | return f(request, *args, **kwargs) |
|---|
| 167 | raise Forbidden() |
|---|
| 168 | decorated.__name__ = f.__name__ |
|---|
| 169 | decorated.__module__ = f.__module__ |
|---|
| 170 | decorated.__doc__ = f.__doc__ |
|---|
| 171 | return decorated |
|---|
| 172 | return wrapped |
|---|
| 173 | |
|---|
| 174 | |
|---|
| 175 | def assert_privilege(expr): |
|---|
| 176 | """Like the `require_privilege` decorator but for asserting.""" |
|---|
| 177 | if not get_request().user.has_privilege(expr): |
|---|
| 178 | raise Forbidden() |
|---|
| 179 | |
|---|
| 180 | |
|---|
| 181 | def privilege_attribute(lowlevel_attribute): |
|---|
| 182 | """Returns a proxy attribute for privilege access.""" |
|---|
| 183 | def creator_func(privilege): |
|---|
| 184 | if not isinstance(privilege, Privilege): |
|---|
| 185 | raise TypeError('%r is not a privilege object' % |
|---|
| 186 | type(privilege).__name__) |
|---|
| 187 | priv = _Privilege.query.filter_by(name=privilege.name).first() |
|---|
| 188 | if priv is None: |
|---|
| 189 | priv = _Privilege(privilege.name) |
|---|
| 190 | return priv |
|---|
| 191 | return db.association_proxy(lowlevel_attribute, 'privilege', |
|---|
| 192 | creator=creator_func) |
|---|
| 193 | |
|---|
| 194 | |
|---|
| 195 | def _register(name, description, privilege_dependencies=None): |
|---|
| 196 | """Register a new builtin privilege.""" |
|---|
| 197 | priv = Privilege(name, description, privilege_dependencies) |
|---|
| 198 | DEFAULT_PRIVILEGES[name] = priv |
|---|
| 199 | globals()[name] = priv |
|---|
| 200 | __all__.append(name) |
|---|
| 201 | |
|---|
| 202 | |
|---|
| 203 | _register('ENTER_ADMIN_PANEL', lazy_gettext(u'can enter admin panel')) |
|---|
| 204 | _register('BLOG_ADMIN', lazy_gettext(u'can administer the blog')) |
|---|
| 205 | _register('CREATE_ENTRIES', lazy_gettext(u'can create new entries'), |
|---|
| 206 | ENTER_ADMIN_PANEL) |
|---|
| 207 | _register('EDIT_OWN_ENTRIES', lazy_gettext(u'can edit their own entries'), |
|---|
| 208 | (ENTER_ADMIN_PANEL | CREATE_ENTRIES)) |
|---|
| 209 | _register('EDIT_OTHER_ENTRIES', lazy_gettext(u'can edit another person\'s entries'), |
|---|
| 210 | ENTER_ADMIN_PANEL) |
|---|
| 211 | _register('CREATE_PAGES', lazy_gettext(u'can create new pages'), |
|---|
| 212 | ENTER_ADMIN_PANEL) |
|---|
| 213 | _register('EDIT_OWN_PAGES', lazy_gettext(u'can edit their own pages'), |
|---|
| 214 | (ENTER_ADMIN_PANEL | CREATE_PAGES)) |
|---|
| 215 | _register('EDIT_OTHER_PAGES', lazy_gettext(u'can edit another person\'s pages'), |
|---|
| 216 | ENTER_ADMIN_PANEL) |
|---|
| 217 | _register('VIEW_DRAFTS', lazy_gettext(u'can view drafts')) |
|---|
| 218 | _register('MANAGE_CATEGORIES', lazy_gettext(u'can manage categories'), |
|---|
| 219 | ENTER_ADMIN_PANEL) |
|---|
| 220 | _register('MODERATE_COMMENTS', lazy_gettext(u'can moderate comments'), |
|---|
| 221 | ENTER_ADMIN_PANEL) |
|---|
| 222 | _register('MODERATE_OWN_ENTRIES', lazy_gettext(u'can moderate comments on it\'s own entries'), |
|---|
| 223 | (ENTER_ADMIN_PANEL | CREATE_ENTRIES)) |
|---|
| 224 | _register('MODERATE_OWN_PAGES', lazy_gettext(u'can moderate comments on it\'s own pages'), |
|---|
| 225 | (ENTER_ADMIN_PANEL | CREATE_PAGES)) |
|---|
| 226 | _register('VIEW_PROTECTED', lazy_gettext(u'can view protected entries')) |
|---|
| 227 | |
|---|
| 228 | # Regular users permissions |
|---|
| 229 | _register('ENTER_ACCOUNT_PANEL', lazy_gettext(u'can enter his account panel')) |
|---|
| 230 | _register('EDIT_OWN_COMMENTS', lazy_gettext(u'can edit own comments'), |
|---|
| 231 | ENTER_ACCOUNT_PANEL) |
|---|
| 232 | _register('DELETE_OWN_COMMENTS', lazy_gettext(u'can delete own comments'), |
|---|
| 233 | ENTER_ACCOUNT_PANEL) |
|---|
| 234 | |
|---|
| 235 | |
|---|
| 236 | CONTENT_TYPE_PRIVILEGES = { |
|---|
| 237 | 'entry': (CREATE_ENTRIES, EDIT_OWN_ENTRIES, EDIT_OTHER_ENTRIES), |
|---|
| 238 | 'page': (CREATE_PAGES, EDIT_OWN_PAGES, EDIT_OTHER_PAGES) |
|---|
| 239 | } |
|---|