Skip to content

Commit 90e3484

Browse files
author
Dave Mun
committed
Initial implementation of REST API JWT header auth.
1 parent 0358a24 commit 90e3484

File tree

5 files changed

+137
-22
lines changed

5 files changed

+137
-22
lines changed

opentok/opentok.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import platform # user-agent
1111
from socket import inet_aton # create_session
1212
import xml.dom.minidom as xmldom # create_session
13+
from jose import jwt # _create_jwt_auth_header
14+
import random # _create_jwt_auth_header
1315

1416
# compat
1517
from six.moves.urllib.parse import urlencode
@@ -273,7 +275,7 @@ def headers(self):
273275
"""For internal use."""
274276
return {
275277
'User-Agent': 'OpenTok-Python-SDK/' + __version__ + ' ' + platform.python_version(),
276-
'X-TB-PARTNER-AUTH': self.api_key + ':' + self.api_secret
278+
'X-TB-OPENTOK-AUTH': self._create_jwt_auth_header(self.api_key, self.api_secret)
277279
}
278280

279281
def archive_headers(self):
@@ -444,3 +446,13 @@ def get_archives(self, offset=None, count=None):
444446

445447
def _sign_string(self, string, secret):
446448
return hmac.new(secret.encode('utf-8'), string.encode('utf-8'), hashlib.sha1).hexdigest()
449+
450+
def _create_jwt_auth_header(self, api_key, api_secret):
451+
payload = {
452+
'ist': 'project',
453+
'iss': api_key,
454+
'exp': int(time.time()) + (60*5), # 5 minutes
455+
'jti': '{:f}'.format(random.random())
456+
}
457+
458+
return jwt.encode(payload, api_secret, algorithm='HS256')

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def find_version(*file_paths):
3131
'requests',
3232
'six',
3333
'pytz',
34+
'python-jose'
3435
]
3536

3637
if sys.version_info[0] < 3 or sys.version_info[1] < 4:

tests/test_archive.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import json
88
import datetime
99
import pytz
10+
from jose import jwt
11+
import time
1012

1113
from opentok import OpenTok, Archive, __version__, OutputModes
1214

@@ -56,7 +58,12 @@ def test_stop_archive(self):
5658

5759
archive.stop()
5860

59-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
61+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
62+
expect(claims[u('iss')]).to.equal(self.api_key)
63+
expect(claims[u('ist')]).to.equal(u('project'))
64+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
65+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
66+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
6067
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
6168
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
6269
expect(archive).to.be.an(Archive)
@@ -91,7 +98,7 @@ def test_delete_archive(self):
9198
u('size'): 0,
9299
u('status'): u('available'),
93100
u('hasAudio'): True,
94-
u('hasVideo'): True,
101+
u('hasVideo'): True,
95102
u('outputMode'): OutputModes.composed.value,
96103
u('url'): None,
97104
})
@@ -101,7 +108,12 @@ def test_delete_archive(self):
101108

102109
archive.delete()
103110

104-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
111+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
112+
expect(claims[u('iss')]).to.equal(self.api_key)
113+
expect(claims[u('ist')]).to.equal(u('project'))
114+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
115+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
116+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
105117
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
106118
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
107119
# TODO: test that the object is invalidated

tests/test_archive_api.py

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import json
88
import datetime
99
import pytz
10+
from jose import jwt
11+
import time
1012

1113
from opentok import OpenTok, Archive, ArchiveList, OutputModes, __version__
1214

@@ -41,7 +43,12 @@ def test_start_archive(self):
4143

4244
archive = self.opentok.start_archive(self.session_id)
4345

44-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
46+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
47+
expect(claims[u('iss')]).to.equal(self.api_key)
48+
expect(claims[u('ist')]).to.equal(u('project'))
49+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
50+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
51+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
4552
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
4653
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
4754
# non-deterministic json encoding. have to decode to test it properly
@@ -92,7 +99,8 @@ def test_start_archive_with_name(self):
9299

93100
archive = self.opentok.start_archive(self.session_id, name=u('ARCHIVE NAME'))
94101

95-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
102+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
103+
96104
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
97105
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
98106
# non-deterministic json encoding. have to decode to test it properly
@@ -141,7 +149,12 @@ def test_start_voice_archive(self):
141149

142150
archive = self.opentok.start_archive(self.session_id, name=u('ARCHIVE NAME'), has_video=False)
143151

144-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
152+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
153+
expect(claims[u('iss')]).to.equal(self.api_key)
154+
expect(claims[u('ist')]).to.equal(u('project'))
155+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
156+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
157+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
145158
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
146159
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
147160
# non-deterministic json encoding. have to decode to test it properly
@@ -192,7 +205,12 @@ def test_start_individual_archive(self):
192205

