/[theodore]/pyBB/modules/logintools/newlogin.py


UCC Code Repository

Contents of /pyBB/modules/logintools/newlogin.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show annotations) (download) (as text)
Tue Jan 29 14:32:01 2008 UTC (12 years, 2 months ago) by svn-admin
File MIME type: text/x-python
File size: 16386 byte(s)
Re-import of repository after repository database corruption.

1 # newlogin.py
2 # part of logintools
3 # A CGI Authentication and user account system
4 # Copyright (C) 2004/2005 Michael Foord
5 # E-mail: fuzzyman AT voidspace DOT org DOT uk
6
7 # Released subject to the BSD License
8 # Please see http://www.voidspace.org.uk/documents/BSD-LICENSE.txt
9
10 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
11 # Comments, suggestions and bug reports welcome.
12
13 # For information about bugfixes, updates and support, please join the Pythonutils mailing list.
14 # http://voidspace.org.uk/mailman/listinfo/pythonutils_voidspace.org.uk
15 # Comments, suggestions and bug reports welcome.
16
17 import sys
18 import os
19 sys.path.append('../modules')
20 from configobj import ConfigObj
21 from pathutils import *
22 from cgiutils import *
23 from loginutils import *
24
25 ####################################################
26 # Various default values etc
27
28 newlogintemplate = 'newlogin_page.html'
29 newform_nojs = 'newlogin_nojs.txt'
30 logindone = 'login_done.html'
31 editform_nojs = 'editform_nojs.txt'
32 edacctemplate = 'edacc_page.html'
33 newloginlist = ['email', 'username', 'realname', ]
34 newloginformkeys = {'username' : 'Login Name', 'pass1' : 'Password', 'pass2' : 'Confirmation Password',
35 'email' : 'Email Address', 'realname' : 'Real Name'}
36 edacckeys = ['realname', 'username', 'pass0', 'pass1', 'pass2', 'email']
37
38 # FIXME: do we trust this in all cases ? (is it part of the CGI spec or optional)
39 # can't we just use SCRIPT_NAME ?
40 SCRIPTLOC = 'http://' + os.environ.get('HTTP_HOST', '')
41
42 ##########################################################
43 # functions for handling new logins
44
45 def newlogin(userdir, thisscript, action=None):
46 """Display the newlogin form."""
47 config = ConfigObj(userdir + 'config.ini')
48 templatedir = config['templatedir']
49 # check that new logins are enabled on this setup
50 checknewlogin(userdir)
51 #
52 newloginform = readfile(templatedir+newform_nojs)
53 newloginform = newloginform.replace('**script**', thisscript)
54 #
55 newloginpage = readfile(templatedir+newlogintemplate)
56 newloginpage = newloginpage.replace('**new login form**', newloginform)
57 newloginpage = filltemplate(newloginpage)
58 #
59 if action:
60 newloginpage = newloginpage.replace('<!-- **action** -->', actionline % action)
61 print serverline
62 print '\r'
63 print newloginpage
64 #
65 sys.exit()
66
67 def donewlogin(theform, userdir, thisscript, action=None):
68 """Process the results from new login form submissions."""
69 loginaction = theform['login'].value
70 if not loginaction == 'donewloginnojs':
71 # only type of newlogin supported so far
72 sys.exit()
73 # check that new logins are enabled on this setup
74 checknewlogin(userdir)
75 allentries = theform.keys()
76 vallist = allentries + [entry for entry in newloginformkeys if entry not in allentries]
77 formdict = getform(vallist, theform, nolist=True)
78 for entry in newloginformkeys:
79 if not formdict[entry]:
80 invalidentry('Required field missing : "%s"' % newloginformkeys[entry], formdict, userdir, thisscript, action)
81 # missingentry should redisplay the form, with values filled in, and then exit
82 email = validateemail(formdict)
83 if not email:
84 invalidentry('Email address appears to be invalid.', formdict, userdir, thisscript, action)
85 # if the address is invalid it should behave like missingentry
86 # also max one login per email address ?
87 login = validatelogin(formdict, userdir, thisscript, action)
88 # if login name already exists or is invalid
89 password = validatepass(formdict, userdir, thisscript, action)
90 # if passwords don't match or are too short
91 realname = formdict['realname']
92 # If we've got this far we've got a valid new login and need to save the
93 # details, send the email and print a mesage
94 #
95 config = ConfigObj(userdir + 'config.ini')
96 link_url = savedetails(userdir, formdict, action) # this includes any 'extra' keys, not just the ones we require
97 msg = config['email_message'] + '\n'
98 msg = msg + SCRIPTLOC + thisscript + '?login=confirm&id=' + link_url
99 writefile('log.txt', msg)
100 sendmailme(email, msg, config['email_subject'], email, html=False)
101 #
102 templatedir = config['templatedir']
103 logindonepage = readfile(templatedir+logindone)
104 logindonepage = logindonepage.replace('**this script**', thisscript)
105 #
106 print serverline
107 print '\r'
108 print logindonepage
109 #
110 sys.exit()
111
112 def confirm(theform, userdir, thisscript):
113 """Confirm a login.
114 Either from an invite or from a user who has registered."""
115 from dataenc import pass_dec, pass_enc
116 from login import encodestring
117 fail = False
118 try:
119 theval, daynumber, timestamp = pass_dec(theform['id'].value)
120 except:
121 # FIXME: bare except....
122 newloginfail()
123 tempstore = ConfigObj(userdir + 'temp.ini')
124 if not tempstore.has_key(theval):
125 newloginfail()
126 uservals = tempstore[theval]
127 del tempstore[theval]
128 username = uservals['username']
129 if username in tempstore['pending']:
130 tempstore['pending'].remove(username)
131 tempstore.write()
132 #
133 newconfig = ConfigObj(userdir + 'default.ini')
134 newpath = userdir + username + '.ini'
135 if os.path.isfile(newpath):
136 newloginfail()
137 newconfig.filename = newpath
138 # FIXME: should this be '' ?
139 action = None
140 for entry in uservals:
141 if entry == 'action':
142 action = uservals[entry]
143 elif entry == 'password':
144 password = uservals[entry]
145 newconfig[entry] = pass_enc(password, timestamp=True, daynumber=True)
146 else:
147 newconfig[entry] = uservals[entry]
148 newconfig.write()
149 #
150 # next we need to create the cookie header to return it
151 from Cookie import SimpleCookie
152 thecookie = SimpleCookie()
153 thecookie['userid'] = encodestring(newconfig['username'], password)
154 config = ConfigObj(userdir + 'config.ini')
155 maxage = newconfig['max-age']
156 cookiepath = config['cookiepath']
157 if maxage and int(maxage): # possible cause of error here if the maxage value in a users file isn't an integer !!
158 thecookie['userid']['max-age'] = int(maxage)
159 if cookiepath:
160 thecookie['userid']['path'] = cookiepath
161 if config['adminmail']:
162 msg = 'A new user has created a login - "%s".\n\n' % thisscript
163 for entry in newconfig:
164 if entry != 'password':
165 msg += entry + ' : ' + newconfig[entry] + '\n'
166 # FIXME: should be mailme
167 sendmailme(config['adminmail'], msg, config['email_subject'],
168 config['adminmail'], html=False)
169 return action, newconfig, thecookie.output()
170
171 def editaccount(userdir, thisscript, userconfig, action, newcookie):
172 """Display the form for the user to edit their account details."""
173 msg = ''
174 display_edit(userconfig, userdir, thisscript, msg, action, newcookie)
175
176 def doeditaccount(theform, userconfig, userdir, thisscript, action, newcookie):
177 """Process the results from edit account form submissions."""
178 from dataenc import pass_enc, pass_dec
179 loginaction = theform['login'].value
180 if not loginaction == 'doeditaccountnojs': # only type of newlogin supported so far
181 sys.exit()
182 allentries = theform.keys()
183 vallist = allentries + [entry for entry in edacckeys if entry not in allentries]
184 formdict = getform(vallist, theform, nolist=True)
185 #
186 oldpass = formdict['pass0']
187 storedpass = pass_dec(userconfig['password'])[0]
188 pass1 = formdict['pass1']
189 pass2 = formdict['pass2']
190 #
191 email = validateemail(formdict)
192 oldemail = userconfig['email']
193 if not email:
194 msg = 'The email address you supplied appears to be invalid.'
195 display_edit(formdict, userdir, thisscript, msg, action, newcookie, userconfig)
196 if email != oldemail and (not oldpass or oldpass != storedpass):
197 msg = 'You must correctly enter your password to change your email address.'
198 display_edit(formdict, userdir, thisscript, msg, action, newcookie, userconfig)
199 userconfig['email'] = email
200 if not formdict['realname']:
201 msg = 'You need to enter a name for us to use.'
202 display_edit(formdict, userdir, thisscript, msg, action, newcookie, userconfig)
203 userconfig['realname'] = formdict['realname']
204 if pass1 or pass2:
205 if pass1 != pass2:
206 msg = "The two passwords don't match."
207 display_edit(formdict, userdir, thisscript, msg, action, newcookie, userconfig)
208 if len(pass1) < 5:
209 msg = "The password must be longer than 5 characters."
210 display_edit(formdict, userdir, thisscript, msg, action, newcookie, userconfig)
211 if not oldpass or oldpass != storedpass:
212 msg = 'You must correctly enter your current password to change it.'
213 display_edit(formdict, userdir, thisscript, msg, action, newcookie, userconfig)
214 userconfig['password'] = pass_enc(pass1, daynumber=True, timestamp=True)
215 newcookie = makecookie(userconfig, pass1, ConfigObj(userdir+'config.ini')['cookiepath'])
216 for entry in formdict:
217 if entry not in edacckeys:
218 userconfig[entry] = formdict[entry]
219 userconfig.write()
220 return action, userconfig, newcookie # XXXXX display values changed page
221 # XXXXX must change cookie if password changed
222
223 #################################################
224 # helper functions
225
226 # in the new login and edit account templates you need value="**keyname1**" in the relevant input fields
227 # plus a comment like this somewhere in the document:
228 # <!-- **keynamelist** keyname1, keyname2, keyname3 -->
229 # When a validation error occurs - the template is reprinted with the existing values filled into the template
230 # XXXX There's no way (yet) of making any of these new keys required keys though.
231 # XXXX the first time this happens it ought to fill in the values from 'default.ini'
232 def filltemplate(template, formdict=None, vallist=None):
233 """Fill in the blanks in a new login form."""
234 if not formdict:
235 formdict = {}
236 if not vallist:
237 vallist = newloginlist
238 index = template.find('**keynamelist**')
239 if index == -1:
240 keynames = []
241 else:
242 start = index
243 while index < len(template)-1:
244 index += 1
245 if template[index] in '"\'><':
246 break
247 keynames = [entry.strip() for entry in template[start:index].split(',') if entry not in vallist]
248 for entry in keynames+vallist:
249 if entry in ['pass1', 'pass2']:
250 continue
251 template = template.replace('**%s**' % entry, formdict.get(entry, ''))
252 return template
253
254
255 def validateemail(formdict):
256 email = formdict['email'].strip()
257 return validemail(email)
258
259 def validatelogin(formdict, userdir, thisscript, action):
260 name = formdict['username']
261 tempstore = ConfigObj(userdir + 'temp.ini')
262 pendinglist = tempstore.get('pending', [])
263 if os.path.isfile(userdir+name+'.ini') or name in pendinglist or name.lower() in RESERVEDNAMES:
264 invalidentry('Username already exists.', formdict, userdir, thisscript, action)
265 for char in name.lower():
266 if not char in validchars:
267 invalidentry('Username contains invalid characters.', formdict, userdir, thisscript, action)
268 return name
269
270 def validatepass(formdict, userdir, thisscript, action):
271 pass1 = formdict['pass1']
272 pass2 = formdict['pass2']
273 if pass1 != pass2:
274 invalidentry('The two passwords don\'t match.', formdict, userdir, thisscript, action)
275 if len(pass1) < 5:
276 invalidentry('The password must be at least 5 characters long.', formdict, userdir, thisscript, action)
277 return pass1
278
279 def invalidentry(msg, formdict, userdir, thisscript, action=None):
280 """Display the newlogin form."""
281 config = ConfigObj(userdir + 'config.ini')
282 templatedir = config['templatedir']
283 #
284 newloginform = readfile(templatedir+newform_nojs)
285 newloginform = newloginform.replace('**script**', thisscript)
286 #
287 newloginpage = readfile(templatedir+newlogintemplate)
288 newloginpage = newloginpage.replace('**new login form**', newloginform)
289 newloginpage = filltemplate(newloginpage, formdict)
290 newloginpage = newloginpage.replace('<!-- **message** -->', '<h2>'+msg+'</h2><br>')
291 #
292 if action:
293 newloginpage = newloginpage.replace('<!-- **action** -->', actionline % action)
294 print serverline
295 print '\r'
296 print newloginpage
297 #
298 sys.exit()
299
300 def savedetails(userdir, formdict, action=None):
301 """
302 Given the form from a validated new login, it saves the details to the
303 temporary store.
304
305 It also cleans up any out of date ones that haven't been used.
306 """
307 from dateutils import returndate, daycount
308 from dataenc import pass_enc
309 #
310 tempstore = ConfigObj(userdir + 'temp.ini')
311 if action:
312 formdict['action'] = action
313 year, month, day = returndate()
314 today = daycount(year, month, day)
315 #
316 for section in tempstore:
317 if section[4:].isdigit():
318 if int(section[4:]) > today + 30:
319 name = tempstore[section]['username']
320 tempstore['pending'].remove(name)
321 del tempstore[section]
322 #
323 ran = randomstring(4)
324 while tempstore.has_key(ran+str(today)):
325 ran = randomstring(4)
326 key = ran+str(today)
327 tempstore[key] = {}
328 store = tempstore[key]
329 for entry in formdict:
330 if entry == 'pass1' or entry == 'pass2':
331 store['password'] = formdict[entry]
332 elif entry == 'login':
333 pass
334 else:
335 store[entry] = formdict[entry]
336 if not tempstore.has_key('pending'):
337 tempstore['pending'] = []
338 tempstore['pending'].append(formdict['username'])
339 tempstore.write()
340 return pass_enc(key, timestamp=True, daynumber=True)
341
342 def checknewlogin(userdir):
343 """Check that new logins are enabled on this setup.
344 Return or display the error message and quit."""
345 config = ConfigObj(userdir + 'config.ini')
346 if istrue(config['newloginlink']):
347 return
348 error('Sorry, New Logins are Disabled on this System.')
349
350 def newloginfail():
351 # FIXME: this could be more useful
352 error('Sorry, you have either already confirmed your login, '
353 'or it is out of date :-)')
354
355 def display_edit(formdict, userdir, thisscript, msg, action, newcookie,
356 userconfig=None):
357 """
358 Display the form for editing account details.
359 This is a different form to creating a new account as it requires
360 confirmation of the old password before doing certain things, like
361 changing email address and password.
362 """
363 from time import ctime
364 print newcookie
365 config = ConfigObj(userdir + 'config.ini')
366 templatedir = config['templatedir']
367 # if we are sending the userconfig as the formdict, we needn't explicitly
368 # pass it in
369 if not userconfig:
370 userconfig = formdict
371 #
372 edaccform = unicode(readfile(templatedir+editform_nojs))
373 edaccform = edaccform.replace('**script**', thisscript)
374 #
375 edaccpage = readfile(templatedir+edacctemplate)
376 edaccpage = edaccpage.replace('**edit account form**', edaccform)
377 edaccpage = filltemplate(edaccpage, formdict)
378 if msg:
379 edaccpage = edaccpage.replace('<!-- **message** -->', '<h2>'+msg+'</h2><br>')
380 edaccpage = edaccpage.replace('**created on**', ctime(float(userconfig['created'])))
381 edaccpage = edaccpage.replace('**num logins**', userconfig['numlogins'])
382 edaccpage = edaccpage.replace('**last used**', ctime(float(userconfig['lastused'])))
383 edaccpage = edaccpage.replace('**num used**', userconfig['numused'])
384 edaccpage = edaccpage.replace('**this script**', thisscript + '?action=' + action)
385 #
386 if action:
387 edaccpage = edaccpage.replace('<!-- **action** -->', actionline % action)
388 print serverline
389 print '\r'
390 print edaccpage
391 #
392 sys.exit()
393
394
395 """
396 CHANGELOG
397 =========
398
399 2005/10/30
400 ----------
401
402 Fixed the email function... oops.
403
404 2005/10/11
405 ----------
406
407 Bugfix in
408
409 2005/09/09
410 ----------
411
412 Changes to work with pythonutils 0.2.1
413
414 """

Managed by UCC Webmasters ViewVC Help
Powered by ViewVC 1.1.26