Ejemplo n.º 1
0
    def _do_login(self, req):
        """Log the remote user in.

        This function expects to be called when the remote user name
        is available. The user name is inserted into the `auth_cookie`
        table and a cookie identifying the user on subsequent requests
        is sent back to the client.

        If the Authenticator was created with `ignore_case` set to
        true, then the authentication name passed from the web server
        in req.remote_user will be converted to lower case before
        being used. This is to avoid problems on installations
        authenticating against Windows which is not case sensitive
        regarding user names and domain names
        """
        if not req.remote_user:
            # TRANSLATOR: ... refer to the 'installation documentation'. (link)
            inst_doc = tag.a(_("installation documentation"),
                             title=_("Configuring Authentication"),
                             href=req.href.wiki('TracInstall') +
                                                "#ConfiguringAuthentication")
            raise TracError(tag_("Authentication information not available. "
                                 "Please refer to the %(inst_doc)s.",
                                 inst_doc=inst_doc))
        remote_user = req.remote_user
        if self.ignore_case:
            remote_user = remote_user.lower()

        if req.authname not in ('anonymous', remote_user):
            raise TracError(_("Already logged in as %(user)s.",
                              user=req.authname))

        with self.env.db_transaction as db:
            # Delete cookies older than 10 days
            db("DELETE FROM auth_cookie WHERE time < %s",
               (int(time_now()) - 86400 * 10,))
            # Insert a new cookie if we haven't already got one
            cookie = None
            trac_auth = req.incookie.get('trac_auth')
            if trac_auth is not None:
                name = self._cookie_to_name(req, trac_auth)
                cookie = trac_auth.value if name == remote_user else None
            if cookie is None:
                cookie = hex_entropy()
                db("""
                    INSERT INTO auth_cookie (cookie, name, ipnr, time)
                         VALUES (%s, %s, %s, %s)
                   """, (cookie, remote_user, req.remote_addr,
                         int(time_now())))
        req.authname = remote_user
        req.outcookie['trac_auth'] = cookie
        if self.auth_cookie_domain:
            req.outcookie['trac_auth']['domain'] = self.auth_cookie_domain
        req.outcookie['trac_auth']['path'] = self.auth_cookie_path \
                                             or req.base_path or '/'
        if self.env.secure_cookies:
            req.outcookie['trac_auth']['secure'] = True
        req.outcookie['trac_auth']['httponly'] = True
        if self.auth_cookie_lifetime > 0:
            req.outcookie['trac_auth']['expires'] = self.auth_cookie_lifetime
Ejemplo n.º 2
0
Archivo: mail.py Proyecto: zxfly/trac
    def send(self, from_addr, recipients, message):
        global local_hostname
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = fix_eol(message, CRLF)

        self.log.info("Sending notification through SMTP at %s:%d to %s",
                      self.smtp_server, self.smtp_port, recipients)
        try:
            server = smtplib.SMTP(self.smtp_server, self.smtp_port,
                                  local_hostname)
            local_hostname = server.local_hostname
        except smtplib.socket.error as e:
            raise ConfigurationError(
                tag_(
                    "SMTP server connection error (%(error)s). Please "
                    "modify %(option1)s or %(option2)s in your "
                    "configuration.",
                    error=to_unicode(e),
                    option1=tag.code("[notification] smtp_server"),
                    option2=tag.code("[notification] smtp_port")))
        # server.set_debuglevel(True)
        if self.use_tls:
            server.ehlo()
            if 'starttls' not in server.esmtp_features:
                raise TracError(
                    _("TLS enabled but server does not support"
                      " TLS"))
            server.starttls()
            server.ehlo()
        if self.smtp_user:
            server.login(self.smtp_user.encode('utf-8'),
                         self.smtp_password.encode('utf-8'))
        start = time_now()
        server.sendmail(from_addr, recipients, message)
        t = time_now() - start
        if t > 5:
            self.log.warning(
                "Slow mail submission (%.2f s), "
                "check your mail setup", t)
        if self.use_tls:
            # avoid false failure detection when the server closes
            # the SMTP connection with TLS enabled
            import socket
            try:
                server.quit()
            except socket.sslerror:
                pass
        else:
            server.quit()
