예제 #1
0
    def test_missingOptionalParameterJSON(self):
        # type: () -> None
        """
        If a required Field is missing from the JSON body, its default value is
        used.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        to = TestObject(mem)
        stub = StubTreq(to.router.resource())
        response = self.successResultOf(
            stub.post(
                "https://localhost/notrequired",
                json=dict(name="one"),
                headers={b"X-Test-Session": session.identifier},
            ))
        response2 = self.successResultOf(
            stub.post(
                "https://localhost/notrequired",
                json=dict(name="two", value=2),
                headers={b"X-Test-Session": session.identifier},
            ))
        self.assertEqual(response.code, 200)
        self.assertEqual(response2.code, 200)
        self.assertEqual(self.successResultOf(content(response)), b"okay")
        self.assertEqual(self.successResultOf(content(response2)), b"okay")
        self.assertEqual(to.calls, [("one", 7.0), ("two", 2.0)])
예제 #2
0
    def test_numberConstraints(self):
        # type: () -> None
        """
        Number parameters have minimum and maximum validations and the object
        will not be called when the values exceed them.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        to = TestObject(mem)
        stub = StubTreq(to.router.resource())
        tooLow = self.successResultOf(
            stub.post('https://localhost/constrained',
                      data=dict(goldilocks='1'),
                      headers={b'X-Test-Session': session.identifier}))
        tooHigh = self.successResultOf(
            stub.post('https://localhost/constrained',
                      data=dict(goldilocks='20'),
                      headers={b'X-Test-Session': session.identifier}))
        justRight = self.successResultOf(
            stub.post('https://localhost/constrained',
                      data=dict(goldilocks='7'),
                      headers={b'X-Test-Session': session.identifier}))

        self.assertEqual(tooHigh.code, 400)
        self.assertEqual(tooLow.code, 400)
        self.assertEqual(justRight.code, 200)
        self.assertEqual(self.successResultOf(content(justRight)), b'got it')
        self.assertEqual(to.calls, [(u'constrained', 7)])
예제 #3
0
 def test_cookieWithToken(self):
     # type: () -> None
     """
     A cookie-authenticated, CRSF-protected form will call the form as
     expected.
     """
     mem = MemorySessionStore()
     session = self.successResultOf(
         mem.newSession(True, SessionMechanism.Cookie))
     to = TestObject(mem)
     stub = StubTreq(to.router.resource())
     response = self.successResultOf(
         stub.post(
             "https://localhost/handle",
             data=dict(
                 name="hello",
                 value="1234",
                 ignoreme="extraneous",
                 __csrf_protection__=session.identifier,
             ),
             cookies={
                 "Klein-Secure-Session": nativeString(session.identifier)
             },
         ))
     self.assertEqual(to.calls, [("hello", 1234)])
     self.assertEqual(response.code, 200)
     self.assertIn(b"yay", self.successResultOf(content(response)))
예제 #4
0
    def test_customValidationHandling(self):
        # type: () -> None
        """
        L{Form.onValidationFailureFor} handles form validation failures by
        handing its thing a renderable form.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        testobj = TestObject(mem)
        stub = StubTreq(testobj.router.resource())
        response = self.successResultOf(
            stub.post(
                "https://localhost/handle-validation",
                headers={b"X-Test-Session": session.identifier},
                json={"value": 300},
            ))
        self.assertEqual(response.code, 200)
        self.assertIn(
            response.headers.getRawHeaders(b"content-type")[0], b"text/html")
        responseText = self.successResultOf(content(response))
        self.assertEqual(responseText, b"~special~")
        self.assertEqual(
            [(k.pythonArgumentName, v)
             for k, v in testobj.calls[-1][1].prevalidationValues.items()],
            [("value", 300)],
        )
예제 #5
0
    def test_missingRequiredParameter(self):
        # type: () -> None
        """
        If required fields are missing, a default error form is presented and
        the form's handler is not called.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        to = TestObject(mem)
        stub = StubTreq(to.router.resource())
        response = self.successResultOf(
            stub.post(
                "https://localhost/handle",
                data=dict(),
                headers={b"X-Test-Session": session.identifier},
            ))
        self.assertEqual(response.code, 400)
        self.assertIn(
            b"a value was required but none was supplied",
            self.successResultOf(content(response)),
        )
        self.assertEqual(to.calls, [])
