Ejemplo n.º 1
0
 def __init__(self):
     self.api = UserSyncAdmin(self.env)
Ejemplo n.º 2
0
class UserSyncAdminPage(Component):

    implements(IAdminPanelProvider, ITemplateProvider)

    def __init__(self):
        self.api = UserSyncAdmin(self.env)

    # panel_providers = ExtensionPoint(IUserManagerPanelProvider)
    # cells_providers = ExtensionPoint(IUserListCellContributor)
    
    default_panel = Option('user_manager', 'admin_default_panel', 'profile',
        """Default user admin panel.""")

    # Configuration options.
    _users_keep = ListOption('user_sync', 'users_keep', '',
      doc = 'Comma separated list of users to keep under all circumstances when'
      ' purging. All your permission groups will be added automatically.')
    _merge_conflicts = Option('user_sync', 'merge_conflicts', 'skip',
      doc = 'What should be done when two field values conflict with each other?'
      ' Valid settings are: "skip" (default: do not sync this user) and "newer"'
      ' (take values from the newer record, i.e. the environment the user was'
      ' last active in). Note that the latter does not necessarily have the more'
      ' recent data, as it only stands for the last login, not the last change.')
    _sync_fields = ListOption('user_sync', 'sync_fields', 'email,name',
      doc = 'Comma separated List of fields to be synced. The default is'
      ' "email,name" and includes the two fields used by the !AccountManagerPlugin.'
      ' If you are additionally using the !UserManagerPlugin, you may have to'
      ' add the fields you enabled there as well.')
    _sql_file_path = Option('user_sync','sql_file_path','',
      doc = 'Path to the directory of the file, where update statements should be'
      ' saved into. If empty (default), the log directory of the executing trac'
      ' environment will be taken.')
    _dryrun = BoolOption('user_sync','dryrun','true',
      doc = 'Stay read-only (true) or really apply the changes (false). As long'
      ' as this plugin is in early-beta state, it defaults to "true" (read-only).')

    # IAdminPageProvider methods
    def get_admin_panels(self, req):
        if req.perm.has_permission('TRAC_ADMIN'):
            yield ('accounts', _('Accounts'), 'usersync', 'User Sync')
            #yield ('accounts', _('Accounts'), 'users', 'Users')
        
    def render_admin_panel( self, req, cat, page, path_info):
        # here comes the page content, handling, etc.
        error = TracError('')
        parentdir = os.getenv('TRAC_ENV_PARENT_DIR')
        self.env.log.debug('TRAC_ENV_PARENT_DIR found: %s' % (parentdir,))

        self.data = {}
        self.data['err'] = []
        self.data['msg'] = []
        self.data['log'] = []
        data = {}

        # if we don't have a parentdir, we are useless
        if parentdir:
           # find all trac environments
           tracenvs = self.api.get_envs(parentdir)

           # read users from password file
           #config.get(section,key,default)
           pwdfile = self.env.config.get('account-manager','password_file')
           if pwdfile and os.path.exists(pwdfile):
              self.env.log.debug('Using password file "%s"' % (pwdfile,))
              self.users = self.api.get_pwd_users(pwdfile)
              if not self.users:
                 self.data['err'].append('No users found in the password file "%s", aborting.' % (pwdfile,))

           else:
              self.env.log.debug('No password file found')
              if pwdfile:
                 self.data['err'].append('The configured password file "%s" was not found, aborting.' % (pwdfile,))
              else:
                 self.data['err'].append('You have no password file (password_file = /path/to/.htpasswd) configured in the [account-manager] section of your trac.ini. Aborting.')

        else:
           self.data['err'].append('TRAC_ENV_PARENT_DIR is not defined - so we cannot do anything!')

        # Made it here - so we have all data to set up the WebIf:
        if len(self.data['err'])<1:
           data['tracenvs'] = tracenvs
           data['pwdfile']  = pwdfile
           # data['users'] is just for debug on display - can be dropped later
           data['users']    = self.users
           # users_keep will be needed for purging once that is implemented
           #data['users_keep'] = ','.join(self.api.get_perm_groups(self.env.path))

           # Form sent? Process!
           if req.method=="POST":
              message = self._do_process(req,tracenvs)

        # append the messages
        data['us_message'] = self.data['msg']
        data['us_error']   = self.data['err']
        data['us_log']     = self.data['log']

        # adding stylesheets
        add_stylesheet(req, 'tracusersync/css/admin_us.css')
        add_script(req, 'tracusersync/js/admin_us.js')
        
        return 'sync.html', data

    # ITemplateProvider
    def get_htdocs_dirs(self):
        """Return the absolute path of a directory containing additional
        static resources (such as images, style sheets, etc).
        """
        from pkg_resources import resource_filename
        return [('tracusersync', resource_filename(__name__, 'htdocs'))] 
        #return []

    def get_templates_dirs(self):
        """Return the absolute path of the directory containing the provided
        ClearSilver/Genshi templates.
        """
        from pkg_resources import resource_filename
        return [resource_filename(__name__, 'templates')]


    # Internal methods
    def _do_process(self, req, tracenvs):
        """Process form data received via POST
        @param req      : the request
        @param tracenvs : trac environments as returned by _get_envs
        """
        self.env.log.debug('Processing form data')
        syncok  = True
        purgeok = True
        if req.args.get('action_sync'):
           syncok = self._do_sync(req,tracenvs)
        if req.args.get('action_purge'):
           purgeok = self._do_purge()
        return syncok and purgeok

    def _do_sync(self, req, tracenvs):
        """Synchronize user data between the environments
        @param req      : the request
        @param tracenvs : trac environments as returned by _get_envs
        """
        
        # Check whether we have all required information
        envs = req.args.getlist('tracenv')
        le = len(envs)
        self.env.log.debug('UserSync: %s environments to synchronize' % (le,))
        if len(envs)<2:
           self.data['err'].append('For a synchronization, you must select at least 2 environments. You only selected: %s' % (', '.join(envs)))
           return False

        self.data['log'].append('Users from password file: %s ' % (', '.join(self.users)))

        # Fetching account data from the environments
        self.data['log'].append('Fetching user data from environments...')
        self.env.log.debug('TracEnvs: %s' % (tracenvs,))
        userlist = "'"+"','".join(self.users)+"'"
        data = {}
        rec_err = {}
        attr = "'name','email','email_verification_sent_to','email_verification_token'"
        for tracenv in tracenvs:
           try:
             recs = self.api.get_tracenv_userdata(req,os.path.join(os.getenv('TRAC_ENV_PARENT_DIR'),tracenv),userlist)
           except PermissionError:
             self.data['log'].append('You do not have the required permissions in %s - excluding it from synchronization' % (tracenv,))
             self.env.log.info('Sorry, but you do not have the required permissions in %s' % (tracenv,))
             del tracenvs[tracenvs.index(tracenv)]
             continue
           # each env must at least have one user (the TRAC_ADMIN). No records returned here means an error (different password file or missing privs)
           if not recs:
             del tracenvs[tracenvs.index(tracenv)]
             self.data['log'].append('Environment %s uses a different password file - excluding it from synchronization' % (tracenv,))
             continue
           ucnt = 0
           for sid in recs:
             if not sid in data: data[sid] = []
             data[sid].append(recs[sid])
             ucnt += 1
           self.env.log.debug('* %i relevant records' % (ucnt,))
           self.data['log'].append('%i relevant records in %s' % (ucnt,tracenv,))

        # Merging data
        res, err = self.api.merge(data)
        self.env.log.debug('%i merged records, %i records skipped' % (len(res),len(err)))
        self.data['log'].append('Merged user data: %i unique records, %i records skipped' % (len(res),len(err)))
        if not res:
           self.data['err'].append('Resulting user data collection is empty - nothing to be updated!')
           self.env.log.debug('Resulting user data collection is empty - nothing to be updated!')
           return False
        #self.env.log.info(res)

        # Updating environments
        env_ok = 0
        env_err = 0
        for tracenv in tracenvs:
          success, msg = self.api.update_tracenv_userdata(req, os.path.join(os.getenv('TRAC_ENV_PARENT_DIR'),tracenv), res)
          if success:
            self.data['log'].append(msg)
            env_ok += 1
          else:
            #self.data['msg'].append('Failed to update environment! For details, see log below.')
            self.data['log'].append(msg)
            env_err += 1
        if env_err > 0:
          self.data['err'].append('%i environments had errors on updating. Please see the log below for details.' % (env_err,))
          return False
        else:
          self.data['msg'].append('Users successfully synchronized for %i environments. Please see the log below for details.' % (env_ok,))
          return True

    def _do_purge(self):
      """Purge obsolete data - i.e. environment data (sessions, preferences,
      permissions) from users no longer existing. Wrapper to api.do_purge()
      """
      self.data['log'].append('Purge was requested - but is not yet available, sorry.')
      ok, msg = self.api.do_purge()
      if ok: self.data['msg'].append(msg)
      else: self.data['err'].append(msg)
      return ok