Ejemplo n.º 3
0
    def test_purge_anonymous_session(self):
        """
        Verify that old sessions get purged.
        """
        with self.env.db_transaction as db:
            db("INSERT INTO session VALUES ('123456', 0, %s)", (0, ))
            db("INSERT INTO session VALUES ('987654', 0, %s)",
               (int(time_now() - PURGE_AGE - 3600), ))
            db("""
                INSERT INTO session_attribute
                VALUES ('987654', 0, 'foo', 'bar')
                """)

            # We need to modify a different session to trigger the purging
            req = MockRequest(self.env, authname='anonymous')
            req.incookie['trac_session'] = '123456'
            session = Session(self.env, req)
            session['foo'] = 'bar'
            session.save()

        self.assertEqual(
            0,
            self.env.db_query("""
            SELECT COUNT(*) FROM session WHERE sid='987654' AND authenticated=0
            """)[0][0])
Ejemplo n.º 4
0
    def test_delete_empty_session(self):
        """
        Verify that a session gets deleted when it doesn't have any data except
        for the 'last_visit' timestamp.
        """
        now = time_now()

        # Make sure the session has data so that it doesn't get dropped
        with self.env.db_transaction as db:
            db("INSERT INTO session VALUES ('123456', 0, %s)",
               (int(now - UPDATE_INTERVAL - 3600), ))
            db("""
                INSERT INTO session_attribute
                VALUES ('123456', 0, 'foo', 'bar')
                """)

            req = MockRequest(self.env, authname='anonymous')
            req.incookie['trac_session'] = '123456'
            session = Session(self.env, req)
            del session['foo']
            session.save()

        self.assertEqual(
            0,
            self.env.db_query("""
            SELECT COUNT(*) FROM session WHERE sid='123456' AND authenticated=0
            """)[0][0])
Ejemplo n.º 5
0
    def test_update_session(self):
        """
        Verify that accessing a session after one day updates the sessions
        'last_visit' variable so that the session doesn't get purged.
        """
        now = time_now()

        # Make sure the session has data so that it doesn't get dropped
        with self.env.db_transaction as db:
            db("INSERT INTO session VALUES ('123456', 0, 1)")
            db("""
                INSERT INTO session_attribute
                VALUES ('123456', 0, 'foo', 'bar')
                """)

            req = MockRequest(self.env, authname='anonymous')
            req.incookie['trac_session'] = '123456'
            session = Session(self.env, req)
            session['modified'] = True
            session.save()  # updating does require modifications

            self.assertEqual(PURGE_AGE,
                             req.outcookie['trac_session']['expires'])

        self.assertAlmostEqual(
            now,
            int(
                self.env.db_query("""
            SELECT last_visit FROM session
            WHERE sid='123456' AND authenticated=0
            """)[0][0]), -1)
Ejemplo n.º 6
0
    def _purge_anonymous_session(self):
        now = int(time_now())
        lifetime = 90 * 86400  # default lifetime
        with self.env.db_transaction as db:
            db.executemany("INSERT INTO session VALUES (%s, 0, %s)",
                           [('123456', 0), ('987654', now - lifetime - 3600),
                            ('876543', now - lifetime + 3600),
                            ('765432', now - 3600)])
            db.executemany(
                """
                INSERT INTO session_attribute
                VALUES (%s, 0, 'foo', 'bar')
                """, [('987654', ), ('876543', ), ('765432', )])

        with self.env.db_transaction as db:
            # We need to modify a different session to trigger the purging
            req = MockRequest(self.env, authname='anonymous')
            req.incookie['trac_session'] = '123456'
            session = Session(self.env, req)
            session['foo'] = 'bar'
            session.save()

        return [
            row[0] for row in self.env.db_query("""
            SELECT sid FROM session WHERE authenticated=0 ORDER BY sid
            """)
        ]
Ejemplo n.º 7
0
    def get_users_with_permission(self, permission):
        """Return all users that have the specified permission.

        Users are returned as a list of user names.
        """
        now = time_now()
        if now - self.last_reap > self.CACHE_REAP_TIME:
            self.permission_cache = {}
            self.last_reap = now
        timestamp, permissions = self.permission_cache.get(
            permission, (0, None))
        if now - timestamp <= self.CACHE_EXPIRY:
            return permissions

        parent_map = {}
        for parent, children in self.get_actions_dict().iteritems():
            for child in children:
                parent_map.setdefault(child, set()).add(parent)

        satisfying_perms = set()

        def append_with_parents(action):
            if action not in satisfying_perms:
                satisfying_perms.add(action)
                for action in parent_map.get(action, ()):
                    append_with_parents(action)

        append_with_parents(permission)

        perms = self.store.get_users_with_permissions(satisfying_perms) or []
        self.permission_cache[permission] = (now, perms)
        return perms
