ringcentral.platform.platform
1import sys 2try: 3 from urllib.parse import urlparse 4except ImportError: 5 from urlparse import urlparse 6from observable import Observable 7from functools import reduce 8from .auth import Auth 9from .events import Events 10from ..core import base64encode 11import warnings 12 13ACCOUNT_ID = '~' 14ACCOUNT_PREFIX = '/account/' 15URL_PREFIX = '/restapi' 16TOKEN_ENDPOINT = '/restapi/oauth/token' 17REVOKE_ENDPOINT = '/restapi/oauth/revoke' 18AUTHORIZE_ENDPOINT = '/restapi/oauth/authorize' 19API_VERSION = 'v1.0' 20ACCESS_TOKEN_TTL = 3600 # 60 minutes 21REFRESH_TOKEN_TTL = 604800 # 1 week 22KNOWN_PREFIXES = [ 23 URL_PREFIX, 24 '/rcvideo', 25 '/video', 26 '/webinar', 27 '/analytics', 28 '/ai', 29 '/team-messaging', 30 '/scim', 31 '/cx/' 32] 33 34 35class Platform(Observable): 36 def __init__(self, client, key='', secret='', server='', name='', version='', redirect_uri='', 37 known_prefixes=None): 38 39 Observable.__init__(self) 40 if(server == None): 41 raise Exception("SDK init error: RINGCENTRAL_SERVER_URL value not found.") 42 if(key == None): 43 raise Exception("SDK init error: RINGCENTRAL_CLIENT_ID value not found.") 44 if(secret == None): 45 raise Exception("SDK init error: RINGCENTRAL_CLIENT_SECRET value not found.") 46 47 self._server = server 48 self._key = key 49 self._name = name if name else 'Unnamed' 50 self._version = version if version else '0.0.0' 51 self._redirect_uri = redirect_uri 52 self._secret = secret 53 self._client = client 54 self._auth = Auth() 55 self._account = ACCOUNT_ID 56 self._known_prefixes = known_prefixes if known_prefixes else KNOWN_PREFIXES 57 self._userAgent = ((self._name + ('/' + self._version if self._version else '') + ' ') if self._name else '') + \ 58 sys.platform + '/VERSION' + ' ' + \ 59 'PYTHON/VERSION ' + \ 60 'RCPYTHONSDK/VERSION' 61 62 def auth(self): 63 return self._auth 64 65 def create_url(self, url, add_server=False, add_method=None, add_token=False): 66 """ 67 Creates a complete URL based on the provided URL and additional parameters. 68 69 Args: 70 url (str): The base URL. 71 add_server (bool): Whether to prepend the server URL if the provided URL doesn't contain 'http://' or 'https://'. 72 add_method (str, optional): The HTTP method to append as a query parameter. 73 add_token (bool): Whether to append the access token as a query parameter. 74 75 Returns: 76 str: The complete URL. 77 78 Note: 79 - If `add_server` is True and the provided URL doesn't start with 'http://' or 'https://', the server URL will be prepended. 80 - If the provided URL doesn't contain known prefixes or 'http://' or 'https://', the URL_PREFIX and API_VERSION will be appended. 81 - If the provided URL contains ACCOUNT_PREFIX followed by ACCOUNT_ID, it will be replaced with ACCOUNT_PREFIX and the account ID associated with the SDK instance. 82 - If `add_method` is provided, it will be appended as a query parameter '_method'. 83 - If `add_token` is True, the access token associated with the SDK instance will be appended as a query parameter 'access_token'. 84 """ 85 built_url = '' 86 has_http = url.startswith('http://') or url.startswith('https://') 87 88 if add_server and not has_http: 89 built_url += self._server 90 91 if not reduce(lambda res, prefix: res if res else url.find(prefix) == 0, self._known_prefixes, False) and not has_http: 92 built_url += URL_PREFIX + '/' + API_VERSION 93 94 if url.find(ACCOUNT_PREFIX) >= 0: 95 built_url = built_url.replace(ACCOUNT_PREFIX + ACCOUNT_ID, ACCOUNT_PREFIX + self._account) 96 97 built_url += url 98 99 if add_method: 100 built_url += ('&' if built_url.find('?') >= 0 else '?') + '_method=' + add_method 101 102 if add_token: 103 built_url += ('&' if built_url.find('?') >= 0 else '?') + 'access_token=' + self._auth.access_token() 104 105 return built_url 106 107 def logged_in(self): 108 """ 109 Checks if the user is currently logged in. 110 111 Returns: 112 bool: True if the user is logged in, False otherwise. 113 114 Note: 115 - This method checks if the access token is valid. 116 - If the access token is not valid, it attempts to refresh it by calling the `refresh` method. 117 - If any exceptions occur during the process, it returns False. 118 """ 119 try: 120 return self._auth.access_token_valid() or self.refresh() 121 except: 122 return False 123 124 def login_url(self, redirect_uri, state='', challenge='', challenge_method='S256'): 125 """ 126 Generates the URL for initiating the login process. 127 128 Args: 129 redirect_uri (str): The URI to which the user will be redirected after authentication. 130 state (str, optional): A value to maintain state between the request and the callback. Default is ''. 131 challenge (str, optional): The code challenge for PKCE (Proof Key for Code Exchange). Default is ''. 132 challenge_method (str, optional): The code challenge method for PKCE. Default is 'S256'. 133 134 Returns: 135 str: The login URL. 136 """ 137 built_url = self.create_url( AUTHORIZE_ENDPOINT, add_server=True ) 138 built_url += '?response_type=code&client_id=' + self._key + '&redirect_uri=' + urllib.parse.quote(redirect_uri) 139 if state: 140 built_url += '&state=' + urllib.parse.quote(state) 141 if challenge: 142 built_url += '&code_challenge=' + urllib.parse.quote(challenge) + '&code_challenge_method=' + challenge_method 143 return built_url 144 145 def login(self, username='', extension='', password='', code='', redirect_uri='', jwt='', verifier=''): 146 """ 147 Logs in the user using various authentication methods. 148 149 Args: 150 username (str, optional): The username for authentication. Required if password is provided. Default is ''. 151 extension (str, optional): The extension associated with the username. Default is ''. 152 password (str, optional): The password for authentication. Required if username is provided. Default is ''. 153 code (str, optional): The authorization code for authentication. Default is ''. 154 redirect_uri (str, optional): The URI to redirect to after authentication. Default is ''. 155 jwt (str, optional): The JWT (JSON Web Token) for authentication. Default is ''. 156 verifier (str, optional): The code verifier for PKCE (Proof Key for Code Exchange). Default is ''. 157 158 Returns: 159 Response: The response object containing authentication data if successful. 160 161 Raises: 162 Exception: If the login attempt fails or invalid parameters are provided. 163 164 Note: 165 - This method supports multiple authentication flows including password-based, authorization code, and JWT. 166 - It checks for the presence of required parameters and raises an exception if necessary. 167 - Deprecation warning is issued for username-password login; recommend using JWT or OAuth instead. 168 - Constructs the appropriate request body based on the provided parameters. 169 - Uses `create_url` to build the token endpoint URL, adding the server URL if required. 170 - Sends the authentication request using `_request_token`. 171 - Triggers the loginSuccess or loginError event based on the outcome of the login attempt. 172 """ 173 try: 174 if not code and not username and not password and not jwt: 175 raise Exception('Either code, or username with password, or jwt has to be provided') 176 if username and password: 177 warnings.warn("username-password login will soon be deprecated. Please use jwt or OAuth instead.") 178 if not code and not jwt: 179 body = { 180 'grant_type': 'password', 181 'username': username, 182 'password': password, 183 'access_token_ttl': ACCESS_TOKEN_TTL, 184 'refresh_token_ttl': REFRESH_TOKEN_TTL 185 } 186 if extension: 187 body['extension'] = extension 188 elif jwt: 189 body = { 190 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 191 'assertion': jwt 192 } 193 else: 194 body = { 195 'grant_type': 'authorization_code', 196 'redirect_uri': redirect_uri if redirect_uri else self._redirect_uri, 197 'code': code 198 } 199 if verifier: 200 body['code_verifier'] = verifier 201 202 built_url = self.create_url( TOKEN_ENDPOINT, add_server=True ) 203 response = self._request_token( built_url, body=body) 204 self._auth.set_data(response.json_dict()) 205 self.trigger(Events.loginSuccess, response) 206 return response 207 except Exception as e: 208 self.trigger(Events.loginError, e) 209 raise e 210 211 def refresh(self): 212 """ 213 Refreshes the authentication tokens. 214 215 Returns: 216 Response: The response object containing refreshed authentication data if successful. 217 218 Raises: 219 Exception: If the refresh token has expired or if any error occurs during the refresh process. 220 221 Note: 222 - This method checks if the refresh token is still valid using `_auth.refresh_token_valid()`. 223 - Constructs the request body with the grant type as 'refresh_token' and includes the refresh token. 224 - Sends the token refresh request using `_request_token` at this '/restapi/oauth/token end point. 225 - Triggers the refreshSuccess or refreshError event based on the outcome of the refresh attempt. 226 """ 227 try: 228 if not self._auth.refresh_token_valid(): 229 raise Exception('Refresh token has expired') 230 response = self._request_token(TOKEN_ENDPOINT, body={ 231 'grant_type': 'refresh_token', 232 'refresh_token': self._auth.refresh_token(), 233 'access_token_ttl': ACCESS_TOKEN_TTL, 234 'refresh_token_ttl': REFRESH_TOKEN_TTL 235 }) 236 self._auth.set_data(response.json_dict()) 237 self.trigger(Events.refreshSuccess, response) 238 return response 239 except Exception as e: 240 self.trigger(Events.refreshError, e) 241 raise e 242 243 def logout(self): 244 """ 245 Logs out the user by revoking the access token. 246 247 Returns: 248 Response: The response object containing logout confirmation if successful. 249 250 Raises: 251 Exception: If any error occurs during the logout process. 252 253 Note: 254 - Constructs the request body with the access token to be revoked. 255 - Sends the token revoke request using `_request_token` at this /restapi/oauth/revoke end point. 256 - Resets the authentication data using `_auth.reset()` upon successful logout. 257 - Triggers the logoutSuccess or logoutError event based on the outcome of the logout attempt. 258 """ 259 try: 260 response = self._request_token(REVOKE_ENDPOINT, body={ 261 'token': self._auth.access_token() 262 }) 263 self._auth.reset() 264 self.trigger(Events.logoutSuccess, response) 265 return response 266 except Exception as e: 267 self.trigger(Events.logoutError, e) 268 raise e 269 270 def inflate_request(self, request, skip_auth_check=False): 271 """ 272 Inflates the provided request object with necessary headers and URL modifications. 273 274 Args: 275 request (Request): The request object to be inflated. 276 skip_auth_check (bool, optional): Whether to skip the authentication check and header addition. Default is False. 277 278 Note: 279 - If `skip_auth_check` is False (default), it ensures authentication by calling `_ensure_authentication` and adds the 'Authorization' header. 280 - Sets the 'User-Agent' and 'X-User-Agent' headers to the value specified in `_userAgent`. 281 - Modifies the request URL using `create_url`, adding the server URL if necessary. 282 """ 283 if not skip_auth_check: 284 self._ensure_authentication() 285 request.headers['Authorization'] = self._auth_header() 286 287 request.headers['User-Agent'] = self._userAgent 288 request.headers['X-User-Agent'] = self._userAgent 289 request.url = self.create_url(request.url, add_server=True) 290 291 return request 292 293 def send_request(self, request, skip_auth_check=False): 294 return self._client.send(self.inflate_request(request, skip_auth_check=skip_auth_check)) 295 296 def get(self, url, query_params=None, headers=None, skip_auth_check=False): 297 request = self._client.create_request('GET', url, query_params=query_params, headers=headers) 298 return self.send_request(request, skip_auth_check=skip_auth_check) 299 300 def post(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 301 request = self._client.create_request('POST', url, query_params=query_params, headers=headers, body=body) 302 return self.send_request(request, skip_auth_check=skip_auth_check) 303 304 def put(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 305 request = self._client.create_request('PUT', url, query_params=query_params, headers=headers, body=body) 306 return self.send_request(request, skip_auth_check=skip_auth_check) 307 308 def patch(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 309 request = self._client.create_request('PATCH', url, query_params=query_params, headers=headers, body=body) 310 return self.send_request(request, skip_auth_check=skip_auth_check) 311 312 def delete(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 313 request = self._client.create_request('DELETE', url, query_params=query_params, headers=headers, body=body) 314 return self.send_request(request, skip_auth_check=skip_auth_check) 315 316 def _request_token(self, path='', body=None): 317 headers = { 318 'Authorization': 'Basic ' + self._api_key(), 319 'Content-Type': 'application/x-www-form-urlencoded' 320 } 321 request = self._client.create_request('POST', path, body=body, headers=headers) 322 return self.send_request(request, skip_auth_check=True) 323 324 def _api_key(self): 325 return base64encode(self._key + ':' + self._secret) 326 327 def _auth_header(self): 328 return self._auth.token_type() + ' ' + self._auth.access_token() 329 330 def _ensure_authentication(self): 331 if not self._auth.access_token_valid(): 332 self.refresh()
36class Platform(Observable): 37 def __init__(self, client, key='', secret='', server='', name='', version='', redirect_uri='', 38 known_prefixes=None): 39 40 Observable.__init__(self) 41 if(server == None): 42 raise Exception("SDK init error: RINGCENTRAL_SERVER_URL value not found.") 43 if(key == None): 44 raise Exception("SDK init error: RINGCENTRAL_CLIENT_ID value not found.") 45 if(secret == None): 46 raise Exception("SDK init error: RINGCENTRAL_CLIENT_SECRET value not found.") 47 48 self._server = server 49 self._key = key 50 self._name = name if name else 'Unnamed' 51 self._version = version if version else '0.0.0' 52 self._redirect_uri = redirect_uri 53 self._secret = secret 54 self._client = client 55 self._auth = Auth() 56 self._account = ACCOUNT_ID 57 self._known_prefixes = known_prefixes if known_prefixes else KNOWN_PREFIXES 58 self._userAgent = ((self._name + ('/' + self._version if self._version else '') + ' ') if self._name else '') + \ 59 sys.platform + '/VERSION' + ' ' + \ 60 'PYTHON/VERSION ' + \ 61 'RCPYTHONSDK/VERSION' 62 63 def auth(self): 64 return self._auth 65 66 def create_url(self, url, add_server=False, add_method=None, add_token=False): 67 """ 68 Creates a complete URL based on the provided URL and additional parameters. 69 70 Args: 71 url (str): The base URL. 72 add_server (bool): Whether to prepend the server URL if the provided URL doesn't contain 'http://' or 'https://'. 73 add_method (str, optional): The HTTP method to append as a query parameter. 74 add_token (bool): Whether to append the access token as a query parameter. 75 76 Returns: 77 str: The complete URL. 78 79 Note: 80 - If `add_server` is True and the provided URL doesn't start with 'http://' or 'https://', the server URL will be prepended. 81 - If the provided URL doesn't contain known prefixes or 'http://' or 'https://', the URL_PREFIX and API_VERSION will be appended. 82 - If the provided URL contains ACCOUNT_PREFIX followed by ACCOUNT_ID, it will be replaced with ACCOUNT_PREFIX and the account ID associated with the SDK instance. 83 - If `add_method` is provided, it will be appended as a query parameter '_method'. 84 - If `add_token` is True, the access token associated with the SDK instance will be appended as a query parameter 'access_token'. 85 """ 86 built_url = '' 87 has_http = url.startswith('http://') or url.startswith('https://') 88 89 if add_server and not has_http: 90 built_url += self._server 91 92 if not reduce(lambda res, prefix: res if res else url.find(prefix) == 0, self._known_prefixes, False) and not has_http: 93 built_url += URL_PREFIX + '/' + API_VERSION 94 95 if url.find(ACCOUNT_PREFIX) >= 0: 96 built_url = built_url.replace(ACCOUNT_PREFIX + ACCOUNT_ID, ACCOUNT_PREFIX + self._account) 97 98 built_url += url 99 100 if add_method: 101 built_url += ('&' if built_url.find('?') >= 0 else '?') + '_method=' + add_method 102 103 if add_token: 104 built_url += ('&' if built_url.find('?') >= 0 else '?') + 'access_token=' + self._auth.access_token() 105 106 return built_url 107 108 def logged_in(self): 109 """ 110 Checks if the user is currently logged in. 111 112 Returns: 113 bool: True if the user is logged in, False otherwise. 114 115 Note: 116 - This method checks if the access token is valid. 117 - If the access token is not valid, it attempts to refresh it by calling the `refresh` method. 118 - If any exceptions occur during the process, it returns False. 119 """ 120 try: 121 return self._auth.access_token_valid() or self.refresh() 122 except: 123 return False 124 125 def login_url(self, redirect_uri, state='', challenge='', challenge_method='S256'): 126 """ 127 Generates the URL for initiating the login process. 128 129 Args: 130 redirect_uri (str): The URI to which the user will be redirected after authentication. 131 state (str, optional): A value to maintain state between the request and the callback. Default is ''. 132 challenge (str, optional): The code challenge for PKCE (Proof Key for Code Exchange). Default is ''. 133 challenge_method (str, optional): The code challenge method for PKCE. Default is 'S256'. 134 135 Returns: 136 str: The login URL. 137 """ 138 built_url = self.create_url( AUTHORIZE_ENDPOINT, add_server=True ) 139 built_url += '?response_type=code&client_id=' + self._key + '&redirect_uri=' + urllib.parse.quote(redirect_uri) 140 if state: 141 built_url += '&state=' + urllib.parse.quote(state) 142 if challenge: 143 built_url += '&code_challenge=' + urllib.parse.quote(challenge) + '&code_challenge_method=' + challenge_method 144 return built_url 145 146 def login(self, username='', extension='', password='', code='', redirect_uri='', jwt='', verifier=''): 147 """ 148 Logs in the user using various authentication methods. 149 150 Args: 151 username (str, optional): The username for authentication. Required if password is provided. Default is ''. 152 extension (str, optional): The extension associated with the username. Default is ''. 153 password (str, optional): The password for authentication. Required if username is provided. Default is ''. 154 code (str, optional): The authorization code for authentication. Default is ''. 155 redirect_uri (str, optional): The URI to redirect to after authentication. Default is ''. 156 jwt (str, optional): The JWT (JSON Web Token) for authentication. Default is ''. 157 verifier (str, optional): The code verifier for PKCE (Proof Key for Code Exchange). Default is ''. 158 159 Returns: 160 Response: The response object containing authentication data if successful. 161 162 Raises: 163 Exception: If the login attempt fails or invalid parameters are provided. 164 165 Note: 166 - This method supports multiple authentication flows including password-based, authorization code, and JWT. 167 - It checks for the presence of required parameters and raises an exception if necessary. 168 - Deprecation warning is issued for username-password login; recommend using JWT or OAuth instead. 169 - Constructs the appropriate request body based on the provided parameters. 170 - Uses `create_url` to build the token endpoint URL, adding the server URL if required. 171 - Sends the authentication request using `_request_token`. 172 - Triggers the loginSuccess or loginError event based on the outcome of the login attempt. 173 """ 174 try: 175 if not code and not username and not password and not jwt: 176 raise Exception('Either code, or username with password, or jwt has to be provided') 177 if username and password: 178 warnings.warn("username-password login will soon be deprecated. Please use jwt or OAuth instead.") 179 if not code and not jwt: 180 body = { 181 'grant_type': 'password', 182 'username': username, 183 'password': password, 184 'access_token_ttl': ACCESS_TOKEN_TTL, 185 'refresh_token_ttl': REFRESH_TOKEN_TTL 186 } 187 if extension: 188 body['extension'] = extension 189 elif jwt: 190 body = { 191 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 192 'assertion': jwt 193 } 194 else: 195 body = { 196 'grant_type': 'authorization_code', 197 'redirect_uri': redirect_uri if redirect_uri else self._redirect_uri, 198 'code': code 199 } 200 if verifier: 201 body['code_verifier'] = verifier 202 203 built_url = self.create_url( TOKEN_ENDPOINT, add_server=True ) 204 response = self._request_token( built_url, body=body) 205 self._auth.set_data(response.json_dict()) 206 self.trigger(Events.loginSuccess, response) 207 return response 208 except Exception as e: 209 self.trigger(Events.loginError, e) 210 raise e 211 212 def refresh(self): 213 """ 214 Refreshes the authentication tokens. 215 216 Returns: 217 Response: The response object containing refreshed authentication data if successful. 218 219 Raises: 220 Exception: If the refresh token has expired or if any error occurs during the refresh process. 221 222 Note: 223 - This method checks if the refresh token is still valid using `_auth.refresh_token_valid()`. 224 - Constructs the request body with the grant type as 'refresh_token' and includes the refresh token. 225 - Sends the token refresh request using `_request_token` at this '/restapi/oauth/token end point. 226 - Triggers the refreshSuccess or refreshError event based on the outcome of the refresh attempt. 227 """ 228 try: 229 if not self._auth.refresh_token_valid(): 230 raise Exception('Refresh token has expired') 231 response = self._request_token(TOKEN_ENDPOINT, body={ 232 'grant_type': 'refresh_token', 233 'refresh_token': self._auth.refresh_token(), 234 'access_token_ttl': ACCESS_TOKEN_TTL, 235 'refresh_token_ttl': REFRESH_TOKEN_TTL 236 }) 237 self._auth.set_data(response.json_dict()) 238 self.trigger(Events.refreshSuccess, response) 239 return response 240 except Exception as e: 241 self.trigger(Events.refreshError, e) 242 raise e 243 244 def logout(self): 245 """ 246 Logs out the user by revoking the access token. 247 248 Returns: 249 Response: The response object containing logout confirmation if successful. 250 251 Raises: 252 Exception: If any error occurs during the logout process. 253 254 Note: 255 - Constructs the request body with the access token to be revoked. 256 - Sends the token revoke request using `_request_token` at this /restapi/oauth/revoke end point. 257 - Resets the authentication data using `_auth.reset()` upon successful logout. 258 - Triggers the logoutSuccess or logoutError event based on the outcome of the logout attempt. 259 """ 260 try: 261 response = self._request_token(REVOKE_ENDPOINT, body={ 262 'token': self._auth.access_token() 263 }) 264 self._auth.reset() 265 self.trigger(Events.logoutSuccess, response) 266 return response 267 except Exception as e: 268 self.trigger(Events.logoutError, e) 269 raise e 270 271 def inflate_request(self, request, skip_auth_check=False): 272 """ 273 Inflates the provided request object with necessary headers and URL modifications. 274 275 Args: 276 request (Request): The request object to be inflated. 277 skip_auth_check (bool, optional): Whether to skip the authentication check and header addition. Default is False. 278 279 Note: 280 - If `skip_auth_check` is False (default), it ensures authentication by calling `_ensure_authentication` and adds the 'Authorization' header. 281 - Sets the 'User-Agent' and 'X-User-Agent' headers to the value specified in `_userAgent`. 282 - Modifies the request URL using `create_url`, adding the server URL if necessary. 283 """ 284 if not skip_auth_check: 285 self._ensure_authentication() 286 request.headers['Authorization'] = self._auth_header() 287 288 request.headers['User-Agent'] = self._userAgent 289 request.headers['X-User-Agent'] = self._userAgent 290 request.url = self.create_url(request.url, add_server=True) 291 292 return request 293 294 def send_request(self, request, skip_auth_check=False): 295 return self._client.send(self.inflate_request(request, skip_auth_check=skip_auth_check)) 296 297 def get(self, url, query_params=None, headers=None, skip_auth_check=False): 298 request = self._client.create_request('GET', url, query_params=query_params, headers=headers) 299 return self.send_request(request, skip_auth_check=skip_auth_check) 300 301 def post(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 302 request = self._client.create_request('POST', url, query_params=query_params, headers=headers, body=body) 303 return self.send_request(request, skip_auth_check=skip_auth_check) 304 305 def put(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 306 request = self._client.create_request('PUT', url, query_params=query_params, headers=headers, body=body) 307 return self.send_request(request, skip_auth_check=skip_auth_check) 308 309 def patch(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 310 request = self._client.create_request('PATCH', url, query_params=query_params, headers=headers, body=body) 311 return self.send_request(request, skip_auth_check=skip_auth_check) 312 313 def delete(self, url, body=None, query_params=None, headers=None, skip_auth_check=False): 314 request = self._client.create_request('DELETE', url, query_params=query_params, headers=headers, body=body) 315 return self.send_request(request, skip_auth_check=skip_auth_check) 316 317 def _request_token(self, path='', body=None): 318 headers = { 319 'Authorization': 'Basic ' + self._api_key(), 320 'Content-Type': 'application/x-www-form-urlencoded' 321 } 322 request = self._client.create_request('POST', path, body=body, headers=headers) 323 return self.send_request(request, skip_auth_check=True) 324 325 def _api_key(self): 326 return base64encode(self._key + ':' + self._secret) 327 328 def _auth_header(self): 329 return self._auth.token_type() + ' ' + self._auth.access_token() 330 331 def _ensure_authentication(self): 332 if not self._auth.access_token_valid(): 333 self.refresh()
Event system for python
37 def __init__(self, client, key='', secret='', server='', name='', version='', redirect_uri='', 38 known_prefixes=None): 39 40 Observable.__init__(self) 41 if(server == None): 42 raise Exception("SDK init error: RINGCENTRAL_SERVER_URL value not found.") 43 if(key == None): 44 raise Exception("SDK init error: RINGCENTRAL_CLIENT_ID value not found.") 45 if(secret == None): 46 raise Exception("SDK init error: RINGCENTRAL_CLIENT_SECRET value not found.") 47 48 self._server = server 49 self._key = key 50 self._name = name if name else 'Unnamed' 51 self._version = version if version else '0.0.0' 52 self._redirect_uri = redirect_uri 53 self._secret = secret 54 self._client = client 55 self._auth = Auth() 56 self._account = ACCOUNT_ID 57 self._known_prefixes = known_prefixes if known_prefixes else KNOWN_PREFIXES 58 self._userAgent = ((self._name + ('/' + self._version if self._version else '') + ' ') if self._name else '') + \ 59 sys.platform + '/VERSION' + ' ' + \ 60 'PYTHON/VERSION ' + \ 61 'RCPYTHONSDK/VERSION'
66 def create_url(self, url, add_server=False, add_method=None, add_token=False): 67 """ 68 Creates a complete URL based on the provided URL and additional parameters. 69 70 Args: 71 url (str): The base URL. 72 add_server (bool): Whether to prepend the server URL if the provided URL doesn't contain 'http://' or 'https://'. 73 add_method (str, optional): The HTTP method to append as a query parameter. 74 add_token (bool): Whether to append the access token as a query parameter. 75 76 Returns: 77 str: The complete URL. 78 79 Note: 80 - If `add_server` is True and the provided URL doesn't start with 'http://' or 'https://', the server URL will be prepended. 81 - If the provided URL doesn't contain known prefixes or 'http://' or 'https://', the URL_PREFIX and API_VERSION will be appended. 82 - If the provided URL contains ACCOUNT_PREFIX followed by ACCOUNT_ID, it will be replaced with ACCOUNT_PREFIX and the account ID associated with the SDK instance. 83 - If `add_method` is provided, it will be appended as a query parameter '_method'. 84 - If `add_token` is True, the access token associated with the SDK instance will be appended as a query parameter 'access_token'. 85 """ 86 built_url = '' 87 has_http = url.startswith('http://') or url.startswith('https://') 88 89 if add_server and not has_http: 90 built_url += self._server 91 92 if not reduce(lambda res, prefix: res if res else url.find(prefix) == 0, self._known_prefixes, False) and not has_http: 93 built_url += URL_PREFIX + '/' + API_VERSION 94 95 if url.find(ACCOUNT_PREFIX) >= 0: 96 built_url = built_url.replace(ACCOUNT_PREFIX + ACCOUNT_ID, ACCOUNT_PREFIX + self._account) 97 98 built_url += url 99 100 if add_method: 101 built_url += ('&' if built_url.find('?') >= 0 else '?') + '_method=' + add_method 102 103 if add_token: 104 built_url += ('&' if built_url.find('?') >= 0 else '?') + 'access_token=' + self._auth.access_token() 105 106 return built_url
Creates a complete URL based on the provided URL and additional parameters.
Args: url (str): The base URL. add_server (bool): Whether to prepend the server URL if the provided URL doesn't contain 'http://' or 'https://'. add_method (str, optional): The HTTP method to append as a query parameter. add_token (bool): Whether to append the access token as a query parameter.
Returns: str: The complete URL.
Note:
- If add_server
is True and the provided URL doesn't start with 'http://' or 'https://', the server URL will be prepended.
- If the provided URL doesn't contain known prefixes or 'http://' or 'https://', the URL_PREFIX and API_VERSION will be appended.
- If the provided URL contains ACCOUNT_PREFIX followed by ACCOUNT_ID, it will be replaced with ACCOUNT_PREFIX and the account ID associated with the SDK instance.
- If add_method
is provided, it will be appended as a query parameter '_method'.
- If add_token
is True, the access token associated with the SDK instance will be appended as a query parameter 'access_token'.
108 def logged_in(self): 109 """ 110 Checks if the user is currently logged in. 111 112 Returns: 113 bool: True if the user is logged in, False otherwise. 114 115 Note: 116 - This method checks if the access token is valid. 117 - If the access token is not valid, it attempts to refresh it by calling the `refresh` method. 118 - If any exceptions occur during the process, it returns False. 119 """ 120 try: 121 return self._auth.access_token_valid() or self.refresh() 122 except: 123 return False
Checks if the user is currently logged in.
Returns: bool: True if the user is logged in, False otherwise.
Note:
- This method checks if the access token is valid.
- If the access token is not valid, it attempts to refresh it by calling the refresh
method.
- If any exceptions occur during the process, it returns False.
125 def login_url(self, redirect_uri, state='', challenge='', challenge_method='S256'): 126 """ 127 Generates the URL for initiating the login process. 128 129 Args: 130 redirect_uri (str): The URI to which the user will be redirected after authentication. 131 state (str, optional): A value to maintain state between the request and the callback. Default is ''. 132 challenge (str, optional): The code challenge for PKCE (Proof Key for Code Exchange). Default is ''. 133 challenge_method (str, optional): The code challenge method for PKCE. Default is 'S256'. 134 135 Returns: 136 str: The login URL. 137 """ 138 built_url = self.create_url( AUTHORIZE_ENDPOINT, add_server=True ) 139 built_url += '?response_type=code&client_id=' + self._key + '&redirect_uri=' + urllib.parse.quote(redirect_uri) 140 if state: 141 built_url += '&state=' + urllib.parse.quote(state) 142 if challenge: 143 built_url += '&code_challenge=' + urllib.parse.quote(challenge) + '&code_challenge_method=' + challenge_method 144 return built_url
Generates the URL for initiating the login process.
Args: redirect_uri (str): The URI to which the user will be redirected after authentication. state (str, optional): A value to maintain state between the request and the callback. Default is ''. challenge (str, optional): The code challenge for PKCE (Proof Key for Code Exchange). Default is ''. challenge_method (str, optional): The code challenge method for PKCE. Default is 'S256'.
Returns: str: The login URL.
146 def login(self, username='', extension='', password='', code='', redirect_uri='', jwt='', verifier=''): 147 """ 148 Logs in the user using various authentication methods. 149 150 Args: 151 username (str, optional): The username for authentication. Required if password is provided. Default is ''. 152 extension (str, optional): The extension associated with the username. Default is ''. 153 password (str, optional): The password for authentication. Required if username is provided. Default is ''. 154 code (str, optional): The authorization code for authentication. Default is ''. 155 redirect_uri (str, optional): The URI to redirect to after authentication. Default is ''. 156 jwt (str, optional): The JWT (JSON Web Token) for authentication. Default is ''. 157 verifier (str, optional): The code verifier for PKCE (Proof Key for Code Exchange). Default is ''. 158 159 Returns: 160 Response: The response object containing authentication data if successful. 161 162 Raises: 163 Exception: If the login attempt fails or invalid parameters are provided. 164 165 Note: 166 - This method supports multiple authentication flows including password-based, authorization code, and JWT. 167 - It checks for the presence of required parameters and raises an exception if necessary. 168 - Deprecation warning is issued for username-password login; recommend using JWT or OAuth instead. 169 - Constructs the appropriate request body based on the provided parameters. 170 - Uses `create_url` to build the token endpoint URL, adding the server URL if required. 171 - Sends the authentication request using `_request_token`. 172 - Triggers the loginSuccess or loginError event based on the outcome of the login attempt. 173 """ 174 try: 175 if not code and not username and not password and not jwt: 176 raise Exception('Either code, or username with password, or jwt has to be provided') 177 if username and password: 178 warnings.warn("username-password login will soon be deprecated. Please use jwt or OAuth instead.") 179 if not code and not jwt: 180 body = { 181 'grant_type': 'password', 182 'username': username, 183 'password': password, 184 'access_token_ttl': ACCESS_TOKEN_TTL, 185 'refresh_token_ttl': REFRESH_TOKEN_TTL 186 } 187 if extension: 188 body['extension'] = extension 189 elif jwt: 190 body = { 191 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 192 'assertion': jwt 193 } 194 else: 195 body = { 196 'grant_type': 'authorization_code', 197 'redirect_uri': redirect_uri if redirect_uri else self._redirect_uri, 198 'code': code 199 } 200 if verifier: 201 body['code_verifier'] = verifier 202 203 built_url = self.create_url( TOKEN_ENDPOINT, add_server=True ) 204 response = self._request_token( built_url, body=body) 205 self._auth.set_data(response.json_dict()) 206 self.trigger(Events.loginSuccess, response) 207 return response 208 except Exception as e: 209 self.trigger(Events.loginError, e) 210 raise e
Logs in the user using various authentication methods.
Args: username (str, optional): The username for authentication. Required if password is provided. Default is ''. extension (str, optional): The extension associated with the username. Default is ''. password (str, optional): The password for authentication. Required if username is provided. Default is ''. code (str, optional): The authorization code for authentication. Default is ''. redirect_uri (str, optional): The URI to redirect to after authentication. Default is ''. jwt (str, optional): The JWT (JSON Web Token) for authentication. Default is ''. verifier (str, optional): The code verifier for PKCE (Proof Key for Code Exchange). Default is ''.
Returns: Response: The response object containing authentication data if successful.
Raises: Exception: If the login attempt fails or invalid parameters are provided.
Note:
- This method supports multiple authentication flows including password-based, authorization code, and JWT.
- It checks for the presence of required parameters and raises an exception if necessary.
- Deprecation warning is issued for username-password login; recommend using JWT or OAuth instead.
- Constructs the appropriate request body based on the provided parameters.
- Uses create_url
to build the token endpoint URL, adding the server URL if required.
- Sends the authentication request using _request_token
.
- Triggers the loginSuccess or loginError event based on the outcome of the login attempt.
212 def refresh(self): 213 """ 214 Refreshes the authentication tokens. 215 216 Returns: 217 Response: The response object containing refreshed authentication data if successful. 218 219 Raises: 220 Exception: If the refresh token has expired or if any error occurs during the refresh process. 221 222 Note: 223 - This method checks if the refresh token is still valid using `_auth.refresh_token_valid()`. 224 - Constructs the request body with the grant type as 'refresh_token' and includes the refresh token. 225 - Sends the token refresh request using `_request_token` at this '/restapi/oauth/token end point. 226 - Triggers the refreshSuccess or refreshError event based on the outcome of the refresh attempt. 227 """ 228 try: 229 if not self._auth.refresh_token_valid(): 230 raise Exception('Refresh token has expired') 231 response = self._request_token(TOKEN_ENDPOINT, body={ 232 'grant_type': 'refresh_token', 233 'refresh_token': self._auth.refresh_token(), 234 'access_token_ttl': ACCESS_TOKEN_TTL, 235 'refresh_token_ttl': REFRESH_TOKEN_TTL 236 }) 237 self._auth.set_data(response.json_dict()) 238 self.trigger(Events.refreshSuccess, response) 239 return response 240 except Exception as e: 241 self.trigger(Events.refreshError, e) 242 raise e
Refreshes the authentication tokens.
Returns: Response: The response object containing refreshed authentication data if successful.
Raises: Exception: If the refresh token has expired or if any error occurs during the refresh process.
Note:
- This method checks if the refresh token is still valid using _auth.refresh_token_valid()
.
- Constructs the request body with the grant type as 'refresh_token' and includes the refresh token.
- Sends the token refresh request using _request_token
at this '/restapi/oauth/token end point.
- Triggers the refreshSuccess or refreshError event based on the outcome of the refresh attempt.
244 def logout(self): 245 """ 246 Logs out the user by revoking the access token. 247 248 Returns: 249 Response: The response object containing logout confirmation if successful. 250 251 Raises: 252 Exception: If any error occurs during the logout process. 253 254 Note: 255 - Constructs the request body with the access token to be revoked. 256 - Sends the token revoke request using `_request_token` at this /restapi/oauth/revoke end point. 257 - Resets the authentication data using `_auth.reset()` upon successful logout. 258 - Triggers the logoutSuccess or logoutError event based on the outcome of the logout attempt. 259 """ 260 try: 261 response = self._request_token(REVOKE_ENDPOINT, body={ 262 'token': self._auth.access_token() 263 }) 264 self._auth.reset() 265 self.trigger(Events.logoutSuccess, response) 266 return response 267 except Exception as e: 268 self.trigger(Events.logoutError, e) 269 raise e
Logs out the user by revoking the access token.
Returns: Response: The response object containing logout confirmation if successful.
Raises: Exception: If any error occurs during the logout process.
Note:
- Constructs the request body with the access token to be revoked.
- Sends the token revoke request using _request_token
at this /restapi/oauth/revoke end point.
- Resets the authentication data using _auth.reset()
upon successful logout.
- Triggers the logoutSuccess or logoutError event based on the outcome of the logout attempt.
271 def inflate_request(self, request, skip_auth_check=False): 272 """ 273 Inflates the provided request object with necessary headers and URL modifications. 274 275 Args: 276 request (Request): The request object to be inflated. 277 skip_auth_check (bool, optional): Whether to skip the authentication check and header addition. Default is False. 278 279 Note: 280 - If `skip_auth_check` is False (default), it ensures authentication by calling `_ensure_authentication` and adds the 'Authorization' header. 281 - Sets the 'User-Agent' and 'X-User-Agent' headers to the value specified in `_userAgent`. 282 - Modifies the request URL using `create_url`, adding the server URL if necessary. 283 """ 284 if not skip_auth_check: 285 self._ensure_authentication() 286 request.headers['Authorization'] = self._auth_header() 287 288 request.headers['User-Agent'] = self._userAgent 289 request.headers['X-User-Agent'] = self._userAgent 290 request.url = self.create_url(request.url, add_server=True) 291 292 return request
Inflates the provided request object with necessary headers and URL modifications.
Args: request (Request): The request object to be inflated. skip_auth_check (bool, optional): Whether to skip the authentication check and header addition. Default is False.
Note:
- If skip_auth_check
is False (default), it ensures authentication by calling _ensure_authentication
and adds the 'Authorization' header.
- Sets the 'User-Agent' and 'X-User-Agent' headers to the value specified in _userAgent
.
- Modifies the request URL using create_url
, adding the server URL if necessary.
Inherited Members
- observable.core.Observable
- events
- on
- off
- once
- trigger