StringTypes has gone
[plcapi.git] / PLC / Methods / ResetPassword.py
1 import random
2 import base64
3 import time
4 import urllib.request, urllib.parse, urllib.error
5
6 from PLC.Logger import logger
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         # we need to search in local objects only
45         if isinstance (person_id_or_email, str):
46             filter = {'email': person_id_or_email}
47         else:
48             filter = {'person_id': person_id_or_email}
49         filter['peer_id']=None
50         persons = Persons(self.api, filter)
51         if not persons:
52             raise PLCInvalidArgument("No such account")
53         person = persons[0]
54
55         if person['peer_id'] is not None:
56             raise PLCInvalidArgument("Not a local account")
57
58         if not person['enabled']:
59             raise PLCInvalidArgument("Account must be enabled")
60
61         # Be paranoid and deny password resets for admins
62         if 'admin' in person['roles']:
63             raise PLCInvalidArgument("Cannot reset admin passwords")
64
65         # Generate 32 random bytes
66         bytes = random.sample(range(0, 256), 32)
67         # Base64 encode their string representation
68         random_key = base64.b64encode("".join(map(chr, bytes)))
69
70         if verification_key is not None:
71             if person['verification_key'] is None or \
72                person['verification_expires'] is None or \
73                person['verification_expires'] < time.time():
74                 raise PLCPermissionDenied("Verification key has expired")
75             elif person['verification_key'] != verification_key:
76                 raise PLCPermissionDenied("Verification key incorrect")
77             else:
78                 # Reset password to random string
79                 person['password'] = random_key
80                 person['verification_key'] = None
81                 person['verification_expires'] = None
82                 person.sync()
83
84                 message_id = 'Password reset'
85         else:
86             # Only allow one reset at a time
87             if person['verification_expires'] is not None and \
88                person['verification_expires'] > time.time():
89                 raise PLCPermissionDenied("Password reset request already pending")
90
91             if verification_expires is None:
92                 verification_expires = int(time.time() + (24 * 60 * 60))
93
94             person['verification_key'] = random_key
95             person['verification_expires'] = verification_expires
96             person.sync()
97
98             message_id = 'Password reset requested'
99
100         messages = Messages(self.api, [message_id])
101         if messages:
102             # Send password to user
103             message = messages[0]
104
105             params = {'PLC_NAME': self.api.config.PLC_NAME,
106                       'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS,
107                       'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST,
108                       'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT,
109                       'person_id': person['person_id'],
110                       # Will be used in a URL, so must quote appropriately
111                       'verification_key': urllib.parse.quote_plus(random_key),
112                       'password': random_key,
113                       'email': person['email']}
114
115             sendmail(self.api,
116                      To = ("%s %s" % (person['first_name'], person['last_name']), person['email']),
117                      Subject = message['subject'] % params,
118                      Body = message['template'] % params)
119         else:
120             logger.warning("No message template '%s'" % message_id)
121
122         # Logging variables
123         self.event_objects = {'Person': [person['person_id']]}
124         self.message = message_id
125
126         return 1