Skip to content
Browse files

Fix Python code so it actually works.

There was a bug in the Python code where it was stripping off the first 16
bytes of data.  This caused json decode errors on the desk.com side.  That bug
is now fixed.

Also, remove the dependency of the isoformat library and reformat the code,
removing the hard tabs.
  • Loading branch information...
1 parent 9807286 commit 0f06b66df2a6f1cc70e1a016919f707c9cdf7069 @brianz committed Feb 20, 2014
Showing with 84 additions and 54 deletions.
  1. +84 −54 python.py
View
138 python.py
@@ -1,68 +1,98 @@
-# You will need Python 2.6 or higher, and the following Python modules:
-# isodate
-# pycrypto
-
-import json
+#
+# This is almost identical to the code here:
+# https://github.com/assistly/multipass-examples/blob/master/python.py
+#
+# Note that the code above does not work with the default Private Access
+# settings in your desk.com admin panel.
+#
+# The only other change is that this code doesn't rely on the isodate module.
+# You *will* need to install the pycryto library.
+#
import hashlib
+import hmac
+import json
import random
+import urllib
+
from datetime import datetime, timedelta
from base64 import b64encode
-
-from isodate import datetime_isoformat
from Crypto.Cipher import AES
-import hmac
-import hashlib
-import urllib
-ACCOUNT_KEY = 'YOUR SITE KEY'
-API_KEY = 'YOUR MULTIPASS API KEY'
+
+ACCOUNT_KEY = 'YOUR_SITE_SUBDOMAIN'
+API_KEY = 'YOUR_MULTIPASS_API_KEY'
+
def multipass_string(user_id, user_name, user_email):
- # Create the JSON for the multipass.
- multipass_json = json.dumps({
- 'uid': user_id,
- 'expires': datetime_isoformat(
- datetime.utcnow() + timedelta(minutes=2)),
- 'customer_email': user_email,
- 'customer_name': user_name
- })
-
- # Create the 16 byte salted hash.
- salt = API_KEY + ACCOUNT_KEY
- salted_hash = hashlib.sha1(salt).digest()[:16]
-
- # Seed the random number generator with the current time, then create a
- # random Initialisation Vector (IV).
- random.seed()
- iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
-
- # Pad the JSON to a multiple of 16 bytes using PKCS#5 padding.
- padding_length = 16 - len(multipass_json) % 16
- multipass_json += ''.ljust(padding_length, chr(padding_length))
-
- # XOR the first 16-byte block with the IV.
- first_block = ''
- for i in range(16):
- first_block += chr(ord(multipass_json[i]) ^ ord(iv[i]))
- multipass_xored = first_block + multipass_json[16:]
-
- # Encrypt the multipass using AES encryption in CBC mode.
- encryptor = AES.new(salted_hash, AES.MODE_CBC, iv)
- multipass_encrypted = encryptor.encrypt(multipass_xored)
-
- # Encode the encrypted data using Base64 encoding
- return b64encode(multipass_encrypted)
+ # Create the JSON for the multipass.
+ dt = datetime.utcnow() + timedelta(minutes=2)
+ dt = dt.replace(microsecond=0)
+ multipass_json = json.dumps({
+ 'uid': user_id,
+ 'expires': dt.isoformat(),
+ 'customer_email': user_email,
+ 'customer_name': user_name,
+ })
+
+ # Create the 16 byte salted hash.
+ salt = API_KEY + ACCOUNT_KEY
+ salted_hash = hashlib.sha1(salt).digest()[:16]
+
+ # Seed the random number generator with the current time, then create a
+ # random Initialisation Vector (IV).
+ random.seed()
+ iv = ''.join(chr(random.randint(0, 0xFF)) for i in xrange(16))
+
+ # Pad the JSON to a multiple of 16 bytes using PKCS#5 padding.
+ padding_length = 16 - len(multipass_json) % 16
+ multipass_json += ''.ljust(padding_length, chr(padding_length))
+
+ # XOR the first 16-byte block with the IV.
+ first_block = ''
+ for i in xrange(16):
+ first_block += chr(ord(multipass_json[i]) ^ ord(iv[i]))
+ # Note, the original code had a bug here where the first 16 bytes were
+ # stripped off. There is probably a hard dependency on whether the
+ # "Multipass: Random IV" setting is on or off in the desk.com Private
+ # Access admin panel. Assuming that this setting is ON, this code works.
+ multipass_xored = first_block + multipass_json
+
+ # Encrypt the multipass using AES encryption in CBC mode.
+ encryptor = AES.new(salted_hash, AES.MODE_CBC, iv)
+ multipass_encrypted = encryptor.encrypt(multipass_xored)
+
+ # Encode the encrypted data using Base64 encoding
+ return b64encode(multipass_encrypted)
+
def signature_string(multipass):
- signature = hmac.new(API_KEY, multipass, hashlib.sha1).digest()
- return b64encode(signature)
+ signature = hmac.new(API_KEY, multipass, hashlib.sha1).digest()
+ return b64encode(signature)
+
+
+def generate_multipass_and_signature(user):
+ # Pass whatever agruments make sense for the "user" object. In this
+ # example, the user object is a Django User model.
+ multipass = multipass_string(
+ user.id,
+ '%s %s' % (user.first_name, user.last_name),
+ user.email
+ )
+ signature = signature_string(multipass)
+
+ multipass = urllib.quote(multipass, '')
+ signature = urllib.quote(signature, '')
+ return (multipass, signature)
+
if __name__ == '__main__':
- multipass = multipass_string('0123457', 'Jan Anonymous', 'jan@anon.anon')
- signature = signature_string(multipass)
+ class User(object):
+ id = 123456
+ first_name = 'John'
+ last_name = 'Doe'
+ email = 'john@doe.com'
- # URL encode the multipass and signature parameters
- multipass = urllib.quote(multipass, '')
- signature = urllib.quote(signature, '')
+ multipass, signature = generate_multipass_and_signature(User())
- print "Multipass: %s\nSignature: %s" % (multipass, signature)
+ print "Multipass: %s" % (multipass, )
+ print "Signature: %s" % (multipass, signature)

0 comments on commit 0f06b66

Please sign in to comment.
Something went wrong with that request. Please try again.