Ejemplo n.º 8
0
 def urandom(n):
     result = []
     hasher = hashlib.sha1(str(os.getpid()) + str(time_now()))
     while len(result) * hasher.digest_size < n:
         hasher.update(str(_entropy.random()))
         result.append(hasher.digest())
     result = ''.join(result)
     return result[:n] if len(result) > n else result
Ejemplo n.º 9
0
 def test_touch_changes_mtime(self):
     """Test that each touch command changes the file modification time."""
     config = self._read()
     time.sleep(1.0 - time_now() % 1.0)
     config.touch()
     mtime = os.stat(self.filename).st_mtime
     config.touch()
     self.assertNotEqual(mtime, os.stat(self.filename).st_mtime)
Ejemplo n.º 10
0
    def check_permission(self, action, username, resource, perm):
        now = time_now()

        if now - self.last_reap > self.CACHE_REAP_TIME:
            self.permission_cache = {}
            self.last_reap = time_now()

        timestamp, permissions = self.permission_cache.get(username, (0, None))

        # Cache hit?
        if now - timestamp > self.CACHE_EXPIRY:
            # No, pull permissions from database.
            permissions = PermissionSystem(self.env). \
                          get_user_permissions(username)
            self.permission_cache[username] = (now, permissions)

        return action in permissions or None
Ejemplo n.º 11
0
 def pre_process_request(self, req, handler):
     from trac.web.chrome import Chrome, add_warning
     if handler is not Chrome(self.env):
         for reponame in self.repository_sync_per_request:
             start = time_now()
             if is_default(reponame):
                 reponame = ''
             try:
                 repo = self.get_repository(reponame)
                 if repo:
                     repo.sync()
                 else:
                     self.log.warning(
                         "Unable to find repository '%s' for "
                         "synchronization", reponame or '(default)')
                     continue
             except TracError, e:
                 add_warning(
                     req,
                     _(
                         "Can't synchronize with repository \"%(name)s\" "
                         "(%(error)s). Look in the Trac log for more "
                         "information.",
                         name=reponame or '(default)',
                         error=to_unicode(e)))
             except Exception, e:
                 add_warning(
                     req,
                     _(
                         "Failed to sync with repository \"%(name)s\": "
                         "%(error)s; repository information may be out of "
                         "date. Look in the Trac log for more information "
                         "including mitigation strategies.",
                         name=reponame or '(default)',
                         error=to_unicode(e)))
                 self.log.error(
                     "Failed to sync with repository \"%s\"; You may be "
                     "able to reduce the impact of this issue by "
                     "configuring [trac] repository_sync_per_request; see "
                     "http://trac.edgewall.org/wiki/TracRepositoryAdmin"
                     "#ExplicitSync for more detail: %s",
                     reponame or '(default)',
                     exception_to_unicode(e, traceback=True))
             self.log.info("Synchronized '%s' repository in %0.2f seconds",
                           reponame or '(default)',
                           time_now() - start)
Ejemplo n.º 12
0
 def urandom(n):
     result = []
     hasher = hashlib.sha1('{}{}'.format(os.getpid(),
                                         time_now()).encode('utf-8'))
     while len(result) * hasher.digest_size < n:
         hasher.update(str(_entropy.random()).encode('utf-8'))
         result.append(hasher.digest())
     result = b''.join(result)
     return result[:n] if len(result) > n else result
