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