- overloading verification_key for account verification as well (i.e.,
[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         if not person['enabled']:
53             raise PLCInvalidArgument, "Account must be enabled"
54
55         # Be paranoid and deny password resets for admins
56         if 'admin' in person['roles']:
57             raise PLCInvalidArgument, "Cannot reset admin passwords"
58
59         # Generate 32 random bytes
60         bytes = random.sample(xrange(0, 256), 32)
61         # Base64 encode their string representation
62         random_key = base64.b64encode("".join(map(chr, bytes)))
63
64         if verification_key is not None:
65             if person['verification_key'] is None or \
66                person['verification_expires'] is None or \
67                person['verification_expires'] < time.time():
68                 raise PLCPermissionDenied, "Verification key has expired"
69             elif person['verification_key'] != verification_key:
70                 raise PLCPermissionDenied, "Verification key incorrect"
71             else:
72                 # Reset password to random string
73                 person['password'] = random_key
74                 person['verification_key'] = None
75                 person['verification_expires'] = None
76                 person.sync()
77
78                 message_id = 'Password reset'
79         else:
80             # Only allow one reset at a time
81             if person['verification_expires'] is not None and \
82                person['verification_expires'] > time.time():
83                 raise PLCPermissionDenied, "Password reset request already pending"
84
85             if verification_expires is None:
86                 verification_expires = int(time.time() + (24 * 60 * 60))
87
88             person['verification_key'] = random_key
89             person['verification_expires'] = verification_expires
90             person.sync()
91
92             message_id = 'Password reset requested'
93
94         messages = Messages(self.api, [message_id])
95         if messages:
96             # Send password to user
97             message = messages[0]
98
99             params = {'PLC_NAME': self.api.config.PLC_NAME,
100                       'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
101                       'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
102                       'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT,
103                       'person_id': person['person_id'],
104                       # Will be used in a URL, so must quote appropriately
105                       'verification_key': urllib.quote_plus(random_key),
106                       'password': random_key,
107                       'email': person['email']}
108
109             sendmail(self.api,
110                      To = ("%s %s" % (person['first_name'], person['last_name']), person['email']),
111                      Subject = message['subject'] % params,
112                      Body = message['template'] % params)
113         else:
114             print >> log, "Warning: No message template '%s'" % message_id
115
116         # Logging variables
117         self.object_ids = [person['person_id']]
118         self.message = message_id
119
120         return 1