import random import base64 import time import urllib from types import StringTypes from PLC.Logger import logger from PLC.Faults import * from PLC.Method import Method from PLC.Parameter import Parameter, Mixed from PLC.Persons import Person, Persons from PLC.Messages import Message, Messages from PLC.Auth import Auth from PLC.sendmail import sendmail class ResetPassword(Method): """ If verification_key is not specified, then a new verification_key will be generated and stored with the user's account. The key will be e-mailed to the user in the form of a link to a web page. The web page should verify the key by calling this function again and specifying verification_key. If the key matches what has been stored in the user's account, a new random password will be e-mailed to the user. Returns 1 if verification_key was not specified, or was specified and is valid, faults otherwise. """ roles = ['admin'] accepts = [ Auth(), Mixed(Person.fields['person_id'], Person.fields['email']), Person.fields['verification_key'], Person.fields['verification_expires'] ] returns = Parameter(int, '1 if verification_key is valid') def call(self, auth, person_id_or_email, verification_key = None, verification_expires = None): # Get account information # we need to search in local objects only if isinstance (person_id_or_email,StringTypes): filter={'email':person_id_or_email} else: filter={'person_id':person_id_or_email} filter['peer_id']=None persons = Persons(self.api, filter) if not persons: raise PLCInvalidArgument, "No such account" person = persons[0] if person['peer_id'] is not None: raise PLCInvalidArgument, "Not a local account" if not person['enabled']: raise PLCInvalidArgument, "Account must be enabled" # Be paranoid and deny password resets for admins if 'admin' in person['roles']: raise PLCInvalidArgument, "Cannot reset admin passwords" # Generate 32 random bytes bytes = random.sample(xrange(0, 256), 32) # Base64 encode their string representation random_key = base64.b64encode("".join(map(chr, bytes))) if verification_key is not None: if person['verification_key'] is None or \ person['verification_expires'] is None or \ person['verification_expires'] < time.time(): raise PLCPermissionDenied, "Verification key has expired" elif person['verification_key'] != verification_key: raise PLCPermissionDenied, "Verification key incorrect" else: # Reset password to random string person['password'] = random_key person['verification_key'] = None person['verification_expires'] = None person.sync() message_id = 'Password reset' else: # Only allow one reset at a time if person['verification_expires'] is not None and \ person['verification_expires'] > time.time(): raise PLCPermissionDenied, "Password reset request already pending" if verification_expires is None: verification_expires = int(time.time() + (24 * 60 * 60)) person['verification_key'] = random_key person['verification_expires'] = verification_expires person.sync() message_id = 'Password reset requested' messages = Messages(self.api, [message_id]) if messages: # Send password to user message = messages[0] params = {'PLC_NAME': self.api.config.PLC_NAME, 'PLC_MAIL_SUPPORT_ADDRESS': self.api.config.PLC_MAIL_SUPPORT_ADDRESS, 'PLC_WWW_HOST': self.api.config.PLC_WWW_HOST, 'PLC_WWW_SSL_PORT': self.api.config.PLC_WWW_SSL_PORT, 'person_id': person['person_id'], # Will be used in a URL, so must quote appropriately 'verification_key': urllib.quote_plus(random_key), 'password': random_key, 'email': person['email']} sendmail(self.api, To = ("%s %s" % (person['first_name'], person['last_name']), person['email']), Subject = message['subject'] % params, Body = message['template'] % params) else: logger.warning("No message template '%s'" % message_id) # Logging variables self.event_objects = {'Person': [person['person_id']]} self.message = message_id return 1