Ejemplo n.º 13
0
    def promote_session(self, sid):
        """Promotes an anonymous session to an authenticated session, if there
        is no preexisting session data for that user name.
        """
        assert self.req.is_authenticated, \
               "Cannot promote session of anonymous user"

        with self.env.db_transaction as db:
            authenticated_flags = [
                authenticated for authenticated, in db(
                    "SELECT authenticated FROM session WHERE sid=%s OR sid=%s",
                    (sid, self.req.authname))
            ]

            if len(authenticated_flags) == 2:
                # There's already an authenticated session for the user,
                # we simply delete the anonymous session
                db("DELETE FROM session WHERE sid=%s AND authenticated=0",
                   (sid, ))
                db(
                    """DELETE FROM session_attribute
                      WHERE sid=%s AND authenticated=0
                      """, (sid, ))
            elif len(authenticated_flags) == 1:
                if not authenticated_flags[0]:
                    # Update the anonymous session records so the session ID
                    # becomes the user name, and set the authenticated flag.
                    self.env.log.debug(
                        "Promoting anonymous session %s to "
                        "authenticated session for user %s", sid,
                        self.req.authname)
                    db(
                        """UPDATE session SET sid=%s, authenticated=1
                          WHERE sid=%s AND authenticated=0
                          """, (self.req.authname, sid))
                    db(
                        """UPDATE session_attribute SET sid=%s, authenticated=1
                          WHERE sid=%s
                          """, (self.req.authname, sid))
            else:
                # We didn't have an anonymous session for this sid. The
                # authenticated session might have been inserted between the
                # SELECT above and here, so we catch the error.
                try:
                    db(
                        """INSERT INTO session (sid, last_visit, authenticated)
                          VALUES (%s, %s, 1)
                          """, (self.req.authname, int(time_now())))
                except self.env.db_exc.IntegrityError:
                    self.env.log.warning(
                        'Authenticated session for %s '
                        'already exists', self.req.authname)
                    db.rollback()
        self._new = False

        self.sid = sid
        self.bake_cookie(0)  # expire the cookie
Ejemplo n.º 14
0
 def test_save_changes_mtime(self):
     """Test that each save operation changes the file modification time."""
     class Foo(object):
         IntOption('section', 'option', 1)
     sconfig = self._read()
     sconfig.set_defaults()
     sconfig.save()
     rconfig = self._read()
     self.assertEqual(1, rconfig.getint('section', 'option'))
     sconfig.set('section', 'option', 2)
     time.sleep(1.0 - time_now() % 1.0)
     sconfig.save()
     rconfig.parse_if_needed()
     self.assertEqual(2, rconfig.getint('section', 'option'))
Ejemplo n.º 15
0
    def get_session(self, sid, authenticated=False):
        refresh_cookie = False

        if not authenticated and not self._valid_sid_re.match(sid):
            raise TracValueError(_("Session ID must be alphanumeric."))
        if self.sid and sid != self.sid:
            refresh_cookie = True

        super(Session, self).get_session(sid, authenticated)
        if self.last_visit and time_now() - self.last_visit > UPDATE_INTERVAL:
            refresh_cookie = True

        # Refresh the session cookie if this is the first visit after a day
        if not authenticated and refresh_cookie:
            self.bake_cookie()
Ejemplo n.º 16
0
 def shutdown(self, tid=None):
     """Close pooled connections not used in a while"""
     delay = 120
     if tid is None:
         delay = 0
     when = time_now() - delay
     with self._available:
         if tid is None:  # global shutdown, also close active connections
             for db, num in self._active.values():
                 db.close()
             self._active = {}
         while self._pool_time and self._pool_time[0] <= when:
             db = self._pool.pop(0)
             db.close()
             self._pool_key.pop(0)
             self._pool_time.pop(0)
Ejemplo n.º 17
0
 def _do_add(self, sid, name=None, email=None):
     sid, authenticated = self._split_sid(sid)
     with self.env.db_transaction as db:
         try:
             db("INSERT INTO session VALUES (%s, %s, %s)",
                (sid, authenticated, int(time_now())))
         except Exception:
             raise AdminCommandError(
                 _("Session '%(sid)s' already exists", sid=sid))
         if name:
             db("INSERT INTO session_attribute VALUES (%s,%s,'name',%s)",
                (sid, authenticated, name))
         if email:
             db("INSERT INTO session_attribute VALUES (%s,%s,'email',%s)",
                (sid, authenticated, email))
     self.env.invalidate_known_users_cache()
