# IssueTrackerProduct
#
# www.issuetrackerproduct.com
# Peter Bengtsson <mail@peterbe.com>
# License: ZPL
#
__version__='0.0.8'
# python
# Zope
from AccessControl import User,AuthEncoding
from App.Dialogs import MessageDialog
from AccessControl import ClassSecurityInfo
from AccessControl.Role import DEFAULTMAXLISTUSERS
try:
# >= Zope 2.12
from App.special_dtml import DTMLFile
from Persistence import Persistent
except ImportError:
# < Zope 2.12
from Globals import DTMLFile,Persistent
# Product
import Utils
from I18N import _
from Constants import *
from Permissions import IssueTrackerManagerRole,IssueTrackerUserRole,VMS
#----------------------------------------------------------------------------
manage_addIssueUserFolderForm=DTMLFile('dtml/addIssueUserFolder', globals())
def manage_addIssueUserFolder(self, title='', webmaster_email='',
keep_usernames=None, REQUEST=None):
""" ads """
id="acl_users"
webmaster_email = str(webmaster_email).strip()
old_users = None
if keep_usernames and id in self.objectIds('User Folder'):
old_users = self.manage_getUsersToConvert(withpasswords=True)
self.manage_delObjects([id])
i=IssueUserFolder(webmaster_email)
self._setObject(id,i)
userfolder = self._getOb(id)
for role in [IssueTrackerManagerRole, IssueTrackerUserRole]:
# only add these roles if they don't already exist
if role not in self.valid_roles():
#self.REQUEST.set('role', role)
#self.manage_defined_roles(submit='Add Role', REQUEST=self.REQUEST)
aq_self = self.this()
roles = list(aq_self.__ac_roles__)
roles.append(role)
aq_self.__ac_roles__ = tuple(roles)
if old_users and REQUEST is not None:
old_users_dict = {}
for user in old_users:
old_users_dict[user['username'].replace(' ','')] = user
keys = REQUEST.get('keys')
for key in keys:
username = REQUEST.get('username_%s'%key)
if key not in keep_usernames:
continue
if not username:
raise "IllegalValue", 'A username must be specified'
password = old_users_dict[key]['__']
fullname = REQUEST.get('fullname_%s'%key)
if not fullname:
fullname = username
email = REQUEST.get('email_%s'%key)
if not Utils.ValidEmailAddress(email):
raise "InvalidEmail", "Email (%r) not valid email address"%email
roles = REQUEST.get('roles_%s'%key)
domains = REQUEST.get('domains_%s'%key)
if domains and not self.domainSpecValidate(domains):
raise "IllegalValue", 'Illegal domain specification'
userfolder._doAddUser(username, password, roles, domains,
email=email,
fullname=fullname)
if REQUEST:
return self.manage_main(self, REQUEST)
def _uniqify(somelist):
d={}
for i in somelist:
d[i]=1
return d.keys()
def _merge_dicts_nicely(dict1, dict2):
""" make all dict values into lists
into one dict """
new = {}
for k,v in (dict1.items()+dict2.items()):
if new.has_key(k):
# that we haven't seen before
if type(v)==type([]):
new[k].extend(v)
else:
new[k].append(v)
else:
if type(v)==type([]):
new[k] = v
else:
new[k] = [v]
for k,v in new.items():
thatlist = _uniqify(v)
thatlist.sort()
new[k] = thatlist
return new
def _find_issuetrackers(context):
issuetrackers = []
for object in context.objectValues():
if object.meta_type == ISSUETRACKER_METATYPE:
issuetrackers.append(object)
elif object.isPrincipiaFolderish:
issuetrackers.extend(_find_issuetrackers(object))
return issuetrackers
def manage_getUsersToConvert(self, withpasswords=False):
""" find all the users in the acl_users folder here, and
try to find a suitable name and email address. """
if not 'acl_users' in self.objectIds('User Folder'):
# just double checking that we have a old user folder here
return []
old_user_folder = self.acl_users
old_users = []
issuetrackers = _find_issuetrackers(self)
if self.meta_type == ISSUETRACKER_METATYPE:
if self not in issuetrackers:
issuetrackers.append(self)
acl_cookienames = acl_cookieemails = {}
for issuetracker in issuetrackers:
_cookienames = issuetracker.getACLCookieNames()
if _cookienames:
acl_cookienames = _merge_dicts_nicely(acl_cookienames, _cookienames)
_cookieemails = issuetracker.getACLCookieEmails()
if _cookieemails:
acl_cookieemails = _merge_dicts_nicely(acl_cookieemails, _cookieemails)
for user in old_user_folder.getUsers():
fullname = acl_cookienames.get(str(user), [])
email = acl_cookieemails.get(str(user),[])
if not fullname and email:
_email1 = email[0].split('@')[0]
if len(_email1.split('.'))>1:
fullname = [x.capitalize() \
for x in _email1.split('.')]
fullname = ' '.join(fullname)
elif len(_email1.split('_'))>1:
fullname = [x.capitalize() \
for x in _email1.split('_')]
fullname = ' '.join(fullname)
else:
fullname = str(user).capitalize()
d = {'username':str(user),
'domains':user.domains,
'roles':user.roles,
'fullname':fullname,
'email':email}
if email and email[0] and Utils.ValidEmailAddress(email[0]):
d['invalid_email'] = False
else:
d['invalid_email'] = True
if withpasswords:
d['__'] = user.__
old_users.append(d)
return old_users
#----------------------------------------------------------------------------
class IssueUserFolder(User.UserFolder):
""" user folder for managing IssueUsers """
meta_type = ISSUEUSERFOLDER_METATYPE
## these variables need to be in the new class so they are used in the
## correct context and won't be taken from the base class and consequently
## from the wrong directory
_mainUser = DTMLFile('dtml/mainIssueUser', globals())
_mainUser._setName('_mainUser')
manage = _mainUser
manage_main = _mainUser
_add_User = DTMLFile('dtml/addIssueUser', globals())
_editUser = DTMLFile('dtml/editIssueUser', globals())
_passwordReminder = DTMLFile('dtml/passwordReminder', globals())
_userFolderProperties = DTMLFile('dtml/userFolderProps', globals())
security = ClassSecurityInfo()
def __init__(self, webmaster_email=''):
""" Same as inherited but a possible webmaster_email attribute """
self.webmaster_email = webmaster_email
apply(User.UserFolder.__init__, (self,), {})
def manage_setUserFolderProperties(self, encrypt_passwords=0,
update_passwords=0,
maxlistusers=DEFAULTMAXLISTUSERS,
webmaster_email=None,
REQUEST=None):
""" wrapper on manage_setUserFolderProperties() from base class
that also sets webmaster_email """
if webmaster_email is not None:
self.webmaster_email = webmaster_email.strip()
return User.UserFolder.manage_setUserFolderProperties(self,
encrypt_passwords=encrypt_passwords,
update_passwords=update_passwords,
maxlistusers=maxlistusers,
REQUEST=REQUEST)
def _addUser(self, name, password, confirm, roles, domains, REQUEST=None):
if not name:
return MessageDialog(
title ='Illegal value',
message='A username must be specified',
action ='manage_main')
if not password or not confirm:
if not domains:
return MessageDialog(
title ='Illegal value',
message='Password and confirmation must be specified',
action ='manage_main')
if self.getUser(name) or (self._emergency_user and
name == self._emergency_user.getUserName()):
return MessageDialog(
title ='Illegal value',
message='A user with the specified name already exists',
action ='manage_main')
if (password or confirm) and (password != confirm):
return MessageDialog(
title ='Illegal value',
message='Password and confirmation do not match',
action ='manage_main')
if not roles: roles=[]
if not domains: domains=[]
if domains and not self.domainSpecValidate(domains):
return MessageDialog(
title ='Illegal value',
message='Illegal domain specification',
action ='manage_main')
if not Utils.ValidEmailAddress(REQUEST['email']):
return MessageDialog(
title ='Illegal value',
message='Not a valid email address',
action ='manage_main')
if not REQUEST.get('fullname',''):
REQUEST.set('fullname',name)
self._doAddUser(name, password, roles, domains,
email=REQUEST['email'],
fullname=REQUEST['fullname'],
must_change_password=REQUEST.get('must_change_password', False),
display_format=REQUEST.get('display_format',''),
)
if REQUEST: return self._mainUser(self, REQUEST)
def _changeUser(self,name,password,confirm,roles,domains,REQUEST=None):
if password == 'password' and confirm == 'pconfirm':
# Protocol for editUser.dtml to indicate unchanged password
password = confirm = None
if not name:
return MessageDialog(
title ='Illegal value',
message='A username must be specified',
action ='manage_main')
if password == confirm == '':
if not domains:
return MessageDialog(
title ='Illegal value',
message='Password and confirmation must be specified',
action ='manage_main')
if not self.getUser(name):
return MessageDialog(
title ='Illegal value',
message='Unknown user',
action ='manage_main')
if (password or confirm) and (password != confirm):
return MessageDialog(
title ='Illegal value',
message='Password and confirmation do not match',
action ='manage_main')
if not roles: roles=[]
if not domains: domains=[]
if domains and not self.domainSpecValidate(domains):
return MessageDialog(
title ='Illegal value',
message='Illegal domain specification',
action ='manage_main')
if REQUEST.get('email') and not Utils.ValidEmailAddress(REQUEST['email']):
return MessageDialog(
title ='Illegal value',
message='Not a valid email address',
action ='manage_main')
self._doChangeUser(name, password, roles, domains,
email=REQUEST.get('email'),
fullname=REQUEST.get('fullname'),
must_change_password=REQUEST.get('must_change_password'))
if REQUEST:
return self._mainUser(self, REQUEST)
def _changeUserDetails(self, name, fullname, email, REQUEST=None):
""" Simple method that does what _changeUser() does but without
password and roles """
fullname = fullname.strip()
email = email.strip()
if not fullname:
raise "NoFullname", "Full name must be specified"
if not email:
raise "NoEmail", "Email must be specified"
elif not Utils.ValidEmailAddress(email):
raise "InvalidEmail", "Email not valid email address"
self._doChangeUserDetails(name, fullname, email)
if REQUEST:
return self._mainUser(self, REQUEST)
def _doChangeUserDetails(self, name, fullname, email,
must_change_password=None):
user = self.data[name]
user.fullname = fullname
user.email = email
if must_change_password is not None:
user.must_change_password = must_change_password
self.data[name] = user
def _doAddUser(self, name, password, roles, domains, **kw):
"""Create a new user"""
email=kw['email']
fullname=kw['fullname']
must_change_password=kw.get('must_change_password',False)
display_format = kw.get('display_format','')
if password is not None and self.encrypt_passwords:
password = self._encryptPassword(password)
self.data[name]=IssueUser(name, password, roles, domains,
email, fullname, must_change_password,
display_format)
def _doChangeUser(self, name, password, roles, domains, **kw):
user = self.data[name]
if password is not None:
if self.encrypt_passwords:
password = self._encryptPassword(password)
user.__ = password
user.roles = roles
user.domains = domains
if kw.get('email'):
email=kw['email']
user.email = email
if kw.get('fullname'):
fullname=kw['fullname']
user.fullname = fullname
if kw.get('display_format'):
display_format=kw['display_format']
user.display_format = display_format
user.must_change_password=kw.get('must_change_password', False)
self.data[name]=user
def getIssueTrackerRoot(self):
""" Try to return the IssueTracker instance root or None """
try:
root = self.getRoot() # from aquisition
if root.meta_type == ISSUETRACKER_METATYPE:
return root
else:
return None
except:
# Means it's deploy outside an issuetracker
return None
def getWebmasterEmail(self):
""" return webmaster_email or try to find a IssueTracker instance """
# returns None if not found
issuetrackerroot = self.getIssueTrackerRoot()
if issuetrackerroot:
wherefrom = "Issue Tracker"
email = issuetrackerroot.getSitemasterEmail()
else:
wherefrom = "Issue User Folder"
email = self.webmaster_email
if not Utils.ValidEmailAddress(email):
m = "Webmaster email (%s) taken from %s invalid"
m = m%(email, wherefrom)
raise "InvalidWebmasterEmail", m
def getIssueUserFolderPath(self):
""" return the absolute real path of this object parent """
return '/'.join(self.getPhysicalPath())
security.declareProtected(VMS, 'manage_sendReminder')
def manage_sendReminder(self, name, email_from, email_subject,
remindertext):
""" actually send the password reminder """
try:
user = self.getUser(name)
except:
return MessageDialog(
title ='Illegal value',
message='The specified user does not exist',
action ='manage_main')
issuetrackerroot = self.getIssueTrackerRoot()
if not email_from:
raise "NoEmailFromError", "You must specify a from email address"
elif not self.webmaster_email:
self.webmaster_email = email_from
email_to = user.getEmail()
if not email_to or email_to and not Utils.ValidEmailAddress(email_to):
raise "NoEmailToError", "User does not have a valid email address"
replacement_key = "<password shown here>"
if remindertext.find(replacement_key) == -1:
raise "NoPasswordReplacementError",\
"No place to put the password reminder"
if self.encrypt_passwords:
# generate a new password and save it
password = Utils.getRandomString(length=6, loweronly=1)
user.__ = password
else:
password = user.__
if not email_subject:
email_subject = "Issue Tracker password reminder"
remindertext = remindertext.replace(replacement_key, password)
# send it!
if issuetrackerroot:
# send via the issuetracker
issuetrackerroot.sendEmail(remindertext, email_to, email_from,
email_subject, swallowerrors=False)
else:
body = '\r\n'.join(['From: %s'%email_from, 'To: %s'%email_to,
'Subject: %s'%email_subject, "", remindertext])
# Expect a mailhost object. Note that here we're outside the Issuetracker
try:
mailhost = self.MailHost
except:
try:
mailhost = self.SecureMailHost
except:
try:
mailhost = self.superValues('MailHost')[0]
except IndexError:
raise "NoMailHostError", "No 'MailHost' available to send from"
if hasattr(mailhost, 'secureSend'):
mailhost.secureSend(remindertext, email_to, email_from, email_subject)
else:
mailhost.send(body, email_to, email_from, email_subject)
m = "Password reminder sent to %s" % email_to
return self.manage_main(self, self.REQUEST, manage_tabs_message=m)
security.declareProtected(VMS, 'manage_passwordReminder')
def manage_passwordReminder(self, name):
""" wrap up a template """
try:
user = self.getUser(name)
except:
return MessageDialog(
title ='Illegal value',
message='The specified user does not exist',
action ='manage_main')
issuetrackerroot = self.getIssueTrackerRoot()
if self.webmaster_email:
from_field = self.webmaster_email
elif issuetrackerroot:
from_field = issuetrackerroot.getSitemasterFromField()
else:
from_field = ""
subject = _("Password reminder")
if issuetrackerroot is not None:
subject = "%s: %s" % (issuetrackerroot.getTitle(), subject)
msg = "Dear %(fullname)s,\n\n"
msg += "This is a password reminder for you to use on %(url)s.\n"
if self.encrypt_passwords:
msg += "Since your previous password was encrypted we have had "\
"to recreate a new password for you.\n"
msg += "Your username is still: %(username)s\nand your password is: "\
"<password shown here>"
msg += "\n\n"
msg += "PS. The administrator sending this password reminder will"\
" not able to read your password at any time."
#if issuetrackerroot is not None:
# msg += "Now you can go to %s and log in"%(issuetrackerroot.absolute_url()
d = {'fullname':user.getFullname(),
'username':name}
if issuetrackerroot is not None:
d['url'] = issuetrackerroot.absolute_url()
else:
d['url'] = self.absolute_url().replace('/acl_users','')
msg = msg % d
return self._passwordReminder(self, self.REQUEST, user=user,
username=name,
subject=subject, message=msg, from_field=from_field)
#----------------------------------------------------------------------------
class IssueUser(User.SimpleUser, Persistent):
""" User with additional email property """
misc_properties = {} # backwardcompatability
def __init__(self, name, password, roles, domains,
email, fullname, must_change_password=False,
display_format='', use_accesskeys=False,
remember_savedfilter_persistently=False,
show_nextactions=False):
""" constructor method """
self.name = name
self.__ = password
self.roles = roles
self.domains = domains
self.email = email
self.fullname = fullname
self.must_change_password = must_change_password
self.display_format = display_format
self.use_accesskeys = use_accesskeys
self.remember_savedfilter_persistently = remember_savedfilter_persistently
self.show_nextactions = show_nextactions
self._user_lists = None # For the User page. if None, not set
#self._user_display_format = None
self.misc_properties = {}
def getIssueUserPath(self):
""" return the absolute real path of this object parent """
return '/'.join(self.getPhysicalPath())
def getIssueUserIdentifier(self):
""" return the parents physical path and username """
return self.getIssueUserPath(), self.name
def getIssueUserIdentifierString(self):
""" return getIssueUserIdentifier() as a comma separated
string. """
return ','.join(self.getIssueUserIdentifier())
def getIssueUserIdentifierstring(self):
""" return getIssueUserIdentifier() as one string """
path, name = self.getIssueUserIdentifier()
return "%s,%s"%(path, name)
def getEmail(self):
""" returns the user's email """
return self.email
def getFullname(self):
""" returns the fullname """
return self.fullname
def getUserLists(self):
""" return _user_lists """
if not hasattr(self, '_user_lists'):
self._user_lists = None
return None
return self._user_lists
def setUserLists(self, lists):
""" add these lists """
was = self.getUserLists()
if was is None:
was = []
new = was+lists
self._user_lists = Utils.uniqify(new)
def getDisplayFormat(self):
""" return prefered displayformat """
if hasattr(self, 'display_format'):
return getattr(self, 'display_format')
else:
# old bad code
return getattr(self, '_user_display_format', None)
def setDisplayFormat(self, displayformat):
""" set prefered displayformat """
self.display_format = displayformat
def useAccessKeys(self, default=False):
""" return prefered displayformat """
return getattr(self, 'use_accesskeys', default)
def rememberSavedfilterPersistently(self, default=False):
""" return if last savedfilter id should be remembered persistently
(for more info read rememberSavedfilterPersistently() in IssueTracker.py)
"""
return getattr(self, 'remember_savedfilter_persistently', default)
def showNextActionIssues(self, default=False):
""" return if 'Your next action issues' should be shown on the homepage
"""
return getattr(self, 'show_nextactions', default)
def useIssueNotes(self, default=False):
"""return if 'Use Issue notes' should be shown on the homepage
"""
return getattr(self, 'use_issuenotes', default)
def setRememberSavedfilterPersistently(self, toggle):
""" set to saved last savedfilter id persistently or not """
self.remember_savedfilter_persistently = not not toggle
def setUseNextActionIssues(self, toggle):
""" set to saved last savedfilter id persistently or not """
self.show_nextactions = not not toggle
def setUseIssueNotes(self, toggle):
"""enable issue notes"""
self.use_issuenotes = not not toggle
def setAccessKeys(self, toggle):
""" set prefered displayformat """
self.use_accesskeys = not not toggle # makes sure it's bool type
def getMiscProperty(self, key, default=None):
""" return from misc_properties """
return self.misc_properties.get(key, default)
def hasMiscProperty(self, key):
""" do we have it in misc_properties """
return self.misc_properties.has_key(key)
#if not hasattr(self, 'misc_properties'):
# return False
#else:
def setMiscProperty(self, key, value):
""" set in misc_properties dict """
was = self.misc_properties
was[key] = value
self.misc_properties = was
def debugInfo(self):
""" return the misc_properties """
if DEBUG: # from Constants
out = []
out.append("misc_properties:%s" % self.misc_properties)
out.append("must_change_password:%s" % self.must_change_password)
out.append("display_format:%s" % self.display_format)
out.append("use_accesskeys:%s" % getattr(self, 'use_accesskeys', False))
out.append("remember_savedfilter_persistently:%s" % getattr(self, 'remember_savedfilter_persistently', False))
out.append("_user_lists:%s" % self._user_lists)
return ', '.join(out)
else:
return "Not in debug mode"
def mustChangePassword(self):
""" return if 'self.must_change_password' is True """
if not hasattr(self, 'must_change_password'):
self.must_change_password = False # default
return self.must_change_password
def _unmust_mustChangePassword(self):
""" toggle the boolean value """
newvalue = False
self.must_change_password = newvalue
def sendPasswordReminder(self):
""" will send the password in an email """
raise "DeprecatedError"
if self.encrypt_passwords:
m = "Password reminders disabled since passwords are encrypted"
raise "PasswordsEncrypted", m
S = "Issue User Password Reminder"
M = "Dear %s,\nYour password is: "%self.getFullname()
M += self.__
M += '\n'
F = self.getWebmasterEmail()
T = self.getEmail()
# Find the nearest MailHost object
mailhost = self.MailHost
mailhost.send(M, T, F, S)
page = self.manage_main
m = "Email password reminder sent to %s"%T
return page(self, self.REQUEST, manage_tabs_message=m)
|