Ticket #3461: issue.3461.patch

File issue.3461.patch, 9.9 KB (added by steiza, 5 years ago)

Proposed fix

  • twisted/web/error.py

    diff --git twisted/web/error.py twisted/web/error.py
    index f4fc8ff..c29326f 100644
    class SchemeNotSupported(Exception): 
    185185
    186186
    187187
     188class SecurityIssue(Exception):
     189    """
     190    The requested action would result in secure information leaking.
     191    """
     192
     193
    188194from twisted.web import resource as _resource
    189195
    190196class ErrorPage(_resource.ErrorPage):
  • twisted/web/server.py

    diff --git twisted/web/server.py twisted/web/server.py
    index 46c461a..9190d8e 100644
    from twisted.web import iweb, http 
    3434from twisted.python import log, reflect, failure, components
    3535from twisted import copyright
    3636from twisted.web import util as webutil, resource
    37 from twisted.web.error import UnsupportedMethod
     37from twisted.web.error import UnsupportedMethod, SecurityIssue
    3838
    3939# backwards compatability
    4040date_time_string = http.datetimeToString
    def _addressToTuple(addr): 
    5353        return tuple(addr)
    5454
    5555class Request(pb.Copyable, http.Request, components.Componentized):
     56    """
     57    An HTTP request.
     58
     59    @ivar session: This stores a session available to HTTP and HTTPS requests.
     60    @ivar secure_session: This stores a session only available to HTTPS
     61            requests.
     62    """
    5663    implements(iweb.IRequest)
    5764
    5865    site = None
    class Request(pb.Copyable, http.Request, components.Componentized): 
    264271    ### these calls remain local
    265272
    266273    session = None
     274    secure_session = None
     275
     276    def getSession(self, sessionInterface=None, secure=False):
     277        """
     278        Check if there is a session cookie, and if not, create it.
     279
     280        By default this session is available on HTTP and HTTPS requests. Set
     281        L{secure} = True if you want a session that's only available on HTTPS.
     282
     283        If you try to create a secure session on a non-secure page, this will
     284        raise a L{twisted.web.error.SecurityIssue}.
     285        """
     286        # Make sure we aren't creating a secure session on a non-secure page
     287        if secure and not self.isSecure():
     288            raise SecurityIssue('Cannot create secure session on insecure page')
     289
     290        cookie_string = ''
     291        session = None
     292
     293        if not secure:
     294            cookie_string = 'TWISTED_SESSION'
     295            session = self.session
     296
     297        else:
     298            cookie_string = 'TWISTED_SECURE_SESSION'
     299            session = self.secure_session
    267300
    268     def getSession(self, sessionInterface = None):
    269301        # Session management
    270         if not self.session:
    271             cookiename = string.join(['TWISTED_SESSION'] + self.sitepath, "_")
     302        if not session:
     303            cookiename = string.join([cookie_string] + self.sitepath, "_")
    272304            sessionCookie = self.getCookie(cookiename)
    273305            if sessionCookie:
    274306                try:
    275                     self.session = self.site.getSession(sessionCookie)
     307                    session = self.site.getSession(sessionCookie)
    276308                except KeyError:
    277309                    pass
    278310            # if it still hasn't been set, fix it up.
    279             if not self.session:
    280                 self.session = self.site.makeSession()
    281                 self.addCookie(cookiename, self.session.uid, path='/')
    282         self.session.touch()
     311            if not session:
     312                session = self.site.makeSession()
     313                self.addCookie(cookiename, session.uid, path='/',
     314                               secure=secure)
     315
     316        session.touch()
     317
     318        # Save the session to the proper place
     319        if not secure:
     320            self.session = session
     321        else:
     322            self.secure_session = session
     323
    283324        if sessionInterface:
    284             return self.session.getComponent(sessionInterface)
    285         return self.session
     325            return session.getComponent(sessionInterface)
     326
     327        return session
    286328
    287329    def _prePathURL(self, prepath):
    288330        port = self.getHost().port
  • twisted/web/test/test_web.py

    diff --git twisted/web/test/test_web.py twisted/web/test/test_web.py
    index 6306a56..b1bca02 100644
    from twisted.internet.defer import Deferred 
    1717from twisted.web import server, resource, util
    1818from twisted.internet import defer, interfaces, task
    1919from twisted.web import iweb, http, http_headers
     20from twisted.web.error import SecurityIssue
    2021from twisted.python import log
    2122
    2223
    class DummyRequest: 
    8586        """
    8687        return self.headers.get(name.lower(), None)
    8788
    88 
    8989    def setHeader(self, name, value):
    9090        """TODO: make this assert on write() if the header is content-length
    9191        """
    9292        self.outgoingHeaders[name.lower()] = value
    9393
    94     def getSession(self):
    95         if self.session:
    96             return self.session
    97         assert not self.written, "Session cannot be requested after data has been written."
    98         self.session = self.protoSession
    99         return self.session
    100 
    101 
    10294    def render(self, resource):
    10395        """
    10496        Render the given resource as a response to this request.
    class RequestTests(unittest.TestCase): 
    568560        self.assertTrue(
    569561            verifyObject(iweb.IRequest, server.Request(DummyChannel(), True)))
    570562
    571 
    572     def testChildLink(self):
     563    def test_childLink(self):
    573564        request = server.Request(DummyChannel(), 1)
    574565        request.gotLength(0)
    575566        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    class RequestTests(unittest.TestCase): 
    579570        request.requestReceived('GET', '/foo/bar/', 'HTTP/1.0')
    580571        self.assertEqual(request.childLink('baz'), 'baz')
    581572
    582     def testPrePathURLSimple(self):
     573    def test_prePathURLSimple(self):
    583574        request = server.Request(DummyChannel(), 1)
    584575        request.gotLength(0)
    585576        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    586577        request.setHost('example.com', 80)
    587578        self.assertEqual(request.prePathURL(), 'http://example.com/foo/bar')
    588579
    589     def testPrePathURLNonDefault(self):
     580    def test_prePathURLNonDefault(self):
    590581        d = DummyChannel()
    591582        d.transport.port = 81
    592583        request = server.Request(d, 1)
    class RequestTests(unittest.TestCase): 
    595586        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    596587        self.assertEqual(request.prePathURL(), 'http://example.com:81/foo/bar')
    597588
    598     def testPrePathURLSSLPort(self):
     589    def test_prePathURLSSLPort(self):
    599590        d = DummyChannel()
    600591        d.transport.port = 443
    601592        request = server.Request(d, 1)
    class RequestTests(unittest.TestCase): 
    604595        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    605596        self.assertEqual(request.prePathURL(), 'http://example.com:443/foo/bar')
    606597
    607     def testPrePathURLSSLPortAndSSL(self):
     598    def test_prePathURLSSLPortAndSSL(self):
    608599        d = DummyChannel()
    609600        d.transport = DummyChannel.SSL()
    610601        d.transport.port = 443
    class RequestTests(unittest.TestCase): 
    614605        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    615606        self.assertEqual(request.prePathURL(), 'https://example.com/foo/bar')
    616607
    617     def testPrePathURLHTTPPortAndSSL(self):
     608    def test_prePathURLHTTPPortAndSSL(self):
    618609        d = DummyChannel()
    619610        d.transport = DummyChannel.SSL()
    620611        d.transport.port = 80
    class RequestTests(unittest.TestCase): 
    624615        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    625616        self.assertEqual(request.prePathURL(), 'https://example.com:80/foo/bar')
    626617
    627     def testPrePathURLSSLNonDefault(self):
     618    def test_prePathURLSSLNonDefault(self):
    628619        d = DummyChannel()
    629620        d.transport = DummyChannel.SSL()
    630621        d.transport.port = 81
    class RequestTests(unittest.TestCase): 
    634625        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    635626        self.assertEqual(request.prePathURL(), 'https://example.com:81/foo/bar')
    636627
    637     def testPrePathURLSetSSLHost(self):
     628    def test_prePathURLSetSSLHost(self):
    638629        d = DummyChannel()
    639630        d.transport.port = 81
    640631        request = server.Request(d, 1)
    class RequestTests(unittest.TestCase): 
    643634        request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
    644635        self.assertEqual(request.prePathURL(), 'https://foo.com:81/foo/bar')
    645636
    646 
    647637    def test_prePathURLQuoting(self):
    648638        """
    649639        L{Request.prePathURL} quotes special characters in the URL segments to
    class RequestTests(unittest.TestCase): 
    656646        request.requestReceived('GET', '/foo%2Fbar', 'HTTP/1.0')
    657647        self.assertEqual(request.prePathURL(), 'http://example.com/foo%2Fbar')
    658648
     649    def test_sessionDifferentFromSecureSession(self):
     650        """
     651        Ensure L{Request.session} and L{Request.secure_session} are different.
     652        """
     653        d = DummyChannel()
     654        d.transport = DummyChannel.SSL()
     655        request = server.Request(d, 1)
     656        request.site = server.Site('/')
     657        request.sitepath = []
     658        session = request.getSession()
     659        secure_session = request.getSession(secure=True)
     660
     661        # Check that the sessions are not None
     662        self.assertTrue(session != None)
     663        self.assertTrue(secure_session != None)
     664
     665        # Check that the sessions are different
     666        self.assertNotEqual(session.uid, secure_session.uid)
     667
     668        # Check that the sessions are getting saved
     669        self.assertEqual(session, request.session)
     670        self.assertEqual(secure_session, request.secure_session)
     671
     672        session.expire()
     673        secure_session.expire()
    659674
     675    def test_secureSessionRaiseExceptionOnInsecureRequest(self):
     676        """
     677        Ensure L{Request.getSession} raises L{error.SecurityIssue} if
     678        secure = True but the request is on an insecure page.
     679        """
     680        d = DummyChannel()
     681        request = server.Request(d, 1)
     682        request.site = server.Site('/')
     683        request.sitepath = []
     684        self.assertRaises(SecurityIssue, request.getSession, secure=True)
    660685
    661686class RootResource(resource.Resource):
    662687    isLeaf=0