Ejemplo n.º 18
0
 def _return_cnx(self, cnx, key, tid):
     # Decrement active refcount, clear slot if 1
     with self._available:
         assert (tid, key) in self._active
         cnx, num = self._active[(tid, key)]
         if num == 1:
             del self._active[(tid, key)]
         else:
             self._active[(tid, key)] = (cnx, num - 1)
     if num == 1:
         # Reset connection outside of critical section
         try:
             cnx.rollback()  # resets the connection
         except Exception:
             cnx.close()
             cnx = None
         # Connection available, from reuse or from creation of a new one
         with self._available:
             if cnx and cnx.poolable:
                 self._pool.append(cnx)
                 self._pool_key.append(key)
                 self._pool_time.append(time_now())
             self._available.notify()
Ejemplo n.º 19
0
    def insert_users(self, users):
        """Insert a tuple representing a user session to the
        `session` and `session_attributes` tables.

        The tuple can be length 3 with entries username, name and
        email, in which case an authenticated user is assumed. The
        tuple can also be length 4, with the last entry specifying
        `1` for an authenticated user or `0` for an unauthenticated
        user.
        """
        with self.db_transaction as db:
            for row in users:
                if len(row) == 3:
                    username, name, email = row
                    authenticated = 1
                else:  # len(row) == 4
                    username, name, email, authenticated = row
                db("INSERT INTO session VALUES (%s, %s, %s)",
                   (username, authenticated, int(time_now())))
                db("INSERT INTO session_attribute VALUES (%s,%s,'name',%s)",
                   (username, authenticated, name))
                db("INSERT INTO session_attribute VALUES (%s,%s,'email',%s)",
                   (username, authenticated, email))
Ejemplo n.º 20
0
class SmtpEmailSender(Component):
    """E-mail sender connecting to an SMTP server."""

    implements(IEmailSender)

    smtp_server = Option('notification', 'smtp_server', 'localhost',
        """SMTP server hostname to use for email notifications.""")

    smtp_port = IntOption('notification', 'smtp_port', 25,
        """SMTP server port to use for email notification.""")

    smtp_user = Option('notification', 'smtp_user', '',
        """Username for SMTP server. (''since 0.9'')""")

    smtp_password = Option('notification', 'smtp_password', '',
        """Password for SMTP server. (''since 0.9'')""")

    use_tls = BoolOption('notification', 'use_tls', 'false',
        """Use SSL/TLS to send notifications over SMTP. (''since 0.10'')""")

    def send(self, from_addr, recipients, message):
        global local_hostname
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = fix_eol(message, CRLF)

        self.log.info("Sending notification through SMTP at %s:%d to %s",
                      self.smtp_server, self.smtp_port, recipients)
        try:
            server = smtplib.SMTP(self.smtp_server, self.smtp_port,
                                  local_hostname)
            local_hostname = server.local_hostname
        except smtplib.socket.error, e:
            raise ConfigurationError(
                tag_("SMTP server connection error (%(error)s). Please "
                     "modify %(option1)s or %(option2)s in your "
                     "configuration.",
                     error=to_unicode(e),
                     option1=tag.tt("[notification] smtp_server"),
                     option2=tag.tt("[notification] smtp_port")))
        # server.set_debuglevel(True)
        if self.use_tls:
            server.ehlo()
            if 'starttls' not in server.esmtp_features:
                raise TracError(_("TLS enabled but server does not support "
                                  "TLS"))
            server.starttls()
            server.ehlo()
        if self.smtp_user:
            server.login(self.smtp_user.encode('utf-8'),
                         self.smtp_password.encode('utf-8'))
        start = time_now()
        server.sendmail(from_addr, recipients, message)
        t = time_now() - start
        if t > 5:
            self.log.warning('Slow mail submission (%.2f s), '
                             'check your mail setup', t)
        if self.use_tls:
            # avoid false failure detection when the server closes
            # the SMTP connection with TLS enabled
            import socket
            try:
                server.quit()
            except socket.sslerror:
                pass
        else:
            server.quit()
Ejemplo n.º 21
0
 def __init__(self):
     self.permission_cache = {}
     self.last_reap = time_now()
