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