1
1
from datetime import datetime # generate_token
2
+ import re
2
3
from typing import List , Optional # imports List, Optional type hint
3
4
import calendar # generate_token
4
5
import base64 # generate_token
7
8
import hmac # _sign_string
8
9
import hashlib
9
10
from typing import List
11
+ import uuid
10
12
import requests # create_session, archiving
11
13
import json # archiving
12
14
import platform # user-agent
19
21
20
22
21
23
# compat
22
- from six . moves . urllib .parse import urlencode
24
+ from urllib .parse import urlencode
23
25
from six import text_type , u , b , PY3
24
26
from enum import Enum
25
27
@@ -111,6 +113,11 @@ class ArchiveModes(Enum):
111
113
class Client (object ):
112
114
"""Use this SDK to create tokens and interface with the server-side portion
113
115
of the Opentok API.
116
+
117
+ You can also interact with this client object with Vonage credentials. Instead of passing
118
+ on OpenTok API key and secret, you can pass in a Vonage application ID and private key,
119
+ e.g. api_key=VONAGE_APPLICATION_ID, api_secret=VONAGE_PRIVATE_KEY. You do not need to set the API
120
+ URL differently, the SDK will set this for you.
114
121
"""
115
122
116
123
TOKEN_SENTINEL = "T1=="
@@ -124,11 +131,25 @@ def __init__(
124
131
timeout = None ,
125
132
app_version = None ,
126
133
):
134
+
135
+ if isinstance (api_secret , (str , bytes )) and re .search (
136
+ "[.][a-zA-Z0-9_]+$" , api_secret
137
+ ):
138
+ # We have a private key so we assume we are using Vonage credentials
139
+ self ._using_vonage = True
140
+ self ._api_url = 'https://video.api.vonage.com'
141
+ with open (api_secret , "rb" ) as key_file :
142
+ self .api_secret = key_file .read ()
143
+ else :
144
+ # We are using OpenTok credentials
145
+ self ._using_vonage = False
146
+ self .api_secret = api_secret
147
+ self ._api_url = api_url
148
+
127
149
self .api_key = str (api_key )
128
- self .api_secret = api_secret
129
150
self .timeout = timeout
130
151
self ._proxies = None
131
- self .endpoints = Endpoints (api_url , self .api_key )
152
+ self .endpoints = Endpoints (self . _api_url , self .api_key )
132
153
self ._app_version = __version__ if app_version == None else app_version
133
154
self ._user_agent = (
134
155
f"OpenTok-Python-SDK/{ self .app_version } python/{ platform .python_version ()} "
@@ -306,24 +327,41 @@ def generate_token(
306
327
307
328
if use_jwt :
308
329
payload = {}
309
- payload ['iss' ] = self .api_key
310
- payload ['ist' ] = 'project'
330
+
331
+ payload ['session_id' ] = session_id
332
+ payload ['role' ] = role .value
311
333
payload ['iat' ] = now
312
334
payload ["exp" ] = expire_time
313
- payload ['nonce' ] = random .randint (0 , 999999 )
314
- payload ['role' ] = role .value
315
335
payload ['scope' ] = 'session.connect'
316
- payload [ 'session_id' ] = session_id
336
+
317
337
if initial_layout_class_list :
318
338
payload ['initial_layout_class_list' ] = (
319
339
initial_layout_class_list_serialized
320
340
)
321
341
if data :
322
342
payload ['connection_data' ] = data
323
343
324
- headers = {'alg' : 'HS256' , 'typ' : 'JWT' }
344
+ if not self ._using_vonage :
345
+ payload ['iss' ] = self .api_key
346
+ payload ['ist' ] = 'project'
347
+ payload ['nonce' ] = random .randint (0 , 999999 )
348
+
349
+ headers = {'alg' : 'HS256' , 'typ' : 'JWT' }
325
350
326
- token = encode (payload , self .api_secret , algorithm = "HS256" , headers = headers )
351
+ token = encode (
352
+ payload , self .api_secret , algorithm = "HS256" , headers = headers
353
+ )
354
+ else :
355
+ payload ['application_id' ] = self .api_key
356
+ payload ['jti' ] = str (uuid .uuid4 ())
357
+ payload ['subject' ] = 'video'
358
+ payload ['acl' ] = {'paths' : {'/session/**' : {}}}
359
+
360
+ headers = {'alg' : 'RS256' , 'typ' : 'JWT' }
361
+
362
+ token = encode (
363
+ payload , self .api_secret , algorithm = "RS256" , headers = headers
364
+ )
327
365
328
366
return token
329
367
@@ -500,39 +538,54 @@ def create_session(
500
538
"POST to %r with params %r, headers %r, proxies %r" ,
501
539
self .endpoints .get_session_url (),
502
540
options ,
503
- self .get_headers (),
541
+ self .get_json_headers (),
504
542
self .proxies ,
505
543
)
506
- response = requests .post (
507
- self .endpoints .get_session_url (),
508
- data = options ,
509
- headers = self .get_headers (),
510
- proxies = self .proxies ,
511
- timeout = self .timeout ,
512
- )
544
+ if not self ._using_vonage :
545
+ response = requests .post (
546
+ self .endpoints .get_session_url (),
547
+ data = options ,
548
+ headers = self .get_headers (),
549
+ proxies = self .proxies ,
550
+ timeout = self .timeout ,
551
+ )
552
+ else :
553
+ headers = self .get_headers ()
554
+ response = requests .post (
555
+ self .endpoints .get_session_url (),
556
+ data = options ,
557
+ headers = headers ,
558
+ proxies = self .proxies ,
559
+ timeout = self .timeout ,
560
+ )
513
561
response .encoding = "utf-8"
514
-
515
562
if response .status_code == 403 :
516
563
raise AuthError ("Failed to create session, invalid credentials" )
517
564
if not response .content :
518
565
raise RequestError ()
519
- dom = xmldom .parseString (response .content .decode ("utf-8" ))
520
566
except Exception as e :
521
567
raise RequestError ("Failed to create session: %s" % str (e ))
522
568
523
569
try :
524
- error = dom .getElementsByTagName ("error" )
525
- if error :
526
- error = error [0 ]
527
- raise AuthError (
528
- "Failed to create session (code=%s): %s"
529
- % (
530
- error .attributes ["code" ].value ,
531
- error .firstChild .attributes ["message" ].value ,
570
+ content_type = response .headers ["Content-Type" ]
571
+ # Legacy behaviour
572
+ if content_type != "application/json" :
573
+ dom = xmldom .parseString (response .content .decode ("utf-8" ))
574
+ error = dom .getElementsByTagName ("error" )
575
+ if error :
576
+ error = error [0 ]
577
+ raise AuthError (
578
+ "Failed to create session (code=%s): %s"
579
+ % (
580
+ error .attributes ["code" ].value ,
581
+ error .firstChild .attributes ["message" ].value ,
582
+ )
532
583
)
584
+ session_id = (
585
+ dom .getElementsByTagName ("session_id" )[0 ].childNodes [0 ].nodeValue
533
586
)
534
-
535
- session_id = dom . getElementsByTagName ( "session_id" )[0 ]. childNodes [ 0 ]. nodeValue
587
+ else :
588
+ session_id = response . json ( )[0 ][ "session_id" ]
536
589
return Session (
537
590
self ,
538
591
session_id ,
@@ -546,12 +599,19 @@ def create_session(
546
599
547
600
def get_headers (self ):
548
601
"""For internal use."""
602
+ if not self ._using_vonage :
603
+ return {
604
+ "User-Agent" : "OpenTok-Python-SDK/"
605
+ + self .app_version
606
+ + " python/"
607
+ + platform .python_version (),
608
+ "X-OPENTOK-AUTH" : self ._create_jwt_auth_header (),
609
+ "Accept" : "application/json" ,
610
+ }
549
611
return {
550
- "User-Agent" : "OpenTok-Python-SDK/"
551
- + self .app_version
552
- + " python/"
553
- + platform .python_version (),
554
- "X-OPENTOK-AUTH" : self ._create_jwt_auth_header (),
612
+ "User-Agent" : self .user_agent + " OpenTok-With-Vonage-API-Backend" ,
613
+ "Authorization" : "Bearer " + self ._create_jwt_auth_header (),
614
+ "Accept" : "application/json" ,
555
615
}
556
616
557
617
def headers (self ):
@@ -1859,13 +1919,13 @@ def stop_render(self, render_id):
1859
1919
logger .debug (
1860
1920
"DELETE to %r with headers %r, proxies %r" ,
1861
1921
self .endpoints .get_render_url (render_id = render_id ),
1862
- self .get_headers (),
1922
+ self .get_json_headers (),
1863
1923
self .proxies ,
1864
1924
)
1865
1925
1866
1926
response = requests .delete (
1867
1927
self .endpoints .get_render_url (render_id = render_id ),
1868
- headers = self .get_headers (),
1928
+ headers = self .get_json_headers (),
1869
1929
proxies = self .proxies ,
1870
1930
timeout = self .timeout ,
1871
1931
)
@@ -1896,14 +1956,14 @@ def list_renders(self, offset=0, count=50):
1896
1956
logger .debug (
1897
1957
"GET to %r with headers %r, params %r, proxies %r" ,
1898
1958
self .endpoints .get_render_url (),
1899
- self .get_headers (),
1959
+ self .get_json_headers (),
1900
1960
query_params ,
1901
1961
self .proxies ,
1902
1962
)
1903
1963
1904
1964
response = requests .get (
1905
1965
self .endpoints .get_render_url (),
1906
- headers = self .get_headers (),
1966
+ headers = self .get_json_headers (),
1907
1967
params = query_params ,
1908
1968
proxies = self .proxies ,
1909
1969
timeout = self .timeout ,
@@ -2090,14 +2150,21 @@ def _sign_string(self, string, secret):
2090
2150
def _create_jwt_auth_header (self ):
2091
2151
payload = {
2092
2152
"ist" : "project" ,
2093
- "iss" : self .api_key ,
2094
2153
"iat" : int (time .time ()), # current time in unix time (seconds)
2095
2154
"exp" : int (time .time ())
2096
2155
+ (60 * self ._jwt_livetime ), # 3 minutes in the future (seconds)
2097
- "jti" : "{0}" .format (0 , random .random ()),
2098
2156
}
2099
2157
2100
- return encode (payload , self .api_secret , algorithm = "HS256" )
2158
+ if not self ._using_vonage :
2159
+ payload ["iss" ] = self .api_key
2160
+ payload ["jti" ] = str (random .random ())
2161
+ return encode (payload , self .api_secret , algorithm = "HS256" )
2162
+
2163
+ payload ["application_id" ] = self .api_key
2164
+ payload ["jti" ] = str (uuid .uuid4 ())
2165
+ headers = {"typ" : "JWT" , "alg" : "RS256" }
2166
+
2167
+ return encode (payload , self .api_secret , algorithm = 'RS256' , headers = headers )
2101
2168
2102
2169
def mute_all (
2103
2170
self , session_id : str , excludedStreamIds : Optional [List [str ]]
@@ -2127,7 +2194,7 @@ def mute_all(
2127
2194
options = {"active" : True , "excludedStreams" : []}
2128
2195
2129
2196
response = requests .post (
2130
- url , headers = self .get_headers (), data = json .dumps (options )
2197
+ url , headers = self .get_json_headers (), data = json .dumps (options )
2131
2198
)
2132
2199
2133
2200
if response :
@@ -2164,7 +2231,7 @@ def disable_force_mute(self, session_id: str) -> requests.Response:
2164
2231
url = self .endpoints .get_mute_all_url (session_id )
2165
2232
2166
2233
response = requests .post (
2167
- url , headers = self .get_headers (), data = json .dumps (options )
2234
+ url , headers = self .get_json_headers (), data = json .dumps (options )
2168
2235
)
2169
2236
2170
2237
try :
@@ -2198,7 +2265,7 @@ def mute_stream(self, session_id: str, stream_id: str) -> requests.Response:
2198
2265
if stream_id :
2199
2266
url = self .endpoints .get_stream_url (session_id , stream_id ) + "/mute"
2200
2267
2201
- response = requests .post (url , headers = self .get_headers ())
2268
+ response = requests .post (url , headers = self .get_json_headers ())
2202
2269
2203
2270
if response :
2204
2271
return response
@@ -2315,7 +2382,7 @@ def mute_all(
2315
2382
options = {"active" : True , "excludedStreams" : []}
2316
2383
2317
2384
response = requests .post (
2318
- url , headers = self .get_headers (), data = json .dumps (options )
2385
+ url , headers = self .get_json_headers (), data = json .dumps (options )
2319
2386
)
2320
2387
2321
2388
if response :
@@ -2350,7 +2417,7 @@ def disable_force_mute(self, session_id: str) -> requests.Response:
2350
2417
url = self .endpoints .get_mute_all_url (session_id )
2351
2418
2352
2419
response = requests .post (
2353
- url , headers = self .get_headers (), data = json .dumps (options )
2420
+ url , headers = self .get_json_headers (), data = json .dumps (options )
2354
2421
)
2355
2422
2356
2423
try :
@@ -2382,7 +2449,7 @@ def mute_stream(self, session_id: str, stream_id: str) -> requests.Response:
2382
2449
if stream_id :
2383
2450
url = self .endpoints .get_stream_url (session_id , stream_id ) + "/mute"
2384
2451
2385
- response = requests .post (url , headers = self .get_headers ())
2452
+ response = requests .post (url , headers = self .get_json_headers ())
2386
2453
2387
2454
if response :
2388
2455
return response
0 commit comments