Back to index

plone3  3.1.7
Install.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # PlonePAS - Adapt PluggableAuthService for use in Plone
00004 # Copyright (C) 2005 Enfold Systems, Kapil Thangavelu, et al
00005 #
00006 # This software is subject to the provisions of the Zope Public License,
00007 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this
00008 # distribution.
00009 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00010 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00011 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00012 # FOR A PARTICULAR PURPOSE.
00013 #
00014 ##############################################################################
00015 """
00016 """
00017 
00018 from sets import Set
00019 from StringIO import StringIO
00020 
00021 from Products.CMFCore.utils import getToolByName
00022 from Products.CMFCore.Expression import Expression
00023 
00024 from zope.component.interfaces import ComponentLookupError
00025 
00026 from Products.PluggableAuthService.interfaces.authservice \
00027         import IPluggableAuthService
00028 from Products.PluggableAuthService.interfaces.plugins import IPropertiesPlugin
00029 from Products.PluggableAuthService.interfaces.plugins import IGroupsPlugin
00030 from Products.PluggableAuthService.interfaces.plugins \
00031      import IGroupEnumerationPlugin
00032 from Products.PluggableAuthService.interfaces.plugins \
00033      import ICredentialsResetPlugin
00034 from Products.PluggableAuthService.interfaces.plugins \
00035      import IChallengePlugin
00036 from Products.PluggableAuthService.Extensions.upgrade \
00037      import replace_acl_users
00038 
00039 from Products.PlonePAS import config
00040 from Products.PlonePAS.interfaces.plugins import IUserManagement
00041 from Products.PlonePAS.interfaces.plugins import IUserIntrospection
00042 from Products.PlonePAS.interfaces.plugins import ILocalRolesPlugin
00043 from Products.PlonePAS.interfaces import group as igroup
00044 
00045 from Products.PlonePAS.tools.groups import GroupsTool
00046 from Products.PlonePAS.tools.groupdata import GroupDataTool
00047 from Products.PlonePAS.tools.membership import MembershipTool
00048 from Products.PlonePAS.tools.memberdata import MemberDataTool
00049 
00050 from Products.PlonePAS.MigrationCheck import canAutoMigrate
00051 from plone.session.plugins.session import manage_addSessionPlugin
00052 
00053 try:
00054     import Products.LDAPMultiPlugins
00055     CAN_LDAP = True
00056 except ImportError:
00057     CAN_LDAP = False
00058 
00059 
00060 def activatePluginInterfaces(portal, plugin, out, disable=[]):
00061     pas = portal.acl_users
00062     plugin_obj = pas[plugin]
00063 
00064     activatable = []
00065 
00066     for info in plugin_obj.plugins.listPluginTypeInfo():
00067         interface = info['interface']
00068         interface_name = info['id']
00069         if plugin_obj.testImplements(interface):
00070             if interface_name in disable:
00071                 disable.append(interface_name)
00072                 print >> out, " - Disabling: " + info['title']
00073             else:
00074                 activatable.append(interface_name)
00075                 print >> out, " - Activating: " + info['title']
00076     plugin_obj.manage_activateInterfaces(activatable)
00077     print >> out, plugin + " activated."
00078 
00079 
00080 def installProducts(portal, out):
00081     print >> out, "\nInstalling other products"
00082     qi = getToolByName(portal, 'portal_quickinstaller')
00083 
00084     print >> out, " - PasswordResetTool"
00085     qi.installProduct('PasswordResetTool')
00086 
00087 
00088 def setupRoles(portal):
00089     rmanager = portal.acl_users.role_manager
00090     rmanager.addRole('Member', title="Portal Member")
00091     rmanager.addRole('Reviewer', title="Content Reviewer")
00092 
00093 
00094 def registerPluginType(pas, plugin_type, plugin_info):
00095     # Make sure there's no dupes in _plugin_types, otherwise your PAS
00096     # will *CRAWL*
00097     plugin_types = list(Set(pas.plugins._plugin_types))
00098     if not plugin_type in plugin_types:
00099         plugin_types.append(plugin_type)
00100 
00101     # Order doesn't seem to matter, but let's store it ordered.
00102     plugin_types.sort()
00103 
00104     # Re-assign to the object, because this is a non-persistent list.
00105     pas.plugins._plugin_types = plugin_types
00106 
00107     # It's safe to assign over a existing key here.
00108     pas.plugins._plugin_type_info[plugin_type] =  plugin_info
00109 
00110 
00111 def registerPluginTypes(pas):
00112 
00113     PluginInfo = {
00114         'id' : 'IUserManagement',
00115         'title': 'user_management',
00116         'description': ("The User Management plugins allow the "
00117                         "Pluggable Auth Service to add/delete/modify users")
00118         }
00119 
00120     registerPluginType(pas, IUserManagement, PluginInfo)
00121 
00122     PluginInfo = {
00123         'id' : 'IUserIntrospection',
00124         'title': 'user_introspection',
00125         'description': ("The User Introspection plugins allow the "
00126                         "Pluggable Auth Service to provide lists of users")
00127         }
00128 
00129     registerPluginType(pas, IUserIntrospection, PluginInfo)
00130 
00131     PluginInfo = {
00132         'id' : 'IGroupManagement',
00133         'title': 'group_management',
00134         'description': ("Group Management provides add/write/deletion "
00135                         "of groups and member management")
00136         }
00137 
00138     registerPluginType(pas, igroup.IGroupManagement, PluginInfo)
00139 
00140     PluginInfo = {
00141         'id' : 'IGroupIntrospection',
00142         'title': 'group_introspection',
00143         'description': ("Group Introspection provides listings "
00144                         "of groups and membership")
00145         }
00146 
00147     registerPluginType(pas, igroup.IGroupIntrospection, PluginInfo)
00148 
00149     PluginInfo = {
00150         'id' : 'ILocalRolesPlugin',
00151         'title': 'local_roles',
00152         'description': "Defines Policy for getting Local Roles"
00153         }
00154 
00155     registerPluginType(pas, ILocalRolesPlugin, PluginInfo)
00156 
00157 
00158 def setupPlugins(portal, out):
00159     uf = portal.acl_users
00160     print >> out, "\nPlugin setup"
00161 
00162     pas = uf.manage_addProduct['PluggableAuthService']
00163     plone_pas = uf.manage_addProduct['PlonePAS']
00164 
00165     setupAuthPlugins(portal, pas, plone_pas, out)
00166 
00167     found = uf.objectIds(['User Manager'])
00168     if not found:
00169         plone_pas.manage_addUserManager('source_users')
00170         print >> out, "Added User Manager."
00171     activatePluginInterfaces(portal, 'source_users', out)
00172 
00173     found = uf.objectIds(['Group Aware Role Manager'])
00174     if not found:
00175         plone_pas.manage_addGroupAwareRoleManager('portal_role_manager')
00176         print >> out, "Added Group Aware Role Manager."
00177         activatePluginInterfaces(portal, 'portal_role_manager', out)
00178 
00179     found = uf.objectIds(['Local Roles Manager'])
00180     if not found:
00181         plone_pas.manage_addLocalRolesManager('local_roles')
00182         print >> out, "Added Group Aware Role Manager."
00183         activatePluginInterfaces(portal, 'local_roles', out)
00184 
00185     found = uf.objectIds(['Group Manager'])
00186     if not found:
00187         plone_pas.manage_addGroupManager('source_groups')
00188         print >> out, "Added ZODB Group Manager."
00189         activatePluginInterfaces(portal, 'source_groups', out)
00190 
00191     found = uf.objectIds(['Plone User Factory'])
00192     if not found:
00193         plone_pas.manage_addPloneUserFactory('user_factory')
00194         print >> out, "Added Plone User Factory."
00195         activatePluginInterfaces(portal, "user_factory", out)
00196 
00197     found = uf.objectIds(['ZODB Mutable Property Provider'])
00198     if not found:
00199         plone_pas.manage_addZODBMutablePropertyProvider('mutable_properties')
00200         print >> out, "Added Mutable Property Manager."
00201         activatePluginInterfaces(portal, "mutable_properties", out)
00202 
00203     found = uf.objectIds(['Automatic Group Plugin'])
00204     if not found:
00205         plone_pas.manage_addAutoGroup('auto_group', "Automatic Group Provider",
00206                 "AuthenticatedUsers", "Authenticated Users (Virtual Group)")
00207         print >> out, "Added Automatic Group."
00208         activatePluginInterfaces(portal, "auto_group", out)
00209 
00210     found = uf.objectIds(['Plone Session Plugin'])
00211     if not found:
00212         manage_addSessionPlugin(plone_pas, 'session')
00213         print >> out, "Added Plone Session Plugin."
00214         activatePluginInterfaces(portal, "session", out)
00215 
00216 
00217 def setupAuthPlugins(portal, pas, plone_pas, out,
00218                      deactivate_basic_reset=True,
00219                      deactivate_cookie_challenge=False):
00220     uf = portal.acl_users
00221     print >> out, " cookie plugin setup"
00222 
00223     login_path = 'login_form'
00224     logout_path = 'logged_out'
00225     cookie_name = '__ac'
00226 
00227     crumbler = getToolByName(portal, 'cookie_authentication', None)
00228     if crumbler is not None:
00229         login_path = crumbler.auto_login_page
00230         logout_path = crumbler.logout_page
00231         cookie_name = crumbler.auth_cookie
00232 
00233     # note: old versions of PlonePAS (< 0.4.2) may leave a 'Cookie
00234     #       Auth Helper' by the same name
00235     found = uf.objectIds(['Cookie Auth Helper'])
00236     if found and 'credentials_cookie_auth' in found:
00237         print >> out, " old credentials_cookie_auth found; removing"
00238         login_path = uf.credentials_cookie_auth.login_path
00239         cookie_name = uf.credentials_cookie_auth.cookie_name
00240         uf.manage_delObjects(['credentials_cookie_auth'])
00241 
00242     found = uf.objectIds(['Extended Cookie Auth Helper'])
00243     if not found:
00244         plone_pas.manage_addExtendedCookieAuthHelper('credentials_cookie_auth',
00245                                                      cookie_name=cookie_name)
00246     print >> out, "Added Extended Cookie Auth Helper."
00247     if deactivate_basic_reset:
00248         disable=['ICredentialsResetPlugin', 'ICredentialsUpdatePlugin']
00249     else:
00250         disable=[]
00251     activatePluginInterfaces(portal, 'credentials_cookie_auth', out,
00252             disable=disable)
00253 
00254     credentials_cookie_auth = uf._getOb('credentials_cookie_auth')
00255     if 'login_form' in credentials_cookie_auth.objectIds():
00256         credentials_cookie_auth.manage_delObjects(ids=['login_form'])
00257         print >> out, "Removed default login_form from credentials cookie auth."
00258     credentials_cookie_auth.cookie_name = cookie_name
00259     credentials_cookie_auth.login_path = login_path
00260 
00261     # remove cookie crumbler(s)
00262     if 'cookie_authentication' in portal.objectIds():
00263         portal.manage_delObjects(['cookie_authentication'])
00264 
00265     ccs = portal.objectValues('Cookie Crumbler')
00266     assert not ccs, "Extra cookie crumblers found."
00267     print >> out, "Removed old Cookie Crumbler"
00268 
00269     found = uf.objectIds(['HTTP Basic Auth Helper'])
00270     if not found:
00271         pas.addHTTPBasicAuthHelper('credentials_basic_auth',
00272                                title="HTTP Basic Auth")
00273     print >> out, "Added Basic Auth Helper."
00274     activatePluginInterfaces(portal, 'credentials_basic_auth', out)
00275 
00276     if deactivate_basic_reset:
00277         uf.plugins.deactivatePlugin(ICredentialsResetPlugin,
00278                                      'credentials_basic_auth')
00279     if deactivate_cookie_challenge:
00280         uf.plugins.deactivatePlugin(IChallengePlugin,
00281                                      'credentials_cookie_auth')
00282 
00283 
00284 def configurePlonePAS(portal, out):
00285     """Add the necessary objects to make a usable PAS instance
00286     """
00287     installProducts(portal, out)
00288     registerPluginTypes(portal.acl_users)
00289     setupPlugins(portal, out)
00290 #    setupRoles( portal )
00291 
00292 
00293 def grabUserData(portal, out):
00294     """Return a list of (id, password, roles, domains, properties)
00295     tuples for the users of the system.
00296 
00297     Password may be encypted or not: addUser will figure it out.
00298     """
00299     print >> out, "\nExtract Member information..."
00300 
00301     userdata = ()
00302     try:
00303         mdtool = getToolByName(portal, "portal_memberdata")
00304         mtool = getToolByName(portal, "portal_membership")
00305     except ComponentLookupError:
00306         return userdata
00307 
00308     props = mdtool.propertyIds()
00309     members = mtool.listMembers()
00310     userids=set()
00311     for member in members:
00312         id = member.getId()
00313         print >> out, " : %s" % id
00314         password = member.getPassword()
00315         roles = [role for role in member.getRoles() if role != 'Authenticated']
00316         print >> out, "with roles %s" % roles
00317         domains = member.getDomains()
00318         properties = {}
00319         for propid in props:
00320             properties[propid] = member.getProperty(propid, None)
00321         portrait=mtool.getPersonalPortrait(id)
00322         if portrait is not None:
00323             portrait=portrait.aq_base
00324         userdata += ((id, password, roles, domains, properties, portrait),)
00325         userids.add(id)
00326 
00327     for (id,data) in mdtool._members.items():
00328         if id not in userids:
00329             userdata+= ((id, None, None, None, data.__dict__, None),)
00330             userids.add(id)
00331 
00332     print >> out, "...extract done"
00333     return userdata
00334 
00335 
00336 def restoreUserData(portal, out, userdata):
00337     print >> out, "\nRestoring Member information..."
00338 
00339     # re-add users
00340     # Password may be encypted or not: addUser will figure it out.
00341     mdtool = getToolByName(portal, "portal_memberdata")
00342     mtool = getToolByName(portal, "portal_membership")
00343     emerg = portal.acl_users._emergency_user.getId()
00344     for u in userdata:
00345         if u[0] == emerg:
00346             print >> out, (" : WARNING! member '%s' has name of "
00347                            "emergency user. Not migrated." % u[0])
00348             print >> out, ("You can undo the install if you want "
00349                            "to fix this condition.")
00350             continue  # skip Emergency User, if present
00351 
00352         # be careful of non-ZODB member sources, like LDAP
00353         member = mtool.getMemberById(u[0])
00354         if member is None:
00355             if u[1] is not None:
00356                 mtool.addMember(*u[:5])
00357                 print >> out, " : adding member '%s'" % u[0]
00358             else:
00359                 print >> out, " : ignored member '%s' without password." % u[0]
00360         else:
00361             # set any properties. do we need anything else? roles, maybe?
00362             member.setMemberProperties(u[4])
00363             print >> out, " : setting props on member '%s'" % member.getId()
00364 
00365         if u[5] is not None:
00366             mdtool._setPortrait(u[5], u[0])
00367 
00368     print >> out, "...restore done"
00369 
00370 
00371 def grabGroupData(portal, out):
00372     """Return a list of (id, roles, groups, properties) tuples for the
00373     users of the system and a mapping of group ids to a list of group
00374     members.
00375     """
00376     print >> out, "\nExtract Group information..."
00377 
00378     groupdata = ()
00379     groupmemberships = {}
00380     gdtool = getToolByName(portal, 'portal_groupdata', None)
00381     gtool = getToolByName(portal, 'portal_groups', None)
00382 
00383     if gdtool is None or gtool is None:
00384         print >> out, ('\nGroup-aware tools not found. Skipping '
00385                        'group data migration.')
00386         return groupdata, groupmemberships
00387 
00388     props = gdtool.propertyIds()
00389 
00390     uf = getToolByName(portal, 'acl_users')
00391     if hasattr(uf, 'getGroups'):
00392         # Must be a GRUF for this to work.
00393         groups = gtool.listGroups()
00394         for group in groups:
00395             id = group.getGroupName()  # in GRUF 2, getGroupId is prefixed!
00396             print >> out, " : %s" % id
00397             roles = [role for role in group.getRoles() if role != 'Authenticated']
00398             print >> out, "with roles %s" % roles
00399             properties = {}
00400             has_groups = [] # we take care of this with the
00401                             # groupmemberships stuff
00402             for propid in props:
00403                 properties[propid] = group.getProperty(propid, None)
00404             groupdata += ((id, roles, has_groups, properties),)
00405             groupmemberships[id] = group.getGroupMemberIds()
00406 
00407     print >> out, "...extract done"
00408     return groupdata, groupmemberships
00409 
00410 
00411 def restoreGroupData(portal, out, groupdata, groupmemberships):
00412     print >> out, "\nRestoring Group information..."
00413 
00414     # re-add groups
00415     gtool = getToolByName(portal, 'portal_groups')
00416     for g in groupdata:
00417         print >> out, " : adding group '%s' with members: " % g[0]
00418         gtool.addGroup(*g)
00419 
00420         # restore group memberships
00421         gid = g[0]
00422         group = gtool.getGroupById(gid)
00423         for mid in groupmemberships[gid]:
00424             group.addMember(mid)
00425             print >> out, "%s " % mid
00426 
00427     print >> out, "...restore done"
00428 
00429 
00430 def setupTools(portal, out):
00431     print >> out, "\nTools:"
00432 
00433     migratePloneTool(portal, out)
00434     migrateMembershipTool(portal, out)
00435     migrateGroupsTool(portal, out)
00436     migrateMemberDataTool(portal, out)
00437     migrateGroupDataTool(portal, out)
00438     modActions(portal, out)
00439 
00440 
00441 def migratePloneTool(portal, out):
00442     print >> out, "Plone Tool (plone_utils)"
00443     pt = portal.plone_utils
00444     if pt.meta_type == 'PlonePAS Utilities Tool':
00445         from Products.CMFPlone.PloneTool import PloneTool
00446         print >> out, " - Removing obsolete PlonePAS version of the Plone Tool"
00447         portal.manage_delObjects(['plone_utils'])
00448         print >> out, " - Installing standard tool"
00449         portal._setObject(PloneTool.id, PloneTool())
00450     print >> out, " ...done"
00451 
00452 
00453 def migrateMembershipTool(portal, out):
00454     print >> out, "Membership Tool (portal_membership)"
00455 
00456     mt = getToolByName(portal, "portal_membership")
00457     print >> out, " ...copying settings"
00458     memberareaCreationFlag = mt.getMemberareaCreationFlag()
00459     role_map = getattr(mt, 'role_map', None)
00460     membersfolder_id = mt.membersfolder_id
00461 
00462     print >> out, " ...copying actions"
00463     actions = getattr(mt, '_actions', None)
00464 
00465     print >> out, " - Removing Default"
00466     portal.manage_delObjects(['portal_membership'])
00467 
00468     print >> out, " - Installing PAS Aware"
00469     portal._setObject(MembershipTool.id, MembershipTool())
00470 
00471     # Get new tool.
00472     mt = getToolByName(portal, 'portal_membership')
00473 
00474     print >> out, " ...restoring settings"
00475     mt.memberareaCreationFlag = memberareaCreationFlag
00476     if role_map:
00477         mt.role_map = role_map
00478     if membersfolder_id:
00479         mt.membersfolder_id = membersfolder_id
00480 
00481     if actions is not None:
00482         print >> out, " ...restoring actions"
00483         mt._actions = actions
00484 
00485     print >> out, " ...done"
00486 
00487 
00488 def migrateGroupsTool(portal, out):
00489     print >> out, "Groups Tool (portal_groups)"
00490     # We only want the physical object in the portal, getToolByName could give
00491     # us a registered utility as well
00492     gt = getattr(portal, 'portal_groups', None)
00493 
00494     HAS_GT = gt is not None
00495 
00496     if HAS_GT:
00497         print >> out, " ...copying settings"
00498         groupworkspaces_id = gt.getGroupWorkspacesFolderId()
00499         groupworkspaces_title =  gt.getGroupWorkspacesFolderTitle()
00500         groupWorkspacesCreationFlag =  gt.getGroupWorkspacesCreationFlag()
00501         groupWorkspaceType =  gt.getGroupWorkspaceType()
00502         groupWorkspaceContainerType =  gt.getGroupWorkspaceContainerType()
00503 
00504         print >> out, " ...copying actions"
00505         actions = getattr(gt, '_actions', None)
00506 
00507         print >> out, " - Removing Default"
00508         portal.manage_delObjects(['portal_groups'])
00509 
00510     print >> out, " - Installing PAS Aware"
00511     portal._setObject(GroupsTool.id, GroupsTool())
00512 
00513     gt = getToolByName(portal, 'portal_groups')
00514 
00515     if HAS_GT:
00516         print >> out, " ...restoring settings"
00517         gt.setGroupWorkspacesFolder(groupworkspaces_id, groupworkspaces_title)
00518         gt.groupWorkspacesCreationFlag = groupWorkspacesCreationFlag
00519         gt.setGroupWorkspaceType(groupWorkspaceType)
00520         gt.setGroupWorkspaceContainerType(groupWorkspaceContainerType)
00521 
00522         if actions is not None:
00523             print >> out, " ...restoring actions"
00524             gt._actions = actions
00525 
00526     print >> out, " ...done"
00527 
00528 
00529 def migrateGroupDataTool(portal, out):
00530     # this could be somewhat combined with migrateMemberDataTool, but
00531     # I don't think it's worth it
00532 
00533     print >> out, "GroupData Tool (portal_groupdata)"
00534     # We only want the physical object in the portal, getToolByName could give
00535     # us a registered utility as well
00536     gt = getattr(portal, 'portal_groupdata', None)
00537 
00538     HAS_GT = gt is not None
00539 
00540     if HAS_GT:
00541         print >> out, " ...copying actions"
00542         actions = getattr(gt, '_actions', None)
00543 
00544         print >> out, " ...extracting data"
00545         properties = gt._properties
00546         for elt in properties:
00547             elt['value'] = gt.getProperty(elt['id'])
00548 
00549         print >> out, " - Removing Default"
00550         portal.manage_delObjects(['portal_groupdata'])
00551 
00552     print >> out, " - Installing PAS Aware"
00553     portal._setObject(GroupDataTool.id, GroupDataTool())
00554     gt = getToolByName(portal, 'portal_groupdata')
00555 
00556     if HAS_GT:
00557         if actions is not None:
00558             print >> out, " ...restoring actions"
00559             gt._actions = actions
00560 
00561         print >> out, " ...restoring data"
00562         
00563         updateProperties(gt, properties)
00564 
00565     print >> out, " ...done"
00566 
00567 
00568 def migrateMemberDataTool(portal, out):
00569     print >> out, "MemberData Tool (portal_memberdata)"
00570 
00571     print >> out, " ...copying actions"
00572     actions = getattr(portal.portal_memberdata, '_actions', None)
00573 
00574     print >> out, "  ...extracting data"
00575     mdtool = portal.portal_memberdata
00576     properties = mdtool._properties
00577     for elt in properties:
00578         elt['value'] = mdtool.getProperty(elt['id'])
00579 
00580     mdtool = None
00581     print >> out, " - Removing existing portal_memberdata tool"
00582     portal.manage_delObjects(['portal_memberdata'])
00583 
00584     print >> out, " - Installing PAS Aware tool"
00585     portal._setObject(MemberDataTool.id, MemberDataTool())
00586 
00587     if actions is not None:
00588         print >> out, " ...restoring actions"
00589         portal.portal_memberdata._actions = actions
00590 
00591     print >> out, " ...restoring data"
00592     mdtool = portal.portal_memberdata
00593     
00594     updateProperties(mdtool, properties)
00595 
00596     print >> out, " ...done"
00597 
00598 
00599 def modActions(portal, out):
00600     """Change any actions necessary to support PAS."""
00601     # condition "set password" on capability
00602     cp = getToolByName(portal, 'portal_controlpanel', None)
00603     _actions = cp._cloneActions()
00604     for action in _actions:
00605         if action.id=='MemberPassword':
00606             action.condition = Expression("python:member.canPasswordSet()")
00607     cp._actions=_actions
00608 
00609 
00610 def updateProperties(tool, properties):
00611     propsWithNoDeps = [prop for prop in properties if prop['type'] not in ('selection', 'multiple selection')]
00612     propsWithDeps = [prop for prop in properties if prop['type'] in ('selection', 'multiple selection')]
00613     for prop in propsWithNoDeps:
00614         updateProp(tool, prop)
00615     for prop in propsWithDeps:
00616         updateProp(tool, prop)
00617 
00618 
00619 def updateProp(prop_manager, prop_dict):
00620     """Provided a PropertyManager and a property dict of {id, value,
00621     type}, set or update that property as applicable.
00622 
00623     Doesn't deal with existing properties changing type.
00624     """
00625     id = prop_dict['id']
00626     value = prop_dict['value']
00627     type = prop_dict['type']
00628     if type in ('selection', 'multiple selection'):
00629         value = prop_dict['select_variable']
00630     if prop_manager.hasProperty(id):
00631         prop_manager._updateProperty(id, value)
00632     else:
00633         prop_manager._setProperty(id, value, type)
00634     if type in ('selection', 'multiple selection'):
00635         prop_manager._updateProperty(id, prop_dict['value'])
00636 
00637 
00638 def grabLDAPFolders(portal, out):
00639     """Get hold of any existing LDAPUserFolders so that we can put
00640     them into LDAPMultiPlugins later.
00641     """
00642     print >> out, "\nPreserving LDAP folders, if any:"
00643 
00644     user_sources = portal.acl_users.listUserSources()
00645     group_source = portal.acl_users.Groups.acl_users
00646 
00647     ldap_ufs = []
00648     ldap_gf = None
00649 
00650     for uf in user_sources:
00651         if uf.meta_type == "LDAPUserFolder":
00652             print >> out, " - LDAPUserFolder %s" % uf.title
00653             ldap_ufs.append(uf)
00654 
00655     if group_source.meta_type == "LDAPGroupFolder %s" % group_source.title:
00656         print >> out, " - LDAPGroupFolder"
00657         ldap_gf = group_source
00658 
00659     print >> out, "...done"
00660     return ldap_ufs, ldap_gf
00661 
00662 
00663 def restoreLDAP(portal, out, ldap_ufs, ldap_gf):
00664     """Create appropriate plugins to replace destroyed LDAP user
00665     folders.
00666     """
00667     if not (ldap_ufs or ldap_gf):
00668         print >> out, "\nNo LDAP auth sources to restore. Skipping."
00669     else:
00670         print >> out, "\nRestoring LDAP auth sources:"
00671         pas = portal.acl_users
00672 
00673         x = ""
00674         if len(ldap_ufs) > 1:
00675             x = 0
00676         for lduf in ldap_ufs:
00677             id = 'ad_multi%s' % x
00678             title = 'ActiveDirectory Multi-plugin %s' % x
00679             LDAP_server = lduf.LDAP_server + ":" + `lduf.LDAP_port`
00680             login_attr = lduf._login_attr
00681             uid_attr = lduf._uid_attr
00682             users_base = lduf.users_base
00683             users_scope = lduf.users_scope
00684             roles = lduf._roles
00685             groups_base = lduf.groups_base
00686             groups_scope = lduf.groups_scope
00687             binduid = lduf._binduid
00688             bindpwd = lduf._bindpwd
00689             binduid_usage = lduf._binduid_usage
00690             rdn_attr = lduf._rdnattr
00691             local_groups = lduf._local_groups
00692             use_ssl = lduf._conn_proto == 'ldaps'
00693             encryption = lduf._pwd_encryption
00694             read_only = lduf.read_only
00695 
00696             # attribute over-rides
00697             uid_attr = login_attr = "sAMAccountName"
00698 
00699             ldapmp = pas.manage_addProduct['LDAPMultiPlugins']
00700             ldapmp.manage_addActiveDirectoryMultiPlugin(
00701                 id, title,
00702                 LDAP_server, login_attr,
00703                 uid_attr, users_base, users_scope, roles,
00704                 groups_base, groups_scope, binduid, bindpwd,
00705                 binduid_usage=1, rdn_attr='cn', local_groups=0,
00706                 use_ssl=0 , encryption='SHA', read_only=0)
00707             getattr(pas,id).groupid_attr = 'cn'
00708 
00709             print >> out, "Added ActiveDirectoryMultiPlugin %s" % x
00710             x = x or 0 + 1
00711 
00712             activatePluginInterfaces(portal, id, out)
00713             # turn off groups
00714             pas.plugins.deactivatePlugin(IGroupsPlugin, id)
00715             pas.plugins.deactivatePlugin(IGroupEnumerationPlugin, id)
00716             # move properties up
00717             pas.plugins.movePluginsUp(IPropertiesPlugin, [id])
00718 
00719 
00720 def replaceUserFolder(portal, out):
00721     print >> out, "\nUser folder replacement:"
00722 
00723     print >> out, " - Removing existing user folder"
00724     portal.manage_delObjects(['acl_users'])
00725 
00726     addPAS(portal, out)
00727 
00728     print >> out, "...replace done"
00729 
00730 
00731 def addPAS(portal, out):
00732     print >> out, " - Adding PAS user folder"
00733     portal.manage_addProduct['PluggableAuthService'].addPluggableAuthService()
00734 
00735 
00736 def goForMigration(portal, out):
00737     """Checks for supported configurations.
00738     Other configurations might work. The migration code is pretty generic.
00739 
00740     Should provide some way to extend this check.
00741     """
00742     if not canAutoMigrate(portal.acl_users):
00743         msg = ("Your user folder is in a configuration not supported "
00744                "by the migration script.\nOnly GroupUserFolders with "
00745                "basic UserFolder and LDAPUserFolder sources can be "
00746                "migrated at this time.\nAny other setup will require "
00747                "custom migration. You may install PlonePAS empty by "
00748                "deleting you current UserFolder.")
00749         raise Exception, msg
00750 
00751     return 1
00752 
00753 def migrate_root_uf(self, out):
00754     # Acquire parent user folder.
00755     parent = self.getPhysicalRoot()
00756     uf = getToolByName(parent, 'acl_users')
00757     if IPluggableAuthService.providedBy(uf):
00758         # It's a PAS already, fixup if needed.
00759         pas_fixup(parent, out)
00760 
00761         # Configure Challenge Chooser plugin if available
00762         challenge_chooser_setup(parent, out)
00763         return
00764 
00765     if not uf.meta_type == 'User Folder':
00766         # It's not a standard User Folder at the root. Nothing we can do.
00767         return
00768 
00769     # It's a standard User Folder, replace it.
00770     replace_acl_users(parent, out)
00771 
00772     # Get the new uf
00773     uf = getToolByName(parent, 'acl_users')
00774 
00775     pas = uf.manage_addProduct['PluggableAuthService']
00776     plone_pas = uf.manage_addProduct['PlonePAS']
00777     # Setup authentication plugins
00778     setupAuthPlugins(parent, pas, plone_pas, out,
00779                      deactivate_basic_reset=False,
00780                      deactivate_cookie_challenge=True)
00781 
00782     # Activate *all* interfaces for user manager. IUserAdder is not
00783     # activated for some reason by default.
00784     activatePluginInterfaces(parent, 'users', out)
00785 
00786     # Configure Challenge Chooser plugin if available
00787     challenge_chooser_setup(parent, out)
00788 
00789 def pas_fixup(self, out):
00790     from Products.PluggableAuthService.PluggableAuthService \
00791          import _PLUGIN_TYPE_INFO, PluggableAuthService
00792 
00793     pas = getToolByName(self, 'acl_users')
00794     if not IPluggableAuthService.providedBy(pas):
00795         print >> out, 'PAS UF not found, skipping PAS fixup'
00796         return
00797 
00798     plugins = pas['plugins']
00799 
00800     plugin_types = list(Set(plugins._plugin_types))
00801     for key, id, title, description in _PLUGIN_TYPE_INFO:
00802         if key in plugin_types:
00803             print >> out, "Plugin type '%s' already registered." % id
00804             continue
00805         print >> out, "Plugin type '%s' was not registered." % id
00806         plugin_types.append(key)
00807         plugins._plugin_type_info[key] = {
00808             'id': id,
00809             'title': title,
00810             'description': description,
00811             }
00812     # Make it ordered
00813     plugin_types.sort()
00814 
00815     # Re-assign because it's a non-persistent property.
00816     plugins._plugin_types = plugin_types
00817 
00818 def challenge_chooser_setup(self, out):
00819     uf = getToolByName(self, 'acl_users')
00820     plugins = uf['plugins']
00821     pas = uf.manage_addProduct['PluggableAuthService']
00822 
00823     # Only install plugins if available
00824     req = ('addChallengeProtocolChooserPlugin',
00825            'addRequestTypeSnifferPlugin')
00826     for m in req:
00827         if getattr(pas, m, None) is None:
00828             print >> out, 'Needed plugins have not been found, ignoring'
00829             return
00830 
00831     found = uf.objectIds(['Challenge Protocol Chooser Plugin'])
00832     if not found:
00833         print >> out, 'Adding Challenge Protocol Chooser Plugin.'
00834         pas.addChallengeProtocolChooserPlugin(
00835             'chooser',
00836             mapping=config.DEFAULT_PROTO_MAPPING)
00837         activatePluginInterfaces(self, 'chooser', out)
00838     else:
00839         assert len(found) == 1, 'Found extra plugins %s' % found
00840         print >> out, 'Found existing Challenge Protocol Chooser Plugin.'
00841         plugin = uf[found[0]]
00842         plugin.manage_updateProtocolMapping(mapping=config.DEFAULT_PROTO_MAPPING)
00843         activatePluginInterfaces(self, found[0], out)
00844 
00845     found = uf.objectIds(['Request Type Sniffer Plugin'])
00846     if not found:
00847         print >> out, 'Adding Request Type Sniffer Plugin.'
00848         pas.addRequestTypeSnifferPlugin('sniffer')
00849         activatePluginInterfaces(self, 'sniffer', out)
00850     else:
00851         assert len(found) == 1, 'Found extra plugins %s' % found
00852         print >> out, 'Found existing Request Type Sniffer Plugin.'
00853         activatePluginInterfaces(self, found[0], out)
00854 
00855 
00856 def install(self):
00857     out = StringIO()
00858     portal = getToolByName(self, 'portal_url').getPortalObject()
00859 
00860     uf = getToolByName(self, 'acl_users')
00861 
00862     EXISTING_UF = 'acl_users' in portal.objectIds()
00863     EXISTING_PAS = IPluggableAuthService.providedBy(uf)
00864 
00865     if EXISTING_PAS:
00866         # Fix possible missing PAS plugins registration.
00867         pas_fixup(self, out)
00868 
00869         # Register PAS Plugin Types
00870         registerPluginTypes(uf)
00871 
00872     ldap_ufs, ldap_gf = None, None
00873     userdata=groupdata=memberships=()
00874 
00875     if not EXISTING_UF:
00876         userdata = grabUserData(portal, out)
00877         addPAS(portal, out)
00878     elif not EXISTING_PAS:
00879         # We've got a existing user folder, but it's not a PAS
00880         # instance.
00881 
00882         goForMigration(portal, out)
00883 
00884         userdata = grabUserData(portal, out)
00885         groupdata, memberships = grabGroupData(portal, out)
00886 
00887         ldap_ufs, ldap_gf = grabLDAPFolders(portal, out)
00888         if (ldap_ufs or ldap_gf) and not CAN_LDAP:
00889             raise Exception, ("LDAPUserFolders present, but LDAPMultiPlugins "
00890                           "not present. To successfully auto-migrate, "
00891                           "the LDAPMultiPlugins product must be installed. "
00892                           "(%s, %s):%s" % (ldap_ufs, ldap_gf, CAN_LDAP))
00893 
00894         replaceUserFolder(portal, out)
00895 
00896     # Configure Challenge Chooser plugin if available
00897     challenge_chooser_setup(self, out)
00898 
00899     configurePlonePAS(portal, out)
00900 
00901     setupTools(portal, out)
00902 
00903     if (EXISTING_UF and CAN_LDAP
00904         and ldap_gf is not None
00905         and ldap_ufs is not None):
00906         restoreLDAP(portal, out, ldap_ufs, ldap_gf)
00907 
00908     if not EXISTING_PAS:
00909         restoreUserData(portal, out, userdata)
00910         restoreGroupData(portal, out, groupdata, memberships)
00911 
00912     # XXX Why do we need to do this?
00913     migrate_root_uf(self, out)
00914 
00915     print >> out, "\nSuccessfully installed %s." % config.PROJECTNAME
00916     return out.getvalue()
00917 
00918 
00919 # Future refactor notes:
00920 #  we cannot tell automatically between LDAP and AD uses of LDAPUserFolder
00921 #    - except maybe sAMAccountName
00922 #    - so some sort of UI is necessary
00923 #  should have some sort of facility for allowing easy extension of migration of UFs
00924 #    - register grab and restore methods, or something
00925 #  cannot currently handle LDAPGroupsFolder
00926 #  can probably handle multiple LDAPUserFolders, but not tested