Ticket #3461: issue.3461.patch

File issue.3461.patch, 9.9 KB (added by steiza, 4 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