4 import urllib.request, urllib.parse, urllib.error
6 from PLC.Logger import logger
7 from PLC.Faults import *
8 from PLC.Method import Method
9 from PLC.Parameter import Parameter, Mixed
10 from PLC.Persons import Person, Persons
11 from PLC.Messages import Message, Messages
12 from PLC.Auth import Auth
13 from PLC.sendmail import sendmail
15 class ResetPassword(Method):
17 If verification_key is not specified, then a new verification_key
18 will be generated and stored with the user's account. The key will
19 be e-mailed to the user in the form of a link to a web page.
21 The web page should verify the key by calling this function again
22 and specifying verification_key. If the key matches what has been
23 stored in the user's account, a new random password will be
26 Returns 1 if verification_key was not specified, or was specified
27 and is valid, faults otherwise.
34 Mixed(Person.fields['person_id'],
35 Person.fields['email']),
36 Person.fields['verification_key'],
37 Person.fields['verification_expires']
40 returns = Parameter(int, '1 if verification_key is valid')
42 def call(self, auth, person_id_or_email, verification_key = None, verification_expires = None):
43 # Get account information
44 # we need to search in local objects only
45 if isinstance (person_id_or_email, str):
46 filter = {'email': person_id_or_email}
48 filter = {'person_id': person_id_or_email}
49 filter['peer_id']=None
50 persons = Persons(self.api, filter)
52 raise PLCInvalidArgument("No such account")
55 if person['peer_id'] is not None:
56 raise PLCInvalidArgument("Not a local account")
58 if not person['enabled']:
59 raise PLCInvalidArgument("Account must be enabled")
61 # Be paranoid and deny password resets for admins
62 if 'admin' in person['roles']:
63 raise PLCInvalidArgument("Cannot reset admin passwords")
65 # Generate 32 random bytes
66 int8s = random.sample(range(0, 256), 32)
67 # Base64 encode their string representation
68 random_key = base64.b64encode(bytes(int8s)).decode()
70 if verification_key is not None:
71 if person['verification_key'] is None or \
72 person['verification_expires'] is None or \
73 person['verification_expires'] < time.time():
74 raise PLCPermissionDenied("Verification key has expired")
75 elif person['verification_key'] != verification_key:
76 raise PLCPermissionDenied("Verification key incorrect")
78 # Reset password to random string
79 person['password'] = random_key
80 person['verification_key'] = None
81 person['verification_expires'] = None
84 message_id = 'Password reset'
86 # Only allow one reset at a time
87 if person['verification_expires'] is not None and \
88 person['verification_expires'] > time.time():
89 raise PLCPermissionDenied("Password reset request already pending")
91 if verification_expires is None:
92 verification_expires = int(time.time() + (24 * 60 * 60))
94 person['verification_key'] = random_key
95 person['verification_expires'] = verification_expires
98 message_id = 'Password reset requested'
100 messages = Messages(self.api, [message_id])
102 # Send password to user
103 message = messages[0]
105 params = {'PLC_NAME': self.api.config.PLC_NAME,
106 'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
107 'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
108 'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT,
109 'person_id': person['person_id'],
110 # Will be used in a URL, so must quote appropriately
111 'verification_key': urllib.parse.quote_plus(random_key),
112 'password': random_key,
113 'email': person['email']}
116 To = ("%s %s" % (person['first_name'], person['last_name']), person['email']),
117 Subject = message['subject'] % params,
118 Body = message['template'] % params)
120 logger.warning("No message template '%s'" % message_id)
123 self.event_objects = {'Person': [person['person_id']]}
124 self.message = message_id