Package lamson :: Module confirm
[hide private]
[frames] | no frames]

Source Code for Module lamson.confirm

  1  """ 
  2  Confirmation handling API that helps you get the whole confirm/pending/verify  
  3  process correct.  It doesn't implement any handlers, but what it does do is 
  4  provide the logic for doing the following: 
  5   
  6      * Take an email, put it in a "pending" queue, and then send out a confirm 
  7      email with a strong random id. 
  8      * Store the pending message ID and the random secret someplace for later 
  9      verification. 
 10      * Verify an incoming email against the expected ID, and get back the 
 11      original. 
 12   
 13  You then just work this into your project's state flow, write your own 
 14  templates, and possibly write your own storage. 
 15  """ 
 16   
 17  import uuid 
 18  from lamson import queue, view 
 19  from email.utils import parseaddr 
 20   
21 -class ConfirmationStorage(object):
22 """ 23 This is the basic confirmation storage. For simple testing purposes 24 you can just use the default hash db parameter. If you do a deployment 25 you can probably get away with a shelf hash instead. 26 27 You can write your own version of this and use it. The confirmation engine 28 only cares that it gets something that supports all of these methods. 29 """
30 - def __init__(self, db={}):
31 """ 32 Change the db parameter to a shelf to get persistent storage. 33 """ 34 self.confirmations = db
35
36 - def clear(self):
37 """ 38 Used primarily in testing, this clears out all pending confirmations. 39 """ 40 self.confirmations.clear()
41
42 - def key(self, target, from_address):
43 """ 44 Used internally to construct a string key, if you write 45 your own you don't need this. 46 47 NOTE: To support proper equality and shelve storage, this encodes the 48 key into ASCII. Make a different subclass if you need unicode and your 49 storage supports it. 50 """ 51 key = target + ':' + from_address 52 53 return key.encode('ascii')
54
55 - def get(self, target, from_address):
56 """ 57 Given a target and a from address, this returns a tuple of (expected_secret, pending_message_id). 58 If it doesn't find that target+from_address, then it should return a (None, None) tuple. 59 """ 60 return self.confirmations.get(self.key(target, from_address), (None, None))
61
62 - def delete(self, target, from_address):
63 """ 64 Removes a target+from_address from the storage. 65 """ 66 try: 67 del self.confirmations[self.key(target, from_address)] 68 except KeyError: 69 pass
70
71 - def store(self, target, from_address, expected_secret, pending_message_id):
72 """ 73 Given a target, from_address it will store the expected_secret and pending_message_id 74 of later verification. The target should be a string indicating what is being 75 confirmed. Like "subscribe", "post", etc. 76 77 When implementing your own you should *never* allow more than one target+from_address 78 combination. 79 """ 80 self.confirmations[self.key(target, from_address)] = (expected_secret, 81 pending_message_id)
82
83 -class ConfirmationEngine(object):
84 """ 85 The confirmation engine is what does the work of sending a confirmation, 86 and verifying that it was confirmed properly. In order to use it you 87 have to construct the ConfirmationEngine (usually in config/settings.py) and 88 you write your confirmation message templates for sending. 89 90 The primary methods you use are ConfirmationEngine.send and ConfirmationEngine.verify. 91 """
92 - def __init__(self, pending_queue, storage):
93 """ 94 The pending_queue should be a string with the path to the lamson.queue.Queue 95 that will store pending messages. These messages are the originals the user 96 sent when they tried to confirm. 97 98 Storage should be something that is like ConfirmationStorage so that this 99 can store things for later verification. 100 """ 101 self.pending = queue.Queue(pending_queue) 102 self.storage = storage
103
104 - def get_pending(self, pending_id):
105 """ 106 Returns the pending message for the given ID. 107 """ 108 return self.pending.get(pending_id)
109
110 - def push_pending(self, message):
111 """ 112 Puts a pending message into the pending queue. 113 """ 114 return self.pending.push(message)
115
116 - def delete_pending(self, pending_id):
117 """ 118 Removes the pending message from the pending queue. 119 """ 120 self.pending.remove(pending_id)
121 122
123 - def cancel(self, target, from_address, expect_secret):
124 """ 125 Used to cancel a pending confirmation. 126 """ 127 name, addr = parseaddr(from_address) 128 129 secret, pending_id = self.storage.get(target, addr) 130 131 if secret == expect_secret: 132 self.storage.delete(target, addr) 133 self.delete_pending(pending_id)
134
135 - def make_random_secret(self):
136 """ 137 Generates a random uuid as the secret, in hex form. 138 """ 139 return uuid.uuid4().hex
140
141 - def register(self, target, message):
142 """ 143 Don't call this directly unless you know what you are doing. 144 It does the job of registering the original message and the 145 expected confirmation into the storage. 146 """ 147 from_address = message.route_from 148 149 pending_id = self.push_pending(message) 150 secret = self.make_random_secret() 151 self.storage.store(target, from_address, secret, pending_id) 152 153 return "%s-confirm-%s" % (target, secret)
154
155 - def verify(self, target, from_address, expect_secret):
156 """ 157 Given a target (i.e. "subscribe", "post", etc), a from_address 158 of someone trying to confirm, and the secret they should use, this 159 will try to verify their confirmation. If the verify works then 160 you'll get the original message back to do what you want with. 161 162 If the verification fails then you are given None. 163 164 The message is *not* deleted from the pending queue. You can do 165 that yourself with delete_pending. 166 """ 167 assert expect_secret, "Must give an expected ID number." 168 name, addr = parseaddr(from_address) 169 170 secret, pending_id = self.storage.get(target, addr) 171 172 if secret == expect_secret: 173 self.storage.delete(target, addr) 174 return self.get_pending(pending_id) 175 else: 176 return None
177
178 - def send(self, relay, target, message, template, vars):
179 """ 180 This is the method you should use to send out confirmation messages. 181 You give it the relay, a target (i.e. "subscribe"), the message they 182 sent requesting the confirm, your confirmation template, and any 183 vars that template needs. 184 185 The result of calling this is that the template message gets sent through 186 the relay, the original message is stored in the pending queue, and 187 data is put into the storage for later calls to verify. 188 """ 189 confirm_address = self.register(target, message) 190 vars.update(locals()) 191 msg = view.respond(vars, template, To=message['from'], 192 From="%(confirm_address)s@%(host)s", 193 Subject="Confirmation required") 194 195 msg['Reply-To'] = "%(confirm_address)s@%(host)s" % vars 196 197 relay.deliver(msg)
198
199 - def clear(self):
200 """ 201 Used in testing to make sure there's nothing in the pending 202 queue or storage. 203 """ 204 self.pending.clear() 205 self.storage.clear()
206