def testLogoutWithRememberMe(self):
     form = BasicHtmlLoginForm(action='/action', loginPath='/login', home='/home', rememberMeCookie=True)
     observer = CallTrace(onlySpecifiedMethods=True)
     observer.returnValues['cookieName'] = 'remember-cookie'
     observer.returnValues['removeCookie'] = None
     form.addObserver(observer)
     session = {'user': '******', 'someother': 'value'}
     result = asString(form.logout(session=session, ignored='kwarg', Headers={'Cookie':'remember-cookie=cookieId;othercookie=value'}))
     self.assertEquals('HTTP/1.0 302 Found\r\nSet-Cookie: remember-cookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/\r\nLocation: /home\r\n\r\n', result)
     self.assertEqual({'someother': 'value'}, session)
     self.assertEqual(['cookieName', 'removeCookie'], observer.calledMethodNames())
     self.assertEquals(('cookieId',), observer.calledMethods[1].args)
class BasicHtmlLoginFormTest(SeecrTestCase):
    def setUp(self):
        SeecrTestCase.setUp(self)

        self.form = BasicHtmlLoginForm(action='/action', loginPath='/login', home='/home')

    def testLoginFormEnglish(self):
        result = asString(self.form.loginForm(session={}, path='/page/login2'))

        self.assertEqualsWS("""<div id="login-form">
    <form method="POST" name="login" action="/action">
    <input type="hidden" name="formUrl" value="/page/login2"/>
        <dl>
            <dt>Username</dt>
            <dd><input type="text" name="username" value=""/></dd>
            <dt>Password</dt>
            <dd><input type="password" name="password"/></dd>
            <dd class="submit"><input type="submit" id="submitLogin" value="Login"/></dd>
        </dl>
    </form>
    <script type="text/javascript">
        document.getElementById("submitLogin").focus()
    </script>
</div>""", result)

    def testLoginFormDutch(self):
        result = asString(self.form.loginForm(session={}, path='/page/login2', lang='nl'))

        self.assertEqualsWS("""<div id="login-form">
    <form method="POST" name="login" action="/action">
    <input type="hidden" name="formUrl" value="/page/login2"/>
        <dl>
            <dt>Gebruikersnaam</dt>
            <dd><input type="text" name="username" value=""/></dd>
            <dt>Wachtwoord</dt>
            <dd><input type="password" name="password"/></dd>
            <dd class="submit"><input type="submit" id="submitLogin" value="Inloggen"/></dd>
        </dl>
    </form>
    <script type="text/javascript">
        document.getElementById("submitLogin").focus()
    </script>
</div>""", result)

    def testNewUserFormEN(self):
        session = {
            'user': BasicHtmlLoginForm.User('username'),
            'BasicHtmlLoginForm.newUserFormValues': {'errorMessage': 'BAD BOY'},
        }
        result = asString(self.form.newUserForm(session=session, path='/page/login2', returnUrl='/return'))
        self.assertEqualsWS("""<div id="login-new-user-form">
    <p class="error">BAD BOY</p>
    <form method="POST" name="newUser" action="/action/newUser">
    <input type="hidden" name="formUrl" value="/page/login2"/>
    <input type="hidden" name="returnUrl" value="/return"/>
        <dl>
            <dt>Username</dt>
            <dd><input type="text" name="username" value=""/></dd>
            <dt>Password</dt>
            <dd><input type="password" name="password"/></dd>
            <dt>Retype password</dt>
            <dd><input type="password" name="retypedPassword"/></dd>
            <dd class="submit"><input type="submit" value="Create"/></dd>
        </dl>
    </form>
</div>""", result)

    def testNewUserFormNL(self):
        session = {
            'user': BasicHtmlLoginForm.User('username'),
            'BasicHtmlLoginForm.newUserFormValues': {'errorMessage': 'BAD BOY'},
        }
        result = asString(self.form.newUserForm(session=session, path='/page/login2', returnUrl='/return', lang="nl"))
        self.assertEqualsWS("""<div id="login-new-user-form">
    <p class="error">BAD BOY</p>
    <form method="POST" name="newUser" action="/action/newUser">
    <input type="hidden" name="formUrl" value="/page/login2"/>
    <input type="hidden" name="returnUrl" value="/return"/>
        <dl>
            <dt>Gebruikersnaam</dt>
            <dd><input type="text" name="username" value=""/></dd>
            <dt>Wachtwoord</dt>
            <dd><input type="password" name="password"/></dd>
            <dt>Herhaal wachtwoord </dt>
            <dd><input type="password" name="retypedPassword"/></dd>
            <dd class="submit"><input type="submit" value="Aanmaken"/></dd>
        </dl>
    </form>
</div>""", result)

    def testRedirectOnGet(self):
        result = asString(self.form.handleRequest(path='/whatever', Client=('127.0.0.1', 3451), Method='GET'))
        header, body = result.split(CRLF*2)
        self.assertTrue('405' in header)

    def testLoginWithPOSTsucceedsRedirectsToOriginalPath(self):
        observer = CallTrace(onlySpecifiedMethods=True, returnValues={'hasUser': True})
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = True
        Body = urlencode(dict(username='******', password='******'))
        session = {ORIGINAL_PATH:'/please/go/here'}

        result = asString(self.form.handleRequest(path='/login', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        self.assertEquals('user', session['user'].name)
        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /please/go/here' in header)
        user = session['user']
        self.assertFalse(user.isAdmin())

        self.assertEquals(['validateUser', 'hasUser'], [m.name for m in observer.calledMethods])
        self.assertEquals({'username': '******', 'password':'******'}, observer.calledMethods[0].kwargs)

    def testLoginWithPOSTsucceedsRedirectsToOriginalPathOnlyOnce(self):
        observer = CallTrace(onlySpecifiedMethods=True, returnValues={'hasUser': True})
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = True
        Body = urlencode(dict(username='******', password='******'))
        session = {ORIGINAL_PATH:'/please/go/here'}

        result = asString(self.form.handleRequest(path='/login', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        self.assertEquals('user', session['user'].name)
        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /please/go/here' in header)
        self.assertFalse(session['user'].isAdmin())

        self.assertEquals(['validateUser', 'hasUser'], [m.name for m in observer.calledMethods])
        self.assertEquals({'username': '******', 'password':'******'}, observer.calledMethods[0].kwargs)

        result = asString(self.form.handleRequest(path='/login', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /home' in header, header)

    def testLoginWithPOSTsucceeds(self):
        observer = CallTrace(onlySpecifiedMethods=True, returnValues={'hasUser': True})
        self.form = BasicHtmlLoginForm(action='/action', loginPath='/login', home='/home')
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = True
        Body = urlencode(dict(username='******', password='******'))
        session = {}

        result = asString(self.form.handleRequest(path='/login', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        self.assertEquals('admin', session['user'].name)
        self.assertEquals(True, session['user'].isAdmin())
        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /home' in header)

        self.assertEquals(['validateUser', 'hasUser'], [m.name for m in observer.calledMethods])
        self.assertEquals({'username': '******', 'password':'******'}, observer.calledMethods[0].kwargs)

    def testLoginWithPOSTfails(self):
        observer = CallTrace()
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = False
        Body = urlencode(dict(username='******', password='******'))
        session = {}

        result = asString(self.form.handleRequest(path='/login', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        self.assertFalse('user' in session)
        self.assertEquals({'username':'******', 'errorMessage': 'Invalid username or password'}, session['BasicHtmlLoginForm.formValues'])
        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /login' in header, header)

        self.assertEquals(['validateUser'], [m.name for m in observer.calledMethods])
        self.assertEquals({'username': '******', 'password':'******'}, observer.calledMethods[0].kwargs)

    def testLoginEnrichesAUserByAnObserver(self):
        observer = CallTrace(returnValues={'hasUser': True, 'validateUser': True})
        def enrichUser(user):
            user.title = lambda: "Full username"
            user.additionalInfo = "more info"
        observer.methods['enrichUser'] = enrichUser
        self.form.addObserver(observer)
        Body = urlencode(dict(username='******', password='******'))
        session = {}

        consume(self.form.handleRequest(path='/login', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        self.assertEquals("more info", session['user'].additionalInfo)
        self.assertEquals('Full username', session['user'].title())
        self.assertEquals(['validateUser', 'hasUser', 'enrichUser'], observer.calledMethodNames())


    def testLoginFormWithError(self):
        session = {}
        session['BasicHtmlLoginForm.formValues']={'username': '******', 'errorMessage': 'Invalid <username> or "password"'}
        result = asString(self.form.loginForm(session=session, path='/show/login'))

        self.assertEqualsWS("""<div id="login-form">
    <p class="error">Invalid &lt;username&gt; or "password"</p>
    <form method="POST" name="login" action="/action">
    <input type="hidden" name="formUrl" value="/show/login"/>
        <dl>
            <dt>Username</dt>
            <dd><input type="text" name="username" value='&lt;us"er&gt;'/></dd>
            <dt>Password</dt>
            <dd><input type="password" name="password"/></dd>
            <dd class="submit"><input type="submit" id="submitLogin" value="Login"/></dd>
        </dl>
    </form>
    <script type="text/javascript">
        document.getElementById("submitLogin").focus()
    </script>
</div>""", result)

    def testShowChangePasswordFormEn(self):
        session = {
            'user': BasicHtmlLoginForm.User('username'),
            'BasicHtmlLoginForm.formValues': {'errorMessage': 'BAD BOY'},
        }
        result = asString(self.form.changePasswordForm(session=session, path='/show/changepasswordform', arguments={}))

        self.assertEqualsWS("""<div id="login-change-password-form">
    <p class="error">BAD BOY</p>
    <form method="POST" name="changePassword" action="/action/changepassword">
    <input type="hidden" name="formUrl" value="/show/changepasswordform"/>
    <input type="hidden" name="returnUrl" value="/show/changepasswordform"/>
    <input type="hidden" name="username" value="username"/>
        <dl>
            <dt>Old password</dt>
            <dd><input type="password" name="oldPassword"/></dd>
            <dt>New password</dt>
            <dd><input type="password" name="newPassword"/></dd>
            <dt>Retype new password</dt>
            <dd><input type="password" name="retypedPassword"/></dd>
            <dd class="submit"><input type="submit" value="Change"/></dd>
        </dl>
    </form>
</div>""", result)

    def testShowChangePasswordFormNl(self):
        session = {
            'user': BasicHtmlLoginForm.User('username'),
            'BasicHtmlLoginForm.formValues': {'errorMessage': 'BAD BOY'},
        }
        result = asString(self.form.changePasswordForm(session=session, path='/show/changepasswordform', lang="nl", arguments={}))

        self.assertEqualsWS("""<div id="login-change-password-form">
    <p class="error">BAD BOY</p>
    <form method="POST" name="changePassword" action="/action/changepassword">
    <input type="hidden" name="formUrl" value="/show/changepasswordform"/>
    <input type="hidden" name="returnUrl" value="/show/changepasswordform"/>
    <input type="hidden" name="username" value="username"/>
        <dl>
            <dt>Oud wachtwoord</dt>
            <dd><input type="password" name="oldPassword"/></dd>
            <dt>Nieuw wachtwoord</dt>
            <dd><input type="password" name="newPassword"/></dd>
            <dt>Herhaal nieuw wachtwoord</dt>
            <dd><input type="password" name="retypedPassword"/></dd>
            <dd class="submit"><input type="submit" value="Aanpassen"/></dd>
        </dl>
    </form>
</div>""", result)

    def testShowChangePasswordFormForSpecifiedUser(self):
        session = {
            'user': BasicHtmlLoginForm.User('username'),
            'BasicHtmlLoginForm.formValues': {'errorMessage': 'BAD BOY'},
        }
        result = asString(self.form.changePasswordForm(session=session, path='/show/changepasswordform', lang="nl", arguments=dict(user=['myuser']), user='******', onlyNewPassword=True))

        self.assertEqualsWS("""<div id="login-change-password-form">
    <p class="error">BAD BOY</p>
    <form method="POST" name="changePassword" action="/action/changepassword">
    <input type="hidden" name="formUrl" value="/show/changepasswordform?user=myuser"/>
    <input type="hidden" name="returnUrl" value="/show/changepasswordform"/>
    <input type="hidden" name="username" value="myuser"/>
        <dl>
            <dt>Nieuw wachtwoord</dt>
            <dd><input type="password" name="newPassword"/></dd>
            <dt>Herhaal nieuw wachtwoord</dt>
            <dd><input type="password" name="retypedPassword"/></dd>
            <dd class="submit"><input type="submit" value="Aanpassen"/></dd>
        </dl>
    </form>
</div>""", result)

    def testShowChangePasswordFormErrorWithoutUser(self):
        session = {}
        result = asString(self.form.changePasswordForm(session=session, path='/show/changepasswordform', arguments={}))

        self.assertEqualsWS("""<div id="login-change-password-form">
    <p class="error">Please login to change password.</p>
</div>""", result)

    def testChangePasswordMismatch(self):
        Body = urlencode(dict(username='******', oldPassword='******', newPassword="******", retypedPassword="******", formUrl='/show/changepasswordform'))
        session = {'user': BasicHtmlLoginForm.User('user')}

        result = asString(self.form.handleRequest(path='/login/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        self.assertEquals({'username':'******', 'errorMessage': 'New passwords do not match'}, session['BasicHtmlLoginForm.formValues'])
        self.assertEqualsWS("""HTTP/1.0 302 Found\r\nLocation: /show/changepasswordform\r\n\r\n""", result)

    def testChangePasswordWrongOld(self):
        observer = CallTrace()
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = False

        Body = urlencode(dict(username='******', oldPassword='******', newPassword="******", retypedPassword="******", formUrl='/show/changepasswordform'))
        session = {'user': BasicHtmlLoginForm.User('user')}

        result = asString(self.form.handleRequest(path='/login/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        self.assertEquals({'username':'******', 'errorMessage': 'Username and password do not match'}, session['BasicHtmlLoginForm.formValues'])
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /show/changepasswordform\r\n\r\n", result)

    def testChangePasswordNoOldNotAllowed(self):
        observer = CallTrace()
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = False

        Body = urlencode(dict(username='******', newPassword="******", retypedPassword="******", formUrl='/show/changepasswordform'))
        session = {
            'user': BasicHtmlLoginForm.User('username'),
            'BasicHtmlLoginForm.formValues': {}
        }

        result = asString(self.form.handleRequest(path='/login/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        self.assertEquals({'username':'******', 'errorMessage': 'Username and password do not match'}, session['BasicHtmlLoginForm.formValues'])
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /show/changepasswordform\r\n\r\n", result)

    def testChangePasswordNoOldForAdminOnlyAllowedForOtherUsers(self):
        observer = CallTrace()
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = False

        Body = urlencode(dict(username='******', newPassword="******", retypedPassword="******", formUrl='/show/changepasswordform', returnUrl='/home'))
        session = {
            'user': BasicHtmlLoginForm.User('admin'),
            'BasicHtmlLoginForm.formValues': {}
        }

        result = asString(self.form.handleRequest(path='/login/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        self.assertEquals(['setPassword'], [m.name for m in observer.calledMethods])
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /home\r\n\r\n", result)

    def testChangePasswordNoOldForAdminNotAllowed(self):
        observer = CallTrace()
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = False

        Body = urlencode(dict(username='******', newPassword="******", retypedPassword="******", formUrl='/show/changepasswordform'))
        session = {
            'user': BasicHtmlLoginForm.User('admin'),
            'BasicHtmlLoginForm.formValues': {}
        }

        result = asString(self.form.handleRequest(path='/login/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        self.assertEquals({'username':'******', 'errorMessage': 'Username and password do not match'}, session['BasicHtmlLoginForm.formValues'])
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /show/changepasswordform\r\n\r\n", result)

    def testChangePassword(self):
        observer = CallTrace()
        self.form.addObserver(observer)
        observer.returnValues['validateUser'] = True

        Body = urlencode(dict( username='******', oldPassword='******', newPassword="******", retypedPassword="******", formUrl='/show/changepasswordform', returnUrl='/home'))
        session = {'user': BasicHtmlLoginForm.User('user')}

        result = asString(self.form.handleRequest(path='/login/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        self.assertEquals(['validateUser', 'setPassword'], [m.name for m in observer.calledMethods])
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /home\r\n\r\n", result)


    def testDeleteUserNoAdmin(self):
        observer = CallTrace(returnValues={'hasUser': True})
        self.form.addObserver(observer)
        result = asString(self.form.handleRequest(
            path='/login/remove',
            Client=('127.0.0.1', 3451),
            Method='POST',
            Body=urlencode(dict(username='******', formUrl='/show/userlist')),
            session={}))
        self.assertEquals(['hasUser', 'enrichUser'], [m.name for m in observer.calledMethods])
        self.assertEquals('HTTP/1.0 401 Unauthorized\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nUnauthorized access.', result)

    def testDeleteUserAsAdmin(self):
        observer = CallTrace(returnValues={'hasUser': True})
        self.form.addObserver(observer)
        result = asString(self.form.handleRequest(
            path='/login/remove',
            Client=('127.0.0.1', 3451),
            Method='POST',
            Body=urlencode(dict(username='******', formUrl='/show/userlist')),
            session={'user': BasicHtmlLoginForm.User('admin')}))

        self.assertEquals(['hasUser', 'enrichUser', 'removeUser', 'removeCookies', 'removeCookies'], [m.name for m in observer.calledMethods])
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /show/userlist\r\n\r\n", result)

    def testDeleteNonExistingUser(self):
        observer = CallTrace(returnValues={'hasUser': False}, )
        self.form.addObserver(observer)
        session = {'user': BasicHtmlLoginForm.User('admin')}
        result = asString(self.form.handleRequest(
            path='/login/remove',
            Client=('127.0.0.1', 3451),
            Method='POST',
            Body=urlencode(dict(username='******', formUrl='/show/userlist')),
            session=session))

        self.assertEquals(['hasUser'], [m.name for m in observer.calledMethods])
        self.assertEquals('HTTP/1.0 401 Unauthorized\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nUnauthorized access.', result)

    def testDeleteByAdministriveUser(self):
        observer = CallTrace(returnValues={'hasUser': True, 'removeUser': None}, onlySpecifiedMethods=True)
        self.form.addObserver(observer)
        session = {'user': self.form.loginAsUser('admin')}
        observer.calledMethods.reset()
        result = asString(self.form.handleRemove(session=session, Body=urlencode(dict(username='******'))))
        self.assertEquals("HTTP/1.0 302 Found\r\nLocation: /home\r\n\r\n", result)
        self.assertEquals(['hasUser', 'removeUser'], [m.name for m in observer.calledMethods])

    def testDeleteByUserOfAdminNotAllowed(self):
        observer = CallTrace(returnValues={'hasUser': True, 'removeUser': None}, onlySpecifiedMethods=True)
        self.form.addObserver(observer)
        session = {'user': self.form.loginAsUser('user')}
        observer.calledMethods.reset()
        result = asString(self.form.handleRemove(session=session, Body=urlencode(dict(username='******'))))
        self.assertEquals('HTTP/1.0 401 Unauthorized\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nUnauthorized access.', result)
        self.assertEquals(['hasUser'], [m.name for m in observer.calledMethods])

    def testDeleteSelfNotAllowed(self):
        observer = CallTrace(returnValues={'hasUser': True, 'removeUser': None}, onlySpecifiedMethods=True)
        self.form.addObserver(observer)
        session = {'user': self.form.loginAsUser('admin')}
        observer.calledMethods.reset()
        result = asString(self.form.handleRemove(session=session, Body=urlencode(dict(username='******'))))
        self.assertEquals('HTTP/1.0 401 Unauthorized\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nUnauthorized access.', result)
        self.assertEquals(['hasUser'], [m.name for m in observer.calledMethods])

    @stdout_replaced
    def testNewUserWithPOSTsucceeds(self):
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        observer = CallTrace()
        self.form.addObserver(observer)
        pf.addUser('existing', 'password')
        Body = urlencode(dict(username='******', password='******', retypedPassword='******', formUrl='/page/newUser', returnUrl='/return'))
        session = {'user': BasicHtmlLoginForm.User('admin')}

        result = asString(self.form.handleRequest(path='/action/newUser', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /return' in header)

        self.assertEquals(set(['existing', 'newuser', 'admin']), set(pf.listUsernames()))
        self.assertTrue(pf.validateUser('newuser', 'secret'))
        self.assertEquals('Added user "newuser"', session['BasicHtmlLoginForm.newUserFormValues']['successMessage'])
        self.assertEqual(['addUser', 'handleNewUser'], observer.calledMethodNames())
        self.assertEqual({'username': '******', 'password': '******'}, observer.calledMethods[0].kwargs)
        self.assertEqual({'Body': 'username=newuser&formUrl=%2Fpage%2FnewUser&password=secret&returnUrl=%2Freturn&retypedPassword=secret', 'username': '******'}, observer.calledMethods[1].kwargs)


    @stdout_replaced
    def testNewUserWithoutAnyUser(self):
        session = {}
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        Body = urlencode(dict(username='******', password='******', retypedPassword='******', formUrl='/page/newUser', returnUrl='/return'))
        result = asString(self.form.handleRequest(path='/action/newUser', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        header, body = result.split(CRLF*2)
        self.assertEquals(['admin'], pf.listUsernames())
        self.assertTrue('401' in header)

    @stdout_replaced
    def testNewUserWithoutRights(self):
        session = {'user': BasicHtmlLoginForm.User('auser')}
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        Body = urlencode(dict(username='******', password='******', retypedPassword='******', formUrl='/page/newUser', returnUrl='/return'))
        result = asString(self.form.handleRequest(path='/action/newUser', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))
        header, body = result.split(CRLF*2)
        self.assertEquals(['admin'], pf.listUsernames())
        self.assertTrue('401' in header)

    @stdout_replaced
    def testNewUserWithPOSTFails(self):
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        pf.addUser('existing', 'password')
        pf.addUser('newuser', 'oldpassword')
        Body = urlencode(dict(username='******', password='******', retypedPassword='******', formUrl='/page/newUser', returnUrl='/return'))
        session = {'user': BasicHtmlLoginForm.User('admin')}

        result = asString(self.form.handleRequest(path='/action/newUser', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /page/newUser' in header)

        self.assertEquals(set(['existing', 'newuser', 'admin']), set(pf.listUsernames()))
        self.assertTrue(pf.validateUser('newuser', 'oldpassword'))
        self.assertFalse(pf.validateUser('newuser', 'newpassword'))
        self.assertEquals({'errorMessage':'User already exists.', 'username':'******'}, session['BasicHtmlLoginForm.newUserFormValues'])

    @stdout_replaced
    def testNewEmptyPassword(self):
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        pf.addUser('existing', 'password')
        Body = urlencode(dict(username='******', oldPassword='******', newPassword='', retypedPassword='', formUrl='/page/newUser', returnUrl='/return'))
        session = {'user': BasicHtmlLoginForm.User('admin')}

        result = asString(self.form.handleRequest(path='/action/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /page/newUser' in header)

        self.assertEquals(set(['existing', 'admin']), set(pf.listUsernames()))
        self.assertTrue(pf.validateUser('existing', 'password'))
        self.assertEquals({'errorMessage':'New password is invalid.', 'username':'******'}, session['BasicHtmlLoginForm.formValues'])

    @stdout_replaced
    def testChangePassword_withEmptySession(self):
        # A.k.a. not logged-in.
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        pf.addUser('existing', 'password')
        Body = urlencode(dict(username='******', oldPassword='******', newPassword='******', retypedPassword='******', formUrl='/page/newUser', returnUrl='/return'))
        session = {}

        result = asString(self.form.handleRequest(path='/action/changepassword', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /page/newUser' in header)

        self.assertEquals(set(['existing', 'admin']), set(pf.listUsernames()))
        self.assertTrue(pf.validateUser('existing', 'password'))
        self.assertEquals(
            {'errorMessage': 'Login required for "change password".',
             'username': '******'},
            session['BasicHtmlLoginForm.formValues'])

    @stdout_replaced
    def testNewUserWithPOSTFailsDifferentPasswords(self):
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        pf.addUser('existing', 'password')
        Body = urlencode(dict(username='******', password='******', retypedPassword='******', formUrl='/page/newUser', returnUrl='/return'))
        session = {'user': BasicHtmlLoginForm.User('admin')}

        result = asString(self.form.handleRequest(path='/action/newUser', Client=('127.0.0.1', 3451), Method='POST', Body=Body, session=session))

        header, body = result.split(CRLF*2)
        self.assertTrue('302' in header)
        self.assertTrue('Location: /page/newUser' in header)

        self.assertEquals(set(['existing', 'admin']), set(pf.listUsernames()))
        self.assertEquals({'errorMessage':'Passwords do not match', 'username':'******'}, session['BasicHtmlLoginForm.newUserFormValues'])

    @stdout_replaced
    def testShowUserList(self):
        pf = PasswordFile(join(self.tempdir, 'passwd'))
        self.form.addObserver(pf)
        pf.addUser('one', 'password')
        pf.addUser('two', 'password')
        pf.addUser('three', 'password')
        def enrichUser(user):
            user.title = lambda: user.name.title()
        o = CallTrace(onlySpecifiedMethods=True, methods=dict(enrichUser=enrichUser))
        self.form.addObserver(o)

        session = {'user': self.form.loginAsUser('two')}
        session['user'].canEdit = lambda username=None: username not in ['two', 'admin']

        result = asString(self.form.userList(session=session, path='/show/login'))

        self.assertEqualsWS("""<div id="login-user-list">
    <script type="text/javascript">
function deleteUser(username) {
    if (confirm("Are you sure?")) {
        document.removeUser.username.value = username;
        document.removeUser.submit();
    }
}
</script>
<form name="removeUser" method="POST" action="/action/remove">
    <input type="hidden" name="formUrl" value="/show/login"/>
    <input type="hidden" name="username"/>
</form>
    <ul>
        <li>Admin</li>
        <li>One <a href="javascript:deleteUser('one');">delete</a></li>
        <li>Three <a href="javascript:deleteUser('three');">delete</a></li>
        <li>Two</li>
    </ul>
</div>""", result)

        result = asString(self.form.userList(session=session, path='/show/login', userLink='/user'))

        self.assertEqualsWS("""<div id="login-user-list">
    <script type="text/javascript">
function deleteUser(username) {
    if (confirm("Are you sure?")) {
        document.removeUser.username.value = username;
        document.removeUser.submit();
    }
}
</script>
<form name="removeUser" method="POST" action="/action/remove">
    <input type="hidden" name="formUrl" value="/show/login"/>
    <input type="hidden" name="username"/>
</form>
    <ul>
        <li><a href="/user?user=admin">Admin</a></li>
        <li><a href="/user?user=one">One</a> <a href="javascript:deleteUser('one');">delete</a></li>
        <li><a href="/user?user=three">Three</a> <a href="javascript:deleteUser('three');">delete</a></li>
        <li><a href="/user?user=two">Two</a></li>
    </ul>
</div>""", result)

    def testBroadcastAddUserToAllObservers(self):
        values = []
        dna = be(
            (Observable(),
                (BasicHtmlLoginForm(action="/action", loginPath="/"),
                    (CallTrace(methods={'addUser': lambda *args, **kwargs: values.append(("1st", args, kwargs))}),),
                    (CallTrace(methods={'addUser': lambda *args, **kwargs: values.append(("2nd", args, kwargs))}),),
                    (CallTrace(methods={'addUser': lambda *args, **kwargs: values.append(("3rd", args, kwargs))}),),
                )
            )
        )

        asString(dna.all.handleNewUser(session={'user': BasicHtmlLoginForm.User('admin')}, Body=urlencode(dict(password="******", retypedPassword="******", username='******'))))
        self.assertEquals(3, len(values))

    def testSetRememberMeCookie(self):
        observer = CallTrace(
            methods={
                'validateUser': lambda username, password: True,
                'createCookie': lambda user: dict(
                    cookie='THIS IS THE COOKIE VALUE',
                    header='Set-Cookie: somevalue',
                )
            },
            onlySpecifiedMethods=True,
            returnValues={'hasUser': True})

        basicHtmlLoginForm = BasicHtmlLoginForm(
            action="/action",
            loginPath="/",
            home="/index",
            rememberMeCookie=True)
        basicHtmlLoginForm._now = lambda: 3600

        dna = be(
            (Observable(),
                (basicHtmlLoginForm,
                    (observer, )
                )
            )
        )

        session = {}
        header, _ = asString(dna.all.handleRequest(
            Method="POST",
            path="/",
            session=session,
            Body=urlencode(dict(username="******", password="******", rememberMe="on"))
        )).split('\r\n\r\n', 1)

        self.assertTrue('user' in session, session)
        headers = headerToDict(header)
        self.assertEquals("/index", headers['Location'])

        self.assertTrue('Set-Cookie' in headers, headers)
        self.assertEquals("somevalue", headers['Set-Cookie'])

    def testLoginForWithRememberMe(self):
        form = BasicHtmlLoginForm(
            action='/action',
            loginPath='/login',
            home='/home',
            rememberMeCookie=True)
        result = asString(form.loginForm(session={}, path='/page/login2'))
        self.assertEqualsWS("""<div id="login-form">
    <form method="POST" name="login" action="/action">
    <input type="hidden" name="formUrl" value="/page/login2"/>
        <dl>
            <dt>Username</dt>
            <dd><input type="text" name="username" value=""/></dd>
            <dt>Password</dt>
            <dd><input type="password" name="password"/></dd>
            <dt>&nbsp;</dt><dd class="rememberMe"><input type="checkbox" name="rememberMe" id="rememberMe" /><label for="rememberMe">Remember me</label></dd>
            <dd class="submit"><input type="submit" id="submitLogin" value="Login"/></dd>
        </dl>
    </form>
    <script type="text/javascript">
        document.getElementById("submitLogin").focus()
    </script>
</div>""", result)

    def testLogout(self):
        session = {'user': '******', 'someother': 'value'}
        result = asString(self.form.logout(session=session, ignored='kwarg', Headers={}))
        self.assertEquals(redirectHttp % '/home', result)
        self.assertEqual({'someother': 'value'}, session)

    def testLogoutWithRememberMe(self):
        form = BasicHtmlLoginForm(action='/action', loginPath='/login', home='/home', rememberMeCookie=True)
        observer = CallTrace(onlySpecifiedMethods=True)
        observer.returnValues['cookieName'] = 'remember-cookie'
        observer.returnValues['removeCookie'] = None
        form.addObserver(observer)
        session = {'user': '******', 'someother': 'value'}
        result = asString(form.logout(session=session, ignored='kwarg', Headers={'Cookie':'remember-cookie=cookieId;othercookie=value'}))
        self.assertEquals('HTTP/1.0 302 Found\r\nSet-Cookie: remember-cookie=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/\r\nLocation: /home\r\n\r\n', result)
        self.assertEqual({'someother': 'value'}, session)
        self.assertEqual(['cookieName', 'removeCookie'], observer.calledMethodNames())
        self.assertEquals(('cookieId',), observer.calledMethods[1].args)

    def testCanEdit(self):
        admin = BasicHtmlLoginForm.User('admin')
        other = BasicHtmlLoginForm.User('other')
        self.assertTrue(admin.canEdit('other'))
        self.assertTrue(admin.canEdit(other))
        self.assertTrue(other.canEdit('other'))
        self.assertTrue(other.canEdit(other))
        self.assertFalse(other.canEdit('admin'))
        self.assertFalse(other.canEdit(admin))