Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import base64 

2import json 

3import logging 

4from abc import abstractmethod 

5 

6import requests 

7from sdc_etl_libs.api_helpers.API import API 

8from sdc_etl_libs.api_helpers.SDCAPIExceptions import MissingSecretError 

9 

10 

11class OAuthAPI(API): 

12 """ 

13 Abstract class representing OAuth process for Sync APIs. Sync means the API return data as the body of 

14 the response. 

15 TODO: google API related behavior should be removed to a GoogleAPI class. This will lead to regression test of all 

16 already implemented API wrappers. 

17 """ 

18 def __init__(self, 

19 authorization_token_=None, 

20 refresh_token_=None, 

21 access_token_=None, 

22 client_id_=None, 

23 client_secret_=None, 

24 scope_=None, 

25 tenant_id_=None, 

26 credential_type_="aws_secrets", 

27 credential_id_=None, 

28 region_="us-east-2"): 

29 self.authorization_token = authorization_token_ 

30 self.refresh_token = refresh_token_ 

31 self.access_token = access_token_ 

32 self.scope = scope_ 

33 self.region = region_ 

34 self.credentials = self.get_credentials(source_=credential_type_, 

35 aws_secret_id_=credential_id_, 

36 aws_secret_region_=region_) 

37 try: 

38 self.refresh_token = self.credentials[ 

39 'refresh_token'] if refresh_token_ is None else refresh_token_ 

40 except BaseException as e: 

41 logging.error(f"Credentials not found on secretVault") 

42 raise MissingSecretError( 

43 f"No refresh token found on AWS secrets services for the key `{credential_id_}`." 

44 ) 

45 # self.access_token = None 

46 self.client_id = self.credentials.get( 

47 'client_id') if client_id_ is None else client_id_ 

48 self.client_secret = self.credentials.get( 

49 'client_secret') if client_secret_ is None else client_secret_ 

50 self.tenant_id = self.credentials.get( 

51 'tenant_id') if tenant_id_ is None else tenant_id_ 

52 

53 @abstractmethod 

54 def __get_new_refresh_token(self, 

55 authorization_code=None, 

56 client_id=None, 

57 client_secret=None): 

58 """ 

59 Ref: An Introduction to OAuth 2: https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2 

60 Given valid 'authorization code' and 'client credentials' obtained from the User resource manager, 

61 return a new refresh token to be updated on the secret vault. 

62 Usually a new refresh token invalidates the previous one and the authorization code is a one time resource 

63 it is invalidated as soon as it is used to create the refresh code. 

64 This function/method is for internal use only, the fresh token should be updated on secrets vault manually or 

65 automatically through a callback url interface. 

66 Args: 

67 authorization_code (str): code retrieved manually from the User resource manager. 

68 client_id (str): client identification provided by User resource manager. 

69 client_secret (str): client secret provided by User resource manager. 

70 Returns: 

71 refresh_token(str): new refresh toke to be updated on secrets vault to allow the retrieving of new access tokens 

72 """ 

73 raise NotImplementedError( 

74 "Please Implement/override the method sdc_etl_libs.api_helpers.OAuthAPI.__get_new_refresh_token(self, authorization_code=None, client_id=None, client_secret=None)" 

75 ) 

76 

77 # @abstractmethod 

78 def get_new_access_token(self, 

79 client_id=None, 

80 client_secret=None, 

81 refresh_token=None, 

82 access_token_url: str = None): 

83 """ 

84 Given a pre-defined refresh token, return a new access token. 

85 Usually this method is implemented and decorated with a @retry function to handle tokens refresh logic. 

86 Args: 

87 client_id: OAuth clientID 

88 client_secret: OAuth client secret 

89 access_token_url: url to get Access token from 

90 Returns: 

91 access_token: New access token 

92 """ 

93 client_id = self.client_id if client_id is None else client_id 

94 client_secret = self.client_secret if client_secret is None else client_secret 

95 refresh_token = self.refresh_token if refresh_token is None else refresh_token 

96 access_token_url = self.access_token_url if access_token_url is None else access_token_url 

97 params = { 

98 'client_id': client_id, 

99 'client_secret': client_secret, 

100 'grant_type': 'refresh_token', 

101 'refresh_token': refresh_token 

102 } 

103 

104 logging.info(f"GOT NEW ACCESS TOKEN") 

105 response = requests.request("POST", access_token_url, params=params) 

106 self.access_token = json.loads(response.text)['access_token'] 

107 self.headers = {'Authorization': f"Bearer {self.access_token}"} 

108 

109 def get_access_token(self, client_id=None, client_secret=None, access_token_url: str = None): 

110 """Given a client_id and client_secret, return a new access token. 

111 

112 :param client_id: OAuth API client id 

113 :type client_id: str 

114 :param client_secret: OAuth API client secret 

115 :type client_secret: str 

116 :param access_token_url: OAuth API renewal access token url 

117 :type access_token_url: str 

118 :return: Nothing, but sets access token 

119 :rtype: None 

120 """ 

121 

122 client_id = self.client_id if client_id is None else client_id 

123 client_secret = self.client_secret if client_secret is None else client_secret 

124 access_token_url = self.access_token_url if access_token_url is None else access_token_url 

125 

126 authorization = client_id + ':' + client_secret 

127 encoded_auth = str(base64.b64encode(authorization.encode('utf-8')), 'utf-8') 

128 self.headers = {'Content-Type': 'application/x-www-form-urlencoded', 

129 'Authorization': 'Basic ' + encoded_auth} 

130 data = {'grant_type': 'client_credentials'} 

131 if self.scope is not None: 

132 data['scope'] = self.scope 

133 

134 response = requests.post(access_token_url, headers=self.headers, data=data) 

135 logging.info("GOT ACCESS TOKEN") 

136 self.access_token = json.loads(response.text)['access_token'] 

137 self.headers = {'Authorization': f"Bearer {self.access_token}"} 

138 

139 def reset_access_token(self, client_id=None, client_secret=None): 

140 """ 

141 Given valid 'client credentials' obtained from the User resource manager 

142 and stored on the secrets vault and dynamically obtained using the 'refresh_token', 

143 return the new 'access token' and update the current connector instance. 

144 Access tokens are valid for 1 hour and once detected as expired the function `__request_with_retry` will retry 

145 the resource request after try to reset the access token. 

146 """ 

147 self.get_new_access_token(client_id=client_id, 

148 client_secret=client_secret) 

149 

150 @abstractmethod 

151 def request_data(self, data_payload): 

152 """ 

153 Given a query payload informed as input, a data report request is created and a value object with the info 

154 needed to track the report processing is returned. 

155 Args: 

156 data_payload: a json like payload following the api rules. Ref. https://developer.verizonmedia.com/dsp/api/docs/reporting/payloadspec.html 

157 Returns: 

158 submission_status: ReportSubmissionStatus object wrapping the response payload. 

159 """ 

160 raise NotImplementedError( 

161 "Please Implement/override the method sdc_etl_libs.api_helpers.OAuthAPI.request_data(self, data_payload)" 

162 )