Package Products :: Package ZenUtils :: Module Security |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.Security

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 or (at your
      8  # option) any later version as published by the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  import os
     15  from random import random
     16  from datetime import datetime
     17  from sets import Set as set
     18
     19  from OFS.Folder import Folder
     20  from Products.PluggableAuthService import plugins
     21  from Products.PluggableAuthService import interfaces
     22  from Products.PluggableAuthService import PluggableAuthService
     23
     24
     25  ZENOSS_ROLES = ['ZenUser', 'ZenManager']
     26
     27
     28 -def backupACLUserFolder(context):
     29      timestamp = datetime.now().strftime('%Y.%d.%m-%H%M%S')
     30      randomBit = int(random() * 10000)
     31      backupFolderName = 'backup_acl_users_%s-%d' % (timestamp, randomBit)
     32      backupFolder = Folder(backupFolderName)
     33      backupFolder._setObject('acl_users', context.acl_users)
     34      context._setObject(backupFolder.getId(), backupFolder)
     35      context._delObject('acl_users')
     36      return backupFolderName
     37
     38
     39 -def _createInitialUser(self):
     40      """
     41      Note: copied and adapted from AccessControl.User.BasicUser
     42
     43      If there are no users or only one user in this user folder,
     44      populates from the 'inituser' file in the instance home.
     45      We have to do this even when there is already a user
     46      just in case the initial user ignored the setup messages.
     47      We don't do it for more than one user to avoid
     48      abuse of this mechanism.
     49      Called only by OFS.Application.initialize().
     50      """
     51      from AccessControl.User import readUserAccessFile
     52
     53      plugins = self.plugins.listPlugins(
     54          interfaces.plugins.IUserEnumerationPlugin)
     55      # get the user count, but only for functional plugins
     56      userCounts = [ len(plugin.listUserInfo()) for id, plugin in plugins if hasattr(plugin, "listUserInfo")]
     57
     58      if len(userCounts) <= 1:
     59          info = readUserAccessFile('inituser')
     60          if info:
     61              import App.config
     62              name, password, domains, remote_user_mode = info
     63              userManagers = self.plugins.listPlugins(interfaces.plugins.IUserAdderPlugin)
     64              roleManagers = self.plugins.listPlugins(interfaces.plugins.IRolesPlugin)
     65              for pluginId, userPlugin in userManagers:
     66                  # delete user
     67                  try:
     68                      userPlugin.removeUser(name)
     69                  except KeyError:
     70                      # user doesn't exist
     71                      pass
     72                  # recreate user
     73                  userPlugin.doAddUser(name, password)
     74                  # add role
     75                  for pluginId, rolePlugin in roleManagers:
     76                      rolePlugin.assignRoleToPrincipal('Manager', name)
     77              cfg = App.config.getConfiguration()
     78              # now that we've loaded from inituser, let's delete the file
     79              try:
     80                  os.remove(os.path.join(cfg.instancehome, 'inituser'))
     81              except:
     82                  pass
     83
     84
     85 -def createPASFolder(context):
     86      # check to see if we need to monkey patch PAS to accomodate inituser files
     87      pas = PluggableAuthService.PluggableAuthService
     88      if not hasattr(pas, '_createInitialUser'):
     89          pas._createInitialUser =  _createInitialUser
     90
     91      # create new PAS
     92      PluggableAuthService.addPluggableAuthService(context)
     93      context.acl_users.title = 'PAS'
     94
     95
     96 -def setupBasciAuthHelper(context):
     97      acl = context.acl_users
     98      id = 'basicAuthHelper'
     99      if not hasattr(acl, id):
    100          plugins.HTTPBasicAuthHelper.addHTTPBasicAuthHelper(acl, id)
    101      interfaces = []
    102      physPath = '/'.join(context.getPhysicalPath())
    103      if physPath == '':
    104          interfaces = ['IExtractionPlugin', 'IChallengePlugin',
    105              'ICredentialsResetPlugin']
    106      elif physPath == '/zport':
    107          interfaces = ['IExtractionPlugin', 'IChallengePlugin']
    108      acl.basicAuthHelper.manage_activateInterfaces(interfaces)
    109
    110
    111 -def setupCookieHelper(context, primaryAuth=False):
    112      acl = context.acl_users
    113      id = 'cookieAuthHelper'
    114      if not hasattr(acl, id):
    115          plugins.CookieAuthHelper.addCookieAuthHelper(acl, id)
    116      interfaces = []
    117      # note that we are only enabling CookieAuth for the Zenoss portal
    118      # acl_users, not for the root acl_users.
    119      physPath = '/'.join(context.getPhysicalPath())
    120      if physPath == '':
    121          interfaces = ['IExtractionPlugin']
    122      elif physPath == '/zport':
    123          interfaces = ['IExtractionPlugin',
    124                        'ICredentialsResetPlugin',
    125                        'IChallengePlugin']
    126      if primaryAuth:
    127          interfaces.append('ICredentialsUpdatePlugin')
    128      acl.cookieAuthHelper.manage_activateInterfaces(interfaces)
    129
    130 -def setupSessionHelper(context, primaryAuth=True):
    131      acl = context.acl_users
    132      id = 'sessionAuthHelper'
    133      if not hasattr(acl, id):
    134          plugins.SessionAuthHelper.manage_addSessionAuthHelper(acl, id)
    135
    136      interfaces = ['IExtractionPlugin',
    137                    'ICredentialsResetPlugin']
    138      if primaryAuth:
    139          interfaces.append('ICredentialsUpdatePlugin')
    140      acl.sessionAuthHelper.manage_activateInterfaces(interfaces)
    141
    142 -def activateCookieBasedAuthentication(context):
    143      """
    144      This sets cookie authentication as the primary auth
    145      mechanism. This means that the users credentials will be stored
    146      encoded in a cookie.
    147      """
    148      setupCookieHelper(context, primaryAuth=True)
    149      setupSessionHelper(context, primaryAuth=False)
    150
    151 -def activateSessionBasedAuthentication(context):
    152      """
    153      Stores the user credentials in the session and the token is sent
    154      to the server.  The user will be forced to re-login when zope
    155      restarts or the session times out.
    156      """
    157      setupCookieHelper(context, primaryAuth=False)
    158      setupSessionHelper(context, primaryAuth=True)
    159
    160 -def setupRoleManager(context):
    161      acl = context.acl_users
    162      id = 'roleManager'
    163      if not hasattr(acl, id):
    164          plugins.ZODBRoleManager.addZODBRoleManager(acl, id)
    165      acl.roleManager.manage_activateInterfaces(['IRolesPlugin',
    166          'IRoleEnumerationPlugin', 'IRoleAssignerPlugin'])
    167      # setup roles
    168      for role in ZENOSS_ROLES:
    169          try:
    170              acl.roleManager.addRole(role)
    171          except KeyError:
    172              # that role already exists
    173              pass
    174
    175
    176 -def setupUserManager(context):
    177      acl = context.acl_users
    178      id = 'userManager'
    179      if not hasattr(acl, id):
    180          plugins.ZODBUserManager.addZODBUserManager(acl, id)
    181      acl.userManager.manage_activateInterfaces(['IAuthenticationPlugin',
    182          'IUserEnumerationPlugin', 'IUserAdderPlugin'])
    183
    184
    185 -def setupTypeSniffer(context):
    186      acl = context.acl_users
    187      id = 'requestTypeSniffer'
    188      if not hasattr(acl, id):
    189          plugins.RequestTypeSniffer.addRequestTypeSnifferPlugin(acl, id)
    190      acl.requestTypeSniffer.manage_activateInterfaces(['IRequestTypeSniffer'])
    191
    192
    193 -def setupProtocolChooser(context):
    194      acl = context.acl_users
    195      id = 'protocolChooser'
    196      if not hasattr(acl, id):
    197          plugins.ChallengeProtocolChooser.addChallengeProtocolChooserPlugin(acl,
    198              id)
    199      acl.protocolChooser.manage_activateInterfaces([
    200          'IChallengeProtocolChooser'])
    201      protocolMapping = {}
    202      # set up non-Browser protocols to use HTTP BasicAuth
    203      physPath = '/'.join(context.getPhysicalPath())
    204      if physPath == '':
    205          protocolMapping = {
    206              'Browser': ['http'],
    207              'FTP': ['http'],
    208              'WebDAV': ['http'],
    209              'XML-RPC': ['http'],
    210          }
    211      elif physPath == '/zport':
    212          protocolMapping = {
    213              'FTP': ['http'],
    214              'WebDAV': ['http'],
    215              'XML-RPC': ['http'],
    216          }
    217          # we don't want to hard-code plugin names here, so let's do a lookup
    218          icookie = plugins.CookieAuthHelper.ICookieAuthHelper
    219          ichallenge = interfaces.plugins.IChallengePlugin
    220          challenge = [ p for id, p in acl.plugins.listPlugins(ichallenge) ]
    221          # valid cooike auth plugins
    222          cookiePlugins = [ p for p in challenge if icookie.providedBy(p) ]
    223          # we want to move the cookie auth instance above the basic auth listing so
    224          # that it is accessed first and we can keep 'Browser' set to any; for
    225          # now, let's just get the first match and use that one (there should
    226          # really only be one...)
    227          cookie = cookiePlugins[0]
    228          index = challenge.index(cookie)
    229          for i in xrange(index):
    230              acl.plugins.movePluginsUp(ichallenge, [cookie.id])
    231      acl.protocolChooser.manage_updateProtocolMapping(protocolMapping)
    232
    233
    234 -def setupPASFolder(context):
    235      setupBasciAuthHelper(context)
    236      setupCookieHelper(context)
    237      setupSessionHelper(context)
    238      setupRoleManager(context)
    239      setupUserManager(context)
    240      setupTypeSniffer(context)
    241      # this one has to go last in case any of the protocol mappings need to make
    242      # reference to an already-installed plugin
    243      setupProtocolChooser(context)
    244
    245
    246 -def replaceACLWithPAS(context, deleteBackup=False):
    247      # archive the old "User Folder"
    248      backupId = backupACLUserFolder(context)
    249
    250      # create a new PAS acl_users
    251      createPASFolder(context)
    252      setupPASFolder(context)
    253
    254      # set up some convenience vars
    255      orig = getattr(context, backupId).acl_users
    256      acl = context.acl_users
    257
    258      # migrate the old user information over to the PAS
    259      for u in orig.getUsers():
    260          user, password, domains, roles = (u.name, u.__, u.domains, u.roles)
    261          acl.userManager.doAddUser(user, password)
    262          for role in roles:
    263              acl.roleManager.assignRoleToPrincipal(role, user)
    264          # initialize UserSettings for each user
    265          try:
    266              dmd = context.getPhysicalRoot().zport.dmd
    267              dmd.ZenUsers.getUserSettings(user)
    268          except AttributeError:
    269              # no dmd, or no ZenUsers
    270              pass
    271
    272      # delete backup?
    273      if deleteBackup:
    274          context._delObject(backupId)
    275
    276
    277 -def migratePAS(context):
    278      # check to see if the current acl_users is a PAS instance or not
    279      newModule = 'Products.PluggableAuthService.PluggableAuthService'
    280      try:
    281          acl = context.acl_users
    282          # if there's an acl_users object, let's see if theres a login_form
    283          # attribute; if there is, we need to delete it
    284          if (hasattr(acl, 'cookieAuthHelper')
    285              and hasattr(acl.cookieAuthHelper, 'login_form')):
    286              acl.cookieAuthHelper._delObject('login_form')
    287      except AttributeError:
    288          createPASFolder(context)
    289          acl = context.acl_users
    290
    291      if acl.__module__ != newModule:
    292          replaceACLWithPAS(context)
    293      else:
    294          # check to see if there are any missing attributes; we have to make the
    295          # dir() call twice, because (when testing in the dmd) the 'plugins'
    296          # attribute doesn't show up on the first call.
    297          dummy = dir(acl)
    298          full = set(dir(acl))
    299          needed = set(['_createInitialUser', 'plugins'])
    300          # if any of 'needed' are missing, the PAS has to be recreated
    301          if not full.issuperset(needed):
    302              backupId = backupACLUserFolder(context)
    303              backup = context._getOb(backupId)
    304              createPASFolder(context)
    305              # now that we have a monkey-patched acl_users, restore the plugins
    306              for itemId in backup.objectIds():
    307                  acl._setObject(itemId, backup._getOb(itemId))
    308              # delete the (empty) backup
    309              context._delObject(backupId)
    310          # the next function calls all the setup functions, each of which do an
    311          # attriibute check and installs anything that's missing
    312          setupPASFolder(context)
    313