예제 #6
0
 def test_noSessionPOST(self):
     # type: () -> None
     """
     An unauthenticated, CSRF-protected form will return a 403 Forbidden
     status code.
     """
     mem = MemorySessionStore()
     to = TestObject(mem)
     stub = StubTreq(to.router.resource())
     response = self.successResultOf(
         stub.post('https://localhost/handle',
                   data=dict(name='hello', value='1234')))
     self.assertEqual(to.calls, [])
     self.assertEqual(response.code, 403)
     self.assertIn(b'CSRF', self.successResultOf(content(response)))
예제 #7
0
 def test_noName(self):
     # type: () -> None
     """
     A handler for a Form with a Field that doesn't have a name will return
     an error explaining the problem.
     """
     mem = MemorySessionStore()
     session = self.successResultOf(
         mem.newSession(True, SessionMechanism.Header))
     to = TestObject(mem)
     stub = StubTreq(to.router.resource())
     response = self.successResultOf(
         stub.post('https://localhost/dangling-param',
                   data=dict(),
                   headers={b'X-Test-Session': session.identifier}))
     self.assertEqual(response.code, 500)
     errors = self.flushLoggedErrors(ValueError)
     self.assertEqual(len(errors), 1)
     self.assertIn(str(errors[0].value),
                   "Cannot extract unnamed form field.")
예제 #8
0
 def test_cookieNoToken(self):
     # type: () -> None
     """
     A cookie-authenticated, CSRF-protected form will return a 403 Forbidden
     status code when a CSRF protection token is not supplied.
     """
     mem = MemorySessionStore()
     session = self.successResultOf(
         mem.newSession(True, SessionMechanism.Cookie)
     )
     to = TestObject(mem)
     stub = StubTreq(to.router.resource())
     response = self.successResultOf(stub.post(
         'https://localhost/handle',
         data=dict(name='hello', value='1234', ignoreme='extraneous'),
         cookies={"Klein-Secure-Session": nativeString(session.identifier)}
     ))
     self.assertEqual(to.calls, [])
     self.assertEqual(response.code, 403)
     self.assertIn(b'CSRF', self.successResultOf(content(response)))
예제 #9
0
    def test_handlingPassword(self):
        # type: () -> None
        """
        From the perspective of form handling, passwords are handled like
        strings.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        to = TestObject(mem)
        stub = StubTreq(to.router.resource())
        response = self.successResultOf(
            stub.post('https://localhost/password-field',
                      data=dict(pw='asdfjkl;'),
                      headers={b'X-Test-Session': session.identifier}))
        self.assertEqual(response.code, 200)
        self.assertEqual(self.successResultOf(content(response)),
                         b'password received')
        self.assertEqual(to.calls, [(u'password', u'asdfjkl;')])
예제 #10
0
    def test_handlingJSON(self) -> None:
        """
        A handler for a form with Fields receives those fields as input, as
        passed by an HTTP client that submits a JSON POST body.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        to = TestObject(mem)
        stub = StubTreq(to.router.resource())
        response = self.successResultOf(
            stub.post(
                "https://localhost/handle",
                json=dict(name="hello", value="1234", ignoreme="extraneous"),
                headers={"X-Test-Session": session.identifier},
            ))
        self.assertEqual(response.code, 200)
        self.assertEqual(self.successResultOf(content(response)), b"yay")
        self.assertEqual(to.calls, [("hello", 1234)])
예제 #11
0
    def test_handling(self):
        # type: () -> None
        """
        A handler for a Form with Fields receives those fields as input, as
        passed by an HTTP client.
        """
        mem = MemorySessionStore()

        session = self.successResultOf(
            mem.newSession(True, SessionMechanism.Header))

        to = TestObject(mem)
        stub = StubTreq(to.router.resource())
        response = self.successResultOf(
            stub.post('https://localhost/handle',
                      data=dict(name='hello',
                                value='1234',
                                ignoreme='extraneous'),
                      headers={b'X-Test-Session': session.identifier}))
        self.assertEqual(response.code, 200)
        self.assertEqual(self.successResultOf(content(response)), b'yay')
        self.assertEqual(to.calls, [(u'hello', 1234)])
