session.py :  » Project-Management » Trac » Trac-0.11.7 » trac » web » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Project Management » Trac 
Trac » Trac 0.11.7 » trac » web » session.py
# -*- coding: utf-8 -*-
#
# Copyright (C) 2004-2009 Edgewall Software
# Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com>
# Copyright (C) 2004-2006 Christopher Lenz <cmlenz@gmx.de>
# Copyright (C) 2006 Jonas Borgstrm <jonas@edgewall.com>
# Copyright (C) 2008 Matt Good <matt@matt-good.net>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Daniel Lundin <daniel@edgewall.com>
#         Christopher Lenz <cmlenz@gmx.de>

import time

from trac.core import TracError
from trac.util import hex_entropy
from trac.util.html import Markup

UPDATE_INTERVAL = 3600*24 # Update session last_visit time stamp after 1 day
PURGE_AGE = 3600*24*90 # Purge session after 90 days idle
COOKIE_KEY = 'trac_session'


class DetachedSession(dict):
    def __init__(self, env, sid):
        dict.__init__(self)
        self.env = env
        self.sid = None
        self.last_visit = 0
        self._new = True
        self._old = {}
        if sid:
            self.get_session(sid, authenticated=True)
        else:
            self.authenticated = False

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, unicode(value))

    def get_session(self, sid, authenticated=False):
        self.env.log.debug('Retrieving session for ID %r', sid)

        db = self.env.get_db_cnx()
        cursor = db.cursor()

        self.sid = sid
        self.authenticated = authenticated

        cursor.execute("SELECT last_visit FROM session "
                       "WHERE sid=%s AND authenticated=%s",
                       (sid, int(authenticated)))
        row = cursor.fetchone()
        if not row:
            return
        self._new = False
        self.last_visit = int(row[0] or 0)

        cursor.execute("SELECT name,value FROM session_attribute "
                       "WHERE sid=%s and authenticated=%s",
                       (sid, int(authenticated)))
        for name, value in cursor:
            self[name] = value
        self._old.update(self)

    def save(self):
        if not self._old and not self.items():
            # The session doesn't have associated data, so there's no need to
            # persist it
            return

        authenticated = int(self.authenticated)
        now = int(time.time())
        db = self.env.get_db_cnx()

        if self._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:
                cursor = db.cursor()
                cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"
                               " VALUES(%s,%s,%s)",
                               (self.sid, self.last_visit, authenticated))
            except Exception, e:
                db.rollback()
                self.env.log.warning('Session %s already exists: %s' % 
                                     (self.sid, e))
        if self._old != self:
            attrs = [(self.sid, authenticated, k, v) for k, v in self.items()]
            cursor = db.cursor()
            cursor.execute("DELETE FROM session_attribute WHERE sid=%s",
                           (self.sid,))
            self._old = dict(self.items())
            if attrs:
                # The session variables might already have been updated by a 
                # concurrent request.
                try:
                    cursor.executemany("INSERT INTO session_attribute "
                                       "(sid,authenticated,name,value) "
                                       "VALUES(%s,%s,%s,%s)", attrs)
                except Exception, e:
                    db.rollback()
                    self.env.log.warning('Attributes for session %s already '
                                         'updated: %s' % (self.sid, e))
            elif not authenticated:
                # No need to keep around empty unauthenticated sessions
                cursor.execute("DELETE FROM session "
                               "WHERE sid=%s AND authenticated=0", (self.sid,))
                db.commit()
                return

        # Update the session last visit time if it is over an hour old,
        # so that session doesn't get purged
        if now - self.last_visit > UPDATE_INTERVAL:
            self.last_visit = now
            self.env.log.info("Refreshing session %s" % self.sid)
            cursor = db.cursor()
            cursor.execute('UPDATE session SET last_visit=%s '
                           'WHERE sid=%s AND authenticated=%s',
                           (self.last_visit, self.sid, authenticated))
            # Purge expired sessions. We do this only when the session was
            # changed as to minimize the purging.
            mintime = now - PURGE_AGE
            self.env.log.debug('Purging old, expired, sessions.')
            cursor.execute("DELETE FROM session_attribute "
                           "WHERE authenticated=0 AND sid "
                           "IN (SELECT sid FROM session WHERE "
                           "authenticated=0 AND last_visit < %s)",
                           (mintime,))
            cursor.execute("DELETE FROM session WHERE "
                           "authenticated=0 AND last_visit < %s",
                           (mintime,))
        db.commit()


