11from datetime import datetime # generate_token
2+ import re
23from typing import List , Optional # imports List, Optional type hint
34import calendar # generate_token
45import base64 # generate_token
78import hmac # _sign_string
89import hashlib
910from typing import List
11+ import uuid
1012import requests # create_session, archiving
1113import json # archiving
1214import platform # user-agent
1921
2022
2123# compat
22- from six . moves . urllib .parse import urlencode
24+ from urllib .parse import urlencode
2325from six import text_type , u , b , PY3
2426from enum import Enum
2527
@@ -111,6 +113,11 @@ class ArchiveModes(Enum):
111113class Client (object ):
112114 """Use this SDK to create tokens and interface with the server-side portion
113115 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.
114121 """
115122
116123 TOKEN_SENTINEL = "T1=="
@@ -124,11 +131,25 @@ def __init__(
124131 timeout = None ,
125132 app_version = None ,
126133 ):
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+
127149 self .api_key = str (api_key )
128- self .api_secret = api_secret
129150 self .timeout = timeout
130151 self ._proxies = None
131- self .endpoints = Endpoints (api_url , self .api_key )
152+ self .endpoints = Endpoints (self . _api_url , self .api_key )
132153 self ._app_version = __version__ if app_version == None else app_version
133154 self ._user_agent = (
134155 f"OpenTok-Python-SDK/{ self .app_version } python/{ platform .python_version ()} "
@@ -306,24 +327,41 @@ def generate_token(
306327
307328 if use_jwt :
308329 payload = {}
309- payload ['iss' ] = self .api_key
310- payload ['ist' ] = 'project'
330+
331+ payload ['session_id' ] = session_id
332+ payload ['role' ] = role .value
311333 payload ['iat' ] = now
312334 payload ["exp" ] = expire_time
313- payload ['nonce' ] = random .randint (0 , 999999 )
314- payload ['role' ] = role .value
315335 payload ['scope' ] = 'session.connect'
316- payload [ 'session_id' ] = session_id
336+
317337 if initial_layout_class_list :
318338 payload ['initial_layout_class_list' ] = (
319339 initial_layout_class_list_serialized
320340 )
321341 if data :
322342 payload ['connection_data' ] = data
323343
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' }
325350
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+ )
327365
328366 return token
329367
@@ -500,39 +538,54 @@ def create_session(
500538 "POST to %r with params %r, headers %r, proxies %r" ,
501539 self .endpoints .get_session_url (),
502540 options ,
503- self .get_headers (),
541+ self .get_json_headers (),
504542 self .proxies ,
505543 )
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+ )
513561 response .encoding = "utf-8"
514-
515562 if response .status_code == 403 :
516563 raise AuthError ("Failed to create session, invalid credentials" )
517564 if not response .content :
518565 raise RequestError ()
519- dom = xmldom .parseString (response .content .decode ("utf-8" ))
520566 except Exception as e :
521567 raise RequestError ("Failed to create session: %s" % str (e ))
522568
523569 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+ )
532583 )
584+ session_id = (
585+ dom .getElementsByTagName ("session_id" )[0 ].childNodes [0 ].nodeValue
533586 )
534-
535- session_id = dom . getElementsByTagName ( "session_id" )[0 ]. childNodes [ 0 ]. nodeValue
587+ else :
588+ session_id = response . json ( )[0 ][ "session_id" ]
536589 return Session (
537590 self ,
538591 session_id ,
@@ -546,12 +599,19 @@ def create_session(
546599
547600 def get_headers (self ):
548601 """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+ }
549611 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" ,
555615 }
556616
557617 def headers (self ):
@@ -1859,13 +1919,13 @@ def stop_render(self, render_id):
18591919 logger .debug (
18601920 "DELETE to %r with headers %r, proxies %r" ,
18611921 self .endpoints .get_render_url (render_id = render_id ),
1862- self .get_headers (),
1922+ self .get_json_headers (),
18631923 self .proxies ,
18641924 )
18651925
18661926 response = requests .delete (
18671927 self .endpoints .get_render_url (render_id = render_id ),
1868- headers = self .get_headers (),
1928+ headers = self .get_json_headers (),
18691929 proxies = self .proxies ,
18701930 timeout = self .timeout ,
18711931 )
@@ -1896,14 +1956,14 @@ def list_renders(self, offset=0, count=50):
18961956 logger .debug (
18971957 "GET to %r with headers %r, params %r, proxies %r" ,
18981958 self .endpoints .get_render_url (),
1899- self .get_headers (),
1959+ self .get_json_headers (),
19001960 query_params ,
19011961 self .proxies ,
19021962 )
19031963
19041964 response = requests .get (
19051965 self .endpoints .get_render_url (),
1906- headers = self .get_headers (),
1966+ headers = self .get_json_headers (),
19071967 params = query_params ,
19081968 proxies = self .proxies ,
19091969 timeout = self .timeout ,
@@ -2090,14 +2150,21 @@ def _sign_string(self, string, secret):
20902150 def _create_jwt_auth_header (self ):
20912151 payload = {
20922152 "ist" : "project" ,
2093- "iss" : self .api_key ,
20942153 "iat" : int (time .time ()), # current time in unix time (seconds)
20952154 "exp" : int (time .time ())
20962155 + (60 * self ._jwt_livetime ), # 3 minutes in the future (seconds)
2097- "jti" : "{0}" .format (0 , random .random ()),
20982156 }
20992157
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 )
21012168
21022169 def mute_all (
21032170 self , session_id : str , excludedStreamIds : Optional [List [str ]]
@@ -2127,7 +2194,7 @@ def mute_all(
21272194 options = {"active" : True , "excludedStreams" : []}
21282195
21292196 response = requests .post (
2130- url , headers = self .get_headers (), data = json .dumps (options )
2197+ url , headers = self .get_json_headers (), data = json .dumps (options )
21312198 )
21322199
21332200 if response :
@@ -2164,7 +2231,7 @@ def disable_force_mute(self, session_id: str) -> requests.Response:
21642231 url = self .endpoints .get_mute_all_url (session_id )
21652232
21662233 response = requests .post (
2167- url , headers = self .get_headers (), data = json .dumps (options )
2234+ url , headers = self .get_json_headers (), data = json .dumps (options )
21682235 )
21692236
21702237 try :
@@ -2198,7 +2265,7 @@ def mute_stream(self, session_id: str, stream_id: str) -> requests.Response:
21982265 if stream_id :
21992266 url = self .endpoints .get_stream_url (session_id , stream_id ) + "/mute"
22002267
2201- response = requests .post (url , headers = self .get_headers ())
2268+ response = requests .post (url , headers = self .get_json_headers ())
22022269
22032270 if response :
22042271 return response
@@ -2315,7 +2382,7 @@ def mute_all(
23152382 options = {"active" : True , "excludedStreams" : []}
23162383
23172384 response = requests .post (
2318- url , headers = self .get_headers (), data = json .dumps (options )
2385+ url , headers = self .get_json_headers (), data = json .dumps (options )
23192386 )
23202387
23212388 if response :
@@ -2350,7 +2417,7 @@ def disable_force_mute(self, session_id: str) -> requests.Response:
23502417 url = self .endpoints .get_mute_all_url (session_id )
23512418
23522419 response = requests .post (
2353- url , headers = self .get_headers (), data = json .dumps (options )
2420+ url , headers = self .get_json_headers (), data = json .dumps (options )
23542421 )
23552422
23562423 try :
@@ -2382,7 +2449,7 @@ def mute_stream(self, session_id: str, stream_id: str) -> requests.Response:
23822449 if stream_id :
23832450 url = self .endpoints .get_stream_url (session_id , stream_id ) + "/mute"
23842451
2385- response = requests .post (url , headers = self .get_headers ())
2452+ response = requests .post (url , headers = self .get_json_headers ())
23862453
23872454 if response :
23882455 return response
0 commit comments