ScoDoc/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py

630 lines
19 KiB
Python

#
# Extensible User Folder
#
# Basic Membership Source for exUserFolder
#
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
# ACN: 082 081 472 ABN: 83 082 081 472
# All Rights Reserved
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# Author: Andrew Milton <akm@theinternet.com.au>
# $Id: basicMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $
#
# Basically membership is a layer between the signup/login form, and
# the authentication layer, it uses the prop source of the users to
# store additional information about a user i.e. doesn't impact on the
# authentication source.
#
# Some membership features imply some extra properties for the user will
# be available; specifically at this time an email property.
#
# You also need a MailHost setup and ready to go for emailing stuff to users
#
import string,Acquisition
from random import choice
from Globals import HTMLFile, INSTANCE_HOME
from OFS.Folder import Folder
from OFS.DTMLMethod import DTMLMethod
from Products.exUserFolder.exUserFolder import exUserFolder
from Products.exUserFolder.Plugins import PluginRegister
from base64 import encodestring
from urllib import quote
import zLOG
"""
Password Policy enforcement (min/max length, caps etc)
Create Password, or User Chooses.
Timing out of passwords...
Empty Password force change on login...
Create Home Directory
Copy files from Skelton Directory
EMail password hint to user (forgot my password)
Reset password and email user (needs plugin?)
Redirect on login to fixed or varying per username location.
Automatically add users, or manually approve of users.
"""
# Stupid little things for making a password
# Don't hassle me, it's supposed to be basic.
nouns=['ace', 'ant', 'arc', 'arm', 'axe',
'bar', 'bat', 'bee', 'bib', 'bin',
'can', 'cap', 'car', 'cat', 'cob',
'day', 'den', 'dog', 'dot', 'dux',
'ear', 'eel', 'egg', 'elf', 'elk',
'fad', 'fan', 'fat', 'fig', 'fez',
'gag', 'gas', 'gin', 'git', 'gum',
'hag', 'hat', 'hay', 'hex', 'hub']
pastConjs = [ 'did', 'has', 'was' ]
suffixes = [ 'ing', 'es', 'ed', 'ious', 'ily']
def manage_addBasicMemberSource(self, REQUEST):
""" Add a Membership Source """
pvfeatures=[]
minLength=0
passwordPolicy=''
createHomedir=0
homeRoot=''
copyFilesFrom=''
postLogin=''
postSignup=''
forgottenPasswords=''
defaultRoles=[]
usersCanChangePasswords=0
baseURL=''
loginPage=''
signupPage=''
passwordPage=''
mailHost=''
fixedDest=''
if REQUEST.has_key('basicmember_pvfeatures'):
pvfeatures=REQUEST['basicmember_pvfeatures']
if REQUEST.has_key('basicmember_roles'):
defaultRoles=REQUEST['basicmember_roles']
if not defaultRoles:
defaultRoles=['Member']
if 'minlength' in pvfeatures:
minLength=REQUEST['basicmember_minpasslen']
if REQUEST.has_key('basicmember_passwordpolicy'):
passwordPolicy=REQUEST['basicmember_passwordpolicy']
if REQUEST.has_key('basicmember_createhomedir'):
homeRoot=REQUEST['basicmember_homeroot']
createHomedir=1
if REQUEST.has_key('basicmember_copyfiles'):
copyFilesFrom=REQUEST['basicmember_copyfiles']
if REQUEST.has_key('basicmember_changepasswords'):
usersCanChangePasswords=1
if REQUEST.has_key('basicmember_fixeddest'):
fixedDest=''
forgottenPasswords=REQUEST['basicmember_forgottenpasswords']
postLogin=REQUEST['basicmember_postlogin']
baseURL=REQUEST['basicmember_baseurl']
loginPage=REQUEST['basicmember_loginpage']
signupPage=REQUEST['basicmember_signuppage']
passwordPage=REQUEST['basicmember_passwordpage']
siteEmail=REQUEST['basicmember_siteemail']
siteName=REQUEST['basicmember_sitename']
mailHost=REQUEST['basicmember_mailhost']
# postSignup=REQUEST['basicmember_postsignup']
#
# Yep this is obscene
#
o = BasicMemberSource(pvfeatures, minLength, passwordPolicy,
createHomedir, copyFilesFrom, postLogin,
homeRoot, forgottenPasswords, defaultRoles,
usersCanChangePasswords, baseURL, loginPage,
signupPage, passwordPage, mailHost,
siteName, siteEmail, fixedDest)
self._setObject('basicMemberSource', o, None, None, 0)
o = getattr(self, 'basicMemberSource')
if hasattr(o, 'postInitialisation'):
o.postInitialisation(REQUEST)
self.currentMembershipSource=o
return ''
manage_addBasicMemberSourceForm=HTMLFile('manage_addBasicMemberSourceForm',
globals())
manage_editBasicMemberSourceForm=HTMLFile('manage_editBasicMemberSourceForm',
globals())
#
# Crap, I don't know why I called this basic, I'd hate to see a
# complicated one.
#
class BasicMemberSource(Folder):
""" Provide High Level User Management """
meta_type="Membership Source"
title="Basic Membership Source"
icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
manage_tabs=Acquisition.Acquired
manage_editForm=manage_editBasicMemberSourceForm
# Ugh...
def __init__(self, pvFeatures=[], minLength=0, passwordPolicy='',
createHomeDir=0, copyFilesFrom='', postLogin='', homeRoot='',
forgottenPasswords='', defaultRoles=[], usersCanChangePasswords=0,
baseURL='', loginPage='', signupPage='', passwordPage='',
mailHost='', siteName='', siteEmail='', fixedDest=''):
self.id='basicMemberSource'
self.pvFeatures=pvFeatures
self.minLength=int(minLength)
self.passwordPolicy=passwordPolicy
self.createHomeDir=createHomeDir
self.copyFilesFrom=copyFilesFrom
self.postLogin=postLogin
self.homeRoot=homeRoot
self.forgottenPasswords=forgottenPasswords
self.defaultRoles=defaultRoles
self.usersCanChangePasswords=usersCanChangePasswords
self.baseURL=baseURL
self.loginPage=loginPage
self.signupPage=signupPage
self.passwordPage=passwordPage
self.siteName=siteName
self.siteEmail=siteEmail
self.fixedDest=fixedDest
_SignupForm=HTMLFile('SignupForm', globals())
SignupForm=DTMLMethod()
SignupForm.manage_edit(data=_SignupForm, title='Signup Form')
self._setObject('SignupForm', SignupForm)
_PasswordForm=HTMLFile('PasswordForm', globals())
PasswordForm=DTMLMethod()
PasswordForm.manage_edit(data=_PasswordForm,
title='Change Password')
self._setObject('PasswordForm', PasswordForm)
self.mailHost=mailHost
_newPasswordEmail=HTMLFile('newPasswordEmail', globals())
newPasswordEmail=DTMLMethod()
newPasswordEmail.manage_edit(data=_newPasswordEmail,
title='Send New Password')
self._setObject('newPasswordEmail', newPasswordEmail)
_forgotPasswordEmail=HTMLFile('forgotPasswordEmail', globals())
forgotPasswordEmail=DTMLMethod()
forgotPasswordEmail.manage_edit(data=_forgotPasswordEmail,
title='Send Forgotten Password')
self._setObject('forgotPasswordEmail', forgotPasswordEmail)
_passwordHintEmail=HTMLFile('passwordHintEmail', globals())
passwordHintEmail=DTMLMethod()
passwordHintEmail.manage_edit(data=_passwordHintEmail,
title='Send Forgotten Password Hint')
self._setObject('passwordHintEmail', passwordHintEmail)
def postInitialisation(self, REQUEST):
if self.createHomeDir and self.homeRoot:
self.findHomeRootObject()
else:
self.homeRootObj=None
if self.copyFilesFrom:
self.findSkelRootObject()
else:
self.homeSkelObj=None
# The nice sendmail tag doesn't allow expressions for
# the mailhost
self.mailHostObject=getattr(self, self.mailHost)
def manage_editMembershipSource(self, REQUEST):
""" Edit a basic Membership Source """
if REQUEST.has_key('pvfeatures'):
self.pvFeatures=REQUEST['pvfeatures']
else:
self.pvFeatures=[]
if REQUEST.has_key('minpasslength'):
self.minLength=REQUEST['minpasslength']
if REQUEST.has_key('createhomedir'):
createHomeDir=1
else:
createHomeDir=0
if createHomeDir:
self.copyFilesFrom=REQUEST['copyfiles']
if self.copyFilesFrom:
self.findSkelRootObject()
else:
self.homeRoot=REQUEST['homeroot']
self.findHomeRootObject()
if REQUEST.has_key('memberroles'):
self.defaultRoles=REQUEST['memberroles']
if REQUEST.has_key('changepasswords'):
self.usersCanChangePasswords=1
else:
self.usersCanChangePasswords=0
self.postLogin=REQUEST['postlogin']
if REQUEST.has_key('fixeddest'):
self.fixedDest=REQUEST['fixeddest']
self.baseURL=REQUEST['baseurl']
self.loginPage=REQUEST['loginpage']
self.signupPage=REQUEST['signuppage']
self.passwordPage=REQUEST['passwordpage']
self.siteName=REQUEST['sitename']
self.siteEmail=REQUEST['siteemail']
return self.MessageDialog(self,
title ='Updated!',
message="Membership was Updated",
action ='manage_editMembershipSourceForm',
REQUEST=REQUEST)
def forgotPassword(self, REQUEST):
username=REQUEST['username']
curUser=self.getUser(username)
if not curUser:
return self.MessageDialog(self,
title ='No such user',
message="No users matching that username were found.",
action ='%s/%s'%(self.baseURL, self.loginPage),
REQUEST=REQUEST)
userEmail=curUser.getProperty('email')
userName=curUser.getProperty('realname')
if self.forgottenPasswords == "hint":
passwordHint=curUser.getProperty('passwordhint')
self.passwordHintEmail(self,
REQUEST=REQUEST,
username=username,
hint=passwordHint,
realname=userName,
email=userEmail)
else:
# make a new password, and mail it to the user
password = self.generatePassword()
curCrypt=self.currentAuthSource.cryptPassword(username,password)
# Update the user
bogusREQUEST={}
#bogusREQUEST['username']=username
bogusREQUEST['password']=password
bogusREQUEST['password_confirm']=password
bogusREQUEST['roles']=curUser.roles
self.manage_editUser(username, bogusREQUEST)
self.forgotPasswordEmail(self,
REQUEST=REQUEST,
username=username,
password=password,
realname=userName,
email=userEmail)
return self.MessageDialog(self,
title ='Sent!',
message="Password details have been emailed to you",
action ='%s/%s'%(self.baseURL, self.loginPage),
REQUEST=REQUEST)
def changeProperties(self, REQUEST):
curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName())
curUser=curUser[0]
if not curUser:
return self.MessageDialog(self,
title ='Erm!',
message="You don't seem to be logged in",
action ='%s/%s'%(self.baseURL, self.passwordPage),
REQUEST=REQUEST)
self.currentPropSource.updateUser(curUser['username'],REQUEST)
return self.MessageDialog(self,
title ='Properties updated',
message="Your properties have been updated",
action =self.baseURL,
REQUEST=REQUEST,
)
def changePassword(self, REQUEST):
if not self.usersCanChangePasswords:
return ''
curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName())
curUser=curUser[0]
if not curUser:
return self.MessageDialog(
title ='Erm!',
message="You don't seem to be logged in",
action ='%s/%s'%(self.baseURL, self.passwordPage),
REQUEST=REQUEST)
curCrypt=self.currentAuthSource.cryptPassword(curUser['username'],REQUEST['current_password'])
if curCrypt != curUser['password']:
return self.MessageDialog(self,
title ='Password Mismatch',
message="Password is incorrect",
action ='%s/%s'%(self.baseURL, self.passwordPage),
REQUEST=REQUEST)
if REQUEST['password'] != REQUEST['password_confirm']:
return self.MessageDialog(self,
title ='Password Mismatch',
message="Passwords do not match",
action ='%s/%s'%(self.baseURL, self.passwordPage),
REQUEST=REQUEST)
# OK the old password matches the one the user provided
# Both new passwords match...
# Time to validate against our normal set of rules...
#
if not self.validatePassword(REQUEST['password'], curUser['username']):
return self.MessageDialog(self,
title ='Password problem',
message="Your password is invalid, please choose another",
action ='%s/%s'%(self.baseURL, self.passwordPage),
REQUEST=REQUEST)
if self.passwordPolicy=='hint':
if not hasattr(REQUEST,'user_passwordhint'):
return self.MessageDialog(self,
title ='Password requires hint',
message='You must choose a password hint',
action ='%s/%s'%(self.baseURL, self.passwordPage),
REQUEST=REQUEST)
bogusREQUEST={}
bogusREQUEST['password']=REQUEST['password']
bogusREQUEST['password_confirm']=REQUEST['password']
bogusREQUEST['roles']=curUser['roles']
self.manage_editUser(curUser['username'],bogusREQUEST)
# update the cookie so he doesnt have to re-login:
if self.cookie_mode:
token='%s:%s' %(curUser['username'], REQUEST['password'])
token=encodestring(token)
token=quote(token)
REQUEST.response.setCookie('__ac', token, path='/')
REQUEST['__ac']=token
return self.MessageDialog(self,
title ='Password updated',
message="Your password has been updated",
action =self.baseURL,
REQUEST=REQUEST)
def goHome(self, REQUEST, RESPONSE):
redirectstring="%s/%s/%s/manage_main"%(self.baseURL, self.homeRoot, REQUEST.AUTHENTICATED_USER.getUserName())
RESPONSE.redirect(redirectstring)
return ''
# Tell exUserFolder where we want to go...
def getLoginDestination(self, REQUEST):
script=''
pathinfo=''
querystring=''
redirectstring=''
if self.postLogin=="destination":
script=REQUEST['SCRIPT_NAME']
pathinfo=REQUEST['PATH_INFO']
elif self.postLogin=="varied":
script=self.baseURL
pathinfo="/acl_users/goHome"
elif self.postLogin=="fixed":
pathinfo="%s"%(self.fixedDest)
if REQUEST.has_key('QUERY_STRING'):
querystring='?'+REQUEST['QUERY_STRING']
redirectstring=script+pathinfo
if querystring:
redirectstring=redirectstring+querystring
return redirectstring
def validatePassword(self, password, username):
if 'minlength' in self.pvFeatures:
if len(password) < self.minLength:
return 0
if 'mixedcase' in self.pvFeatures:
lower = 0
upper = 0
for c in password:
if c in string.lowercase:
lower = 1
if c in string.uppercase:
upper = 1
if not upper and lower:
return 0
if 'specialchar' in self.pvFeatures:
special = 0
for c in password:
if c in string.punctuation:
special = 1
break
elif c in string.digits:
special = 1
break
if not special:
return 0
#
# XXX Move this somewhere else
#
if 'notstupid' in self.pvFeatures:
email=''
# We try some permutations here...
curUser=self.getUser(username)
if curUser:
email = curUser.getProperty('email')
elif hasattr(self, 'REQUEST'):
if self.REQUEST.has_key('user_email'): # new signup
email=self.REQUEST['user_email']
elif self.REQUEST.has_key('email'):
email=self.REQUEST['email']
if ((string.find(password, username)>=0) or
( email and
(string.find(password,
string.split(email,'@')[0]) >=0))):
return 0
return 1
# These next two look the same (and they are for now), but, the reason I
# Don't use one single method, is I think that SkelObj might migrate to
# using full paths, not relative paths.
def findSkelRootObject(self):
# Parent should be acl_users
parent = getattr(self, 'aq_parent')
# This should be the root...
root = getattr(parent, 'aq_parent')
searchPaths = string.split(self.copyFilesFrom, '/')
for o in searchPaths:
if not getattr(root, o):
break
root = getattr(root, o)
self.homeSkelObj=root
def findHomeRootObject(self):
# Parent should be acl_users
parent = getattr(self, 'aq_parent')
# This should be the root...
root = getattr(parent, 'aq_parent')
searchPaths = string.split(self.homeRoot, '/')
for o in searchPaths:
if o not in root.objectIds():
root.manage_addFolder(id=o, title=o, createPublic=0, createUserF=0)
root = getattr(root, o)
self.homeRootObj=root
def makeHomeDir(self, username):
if not self.homeRootObj:
return
self.homeRootObj.manage_addFolder(id=username, title=username, createPublic=0, createUserF=0)
home = getattr(self.homeRootObj, username)
# Allow user to be in charge of their own destiny
# XXXX WARNING THIS IS A NORMAL FOLDER *SO USERS CAN ADD ANYTHING*
# YOU NEED TO CHANGE THE TYPE OF OBJECT ADDED FOR A USER UNLESS
# THIS IS WHAT YOU WANT TO HAPPEN
home.manage_addLocalRoles(userid=username, roles=['Manager'])
if self.copyFilesFrom and self.homeSkelObj and self.homeSkelObj.objectIds():
cp=self.homeSkelObj.manage_copyObjects(
self.homeSkelObj.objectIds())
home.manage_pasteObjects(cp)
# Fix it so the user owns their stuff
curUser=self.getUser(username).__of__(self.aq_parent)
home.changeOwnership(curUser, recursive=1)
def generatePassword(self):
password = (choice(nouns) + choice(pastConjs) +
choice(nouns) + choice(suffixes))
return password
def createUser(self, REQUEST):
if self.passwordPolicy == 'user':
if not self.validatePassword(REQUEST['password'], REQUEST['username']):
return self.MessageDialog(self,
title ='Password problem',
message='Your password is invalid, please choose another',
action ='%s/%s'%(self.baseURL, self.signupPage),
REQUEST=REQUEST)
if self.passwordPolicy=='hint':
if not hasattr(REQUEST,'user_passwordhint'):
return self.MessageDialog(self,
title ='Password requires hint',
message='You must choose a password hint',
action ='%s/%s'%(self.baseURL, self.signupPage),
REQUEST=REQUEST)
elif self.passwordPolicy == 'system':
REQUEST['password']=self.generatePassword()
REQUEST['password_confirm']=REQUEST['password']
# Email the password.
self.newPasswordEmail(self, REQUEST)
zLOG.LOG("exUserFolder.basicMemberSource", zLOG.BLATHER,
"Creating user",
"Passed all tests -- creating [%s]" % REQUEST['username'])
REQUEST['roles']=self.defaultRoles
self.manage_addUser(REQUEST) # Create the User...
if self.createHomeDir:
self.makeHomeDir(REQUEST['username'])
return self.MessageDialog(self,
title ='You have signed up',
message='You have been signed up succesfully',
action ='%s'%(self.baseURL),
REQUEST=REQUEST)
basicMemberReg=PluginRegister('basicMemberSource',
'Basic Membership Source',
BasicMemberSource,
manage_addBasicMemberSourceForm,
manage_addBasicMemberSource,
manage_editBasicMemberSourceForm)
exUserFolder.membershipSources['basicMemberSource']=basicMemberReg