| 1 | ################################################################################## |
|---|
| 2 | # Copyright (c) 2004-2009 Utah State University, All rights reserved. |
|---|
| 3 | # Portions copyright 2009 Massachusetts Institute of Technology, All rights reserved. |
|---|
| 4 | # |
|---|
| 5 | # This program is free software; you can redistribute it and/or modify |
|---|
| 6 | # it under the terms of the GNU General Public License as published by |
|---|
| 7 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 8 | # (at your option) any later version. |
|---|
| 9 | # |
|---|
| 10 | # This program is distributed in the hope that it will be useful, |
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | # GNU General Public License for more details. |
|---|
| 14 | # |
|---|
| 15 | # You should have received a copy of the GNU General Public License |
|---|
| 16 | # along with this program; if not, write to the Free Software |
|---|
| 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 18 | # |
|---|
| 19 | ################################################################################## |
|---|
| 20 | |
|---|
| 21 | __author__ = '''Brent Lambert, David Ray, Jon Thomas''' |
|---|
| 22 | __version__ = '$ Revision 0.0 $'[11:-2] |
|---|
| 23 | |
|---|
| 24 | from zope.component import getUtilitiesFor, queryUtility, getMultiAdapter |
|---|
| 25 | |
|---|
| 26 | from Products.Five.browser import BrowserView |
|---|
| 27 | from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile |
|---|
| 28 | |
|---|
| 29 | from Acquisition import aq_inner, aq_parent, aq_base |
|---|
| 30 | from AccessControl import Unauthorized |
|---|
| 31 | from zExceptions import Forbidden |
|---|
| 32 | |
|---|
| 33 | from Products.CMFCore.utils import getToolByName |
|---|
| 34 | from Products.CMFCore import permissions |
|---|
| 35 | from Products.CMFPlone import PloneMessageFactory as _ |
|---|
| 36 | |
|---|
| 37 | from plone.memoize.instance import memoize, clearafter |
|---|
| 38 | |
|---|
| 39 | from plone.app.workflow.interfaces import ISharingPageRole |
|---|
| 40 | |
|---|
| 41 | from enpraxis.educommons.browser.interfaces import IeduCommonsSharingPageRole |
|---|
| 42 | |
|---|
| 43 | AUTH_GROUP = 'AuthenticatedUsers' |
|---|
| 44 | STICKY = (AUTH_GROUP,) |
|---|
| 45 | |
|---|
| 46 | class SharingView(BrowserView): |
|---|
| 47 | |
|---|
| 48 | # Actions |
|---|
| 49 | template = ViewPageTemplateFile('sharing.pt') |
|---|
| 50 | |
|---|
| 51 | def __call__(self): |
|---|
| 52 | """Perform the update and redirect if necessary, or render the page |
|---|
| 53 | """ |
|---|
| 54 | |
|---|
| 55 | postback = True |
|---|
| 56 | |
|---|
| 57 | form = self.request.form |
|---|
| 58 | submitted = form.get('form.submitted', False) |
|---|
| 59 | |
|---|
| 60 | save_button = form.get('form.button.Save', None) is not None |
|---|
| 61 | cancel_button = form.get('form.button.Cancel', None) is not None |
|---|
| 62 | |
|---|
| 63 | if submitted and not cancel_button: |
|---|
| 64 | |
|---|
| 65 | if not self.request.get('REQUEST_METHOD','GET') == 'POST': |
|---|
| 66 | raise Forbidden |
|---|
| 67 | |
|---|
| 68 | # Update the acquire-roles setting |
|---|
| 69 | inherit = bool(form.get('inherit', False)) |
|---|
| 70 | self.update_inherit(inherit) |
|---|
| 71 | |
|---|
| 72 | # Update settings for users and groups |
|---|
| 73 | entries = form.get('entries', []) |
|---|
| 74 | roles = [r['id'] for r in self.roles()] |
|---|
| 75 | settings = [] |
|---|
| 76 | for entry in entries: |
|---|
| 77 | settings.append( |
|---|
| 78 | dict(id = entry['id'], |
|---|
| 79 | type = entry['type'], |
|---|
| 80 | roles = [r for r in roles if entry.get('role_%s' % r, False)])) |
|---|
| 81 | if settings: |
|---|
| 82 | self.update_role_settings(settings) |
|---|
| 83 | |
|---|
| 84 | # Other buttons return to the sharing page |
|---|
| 85 | if cancel_button: |
|---|
| 86 | postback = False |
|---|
| 87 | |
|---|
| 88 | if postback: |
|---|
| 89 | return self.template() |
|---|
| 90 | else: |
|---|
| 91 | context_state = self.context.restrictedTraverse("@@plone_context_state") |
|---|
| 92 | url = context_state.view_url() |
|---|
| 93 | self.request.response.redirect(url) |
|---|
| 94 | |
|---|
| 95 | # View |
|---|
| 96 | |
|---|
| 97 | @memoize |
|---|
| 98 | def roles(self): |
|---|
| 99 | """Get a list of roles that can be managed. |
|---|
| 100 | |
|---|
| 101 | Returns a list of dics with keys: |
|---|
| 102 | |
|---|
| 103 | - id |
|---|
| 104 | - title |
|---|
| 105 | """ |
|---|
| 106 | context = aq_inner(self.context) |
|---|
| 107 | portal_membership = getToolByName(context, 'portal_membership') |
|---|
| 108 | |
|---|
| 109 | pairs = [] |
|---|
| 110 | |
|---|
| 111 | #check to see if OpenOCW installed |
|---|
| 112 | portal_setup = self.aq_parent.portal_setup |
|---|
| 113 | if 'openOCW-final' in portal_setup.getImportStepRegistry().listSteps(): |
|---|
| 114 | #user default sharing page roles |
|---|
| 115 | for name, utility in getUtilitiesFor(ISharingPageRole): |
|---|
| 116 | permission = utility.required_permission |
|---|
| 117 | if permission is None or portal_membership.checkPermission(permission, context): |
|---|
| 118 | pairs.append(dict(id = name, title = utility.title)) |
|---|
| 119 | else: |
|---|
| 120 | #use eduCommons sharing page roles |
|---|
| 121 | for name, utility in getUtilitiesFor(IeduCommonsSharingPageRole): |
|---|
| 122 | permission = utility.required_permission |
|---|
| 123 | if permission is None or portal_membership.checkPermission(permission, context): |
|---|
| 124 | pairs.append(dict(id = name, title = utility.title)) |
|---|
| 125 | |
|---|
| 126 | pairs.sort(lambda x, y: cmp(x['id'], y['id'])) |
|---|
| 127 | return pairs |
|---|
| 128 | |
|---|
| 129 | @memoize |
|---|
| 130 | def role_settings(self): |
|---|
| 131 | """Get current settings for users and groups for which settings have been made. |
|---|
| 132 | |
|---|
| 133 | Returns a list of dicts with keys: |
|---|
| 134 | |
|---|
| 135 | - id |
|---|
| 136 | - title |
|---|
| 137 | - type (one of 'group' or 'user') |
|---|
| 138 | - roles |
|---|
| 139 | |
|---|
| 140 | 'roles' is a dict of settings, with keys of role ids as returned by |
|---|
| 141 | roles(), and values True if the role is explicitly set, False |
|---|
| 142 | if the role is explicitly disabled and None if the role is inherited. |
|---|
| 143 | """ |
|---|
| 144 | |
|---|
| 145 | existing_settings = self.existing_role_settings() |
|---|
| 146 | user_results = self.user_search_results() |
|---|
| 147 | group_results = self.group_search_results() |
|---|
| 148 | |
|---|
| 149 | return existing_settings + user_results + group_results |
|---|
| 150 | |
|---|
| 151 | def inherited(self, context=None): |
|---|
| 152 | """Return True if local roles are inherited here. |
|---|
| 153 | """ |
|---|
| 154 | if context is None: |
|---|
| 155 | context = self.context |
|---|
| 156 | if getattr(aq_base(context), '__ac_local_roles_block__', None): |
|---|
| 157 | return False |
|---|
| 158 | return True |
|---|
| 159 | |
|---|
| 160 | # helper functions |
|---|
| 161 | |
|---|
| 162 | @memoize |
|---|
| 163 | def existing_role_settings(self): |
|---|
| 164 | """Get current settings for users and groups that have already got |
|---|
| 165 | at least one of the managed local roles. |
|---|
| 166 | |
|---|
| 167 | Returns a list of dicts as per role_settings() |
|---|
| 168 | """ |
|---|
| 169 | context = aq_inner(self.context) |
|---|
| 170 | |
|---|
| 171 | portal_membership = getToolByName(aq_inner(self.context), 'portal_membership') |
|---|
| 172 | portal_groups = getToolByName(aq_inner(self.context), 'portal_groups') |
|---|
| 173 | portal = getToolByName(aq_inner(self.context), 'portal_url').getPortalObject() |
|---|
| 174 | acl_users = getattr(portal, 'acl_users') |
|---|
| 175 | |
|---|
| 176 | info = [] |
|---|
| 177 | |
|---|
| 178 | # This logic is adapted from computeRoleMap.py |
|---|
| 179 | |
|---|
| 180 | local_roles = acl_users.getLocalRolesForDisplay(context) |
|---|
| 181 | acquired_roles = self._inherited_roles() |
|---|
| 182 | available_roles = [r['id'] for r in self.roles()] |
|---|
| 183 | |
|---|
| 184 | # first process acquired roles |
|---|
| 185 | items = {} |
|---|
| 186 | for name, roles, rtype, rid in acquired_roles: |
|---|
| 187 | items[rid] = dict(id = rid, |
|---|
| 188 | name = name, |
|---|
| 189 | type = rtype, |
|---|
| 190 | sitewide = [], |
|---|
| 191 | acquired = roles, |
|---|
| 192 | local = [],) |
|---|
| 193 | |
|---|
| 194 | # second process local roles |
|---|
| 195 | for name, roles, rtype, rid in local_roles: |
|---|
| 196 | if items.has_key(rid): |
|---|
| 197 | items[rid]['local'] = roles |
|---|
| 198 | else: |
|---|
| 199 | items[rid] = dict(id = rid, |
|---|
| 200 | name = name, |
|---|
| 201 | type = rtype, |
|---|
| 202 | sitewide = [], |
|---|
| 203 | acquired = [], |
|---|
| 204 | local = roles,) |
|---|
| 205 | |
|---|
| 206 | # Make sure we always get the authenticated users virtual group |
|---|
| 207 | if AUTH_GROUP not in items: |
|---|
| 208 | items[AUTH_GROUP] = dict(id = AUTH_GROUP, |
|---|
| 209 | name = _(u'Logged-in users'), |
|---|
| 210 | type = 'group', |
|---|
| 211 | sitewide = [], |
|---|
| 212 | acquired = [], |
|---|
| 213 | local = [],) |
|---|
| 214 | |
|---|
| 215 | # Sort the list: first the authenticated users virtual group, then |
|---|
| 216 | # all other groups and then all users, alphabetically |
|---|
| 217 | |
|---|
| 218 | dec_users = [( a['id'] not in STICKY, |
|---|
| 219 | a['type'], |
|---|
| 220 | a['name'], |
|---|
| 221 | a) for a in items.values()] |
|---|
| 222 | dec_users.sort() |
|---|
| 223 | |
|---|
| 224 | # Add the items to the info dict, assigning full name if possible. |
|---|
| 225 | # Also, recut roles in the format specified in the docstring |
|---|
| 226 | |
|---|
| 227 | for d in dec_users: |
|---|
| 228 | item = d[-1] |
|---|
| 229 | name = item['name'] |
|---|
| 230 | rid = item['id'] |
|---|
| 231 | global_roles = set() |
|---|
| 232 | |
|---|
| 233 | if item['type'] == 'user': |
|---|
| 234 | member = acl_users.getUserById(rid) |
|---|
| 235 | if member is not None: |
|---|
| 236 | name = member.getProperty('fullname') or member.getId() or name |
|---|
| 237 | global_roles = set(member.getRoles()) |
|---|
| 238 | elif item['type'] == 'group': |
|---|
| 239 | g = portal_groups.getGroupById(rid) |
|---|
| 240 | name = g.getGroupTitleOrName() |
|---|
| 241 | global_roles = set(g.getRoles()) |
|---|
| 242 | |
|---|
| 243 | # This isn't a proper group, so it needs special treatment :( |
|---|
| 244 | if rid == AUTH_GROUP: |
|---|
| 245 | name = _(u'Logged-in users') |
|---|
| 246 | |
|---|
| 247 | info_item = dict(id = item['id'], |
|---|
| 248 | type = item['type'], |
|---|
| 249 | title = name, |
|---|
| 250 | roles = {}) |
|---|
| 251 | |
|---|
| 252 | # Record role settings |
|---|
| 253 | have_roles = False |
|---|
| 254 | for r in available_roles: |
|---|
| 255 | if r in global_roles: |
|---|
| 256 | info_item['roles'][r] = 'global' |
|---|
| 257 | elif r in item['acquired']: |
|---|
| 258 | info_item['roles'][r] = 'acquired' |
|---|
| 259 | have_roles = True # we want to show acquired roles |
|---|
| 260 | elif r in item['local']: |
|---|
| 261 | info_item['roles'][r] = True |
|---|
| 262 | have_roles = True # at least one role is set |
|---|
| 263 | else: |
|---|
| 264 | info_item['roles'][r] = False |
|---|
| 265 | |
|---|
| 266 | if have_roles or rid in STICKY: |
|---|
| 267 | info.append(info_item) |
|---|
| 268 | |
|---|
| 269 | return info |
|---|
| 270 | |
|---|
| 271 | def user_search_results(self): |
|---|
| 272 | """Return search results for a query to add new users |
|---|
| 273 | |
|---|
| 274 | Returns a list of dicts, as per role_settings() |
|---|
| 275 | """ |
|---|
| 276 | context = aq_inner(self.context) |
|---|
| 277 | acl_users = getToolByName(context, 'acl_users') |
|---|
| 278 | |
|---|
| 279 | search_term = self.request.form.get('search_term', None) |
|---|
| 280 | if not search_term: |
|---|
| 281 | return [] |
|---|
| 282 | |
|---|
| 283 | existing_users = set([u['id'] for u in self.existing_role_settings() |
|---|
| 284 | if u['type'] == 'user']) |
|---|
| 285 | empty_roles = dict([(r['id'], False) for r in self.roles()]) |
|---|
| 286 | info = [] |
|---|
| 287 | |
|---|
| 288 | hunter = getMultiAdapter((context, self.request), name='pas_search') |
|---|
| 289 | for userinfo in hunter.searchUsers(fullname=search_term): |
|---|
| 290 | userid = userinfo['userid'] |
|---|
| 291 | if userid not in existing_users: |
|---|
| 292 | user = acl_users.getUserById(userid) |
|---|
| 293 | roles = empty_roles.copy() |
|---|
| 294 | for r in user.getRoles(): |
|---|
| 295 | if r in roles: |
|---|
| 296 | roles[r] = 'global' |
|---|
| 297 | info.append(dict(id = userid, |
|---|
| 298 | title = user.getProperty('fullname') or user.getId() or userid, |
|---|
| 299 | type = 'user', |
|---|
| 300 | roles = roles)) |
|---|
| 301 | return info |
|---|
| 302 | |
|---|
| 303 | def group_search_results(self): |
|---|
| 304 | """Return search results for a query to add new groups |
|---|
| 305 | |
|---|
| 306 | Returns a list of dicts, as per role_settings() |
|---|
| 307 | """ |
|---|
| 308 | context = aq_inner(self.context) |
|---|
| 309 | portal_groups = getToolByName(context, 'portal_groups') |
|---|
| 310 | |
|---|
| 311 | search_term = self.request.form.get('search_term', None) |
|---|
| 312 | if not search_term: |
|---|
| 313 | return [] |
|---|
| 314 | |
|---|
| 315 | existing_groups = set([g['id'] for g in self.existing_role_settings() |
|---|
| 316 | if g['type'] == 'group']) |
|---|
| 317 | empty_roles = dict([(r['id'], False) for r in self.roles()]) |
|---|
| 318 | info = [] |
|---|
| 319 | |
|---|
| 320 | hunter = getMultiAdapter((context, self.request), name='pas_search') |
|---|
| 321 | for groupinfo in hunter.searchGroups(id=search_term): |
|---|
| 322 | groupid = groupinfo['groupid'] |
|---|
| 323 | if groupid not in existing_groups: |
|---|
| 324 | group = portal_groups.getGroupById(groupid) |
|---|
| 325 | roles = empty_roles.copy() |
|---|
| 326 | for r in group.getRoles(): |
|---|
| 327 | if r in roles: |
|---|
| 328 | roles[r] = 'global' |
|---|
| 329 | info.append(dict(id = groupid, |
|---|
| 330 | title = group.getGroupTitleOrName(), |
|---|
| 331 | type = 'group', |
|---|
| 332 | roles = roles)) |
|---|
| 333 | return info |
|---|
| 334 | |
|---|
| 335 | def _inherited_roles(self): |
|---|
| 336 | """Returns a tuple with the acquired local roles.""" |
|---|
| 337 | context = aq_inner(self.context) |
|---|
| 338 | |
|---|
| 339 | if not self.inherited(context): |
|---|
| 340 | return [] |
|---|
| 341 | |
|---|
| 342 | portal = getToolByName(context, 'portal_url').getPortalObject() |
|---|
| 343 | result = [] |
|---|
| 344 | cont = True |
|---|
| 345 | if portal != context: |
|---|
| 346 | parent = aq_parent(context) |
|---|
| 347 | while cont: |
|---|
| 348 | if not getattr(parent, 'acl_users', False): |
|---|
| 349 | break |
|---|
| 350 | userroles = parent.acl_users._getLocalRolesForDisplay(parent) |
|---|
| 351 | for user, roles, role_type, name in userroles: |
|---|
| 352 | # Find user in result |
|---|
| 353 | found = 0 |
|---|
| 354 | for user2, roles2, type2, name2 in result: |
|---|
| 355 | if user2 == user: |
|---|
| 356 | # Check which roles must be added to roles2 |
|---|
| 357 | for role in roles: |
|---|
| 358 | if not role in roles2: |
|---|
| 359 | roles2.append(role) |
|---|
| 360 | found = 1 |
|---|
| 361 | break |
|---|
| 362 | if found == 0: |
|---|
| 363 | # Add it to result and make sure roles is a list so |
|---|
| 364 | # we may append and not overwrite the loop variable |
|---|
| 365 | result.append([user, list(roles), role_type, name]) |
|---|
| 366 | if parent == portal: |
|---|
| 367 | cont = False |
|---|
| 368 | elif not self.inherited(parent): |
|---|
| 369 | # Role acquired check here |
|---|
| 370 | cont = False |
|---|
| 371 | else: |
|---|
| 372 | parent = aq_parent(parent) |
|---|
| 373 | |
|---|
| 374 | # Tuplize all inner roles |
|---|
| 375 | for pos in range(len(result)-1,-1,-1): |
|---|
| 376 | result[pos][1] = tuple(result[pos][1]) |
|---|
| 377 | result[pos] = tuple(result[pos]) |
|---|
| 378 | |
|---|
| 379 | return tuple(result) |
|---|
| 380 | |
|---|
| 381 | def update_inherit(self, status=True): |
|---|
| 382 | """Enable or disable local role acquisition on the context. |
|---|
| 383 | """ |
|---|
| 384 | context = aq_inner(self.context) |
|---|
| 385 | portal_membership = getToolByName(context, 'portal_membership') |
|---|
| 386 | |
|---|
| 387 | if not portal_membership.checkPermission(permissions.ModifyPortalContent, context): |
|---|
| 388 | raise Unauthorized |
|---|
| 389 | |
|---|
| 390 | if not status: |
|---|
| 391 | context.__ac_local_roles_block__ = True |
|---|
| 392 | else: |
|---|
| 393 | if getattr(aq_base(context), '__ac_local_roles_block__', None): |
|---|
| 394 | context.__ac_local_roles_block__ = None |
|---|
| 395 | |
|---|
| 396 | context.reindexObjectSecurity() |
|---|
| 397 | |
|---|
| 398 | @clearafter |
|---|
| 399 | def update_role_settings(self, new_settings): |
|---|
| 400 | """Update local role settings and reindex object security if necessary. |
|---|
| 401 | |
|---|
| 402 | new_settings is a list of dicts with keys id, for the user/group id; |
|---|
| 403 | type, being either 'user' or 'group'; and roles, containing the list |
|---|
| 404 | of role ids that are set. |
|---|
| 405 | """ |
|---|
| 406 | |
|---|
| 407 | reindex = False |
|---|
| 408 | context = aq_inner(self.context) |
|---|
| 409 | |
|---|
| 410 | managed_roles = frozenset([r['id'] for r in self.roles()]) |
|---|
| 411 | member_ids_to_clear = [] |
|---|
| 412 | for s in new_settings: |
|---|
| 413 | user_id = s['id'] |
|---|
| 414 | |
|---|
| 415 | existing_roles = frozenset(context.get_local_roles_for_userid(userid=user_id)) |
|---|
| 416 | selected_roles = frozenset(s['roles']) |
|---|
| 417 | |
|---|
| 418 | # We will remove those roles that we are managing and which set |
|---|
| 419 | # on the context, but which were not selected |
|---|
| 420 | to_remove = (managed_roles & existing_roles) - selected_roles |
|---|
| 421 | |
|---|
| 422 | # Leaving us with the selected roles, less any roles that we |
|---|
| 423 | # want to remove |
|---|
| 424 | new_roles = (selected_roles | existing_roles) - to_remove |
|---|
| 425 | |
|---|
| 426 | # take away roles that we are managing, that were not selected |
|---|
| 427 | # and which were part of the existing roles |
|---|
| 428 | |
|---|
| 429 | if new_roles: |
|---|
| 430 | context.manage_setLocalRoles(user_id, list(new_roles)) |
|---|
| 431 | reindex = True |
|---|
| 432 | elif existing_roles: |
|---|
| 433 | member_ids_to_clear.append(user_id) |
|---|
| 434 | |
|---|
| 435 | if member_ids_to_clear: |
|---|
| 436 | context.manage_delLocalRoles(userids=member_ids_to_clear) |
|---|
| 437 | reindex = True |
|---|
| 438 | |
|---|
| 439 | if reindex: |
|---|
| 440 | self.context.reindexObjectSecurity() |
|---|