class Session(DetachedSession):
    """Basic session handling and per-session storage."""

    def __init__(self, env, req):
        super(Session, self).__init__(env, None)
        self.req = req
        if req.authname == 'anonymous':
            if not req.incookie.has_key(COOKIE_KEY):
                self.sid = hex_entropy(24)
                self.bake_cookie()
            else:
                sid = req.incookie[COOKIE_KEY].value
                self.get_session(sid)
        else:
            if req.incookie.has_key(COOKIE_KEY):
                sid = req.incookie[COOKIE_KEY].value
                self.promote_session(sid)
            self.get_session(req.authname, authenticated=True)

    def bake_cookie(self, expires=PURGE_AGE):
        assert self.sid, 'Session ID not set'
        self.req.outcookie[COOKIE_KEY] = self.sid
        self.req.outcookie[COOKIE_KEY]['path'] = self.req.base_path or '/'
        self.req.outcookie[COOKIE_KEY]['expires'] = expires
        if self.env.secure_cookies:
            self.req.outcookie[COOKIE_KEY]['secure'] = True

    def get_session(self, sid, authenticated=False):
        refresh_cookie = False

        if self.sid and sid != self.sid:
            refresh_cookie = True

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

        # Refresh the session cookie if this is the first visit since over a day
        if not authenticated and refresh_cookie:
            self.bake_cookie()

    def change_sid(self, new_sid):
        assert self.req.authname == 'anonymous', \
               'Cannot change ID of authenticated session'
        assert new_sid, 'Session ID cannot be empty'
        if new_sid == self.sid:
            return
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("SELECT sid FROM session WHERE sid=%s", (new_sid,))
        if cursor.fetchone():
            raise TracError(Markup('Session "%s" already exists.<br />'
                                   'Please choose a different session ID.')
                            % new_sid, 'Error renaming session')
        self.env.log.debug('Changing session ID %s to %s' % (self.sid, new_sid))
        cursor.execute("UPDATE session SET sid=%s WHERE sid=%s "
                       "AND authenticated=0", (new_sid, self.sid))
        cursor.execute("UPDATE session_attribute SET sid=%s "
                       "WHERE sid=%s and authenticated=0",
                       (new_sid, self.sid))
        db.commit()
        self.sid = new_sid
        self.bake_cookie()

    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.authname != 'anonymous', \
               'Cannot promote session of anonymous user'

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("SELECT authenticated FROM session "
                       "WHERE sid=%s OR sid=%s ", (sid, self.req.authname))
        authenticated_flags = [row[0] for row in cursor.fetchall()]
        
        if len(authenticated_flags) == 2:
            # There's already an authenticated session for the user, we
            # simply delete the anonymous session
            cursor.execute("DELETE FROM session WHERE sid=%s "
                           "AND authenticated=0", (sid,))
            cursor.execute("DELETE FROM session_attribute WHERE sid=%s "
                           "AND authenticated=0", (sid,))
        elif len(authenticated_flags) == 1:
            if not authenticated_flags[0]:
                # Update the anomymous session records so that 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)
                cursor.execute("UPDATE session SET sid=%s,authenticated=1 "
                               "WHERE sid=%s AND authenticated=0",
                               (self.req.authname, sid))
                cursor.execute("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
            cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"
                           " VALUES(%s,%s,1)",
                           (self.req.authname, int(time.time())))
        self._new = False
        db.commit()

        self.sid = sid
        self.bake_cookie(0) # expire the cookie
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.