예제 #12
0
class CsauthTests(unittest.TestCase):
    """
    Tests for L{txwebutils.csauth}.
    """
    WEB_NAME = u"web.localhost"
    AUTH_NAME = u"auth.localhost"

    SITE_A_NAME = u"Site Alpha"
    SITE_A_TOKEN = u"site a token"
    SITE_A_SECRET = u"secret for a"
    SITE_B_NAME = u"Site Beta"
    SITE_B_TOKEN = u"site B token"
    SITE_B_SECRET = u"secret for _b_"

    USER_A_NAME = u"UserA"
    USER_A_PSWD = u"PswdA"
    USER_B_NAME = u"IAmUserB"
    USER_B_PSWD = u"P-S-W-D_B"

    def setUp(self):
        # prepare the authentication systems
        self.userdb = cred.InMemoryUnicodeUsernamePasswordDatabase()
        self.sitedb = cred.InMemoryUnicodeUsernamePasswordDatabase()
        self.userrealm = TestUserRealm()
        self.siterealm = TestSiteRealm()
        self.userportal = portal.Portal(self.userrealm)
        self.siteportal = portal.Portal(self.siterealm)
        self.userportal.registerChecker(self.userdb)
        self.siteportal.registerChecker(self.sitedb)

        # register tokens
        self.sitedb.addUser(
            self.SITE_A_TOKEN,
            self.SITE_A_SECRET,
        )
        self.siterealm.register_csauth_perm(
            self.SITE_A_TOKEN,
            TestWebAuthPermission(
                self.SITE_A_NAME,
                "http://{}/login".format(self.WEB_NAME),
                full_username=True,
            ),
        )
        self.sitedb.addUser(
            self.SITE_B_TOKEN,
            self.SITE_B_SECRET,
        )
        self.siterealm.register_csauth_perm(
            self.SITE_B_TOKEN,
            TestWebAuthPermission(
                self.SITE_B_NAME,
                "http://{}/bpage".format(self.WEB_NAME),
                full_username=False,
            ),
        )

        # register Users
        self.userdb.addUser(
            self.USER_A_NAME,
            self.USER_A_PSWD,
        )
        self.userdb.addUser(
            self.USER_B_NAME,
            self.USER_B_PSWD,
        )

        # prepare the auth site
        lr = BasicLoginResource()
        authpage = AuthResource(
            self.siteportal,
            self.userportal,
            lr,
            url=u"http://{}/auth".format(self.AUTH_NAME),
        )
        arr = DebugResource("auth")
        arr.putChild(b"auth", authpage)
        arr.putChild(b"expire", SessionExpiringResource())

        # prepare the web site
        lrr = DebugResource("web")

        # add the login page
        loginpage = TestLoginResource(
            u"http://{}/auth".format(self.AUTH_NAME),
            self.SITE_A_TOKEN,
            self.SITE_A_SECRET,
        )
        lrr.putChild(b"login", loginpage)

        # add a login page with a different permission
        bpage = TestLoginResource(
            u"http://{}/auth".format(self.AUTH_NAME),
            self.SITE_B_TOKEN,
            self.SITE_B_SECRET,
        )
        lrr.putChild(b"bpage", bpage)

        # add a resource with an invalid token/secret combination
        invalidepage = TestLoginResource(
            u"http://{}/auth".format(self.AUTH_NAME),
            self.SITE_A_TOKEN,
            self.SITE_B_SECRET,  # <- site B secret
        )
        lrr.putChild(b"invalid", invalidepage)

        # create and install treq stubs
        supersite = NameVirtualHost()
        supersite.addHost(self.AUTH_NAME.encode("ascii"), arr)
        supersite.addHost(self.WEB_NAME.encode("ascii"), lrr)
        supersite.default = DebugResource("default")

        self.treq = StubTreq(supersite)
        loginpage._set_request_dispatcher(self.treq)
        bpage._set_request_dispatcher(self.treq)
        invalidepage._set_request_dispatcher(self.treq)

    @defer.inlineCallbacks
    def test_login_correct(self):
        """
        Test a correct login.
        """
        # first, GET loginpage
        r = yield self.treq.get(u"http://{}/login".format(self.WEB_NAME), )
        cookies = r.cookies()
        self.assertEqual(r.code, 200)
        pa_url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected to the auth server
        self.assertNotIn(six.text_type(self.WEB_NAME), pa_url)
        self.assertIn(six.text_type(self.AUTH_NAME), pa_url)
        # ensure paramters were set correctly
        self.assertIn(u"action=login", pa_url)
        self.assertIn(u"ctoken=", pa_url)

        # POST correct login userdata
        r = yield self.treq.post(
            pa_url,
            params={
                u"username": self.USER_A_NAME,
                u"password": self.USER_A_PSWD,
            },
            cookies=cookies,
            browser_like_redirects=True,
        )
        self.assertEqual(r.code, 200)
        url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected back to webserver
        self.assertNotIn(six.text_type(self.AUTH_NAME), url)
        self.assertIn(six.text_type(self.WEB_NAME), url)

        # ensure content was set correctly
        text = yield r.text()
        self.assertEqual(text, self.USER_A_NAME)

        # ensure cookies will keep us logged in
        cr = yield self.treq.get(
            u"http://{}/login".format(self.WEB_NAME),
            cookies=cookies,
        )
        url = cr.request.absoluteURI.decode("ascii")
        self.assertNotIn(six.text_type(self.AUTH_NAME), url)
        self.assertIn(six.text_type(self.WEB_NAME), url)

        # expire session
        er = yield self.treq.get(
            u"http://{}/expire".format(self.AUTH_NAME),
            cookies=cookies,
        )
        self.assertEqual(er.code, 200)

        # ensure we will no longer be logged in automatically
        cr = yield self.treq.get(
            u"http://{}/login".format(self.WEB_NAME),
            cookies=cookies,
        )
        url = cr.request.absoluteURI.decode("ascii")
        self.assertNotIn(six.text_type(self.WEB_NAME), url)
        self.assertIn(six.text_type(self.AUTH_NAME), url)

    @defer.inlineCallbacks
    def test_login_different_perm_correct(self):
        """
        Test a correct login with site b, which has different permissions.
        """
        # first, GET loginpage
        r = yield self.treq.get(u"http://{}/bpage".format(self.WEB_NAME), )
        cookies = r.cookies()
        self.assertEqual(r.code, 200)
        pa_url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected to the auth server
        self.assertNotIn(six.text_type(self.WEB_NAME), pa_url)
        self.assertIn(six.text_type(self.AUTH_NAME), pa_url)
        # ensure paramters were set correctly
        self.assertIn(u"action=login", pa_url)
        self.assertIn(u"ctoken=", pa_url)

        # POST correct login userdata
        # we use user b this time, so we also check whether different
        # users work
        r = yield self.treq.post(
            pa_url,
            params={
                u"username": self.USER_B_NAME,
                u"password": self.USER_B_PSWD,
            },
            cookies=cookies,
            browser_like_redirects=True,
        )
        self.assertEqual(r.code, 200)
        url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected back to webserver
        self.assertNotIn(six.text_type(self.AUTH_NAME), url)
        self.assertIn(six.text_type(self.WEB_NAME), url)
        # ensure we are at the bpade
        self.assertIn(u"bpage", url)

        # ensure content was set correctly
        text = yield r.text()
        self.assertEqual(text, six.text_type(len(self.USER_B_NAME)))

        # ensure cookies will keep us logged in
        cr = yield self.treq.get(
            u"http://{}/bpage".format(self.WEB_NAME),
            cookies=cookies,
        )
        url = cr.request.absoluteURI.decode("ascii")
        self.assertNotIn(six.text_type(self.AUTH_NAME), url)
        self.assertIn(six.text_type(self.WEB_NAME), url)
        self.assertIn(u"bpage", url)

    @defer.inlineCallbacks
    def test_crossite_cookies(self):
        """
        Test that cookies will kept you logged in across sites.
        """
        # first, GET loginpage
        r = yield self.treq.get(u"http://{}/login".format(self.WEB_NAME), )
        cookies = r.cookies()
        self.assertEqual(r.code, 200)
        pa_url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected to the auth server
        self.assertNotIn(six.text_type(self.WEB_NAME), pa_url)
        self.assertIn(six.text_type(self.AUTH_NAME), pa_url)
        self.assertIn(u"login", pa_url)
        # ensure paramters were set correctly
        self.assertIn(u"action=login", pa_url)
        self.assertIn(u"ctoken=", pa_url)

        # POST correct login userdata
        r = yield self.treq.post(
            pa_url,
            params={
                u"username": self.USER_A_NAME,
                u"password": self.USER_A_PSWD,
            },
            cookies=cookies,
            browser_like_redirects=True,
        )
        self.assertEqual(r.code, 200)
        url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected back to webserver
        self.assertNotIn(six.text_type(self.AUTH_NAME), url)
        self.assertIn(six.text_type(self.WEB_NAME), url)

        # ensure content was set correctly
        text = yield r.text()
        self.assertEqual(text, self.USER_A_NAME)

        # GET bpage
        r = yield self.treq.get(
            u"http://{}/bpage".format(self.WEB_NAME),
            cookies=cookies,
        )
        self.assertEqual(r.code, 200)
        url = r.request.absoluteURI.decode("ascii")
        # ensure we were not redirected to the auth server
        self.assertIn(six.text_type(self.WEB_NAME), url)
        self.assertNotIn(six.text_type(self.AUTH_NAME), url)
        self.assertIn(u"bpage", url)
        # ensure content is still correct
        # this is important, since the different sites have different
        # access to the userdata
        text = yield r.text()
        self.assertEqual(text, six.text_type(len(self.USER_A_NAME)))

    @defer.inlineCallbacks
    def test_login_invalid(self):
        """
        Test an invalid login.
        """
        # first, GET loginpage
        r = yield self.treq.get(u"http://{}/login".format(self.WEB_NAME), )
        cookies = r.cookies()
        self.assertEqual(r.code, 200)
        pa_url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected to the auth server
        self.assertNotIn(six.text_type(self.WEB_NAME), pa_url)
        self.assertIn(six.text_type(self.AUTH_NAME), pa_url)
        # ensure paramters were set correctly
        self.assertIn(u"action=login", pa_url)
        self.assertIn(u"ctoken=", pa_url)

        # POST incorrect login userdata
        r = yield self.treq.post(
            pa_url,
            params={
                u"username": self.USER_A_NAME,
                u"password": self.USER_B_PSWD,  # <- user b password
            },
            cookies=cookies,
            browser_like_redirects=True,
        )
        self.assertEqual(r.code, 401)
        url = r.request.absoluteURI.decode("ascii")
        # ensure we were not redirected back to webserver
        self.assertIn(six.text_type(self.AUTH_NAME), url)
        self.assertNotIn(six.text_type(self.WEB_NAME), url)

        # ensure a malicious request back to the webserver will fail
        qs = urlparse(url).query
        ctoken = parse_qs(qs)[u"ctoken"]
        r = yield self.treq.get(
            u"http://{}/login?action=callback&ctoken={}".format(
                self.WEB_NAME,
                ctoken,
            ),
            cookies=cookies,
        )
        cookies = r.cookies()
        self.assertEqual(r.code, 200)
        pa_url = r.request.absoluteURI.decode("ascii")
        # ensure we were redirected to the auth server
        self.assertNotIn(six.text_type(self.WEB_NAME), pa_url)
        self.assertIn(six.text_type(self.AUTH_NAME), pa_url)

    @defer.inlineCallbacks
    def test_token_secret_invalid(self):
        """
        Test an invalid token/secret.
        """
        # first, GET loginpage
        r = yield self.treq.get(u"http://{}/invalid".format(self.WEB_NAME), )
        cookies = r.cookies()
        self.assertEqual(r.code, 500)
        pa_url = r.request.absoluteURI.decode("ascii")
        # ensure we were not redirected to the auth server
        self.assertIn(six.text_type(self.WEB_NAME), pa_url)
        self.assertNotIn(six.text_type(self.AUTH_NAME), pa_url)