Skip to content

Commit 1f232dd

Browse files
sfcbetiucsfdrogojan
sfcbetiuc
authored andcommitted
Added OAuth2 authentication support
Added OAuth2 authentication support
1 parent 58fcfc0 commit 1f232dd

File tree

6 files changed

+150
-22
lines changed

6 files changed

+150
-22
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ objsamples/ET_Client.py
3131
objsamples/config.python
3232
objsamples/*.pyc
3333
config.python
34+
35+
soap_cache_file.json
36+
.idea/

FuelSDK/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '1.1.1'
1+
__version__ = '1.2.0'
22

33
# Runtime patch the suds library
44
from FuelSDK.suds_patch import _PropertyAppender

FuelSDK/client.py

+96-15
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class ET_Client(object):
3434
auth_url = None
3535
soap_endpoint = None
3636
soap_cache_file = "soap_cache_file.json"
37+
use_oAuth2_authentication = None
38+
account_id = None
39+
scope = None
3740

3841
## get_server_wsdl - if True and a newer WSDL is on the server than the local filesystem retrieve it
3942
def __init__(self, get_server_wsdl = False, debug = False, params = None, tokenResponse=None):
@@ -99,8 +102,6 @@ def __init__(self, get_server_wsdl = False, debug = False, params = None, tokenR
99102
self.auth_url = config.get('Web Services', 'authenticationurl')
100103
elif 'FUELSDK_AUTH_URL' in os.environ:
101104
self.auth_url = os.environ['FUELSDK_AUTH_URL']
102-
else:
103-
self.auth_url = 'https://auth.exacttargetapis.com/v1/requestToken?legacy=1'
104105

105106
if params is not None and 'soapendpoint' in params:
106107
self.soap_endpoint = params['soapendpoint']
@@ -120,7 +121,34 @@ def __init__(self, get_server_wsdl = False, debug = False, params = None, tokenR
120121

121122
self.wsdl_file_url = self.load_wsdl(wsdl_server_url, wsdl_file_local_location, get_server_wsdl)
122123

123-
## get the JWT from the params if passed in...or go to the server to get it
124+
if params is not None and "useOAuth2Authentication" in params:
125+
self.use_oAuth2_authentication = params["useOAuth2Authentication"]
126+
elif config.has_option("Auth Service", "useOAuth2Authentication"):
127+
self.use_oAuth2_authentication = config.get("Auth Service", "useOAuth2Authentication")
128+
elif "FUELSDK_USE_OAUTH2" in os.environ:
129+
self.use_oAuth2_authentication = os.environ["FUELSDK_USE_OAUTH2"]
130+
131+
if self.is_none_or_empty_or_blank(self.auth_url) == True:
132+
if self.use_oAuth2_authentication == "True":
133+
raise Exception('authenticationurl (Auth TSE) is mandatory when using OAuth2 authentication')
134+
else:
135+
self.auth_url = 'https://auth.exacttargetapis.com/v1/requestToken?legacy=1'
136+
137+
if params is not None and "accountId" in params:
138+
self.account_id = params["accountId"]
139+
elif config.has_option("Auth Service", "accountId"):
140+
self.account_id = config.get("Auth Service", "accountId")
141+
elif "FUELSDK_ACCOUNT_ID" in os.environ:
142+
self.account_id = os.environ["FUELSDK_ACCOUNT_ID"]
143+
144+
if params is not None and "scope" in params:
145+
self.scope = params["scope"]
146+
elif config.has_option("Auth Service", "scope"):
147+
self.scope = config.get("Auth Service", "scope")
148+
elif "FUELSDK_SCOPE" in os.environ:
149+
self.scope = os.environ["FUELSDK_SCOPE"]
150+
151+
## get the JWT from the params if passed in...or go to the server to get it
124152
if(params is not None and 'jwt' in params):
125153
decodedJWT = jwt.decode(params['jwt'], self.appsignature)
126154
self.authToken = decodedJWT['request']['user']['oauthToken']
@@ -175,26 +203,36 @@ def build_soap_client(self):
175203

176204
self.soap_client = suds.client.Client(self.wsdl_file_url, faults=False, cachingpolicy=1)
177205
self.soap_client.set_options(location=self.soap_endpoint)
178-
self.soap_client.set_options(headers={'user-agent' : 'FuelSDK-Python-v1.1.1'})
179-
180-
element_oAuth = Element('oAuth', ns=('etns', 'http://exacttarget.com'))
181-
element_oAuthToken = Element('oAuthToken').setText(self.internalAuthToken)
182-
element_oAuth.append(element_oAuthToken)
183-
self.soap_client.set_options(soapheaders=(element_oAuth))
206+
self.soap_client.set_options(headers={'user-agent' : 'FuelSDK-Python-v1.2.0'})
184207

185-
security = suds.wsse.Security()
186-
token = suds.wsse.UsernameToken('*', '*')
187-
security.tokens.append(token)
188-
self.soap_client.set_options(wsse=security)
208+
if self.use_oAuth2_authentication == 'True':
209+
element_oAuth = Element('fueloauth', ns=('etns', 'http://exacttarget.com'))
210+
element_oAuth.setText(self.authToken);
211+
self.soap_client.set_options(soapheaders=(element_oAuth))
212+
else:
213+
element_oAuth = Element('oAuth', ns=('etns', 'http://exacttarget.com'))
214+
element_oAuthToken = Element('oAuthToken').setText(self.internalAuthToken)
215+
element_oAuth.append(element_oAuthToken)
216+
self.soap_client.set_options(soapheaders=(element_oAuth))
217+
218+
security = suds.wsse.Security()
219+
token = suds.wsse.UsernameToken('*', '*')
220+
security.tokens.append(token)
221+
self.soap_client.set_options(wsse=security)
189222

190223

191224
def refresh_token(self, force_refresh = False):
192225
"""
193226
Called from many different places right before executing a SOAP call
194227
"""
228+
229+
if self.use_oAuth2_authentication == "True":
230+
self.refresh_token_with_oAuth2(force_refresh)
231+
return
232+
195233
#If we don't already have a token or the token expires within 5 min(300 seconds), get one
196234
if (force_refresh or self.authToken is None or (self.authTokenExpiration is not None and time.time() + 300 > self.authTokenExpiration)):
197-
headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.1.1'}
235+
headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.2.0'}
198236
if (self.authToken is None):
199237
payload = {'clientId' : self.client_id, 'clientSecret' : self.client_secret, 'accessType': 'offline'}
200238
else:
@@ -221,6 +259,44 @@ def refresh_token(self, force_refresh = False):
221259

222260
self.build_soap_client()
223261

262+
def refresh_token_with_oAuth2(self, force_refresh=False):
263+
"""
264+
Called from many different places right before executing a SOAP call
265+
"""
266+
# If we don't already have a token or the token expires within 5 min(300 seconds), get one
267+
if force_refresh or self.authToken is None \
268+
or self.authTokenExpiration is not None and time.time() + 300 > self.authTokenExpiration:
269+
270+
headers = {'content-type': 'application/json',
271+
'user-agent': 'FuelSDK-Python-v1.2.0'}
272+
273+
payload = {'client_id': self.client_id,
274+
'client_secret': self.client_secret,
275+
'grant_type': 'client_credentials'
276+
}
277+
278+
if self.account_id is not None and self.account_id.strip() != '':
279+
payload['account_id'] = self.account_id
280+
if self.scope is not None and self.scope.strip() != '':
281+
payload['scope'] = self.scope
282+
283+
self.auth_url = self.auth_url.strip() + '/v2/token'
284+
285+
r = requests.post(self.auth_url, data=json.dumps(payload), headers=headers)
286+
tokenResponse = r.json()
287+
288+
if 'access_token' not in tokenResponse:
289+
raise Exception('Unable to validate App Keys(ClientID/ClientSecret) provided: ' + repr(r.json()))
290+
291+
self.authToken = tokenResponse['access_token']
292+
self.authTokenExpiration = time.time() + tokenResponse['expires_in']
293+
self.internalAuthToken = tokenResponse['access_token']
294+
self.soap_endpoint = tokenResponse['soap_instance_url'] + 'service.asmx'
295+
self.base_api_url = tokenResponse['rest_instance_url']
296+
297+
self.build_soap_client()
298+
299+
224300
def get_soap_cache_file(self):
225301
json_data = {}
226302
if os.path.isfile(self.soap_cache_file):
@@ -253,7 +329,7 @@ def get_soap_endpoint(self):
253329
"""
254330
try:
255331
r = requests.get(self.base_api_url + '/platform/v1/endpoints/soap', headers={
256-
'user-agent': 'FuelSDK-Python-v1.1.1',
332+
'user-agent': 'FuelSDK-Python-v1.2.0',
257333
'authorization': 'Bearer ' + self.authToken
258334
})
259335

@@ -307,3 +383,8 @@ def CreateDataExtensions(self, dataExtensionDefinitions):
307383
postResponse = newDEs.post()
308384

309385
return postResponse
386+
387+
def is_none_or_empty_or_blank(self, str):
388+
if str and str.strip():
389+
return False
390+
return True

FuelSDK/rest.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def __init__(self, auth_stub, endpoint, qs = None):
331331
fullendpoint += urlSeparator + qStringValue + '=' + str(qs[qStringValue])
332332
urlSeparator = '&'
333333

334-
headers = {'authorization' : 'Bearer ' + auth_stub.authToken, 'user-agent' : 'FuelSDK-Python-v1.1.1'}
334+
headers = {'authorization' : 'Bearer ' + auth_stub.authToken, 'user-agent' : 'FuelSDK-Python-v1.2.0'}
335335
r = requests.get(fullendpoint, headers=headers)
336336

337337

@@ -349,7 +349,7 @@ class ET_PostRest(ET_Constructor):
349349
def __init__(self, auth_stub, endpoint, payload):
350350
auth_stub.refresh_token()
351351

352-
headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.1.1', 'authorization' : 'Bearer ' + auth_stub.authToken}
352+
headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.2.0', 'authorization' : 'Bearer ' + auth_stub.authToken}
353353
r = requests.post(endpoint, data=json.dumps(payload), headers=headers)
354354

355355
obj = super(ET_PostRest, self).__init__(r, True)
@@ -364,7 +364,7 @@ class ET_PatchRest(ET_Constructor):
364364
def __init__(self, auth_stub, endpoint, payload):
365365
auth_stub.refresh_token()
366366

367-
headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.1.1', 'authorization' : 'Bearer ' + auth_stub.authToken}
367+
headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.2.0', 'authorization' : 'Bearer ' + auth_stub.authToken}
368368
r = requests.patch(endpoint , data=json.dumps(payload), headers=headers)
369369

370370
obj = super(ET_PatchRest, self).__init__(r, True)
@@ -379,7 +379,7 @@ class ET_DeleteRest(ET_Constructor):
379379
def __init__(self, auth_stub, endpoint):
380380
auth_stub.refresh_token()
381381

382-
headers = {'authorization' : 'Bearer ' + auth_stub.authToken, 'user-agent' : 'FuelSDK-Python-v1.1.1'}
382+
headers = {'authorization' : 'Bearer ' + auth_stub.authToken, 'user-agent' : 'FuelSDK-Python-v1.2.0'}
383383
r = requests.delete(endpoint, headers=headers)
384384

385385
obj = super(ET_DeleteRest, self).__init__(r, True)

README.md

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,55 @@
1-
# FuelSDK-Python v1.1.1
1+
# FuelSDK-Python v1.2.0
22

33
Salesforce Marketing Cloud Fuel SDK for Python
44

55
## Overview
66

77
The Fuel SDK for Python provides easy access to Salesforce Marketing Cloud's Fuel API Family services, including a collection of REST APIs and a SOAP API. These APIs provide access to Salesforce Marketing Cloud functionality via common collection types such as array/hash.
88

9+
New Features in Version 1.2.0
10+
------------
11+
* Added support for OAuth2 authentication - [More Details](https://developer.salesforce.com/docs/atlas.en-us.mc-app-development.meta/mc-app-development/integration-considerations.htm)
12+
13+
To enable OAuth2 authentication, set `useOAuth2Authentication: True` in the config.python file or pass it in the params argument to the ET_Client constructor.
14+
15+
Sample Config for OAuth2:
16+
17+
```
18+
[Web Services]
19+
appsignature: none
20+
clientid: <CLIENT_ID>
21+
clientsecret: <CLIENT_SECRET>
22+
defaultwsdl: https://webservice.exacttarget.com/etframework.wsdl
23+
authenticationurl: <AUTH TENANT SPECIFIC ENDPOINT>
24+
baseapiurl: <REST TENANT SPECIFIC ENDPOINT>
25+
soapendpoint: <SOAP TENANT SPECIFIC ENDPOINT>
26+
wsdl_file_local_loc: <WSDL_PATH>/ExactTargetWSDL.xml
27+
28+
[Auth Service]
29+
useOAuth2Authentication: True
30+
accountId: <TARGET_ACCOUNT_ID>
31+
scope: <PERMISSION_LIST>
32+
```
33+
34+
Example passing config as a parameter to ET_Client constructor:
35+
36+
```
37+
stubObj = ET_Client.ET_Client(
38+
False, False,
39+
{
40+
'clientid': '<CLIENT_ID>',
41+
'clientsecret': '<CLIENT_SECRET>',
42+
'defaultwsdl': 'https://webservice.exacttarget.com/etframework.wsdl',
43+
'authenticationurl': '<AUTH TENANT SPECIFIC ENDPOINT>',
44+
'baseapiurl': '<REST TENANT SPECIFIC ENDPOINT>',
45+
'soapendpoint': '<SOAP TENANT SPECIFIC ENDPOINT>',
46+
'wsdl_file_local_loc': r'<WSDL_PATH>/ExactTargetWSDL.xml',
47+
'useOAuth2Authentication': 'True',
48+
'accountId': '<TARGET_ACCOUNT_ID>',
49+
'scope': '<PERMISSION_LIST>'
50+
})
51+
```
52+
953
New Features in Version 1.1.1
1054
------------
1155
* Added support for your tenant’s endpoints - [More Details](https://developer.salesforce.com/docs/atlas.en-us.mc-apis.meta/mc-apis/your-subdomain-tenant-specific-endpoints.htm)

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
readme = f.read()
55

66
setup(
7-
version='1.1.1',
7+
version='1.2.0',
88
name='Salesforce-FuelSDK',
99
description='Salesforce Marketing Cloud Fuel SDK for Python',
1010
long_description=readme,

0 commit comments

Comments
 (0)