193206
archive = self.opentok.start_archive(self.session_id, name=u('ARCHIVE NAME'), output_mode=OutputModes.individual)
194207

195-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
208+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
209+
expect(claims[u('iss')]).to.equal(self.api_key)
210+
expect(claims[u('ist')]).to.equal(u('project'))
211+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
212+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
213+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
196214
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
197215
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
198216
# non-deterministic json encoding. have to decode to test it properly
@@ -244,7 +262,12 @@ def test_start_composed_archive(self):
244262

245263
archive = self.opentok.start_archive(self.session_id, name=u('ARCHIVE NAME'), output_mode=OutputModes.composed)
246264

247-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
265+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
266+
expect(claims[u('iss')]).to.equal(self.api_key)
267+
expect(claims[u('ist')]).to.equal(u('project'))
268+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
269+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
270+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
248271
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
249272
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
250273
# non-deterministic json encoding. have to decode to test it properly
@@ -297,7 +320,12 @@ def test_stop_archive(self):
297320

298321
archive = self.opentok.stop_archive(archive_id)
299322

300-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
323+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
324+
expect(claims[u('iss')]).to.equal(self.api_key)
325+
expect(claims[u('ist')]).to.equal(u('project'))
326+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
327+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
328+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
301329
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
302330
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
303331
expect(archive).to.be.an(Archive)
@@ -324,7 +352,12 @@ def test_delete_archive(self):
324352

325353
self.opentok.delete_archive(archive_id)
326354

327-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
355+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
356+
expect(claims[u('iss')]).to.equal(self.api_key)
357+
expect(claims[u('ist')]).to.equal(u('project'))
358+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
359+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
360+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
328361
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
329362
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
330363

@@ -353,7 +386,12 @@ def test_find_archive(self):
353386

354387
archive = self.opentok.get_archive(archive_id)
355388

356-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
389+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
390+
expect(claims[u('iss')]).to.equal(self.api_key)
391+
expect(claims[u('ist')]).to.equal(u('project'))
392+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
393+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
394+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
357395
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
358396
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
359397
expect(archive).to.be.an(Archive)
@@ -468,7 +506,12 @@ def test_find_archives(self):
468506

469507
archive_list = self.opentok.get_archives()
470508

471-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
509+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
510+
expect(claims[u('iss')]).to.equal(self.api_key)
511+
expect(claims[u('ist')]).to.equal(u('project'))
512+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
513+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
514+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
472515
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
473516
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
474517
expect(archive_list).to.be.an(ArchiveList)
@@ -528,7 +571,12 @@ def test_find_archives_with_offset(self):
528571

529572
archive_list = self.opentok.get_archives(offset=3)
530573

531-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
574+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
575+
expect(claims[u('iss')]).to.equal(self.api_key)
576+
expect(claims[u('ist')]).to.equal(u('project'))
577+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
578+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
579+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
532580
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
533581
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
534582
expect(httpretty.last_request()).to.have.property("querystring").being.equal({
@@ -578,7 +626,12 @@ def test_find_archives_with_count(self):
578626

579627
archive_list = self.opentok.get_archives(count=2)
580628

581-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
629+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
630+
expect(claims[u('iss')]).to.equal(self.api_key)
631+
expect(claims[u('ist')]).to.equal(u('project'))
632+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
633+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
634+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
582635
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
583636
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
584637
expect(httpretty.last_request()).to.have.property("querystring").being.equal({
@@ -654,7 +707,12 @@ def test_find_archives_with_offset_and_count(self):
654707

655708
archive_list = self.opentok.get_archives(count=4, offset=2)
656709

657-
expect(httpretty.last_request().headers[u('x-tb-partner-auth')]).to.equal(self.api_key+u(':')+self.api_secret)
710+
claims = jwt.decode(httpretty.last_request().headers[u('x-tb-opentok-auth')], self.api_secret, algorithms=[u('HS256')])
711+
expect(claims[u('iss')]).to.equal(self.api_key)
712+
expect(claims[u('ist')]).to.equal(u('project'))
713+
expect(float(claims[u('exp')])).to.be.greater_than(float(time.time()))
714+
expect(float(claims[u('jti')])).to.be.greater_than_or_equal_to(float(0))
715+
expect(float(claims[u('jti')])).to.be.lower_than(float(1))
658716
expect(httpretty.last_request().headers[u('user-agent')]).to.contain(u('OpenTok-Python-SDK/')+__version__)
659717
expect(httpretty.last_request().headers[u('content-type')]).to.equal(u('application/json'))
660718
expect(httpretty.last_request()).to.have.property("querystring").being.equal({

0 commit comments

Comments
 (0)