967f21f2fb927837e43b18cdf2e9c5f77a333f47
[plcapi.git] / PLC / Methods / ResetPassword.py
1 import random
2 import base64
3 import time
4 import urllib
5
6 from types import StringTypes
7
8 from PLC.Debug import log
9 from PLC.Faults import *
10 from PLC.Method import Method
11 from PLC.Parameter import Parameter, Mixed
12 from PLC.Persons import Person, Persons
13 from PLC.Messages import Message, Messages
14 from PLC.Auth import Auth
15 from PLC.sendmail import sendmail
16
17 class ResetPassword(Method):
18     """
19     If verification_key is not specified, then a new verification_key
20     will be generated and stored with the user's account. The key will
21     be e-mailed to the user in the form of a link to a web page.
22
23     The web page should verify the key by calling this function again
24     and specifying verification_key. If the key matches what has been
25     stored in the user's account, a new random password will be
26     e-mailed to the user.
27
28     Returns 1 if verification_key was not specified, or was specified
29     and is valid, faults otherwise.
30     """
31
32     roles = ['admin']
33
34     accepts = [
35         Auth(),
36         Mixed(Person.fields['person_id'],
37               Person.fields['email']),
38         Person.fields['verification_key'],
39         Person.fields['verification_expires']
40         ]
41
42     returns = Parameter(int, '1 if verification_key is valid')
43
44     def call(self, auth, person_id_or_email, verification_key = None, verification_expires = None):
45         # Get account information
46         # we need to search in local objects only
47         if isinstance (person_id_or_email,StringTypes):
48             filter={'email':person_id_or_email}
49         else:
50             filter={'person_id':person_id_or_email}
51         filter['peer_id']=None
52         persons = Persons(self.api, filter)
53         if not persons:
54             raise PLCInvalidArgument, "No such account"
55         person = persons[0]
56
57         if person['peer_id'] is not None:
58             raise PLCInvalidArgument, "Not a local account"
59
60         if not person['enabled']:
61             raise PLCInvalidArgument, "Account must be enabled"
62
63         # Be paranoid and deny password resets for admins
64         if 'admin' in person['roles']:
65             raise PLCInvalidArgument, "Cannot reset admin passwords"
66
67         # Generate 32 random bytes
68         bytes = random.sample(xrange(0, 256), 32)
69         # Base64 encode their string representation
70         random_key = base64.b64encode("".join(map(chr, bytes)))
71
72         if verification_key is not None:
73             if person['verification_key'] is None or \
74                person['verification_expires'] is None or \
75                person['verification_expires'] < time.time():
76                 raise PLCPermissionDenied, "Verification key has expired"
77             elif person['verification_key'] != verification_key:
78                 raise PLCPermissionDenied, "Verification key incorrect"
79             else:
80                 # Reset password to random string
81                 person['password'] = random_key
82                 person['verification_key'] = None
83                 person['verification_expires'] = None
84                 person.sync()
85
86                 message_id = 'Password reset'
87         else:
88             # Only allow one reset at a time
89             if person['verification_expires'] is not None and \
90                person['verification_expires'] > time.time():
91                 raise PLCPermissionDenied, "Password reset request already pending"
92
93             if verification_expires is None:
94                 verification_expires = int(time.time() + (24 * 60 * 60))
95
96             person['verification_key'] = random_key
97             person['verification_expires'] = verification_expires
98             person.sync()
99
100             message_id = 'Password reset requested'
101
102         messages = Messages(self.api, [message_id])
103         if messages:
104             # Send password to user
105             message = messages[0]
106
107             params = {'PLC_NAME': self.api.config.PLC_NAME,
108                       'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
109                       'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
110                       'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT,
111                       'person_id': person['person_id'],
112                       # Will be used in a URL, so must quote appropriately
113                       'verification_key': urllib.quote_plus(random_key),
114                       'password': random_key,
115                       'email': person['email']}
116
117             sendmail(self.api,
118                      To = ("%s %s" % (person['first_name'], person['last_name']), person['email']),
119                      Subject = message['subject'] % params,
120                      Body = message['template'] % params)
121         else:
122             print >> log, "Warning: No message template '%s'" % message_id
123
124         # Logging variables
125         self.event_objects = {'Person': [person['person_id']]}
126         self.message = message_id
127
128         return 1