Ejemplo n.º 22
0
    def _build_rev_cache(self, refs):
        self.logger.debug("triggered rebuild of commit tree db for '%s'",
                          self.repo_path)
        ts0 = time_now()

        new_db = {}  # db
        new_sdb = {}  # short_rev db

        # helper for reusing strings
        revs_seen = {}

        def _rev_reuse(rev):
            return revs_seen.setdefault(rev, rev)

        refs = {refname: _rev_reuse(rev) for refname, rev in refs.iteritems()}
        head_revs = {
            rev
            for refname, rev in refs.iteritems()
            if refname.startswith('refs/heads/')
        }
        rev_list = [
            map(_rev_reuse, line.split()) for line in self.repo.rev_list(
                '--parents', '--topo-order', '--all').splitlines()
        ]
        revs_seen = None

        if rev_list:
            # first rev seen is assumed to be the youngest one
            youngest = rev_list[0][0]
            # last rev seen is assumed to be the oldest one
            oldest = rev_list[-1][0]
        else:
            youngest = oldest = None

        rheads_seen = {}

        def _rheads_reuse(rheads):
            rheads = frozenset(rheads)
            return rheads_seen.setdefault(rheads, rheads)

        __rev_key = self.__rev_key
        for ord_rev, revs in enumerate(rev_list):
            rev = revs[0]
            parents = revs[1:]

            # shortrev "hash" map
            new_sdb.setdefault(__rev_key(rev), []).append(rev)

            # new_db[rev] = (children(rev), parents(rev),
            #                ordinal_id(rev), rheads(rev))
            if rev in new_db:
                # (incomplete) entry was already created by children
                _children, _parents, _ord_rev, _rheads = new_db[rev]
                assert _children
                assert not _parents
                assert _ord_rev == 0
            else:  # new entry
                _children = set()
                _rheads = set()
            if rev in head_revs:
                _rheads.add(rev)

            # create/update entry
            # transform into frozenset and tuple since entry will be final
            new_db[rev] = (frozenset(_children), tuple(parents), ord_rev + 1,
                           _rheads_reuse(_rheads))

            # update parents(rev)s
            for parent in parents:
                # by default, a dummy ordinal_id is used for the mean-time
                _children, _parents, _ord_rev, _rheads2 = \
                    new_db.setdefault(parent, (set(), [], 0, set()))

                # update parent(rev)'s children
                _children.add(rev)

                # update parent(rev)'s rheads
                _rheads2.update(_rheads)

        rheads_seen = None

        # convert sdb either to dict or array depending on size
        tmp = [()] * (max(new_sdb.keys()) + 1) if len(new_sdb) > 5000 else {}
        try:
            while True:
                k, v = new_sdb.popitem()
                tmp[k] = tuple(v)
        except KeyError:
            pass
        assert len(new_sdb) == 0
        new_sdb = tmp

        rev_cache = self.RevCache(youngest, oldest, new_db, refs, new_sdb)
        self.logger.debug(
            "rebuilt commit tree db for '%s' with %d entries "
            "(took %.1f ms)", self.repo_path, len(new_db),
            1000 * (time_now() - ts0))
        return rev_cache
Ejemplo n.º 23
0
    def get_cnx(self, connector, kwargs, timeout=None):
        cnx = None
        log = kwargs.get('log')
        key = unicode(kwargs)
        start = time_now()
        tid = get_thread_id()
        # Get a Connection, either directly or a deferred one
        with self._available:
            # First choice: Return the same cnx already used by the thread
            if (tid, key) in self._active:
                cnx, num = self._active[(tid, key)]
                num += 1
            else:
                if self._waiters == 0:
                    cnx = self._take_cnx(connector, kwargs, key, tid)
                if not cnx:
                    self._waiters += 1
                    self._available.wait()
                    self._waiters -= 1
                    cnx = self._take_cnx(connector, kwargs, key, tid)
                num = 1
            if cnx:
                self._active[(tid, key)] = (cnx, num)

        deferred = num == 1 and isinstance(cnx, tuple)
        exc_info = (None, None, None)
        if deferred:
            # Potentially lengthy operations must be done without lock held
            op, cnx = cnx
            try:
                if op == 'ping':
                    cnx.ping()
                elif op == 'close':
                    cnx.close()
                if op in ('close', 'create'):
                    cnx = connector.get_connection(**kwargs)
            except TracError:
                exc_info = sys.exc_info()
                cnx = None
            except Exception:
                exc_info = sys.exc_info()
                if log:
                    log.error('Exception caught on %s', op, exc_info=True)
                cnx = None

        if cnx and not isinstance(cnx, tuple):
            if deferred:
                # replace placeholder with real Connection
                with self._available:
                    self._active[(tid, key)] = (cnx, num)
            return PooledConnection(self, cnx, key, tid, log)

        if deferred:
            # cnx couldn't be reused, clear placeholder
            with self._available:
                del self._active[(tid, key)]
            if op == 'ping':  # retry
                return self.get_cnx(connector, kwargs)

        # if we didn't get a cnx after wait(), something's fishy...
        if isinstance(exc_info[1], TracError):
            raise exc_info[0], exc_info[1], exc_info[2]
        timeout = time_now() - start
        errmsg = _(
            "Unable to get database connection within %(time)d seconds.",
            time=timeout)
        if exc_info[1]:
            errmsg += " (%s)" % exception_to_unicode(exc_info[1])
        raise TimeoutError(errmsg)
