diff --git twisted/web/error.py twisted/web/error.py
index f4fc8ff..c29326f 100644
|
|
|
|
| 185 | 185 | |
| 186 | 186 | |
| 187 | 187 | |
| | 188 | class SecurityIssue(Exception): |
| | 189 | """ |
| | 190 | The requested action would result in secure information leaking. |
| | 191 | """ |
| | 192 | |
| | 193 | |
| 188 | 194 | from twisted.web import resource as _resource |
| 189 | 195 | |
| 190 | 196 | class ErrorPage(_resource.ErrorPage): |
diff --git twisted/web/server.py twisted/web/server.py
index 46c461a..9190d8e 100644
|
|
|
|
| 34 | 34 | from twisted.python import log, reflect, failure, components |
| 35 | 35 | from twisted import copyright |
| 36 | 36 | from twisted.web import util as webutil, resource |
| 37 | | from twisted.web.error import UnsupportedMethod |
| | 37 | from twisted.web.error import UnsupportedMethod, SecurityIssue |
| 38 | 38 | |
| 39 | 39 | # backwards compatability |
| 40 | 40 | date_time_string = http.datetimeToString |
| … |
… |
|
| 53 | 53 | return tuple(addr) |
| 54 | 54 | |
| 55 | 55 | class 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 | """ |
| 56 | 63 | implements(iweb.IRequest) |
| 57 | 64 | |
| 58 | 65 | site = None |
| … |
… |
|
| 264 | 271 | ### these calls remain local |
| 265 | 272 | |
| 266 | 273 | 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 |
| 267 | 300 | |
| 268 | | def getSession(self, sessionInterface = None): |
| 269 | 301 | # 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, "_") |
| 272 | 304 | sessionCookie = self.getCookie(cookiename) |
| 273 | 305 | if sessionCookie: |
| 274 | 306 | try: |
| 275 | | self.session = self.site.getSession(sessionCookie) |
| | 307 | session = self.site.getSession(sessionCookie) |
| 276 | 308 | except KeyError: |
| 277 | 309 | pass |
| 278 | 310 | # 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 | |
| 283 | 324 | if sessionInterface: |
| 284 | | return self.session.getComponent(sessionInterface) |
| 285 | | return self.session |
| | 325 | return session.getComponent(sessionInterface) |
| | 326 | |
| | 327 | return session |
| 286 | 328 | |
| 287 | 329 | def _prePathURL(self, prepath): |
| 288 | 330 | port = self.getHost().port |
diff --git twisted/web/test/test_web.py twisted/web/test/test_web.py
index 6306a56..b1bca02 100644
|
|
|
|
| 17 | 17 | from twisted.web import server, resource, util |
| 18 | 18 | from twisted.internet import defer, interfaces, task |
| 19 | 19 | from twisted.web import iweb, http, http_headers |
| | 20 | from twisted.web.error import SecurityIssue |
| 20 | 21 | from twisted.python import log |
| 21 | 22 | |
| 22 | 23 | |
| … |
… |
|
| 85 | 86 | """ |
| 86 | 87 | return self.headers.get(name.lower(), None) |
| 87 | 88 | |
| 88 | | |
| 89 | 89 | def setHeader(self, name, value): |
| 90 | 90 | """TODO: make this assert on write() if the header is content-length |
| 91 | 91 | """ |
| 92 | 92 | self.outgoingHeaders[name.lower()] = value |
| 93 | 93 | |
| 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 | | |
| 102 | 94 | def render(self, resource): |
| 103 | 95 | """ |
| 104 | 96 | Render the given resource as a response to this request. |
| … |
… |
|
| 568 | 560 | self.assertTrue( |
| 569 | 561 | verifyObject(iweb.IRequest, server.Request(DummyChannel(), True))) |
| 570 | 562 | |
| 571 | | |
| 572 | | def testChildLink(self): |
| | 563 | def test_childLink(self): |
| 573 | 564 | request = server.Request(DummyChannel(), 1) |
| 574 | 565 | request.gotLength(0) |
| 575 | 566 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| … |
… |
|
| 579 | 570 | request.requestReceived('GET', '/foo/bar/', 'HTTP/1.0') |
| 580 | 571 | self.assertEqual(request.childLink('baz'), 'baz') |
| 581 | 572 | |
| 582 | | def testPrePathURLSimple(self): |
| | 573 | def test_prePathURLSimple(self): |
| 583 | 574 | request = server.Request(DummyChannel(), 1) |
| 584 | 575 | request.gotLength(0) |
| 585 | 576 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 586 | 577 | request.setHost('example.com', 80) |
| 587 | 578 | self.assertEqual(request.prePathURL(), 'http://example.com/foo/bar') |
| 588 | 579 | |
| 589 | | def testPrePathURLNonDefault(self): |
| | 580 | def test_prePathURLNonDefault(self): |
| 590 | 581 | d = DummyChannel() |
| 591 | 582 | d.transport.port = 81 |
| 592 | 583 | request = server.Request(d, 1) |
| … |
… |
|
| 595 | 586 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 596 | 587 | self.assertEqual(request.prePathURL(), 'http://example.com:81/foo/bar') |
| 597 | 588 | |
| 598 | | def testPrePathURLSSLPort(self): |
| | 589 | def test_prePathURLSSLPort(self): |
| 599 | 590 | d = DummyChannel() |
| 600 | 591 | d.transport.port = 443 |
| 601 | 592 | request = server.Request(d, 1) |
| … |
… |
|
| 604 | 595 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 605 | 596 | self.assertEqual(request.prePathURL(), 'http://example.com:443/foo/bar') |
| 606 | 597 | |
| 607 | | def testPrePathURLSSLPortAndSSL(self): |
| | 598 | def test_prePathURLSSLPortAndSSL(self): |
| 608 | 599 | d = DummyChannel() |
| 609 | 600 | d.transport = DummyChannel.SSL() |
| 610 | 601 | d.transport.port = 443 |
| … |
… |
|
| 614 | 605 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 615 | 606 | self.assertEqual(request.prePathURL(), 'https://example.com/foo/bar') |
| 616 | 607 | |
| 617 | | def testPrePathURLHTTPPortAndSSL(self): |
| | 608 | def test_prePathURLHTTPPortAndSSL(self): |
| 618 | 609 | d = DummyChannel() |
| 619 | 610 | d.transport = DummyChannel.SSL() |
| 620 | 611 | d.transport.port = 80 |
| … |
… |
|
| 624 | 615 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 625 | 616 | self.assertEqual(request.prePathURL(), 'https://example.com:80/foo/bar') |
| 626 | 617 | |
| 627 | | def testPrePathURLSSLNonDefault(self): |
| | 618 | def test_prePathURLSSLNonDefault(self): |
| 628 | 619 | d = DummyChannel() |
| 629 | 620 | d.transport = DummyChannel.SSL() |
| 630 | 621 | d.transport.port = 81 |
| … |
… |
|
| 634 | 625 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 635 | 626 | self.assertEqual(request.prePathURL(), 'https://example.com:81/foo/bar') |
| 636 | 627 | |
| 637 | | def testPrePathURLSetSSLHost(self): |
| | 628 | def test_prePathURLSetSSLHost(self): |
| 638 | 629 | d = DummyChannel() |
| 639 | 630 | d.transport.port = 81 |
| 640 | 631 | request = server.Request(d, 1) |
| … |
… |
|
| 643 | 634 | request.requestReceived('GET', '/foo/bar', 'HTTP/1.0') |
| 644 | 635 | self.assertEqual(request.prePathURL(), 'https://foo.com:81/foo/bar') |
| 645 | 636 | |
| 646 | | |
| 647 | 637 | def test_prePathURLQuoting(self): |
| 648 | 638 | """ |
| 649 | 639 | L{Request.prePathURL} quotes special characters in the URL segments to |
| … |
… |
|
| 656 | 646 | request.requestReceived('GET', '/foo%2Fbar', 'HTTP/1.0') |
| 657 | 647 | self.assertEqual(request.prePathURL(), 'http://example.com/foo%2Fbar') |
| 658 | 648 | |
| | 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() |
| 659 | 674 | |
| | 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) |
| 660 | 685 | |
| 661 | 686 | class RootResource(resource.Resource): |
| 662 | 687 | isLeaf=0 |