replace with single function ResetPassword()
[plcapi.git] / PLC / Methods / ResetPassword.py
1 import random
2 import base64
3 import time
4 import urllib
5
6 from PLC.Debug import log
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
14
15 class ResetPassword(Method):
16     """
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.
20
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
24     e-mailed to the user.
25
26     Returns 1 if verification_key was not specified, or was specified
27     and is valid, faults otherwise.
28     """
29
30     roles = ['admin']
31
32     accepts = [
33         Auth(),
34         Mixed(Person.fields['person_id'],
35               Person.fields['email']),
36         Person.fields['verification_key'],
37         Person.fields['verification_expires']
38         ]
39
40     returns = Parameter(int, '1 if verification_key is valid')
41
42     def call(self, auth, person_id_or_email, verification_key = None, verification_expires = None):
43         # Get account information
44         persons = Persons(self.api, [person_id_or_email])
45         if not persons:
46             raise PLCInvalidArgument, "No such account"
47         person = persons[0]
48
49         if person['peer_id'] is not None:
50             raise PLCInvalidArgument, "Not a local account"
51
52         # Be paranoid and deny password resets for admins
53         if 'admin' in person['roles']:
54             raise PLCInvalidArgument, "Cannot reset admin passwords"
55
56         # Generate 32 random bytes
57         bytes = random.sample(xrange(0, 256), 32)
58         # Base64 encode their string representation
59         random_key = base64.b64encode("".join(map(chr, bytes)))
60
61         if verification_key is not None:
62             if person['verification_key'] is None or \
63                person['verification_expires'] is None or \
64                person['verification_expires'] < time.time():
65                 raise PLCPermissionDenied, "Verification key has expired"
66             elif person['verification_key'] != verification_key:
67                 raise PLCPermissionDenied, "Verification key incorrect"
68             else:
69                 # Reset password to random string
70                 person['password'] = random_key
71                 person['verification_key'] = None
72                 person['verification_expires'] = None
73                 person.sync()
74
75                 message_id = 'Password reset'
76         else:
77             # Only allow one reset at a time
78             if person['verification_expires'] is not None and \
79                person['verification_expires'] > time.time():
80                 raise PLCPermissionDenied, "Password reset request already pending"
81
82             if verification_expires is None:
83                 verification_expires = int(time.time() + (24 * 60 * 60))
84
85             person['verification_key'] = random_key
86             person['verification_expires'] = verification_expires
87             person.sync()
88
89             message_id = 'Password reset requested'
90
91         messages = Messages(self.api, [message_id])
92         if messages:
93             # Send password to user
94             message = messages[0]
95
96             params = {'PLC_NAME': self.api.config.PLC_NAME,
97                       'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
98                       'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
99                       'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_PORT,
100                       'person_id': person['person_id'],
101                       # Will be used in a URL, so must quote appropriately
102                       'verification_key': urllib.quote_plus(random_key),
103                       'password': random_key,
104                       'email': person['email']}
105
106             sendmail(self.api,
107                      To = "%s %s <%s>" % (person['first_name'], person['last_name'], person['email']),
108                      Subject = message['subject'],
109                      Body = message['template'] % params)
110         else:
111             print >> log, "Warning: No message template '%s'" % message-id
112
113         # Logging variables
114         self.object_ids = [person['person_id']]
115         self.message = message_id
116
117         return 1