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

# Source Code for Module Products.ZenUtils.Security

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