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