|
[Webware-checkins] r89 - in LoginKit: . Examples
From: <ianb@we...> - 2004-04-21 04:35
|
Author: ianb
Date: 2004-04-20 22:35:25 -0600 (Tue, 20 Apr 2004)
New Revision: 89
Added:
LoginKit/Examples/ExamplePage.py
LoginKit/Examples/Forgotten.py
LoginKit/Examples/UserIndex.py
LoginKit/__init__.py
LoginKit/forgotten.py
LoginKit/userindex.py
Removed:
LoginKit/Examples/SitePage.py
Modified:
LoginKit/Examples/CreateUser.py
LoginKit/Examples/Login.py
LoginKit/Examples/Private.py
LoginKit/Properties.py
LoginKit/main.py
LoginKit/pickleusermanager.py
Log:
Lots and lots of refactoring; in a more-or-less working state now.
Modified: LoginKit/Examples/CreateUser.py
===================================================================
--- LoginKit/Examples/CreateUser.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/CreateUser.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -1,12 +1,12 @@
-from SitePage import SitePage, manager
+from ExamplePage import ExamplePage, manager
-class CreateUser(SitePage):
+class CreateUser(ExamplePage):
def loginRequired(self):
return 0
def awake(self, trans):
- SitePage.awake(self, trans)
+ ExamplePage.awake(self, trans)
field = self.request().field
if field('username', 0):
if manager.userExists(field('username')):
@@ -15,19 +15,24 @@
user = manager.newUser()
user.setUsername(field('username'))
user.setPassword(field('password'))
+ user.setEmail(field('email'))
+ user.setName(field('name'))
self.message('Created user %s' % field('username'))
+ self.sendRedirectAndEnd('./')
def writeContent(self):
- self.write('''
- <form action="" method="POST">
- <table>
- <tr><td colspan=2>Create a user:</td></tr>
- <tr><td>Username:</td>
- <td><input type="text" name="username" size=20></td></tr>
- <tr><td>Password:</td>
- <td><input type="password" name="password" size=20></td></tr>
- <tr><td colspan=2><input type="submit"></td></tr>
- </table></form>
- ''')
+ self.write('<form action="" method="POST">\n<table>\n')
+ for name, type, length in [
+ ('username', 'text', 20),
+ ('name', 'text', 40),
+ ('email', 'text', 40),
+ ('password', 'password', 20),
+ ('confirm', 'password', 20)]:
+ self.write('<tr><td>%s:</td>\n' % name.capitalize())
+ self.write('<td><input type="%s" name="%s" size=%s value="%s"></td></tr>\n'
+ % (type, name, length,
+ self.htmlEncode(self.request().field(name, ''))))
+ self.write('</table>')
+ self.write('<input type="submit" value="Create User">\n</form>\n')
Added: LoginKit/Examples/ExamplePage.py
===================================================================
--- LoginKit/Examples/ExamplePage.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/ExamplePage.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -0,0 +1,65 @@
+#from Component import CPage
+from WebKit.Examples.ExamplePage import ExamplePage as WebKitExamplePage
+from Component import CPage
+from LoginKit import UserComponent
+from LoginKit.pickleusermanager import PickleUserManager
+from Component.notify import NotifyComponent
+import os
+
+userfiles = '/tmp/usermanager'
+if not os.path.exists(userfiles):
+ os.makedirs(userfiles)
+manager = PickleUserManager(path=userfiles)
+
+class ExamplePage(WebKitExamplePage):
+
+ components = [UserComponent(manager), NotifyComponent()]
+
+ def title(self):
+ return CPage.title(self)
+
+ def writeBodyParts(self):
+ wr = self.writeln
+ wr('<table border=0 cellpadding=0 cellspacing=0 width=100%>')
+
+ # banner
+ self.writeBanner()
+
+ # sidebar
+ wr('<tr> <td bgcolor="#EEEEEF" valign=top nowrap>')
+ self.writeSidebar()
+ wr('</td>')
+
+ # spacer
+ wr('<td> </td>')
+
+ # content
+ wr('<td valign=top width=90%><p><br>')
+ CPage.writeBodyParts(self)
+ wr('</td>')
+
+ # end
+ wr('</tr> </table>')
+
+
+ def writeHeader(self):
+ self.write('''<style type="text/css">
+ .notifyMessage { border: thin black solid;
+ background-color: #aaffaa; }
+ .formError { border: thin blacksolid;
+ background-color: #660000; color: #ffffff; }
+ </style>''')
+ self.write('<h1>%s</h1>\n' % self.htTitle())
+ self.writeMessages()
+
+ def writeFooter(self):
+ self.write('<hr noshade>\n')
+ manager = self.userManager()
+ users = manager.allUsers()
+ for user in users:
+ self.write('%s=%s<br>\n'
+ % (user.username(), user.email()))
+ if not users:
+ self.write('<p>No users in system</p>\n')
+
+
Added: LoginKit/Examples/Forgotten.py
===================================================================
--- LoginKit/Examples/Forgotten.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/Forgotten.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -0,0 +1,13 @@
+from LoginKit.forgotten import ForgottenPasswordComponent
+from ExamplePage import ExamplePage
+
+class Forgotten(ExamplePage):
+
+ components = ExamplePage.components + [ForgottenPasswordComponent()]
+
+ def loginRequired(self):
+ return False
+
+ def defaultAction(self):
+ self.forgottenPasswordForm()
+
Modified: LoginKit/Examples/Login.py
===================================================================
--- LoginKit/Examples/Login.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/Login.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -1,6 +1,6 @@
-from SitePage import SitePage
+from ExamplePage import ExamplePage
-class Login(SitePage):
+class Login(ExamplePage):
def loginRequired(self):
# better not be required...
Modified: LoginKit/Examples/Private.py
===================================================================
--- LoginKit/Examples/Private.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/Private.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -1,6 +1,6 @@
-from SitePage import SitePage
+from ExamplePage import ExamplePage
-class Private(SitePage):
+class Private(ExamplePage):
def loginRequired(self):
return 1
Deleted: LoginKit/Examples/SitePage.py
===================================================================
--- LoginKit/Examples/SitePage.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/SitePage.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -1,25 +0,0 @@
-from Component import CPage
-from Component.NotifyComponent import NotifyComponent
-from Component.UserManager.PickleUserManager import PickleUserManager
-from Component.UserManager.UserComponent import UserComponent
-import os
-
-userfiles = '/tmp/usermanager'
-
-manager = PickleUserManager(path=userfiles)
-
-if not os.path.exists(userfiles):
- os.makedirs(userfiles)
-
-class SitePage(CPage):
-
- components = [UserComponent(manager), NotifyComponent()]
-
- def loginRequired(self):
- return 0
-
- def writeBodyParts(self):
- self.writeMessages()
- self.writeContent()
-
-
Added: LoginKit/Examples/UserIndex.py
===================================================================
--- LoginKit/Examples/UserIndex.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Examples/UserIndex.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -0,0 +1,12 @@
+from LoginKit.userindex import UserIndexComponent
+from ExamplePage import ExamplePage
+
+class UserIndex(ExamplePage):
+
+ components = ExamplePage.components + [UserIndexComponent()]
+
+ def loginRequired(self):
+ return False
+
+ def writeContent(self):
+ self.writeUserIndex()
Modified: LoginKit/Properties.py
===================================================================
--- LoginKit/Properties.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/Properties.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -15,5 +15,6 @@
'CreateUser',
'Login',
'Private',
+ 'Forgotten',
]
}
Added: LoginKit/__init__.py
===================================================================
--- LoginKit/__init__.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/__init__.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -0,0 +1,5 @@
+from main import UserManager, SimpleUser
+from usercomponent import UserComponent
+
+def InstallInWebKit(appServer):
+ pass
Added: LoginKit/forgotten.py
===================================================================
--- LoginKit/forgotten.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/forgotten.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -0,0 +1,271 @@
+import sha
+import time
+import random
+import os
+import smtplib
+from email.MIMEText import MIMEText
+from Component import Component, ServletComponent
+
+
+class ForgottenPasswordServletComponent(ServletComponent):
+
+ _expiration = 24 # hours
+ # @@: need better default
+ _secretPath = 'password_secret.txt'
+ _secret = None
+ _fromAddress = None
+ # @@: should get this from config:
+ _smtpHost = '127.0.0.1'
+
+ _servletMethods = ['remember', 'emailForgotten',
+ 'forgottenPasswordForm', 'resetPassword']
+
+ def __init__(self, expiration=None,
+ secretPath=None,
+ secret=None,
+ fromAddress=None):
+ ServletComponent.__init__(self)
+ if expiration is not None:
+ self._expiration = expiration
+ if secretPath is not None:
+ self._secretPath = secretPath
+ if secret and not self._secret:
+ self.__class__.secret = secret
+ self._fromAddress = fromAddress
+
+ def actions(self):
+ return self._servletMethods
+
+ def writeForgottenPasswordForm(self):
+ error = self._formError
+ req = self.servlet().request()
+ if error:
+ error = '<div class="formError">%s</div><br>\n' % error
+ self.servlet().write('''
+ <form action="" method="POST">
+ <input type="hidden" name="_action_" value="emailForgotten">
+
+ %s
+
+ <p>If you have forgotten your password, you may enter your
+ username or password, and further instructions will be mailed
+ to you.</p>
+
+ Username or email address:<br>
+ <input type="text" name="email" value="%s" size=40><br>
+ <input type="submit" value="Continue">
+
+ </form>
+ '''
+ % (error, self.htmlEncode(req.field('email', ''))))
+
+ def remember(self):
+ req = self.servlet().request()
+ secret = self._getSecret()
+ username = req.field('u', '')
+ expiration = req.field('e', '')
+ signature = req.field('s', '')
+ if not username or not expiration or not signature:
+ self.badRequest('''
+ A field was missing from you input; be sure you copied the
+ complete URL from your email. Be careful of word wrapping!
+ ''')
+ return
+ hasher = sha.new()
+ hasher.update(username)
+ hasher.update(expiration)
+ hasher.update(secret)
+ expected = hasher.hexdigest()
+ if expected != signature:
+ self.badRequest('''
+ The request was malformed. Perhaps you didn\'t copy the
+ entire URL from your email?
+ ''')
+ return
+ expiration = int(expiration) * 360
+ now = time.time()
+ if expiration < now:
+ self.badRequest('''
+ The link in the email is only good for %s hours. Please
+ submit a new request.
+ ''' % self._expiration)
+ return
+ self.resetPasswordForm(username)
+
+ def badRequest(self, msg):
+ self.servlet().setView(self.writeBadRequest)
+ self._badMessage = msg
+ self.servlet()._title = 'Error'
+ self.servlet().writeHTML()
+
+ def writeBadRequest(self):
+ self.servlet().write('<p>%s</p>' % self._badMessage)
+
+ def resetPasswordForm(self, username, error=''):
+ self.servlet().setView(self.writeResetPasswordForm)
+ self.servlet()._title = 'Reset your password'
+ self._username = username
+ self._formError = error
+ self.servlet().writeHTML()
+
+ def writeResetPasswordForm(self):
+ write = self.servlet().response().write
+ hasher = sha.new()
+ hasher.update(self._username)
+ hasher.update(self._getSecret())
+ # @@: There really should be an expiration for this signature,
+ # to avoid replay attacks
+ signature = hasher.hexdigest()
+ error = self._formError
+ if error:
+ error = '<div class="formError">%s</div><br>\n' % error
+ write('''<p>You may reset your password:</p>
+ %s
+ <form action="" method="POST">
+ <input type="hidden" name="_action_" value="resetPassword">
+ <input type="hidden" name="username" value="%s">
+ <input type="hidden" name="signature" value="%s">
+ <table>
+ <tr><td>Password:</td>
+ <td><input type="password" name="password" size=30></td></tr>
+ <tr><td>Confirm:</td>
+ <td><input type="password" name="confirm" size=30></td></tr>
+ <tr><td colspan=2>
+ <input type="submit" value="Change Password"></td></tr>
+ </table>
+ </form>
+ '''
+ % (error,
+ self.htmlEncode(self._username),
+ self.htmlEncode(signature)))
+
+ def resetPassword(self):
+ req = self.servlet().request()
+ username = req.field('username', '')
+ signature = req.field('signature', '')
+ hasher = sha.new()
+ hasher.update(username)
+ hasher.update(self._getSecret())
+ if signature != hasher.hexdigest():
+ # @@: should link back to start of form
+ self.badRequest('The request was bad; please start over and try again')
+ return
+ password = req.field('password', '')
+ confirm = req.field('confirm', '')
+ manager = self.servlet().userManager()
+ msg = manager.passwordInvalid(password)
+ if msg:
+ self.resetPasswordForm(username, error=msg)
+ return
+ if password != confirm:
+ self.resetPasswordForm(username, error='The password and confirmation do not match')
+ return
+ user = manager.userForUsername(username)
+ user.setPassword(password)
+ self.servlet()._title = 'Password reset'
+ self.servlet().setView(self.writeResetPassword)
+ self.servlet().writeHTML()
+
+ def writeResetPassword(self):
+ self.servlet().write('Your password has been reset!')
+
+ def emailForgotten(self):
+ req = self.servlet().request()
+ email = req.field('email', '').strip()
+ if not email:
+ self.forgottenPasswordForm(
+ error='You must enter a username or email address')
+ return
+
+ manager = self.servlet().userManager()
+
+ for user in manager.allUsers():
+ if user.username().lower() == email.lower() \
+ or user.email().lower() == email.lower():
+ break
+ else:
+ # No email found
+ self.forgottenPasswordForm(
+ error='The username or email address you entered was '
+ 'not found in our system')
+ return
+
+ expiration = time.time() + (self._expiration * 360)
+ # Round up, use hours:
+ expiration = int(expiration / 360 + 0.9)
+ secret = self._getSecret()
+ hasher = sha.new()
+ hasher.update(user.username())
+ hasher.update(str(expiration))
+ hasher.update(secret)
+ signature = hasher.hexdigest()
+ url = self.servlet().linkToSelf(
+ args={'_action_': 'remember',
+ 'u': user.username(),
+ 'e': expiration,
+ 's': str(signature)},
+ absolute=True)
+ body = """You have requested that you're email address be reset. To reset your
+password, go to this URL:
+%s
+""" % url
+ self.sendEmail(toAddress=user.email(),
+ fromAddress=self.fromAddress(),
+ subject='Forgotten password',
+ body=body)
+ self.servlet().setView(self.writeEmailForgotten)
+ self._sentUser = user
+ self.servlet()._title = 'Email Sent!'
+ self.servlet().writeHTML()
+
+ def writeEmailForgotten(self):
+ self.servlet().write('Your email has been sent to <tt>%s</tt>'
+ % self.htmlEncode(self._sentUser.email()))
+
+ def fromAddress(self):
+ if self._fromAddress is None:
+ env = self.servlet().request().environ()
+ host = env.get('HTTP_HOST', env['SERVER_NAME'])
+ return 'donotreply@...' % host
+ return self._fromAddress
+
+ def sendEmail(self, toAddress, fromAddress, subject, body):
+ msg = MIMEText(body)
+ msg['Subject'] = subject
+ msg['From'] = fromAddress
+ msg['To'] = toAddress
+ server = smtplib.SMTP(self._smtpHost)
+ #server.set_debuglevel(10)
+ server.sendmail(fromAddress, [toAddress], msg.as_string())
+ server.close()
+
+ def _getSecret(self):
+ # Should join to working directory
+ filename = self._secretPath
+ if self._secret is None:
+ if not os.path.exists(filename):
+ secret = randomString()
+ f = open(filename, 'wb')
+ f.write(secret)
+ f.close()
+ else:
+ f = open(filename, 'r')
+ secret = f.read().strip()
+ f.close()
+ self.__class__._secret = secret
+ return self._secret
+
+ def forgottenPasswordForm(self, error=''):
+ self._formError = error
+ self.servlet()._title = 'Forgotten Password Form'
+ self.servlet().setView(self.writeForgottenPasswordForm)
+ self.servlet().writeHTML()
+
+class ForgottenPasswordComponent(Component):
+ _componentClass = ForgottenPasswordServletComponent
+
+def randomString():
+ return (hex(random.randrange(0xffff))[2:]
+ + hex(random.randrange(0xffff))[2:]
+ + hex(random.randrange(0xffff))[2:]
+ + hex(random.randrange(0xffff))[2:])
Modified: LoginKit/main.py
===================================================================
--- LoginKit/main.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/main.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -36,19 +36,27 @@
userID = self.userIDForUsername(username)
return userID is not None
- def usernameValid(self, username):
+ def usernameInvalid(self, username):
"""
This checks if a username is acceptable, not if it exists.
+ Returns the error message if it is invalid; else None.
"""
- return not not defaultUsernameRE.search(username)
+ if not username:
+ return 'Username must not be empty'
+ if not defaultUsernameRE.search(username):
+ return 'Username must start with a letter, and contain only letters, numbers, _, ., -, and @'
+ return None
- def passwordValid(self, password):
+ def passwordInvalid(self, password):
"""
This checks if a password is acceptable, not if it is correct.
E.g., a policy where each password much have a non-letter
- character would be placed here.
+ character would be placed here. Returns the error message if
+ invalid; else None
"""
- return 1
+ if not password:
+ return 'Your password must not be empty.'
+ return None
def usersConfidential(self):
"""
@@ -88,7 +96,19 @@
"""
pass
+ def allUserIDs(self):
+ """
+ Returns a list of all user IDs.
+ """
+ raise NotImplementedError
+ def allUsers(self):
+ """
+ Returns a list of all user objects.
+ """
+ return [self.userForUserID(userID)
+ for userID in self.allUserIDs()]
+
############################################################
## Hash Functions
############################################################
@@ -117,6 +137,8 @@
self._username = None
self._userID = None
self._password = None
+ self._email = None
+ self._name = None
def __repr__(self):
return '<%s %r at %s>' % (self.__class__.__name__,
@@ -132,6 +154,20 @@
def username(self):
return self._username
+ def email(self):
+ return self._email
+
+ def setEmail(self, value):
+ self._email = value
+ self.changed()
+
+ def name(self):
+ return self._name
+
+ def setName(self, value):
+ self._name = value
+ self.changed()
+
def setUserID(self, userID):
assert self._userID is None
self._userID = userID
Modified: LoginKit/pickleusermanager.py
===================================================================
--- LoginKit/pickleusermanager.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/pickleusermanager.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -4,7 +4,7 @@
from Pickle import dump, load
import threading, os
-from Component.UserManager import UserManager, SimpleUser
+from LoginKit import UserManager, SimpleUser
class PickleUserManager(UserManager):
@@ -108,8 +108,10 @@
def userChanged(self, user):
self.saveUser(user)
+ def allUserIDs(self):
+ return self._userIDMap.values()
+
-
############################################################
## Utility functions
############################################################
Added: LoginKit/userindex.py
===================================================================
--- LoginKit/userindex.py 2004-04-18 21:43:30 UTC (rev 88)
+++ LoginKit/userindex.py 2004-04-21 04:35:25 UTC (rev 89)
@@ -0,0 +1,33 @@
+from Component import Component, ServletComponent
+
+class UserIndexServletComponent(ServletComponent):
+
+ _servletMethods = ['writeUserIndex']
+
+ def writeUserIndex(self):
+ manager = self.servlet().userManager()
+ write = self.servlet().write
+ fields = [('Username', 'username'),
+ ('Real name', 'name'),
+ ('Email', 'email'),
+ ('User ID', 'userID'),
+ ]
+ write('<table><tr>')
+ for name, getter in fields:
+ write('<th>%s</th>\n' % name)
+ write('</tr>\n')
+ users = manager.allUsers()
+ users.sort(lambda a, b: cmp(a.username(), b.username()))
+ for user in users:
+ write('<tr>')
+ for name, getter in fields:
+ write('<td>%s</td>\n'
+ % self.htmlEncode(str(getattr(user, getter)())))
+ write('</tr>\n')
+ write('</table>')
+
+
+
+class UserIndexComponent(Component):
+
+ _componentClass = UserIndexServletComponent
|
| Thread | Author | Date |
|---|---|---|
| [Webware-checkins] r89 - in LoginKit: . Examples | <ianb@we...> |