Ejemplo n.º 24
0
    def save(self):
        authenticated = int(self.authenticated)
        now = int(time_now())
        items = self.items()
        if not authenticated and not self._old and not items:
            # The session for anonymous doesn't have associated data,
            # so there's no need to persist it
            return

        # We can't do the session management in one big transaction,
        # as the intertwined changes to both the session and
        # session_attribute tables are prone to deadlocks (#9705).
        # Therefore we first we save the current session, then we
        # eventually purge the tables.

        session_saved = False

        with self.env.db_transaction as db:
            # Try to save the session if it's a new one. A failure to
            # do so is not critical but we nevertheless skip the
            # following steps.

            new = self._new
            if new:
                self.last_visit = now
                self._new = False
                # The session might already exist even if _new is True since
                # it could have been created by a concurrent request (#3563).
                try:
                    db(
                        """INSERT INTO session (sid, last_visit, authenticated)
                          VALUES (%s,%s,%s)
                          """, (self.sid, self.last_visit, authenticated))
                except self.env.db_exc.IntegrityError:
                    self.env.log.warning('Session %s already exists', self.sid)
                    db.rollback()
                    return

            if authenticated and \
                    (new or self._old.get('name') != self.get('name') or \
                     self._old.get('email') != self.get('email')):
                self.env.invalidate_known_users_cache()

            # Remove former values for session_attribute and save the
            # new ones. The last concurrent request to do so "wins".

            if self._old != self:
                if not items and not authenticated:
                    # No need to keep around empty unauthenticated sessions
                    db("DELETE FROM session WHERE sid=%s AND authenticated=0",
                       (self.sid, ))
                db(
                    """DELETE FROM session_attribute
                      WHERE sid=%s AND authenticated=%s
                      """, (self.sid, authenticated))
                self._old = dict(self.items())
                # The session variables might already have been updated by a
                # concurrent request.
                try:
                    db.executemany(
                        """
                        INSERT INTO session_attribute
                          (sid,authenticated,name,value)
                        VALUES (%s,%s,%s,%s)
                        """,
                        [(self.sid, authenticated, k, v) for k, v in items])
                except self.env.db_exc.IntegrityError:
                    self.env.log.warning(
                        'Attributes for session %s already '
                        'updated', self.sid)
                    db.rollback()
                    return
                session_saved = True

        # Purge expired sessions. We do this only when the session was
        # changed as to minimize the purging.

        if session_saved and now - self.last_visit > UPDATE_INTERVAL:
            self.last_visit = now
            lifetime = self.env.anonymous_session_lifetime
            mintime = now - lifetime * 86400 if lifetime > 0 else None

            with self.env.db_transaction as db:
                # Update the session last visit time if it is over an
                # hour old, so that session doesn't get purged
                self.env.log.info("Refreshing session %s", self.sid)
                db(
                    """UPDATE session SET last_visit=%s
                      WHERE sid=%s AND authenticated=%s
                      """, (self.last_visit, self.sid, authenticated))
                if mintime:
                    self.env.log.debug('Purging old, expired, sessions.')
                    db(
                        """DELETE FROM session_attribute
                          WHERE authenticated=0 AND sid IN (
                              SELECT sid FROM session
                              WHERE authenticated=0 AND last_visit < %s
                          )
                          """, (mintime, ))

            # Avoid holding locks on lot of rows on both session_attribute
            # and session tables
            if mintime:
                with self.env.db_transaction as db:
                    db(
                        """
                        DELETE FROM session
                        WHERE authenticated=0 AND last_visit < %s
                        """, (mintime, ))