Skip to content

Commit 42e68d3

Browse files
authored
Merge pull request #1 from eyazrgeotab/client-certificate
Client certificate support and example
2 parents 5fa0a7c + afde445 commit 42e68d3

File tree

4 files changed

+50
-9
lines changed

4 files changed

+50
-9
lines changed

mygeotab/api.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
server="my.geotab.com",
4040
timeout=DEFAULT_TIMEOUT,
4141
proxies=None,
42+
cert=None
4243
):
4344
"""Initialize the MyGeotab API object with credentials.
4445
@@ -56,6 +57,8 @@ def __init__(
5657
:type timeout: float or None
5758
:param proxies: The proxies dictionary to apply to the request.
5859
:type proxies: dict or None
60+
:param cert: The path to client certificate. A single path to .pem file or a Tuple (.cer file, .key file).
61+
:type cert: str or Tuple or None
5962
:raise Exception: Raises an Exception if a username, or one of the session_id or password is not provided.
6063
"""
6164
if username is None:
@@ -68,6 +71,7 @@ def __init__(
6871
self.timeout = timeout
6972
self._proxies = proxies
7073
self.__reauthorize_count = 0
74+
self._cert = cert
7175

7276
@property
7377
def _server(self):
@@ -105,7 +109,12 @@ def call(self, method, **parameters):
105109

106110
try:
107111
result = _query(
108-
self._server, method, params, self.timeout, verify_ssl=self._is_verify_ssl, proxies=self._proxies
112+
self._server,
113+
method, params,
114+
self.timeout,
115+
verify_ssl=self._is_verify_ssl,
116+
proxies=self._proxies,
117+
cert=self._cert
109118
)
110119
if result is not None:
111120
self.__reauthorize_count = 0
@@ -219,6 +228,7 @@ def authenticate(self):
219228
self.timeout,
220229
verify_ssl=self._is_verify_ssl,
221230
proxies=self._proxies,
231+
cert=self._cert
222232
)
223233
if result:
224234
if "path" not in result and self.credentials.session_id:
@@ -307,7 +317,7 @@ def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
307317
)
308318

309319

310-
def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=True, proxies=None):
320+
def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=True, proxies=None, cert=None):
311321
"""Formats and performs the query against the API.
312322
313323
:param server: The MyGeotab server.
@@ -322,6 +332,8 @@ def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=True,
322332
:type verify_ssl: bool
323333
:param proxies: The proxies dictionary to apply to the request.
324334
:type proxies: dict or None
335+
:param cert: The path to client certificate. A single path to .pem file or a Tuple (.cer file, .pem file)
336+
:type cert: str or Tuple or None
325337
:raise MyGeotabException: Raises when an exception occurs on the MyGeotab server.
326338
:raise TimeoutException: Raises when the request does not respond after some time.
327339
:raise urllib2.HTTPError: Raises when there is an HTTP status code that indicates failure.
@@ -332,6 +344,8 @@ def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=True,
332344
headers = get_headers()
333345
with requests.Session() as session:
334346
session.mount("https://", GeotabHTTPAdapter())
347+
if cert:
348+
session.cert = cert
335349
try:
336350
response = session.post(
337351
api_endpoint,

mygeotab/py3/api_async.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(
3535
server="my.geotab.com",
3636
timeout=DEFAULT_TIMEOUT,
3737
proxies=None,
38+
cert=None
3839
):
3940
"""
4041
Initialize the asynchronous MyGeotab API object with credentials.
@@ -46,9 +47,10 @@ def __init__(
4647
:param server: The server ie. my23.geotab.com. Optional as this usually gets resolved upon authentication.
4748
:param timeout: The timeout to make the call, in seconds. By default, this is 300 seconds (or 5 minutes).
4849
:param proxies: The proxies dictionary to apply to the request.
50+
:param cert: The path to client certificate. A single path to .pem file or a Tuple (.cer file, .pem file)
4951
:raise Exception: Raises an Exception if a username, or one of the session_id or password is not provided.
5052
"""
51-
super().__init__(username, password, database, session_id, server, timeout, proxies=proxies)
53+
super().__init__(username, password, database, session_id, server, timeout, proxies=proxies, cert=cert)
5254

5355
async def call_async(self, method, **parameters):
5456
"""Makes an async call to the API.
@@ -68,7 +70,7 @@ async def call_async(self, method, **parameters):
6870
params["credentials"] = self.credentials.get_param()
6971

7072
try:
71-
result = await _query(self._server, method, params, verify_ssl=self._is_verify_ssl)
73+
result = await _query(self._server, method, params, verify_ssl=self._is_verify_ssl, cert=self._cert)
7274
if result is not None:
7375
self.__reauthorize_count = 0
7476
return result
@@ -181,14 +183,15 @@ async def server_call_async(method, server, timeout=DEFAULT_TIMEOUT, verify_ssl=
181183
return await _query(server, method, parameters, timeout=timeout, verify_ssl=verify_ssl)
182184

183185

184-
async def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=True):
186+
async def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl=True, cert=None):
185187
"""Formats and performs the asynchronous query against the API
186188
187189
:param server: The server to query.
188190
:param method: The method name.
189191
:param parameters: A dict of parameters to send
190192
:param timeout: The timeout to make the call, in seconds. By default, this is 300 seconds (or 5 minutes).
191193
:param verify_ssl: Whether or not to verify SSL connections
194+
:param cert: The path to client certificate. A single path to .pem file or a Tuple (.cer file, .pem file)
192195
:return: The JSON-decoded result from the server
193196
:raise MyGeotabException: Raises when an exception occurs on the MyGeotab server
194197
:raise TimeoutException: Raises when the request does not respond after some time.
@@ -197,7 +200,18 @@ async def _query(server, method, parameters, timeout=DEFAULT_TIMEOUT, verify_ssl
197200
api_endpoint = api.get_api_url(server)
198201
params = dict(id=-1, method=method, params=parameters)
199202
headers = get_headers()
200-
conn = aiohttp.TCPConnector(ssl=ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) if verify_ssl else False)
203+
204+
ssl_context = False
205+
if verify_ssl or cert:
206+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
207+
if cert:
208+
if isinstance(cert, str):
209+
ssl_context.load_cert_chain(cert)
210+
elif isinstance(cert, tuple):
211+
cer, key = cert
212+
ssl_context.load_cert_chain(cer, key)
213+
214+
conn = aiohttp.TCPConnector(ssl=ssl_context)
201215
try:
202216
async with aiohttp.ClientSession(connector=conn) as session:
203217
response = await session.post(

tests/test_api_async.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from mygeotab import API, server_call_async
1010
from mygeotab.exceptions import MyGeotabException, TimeoutException
11-
from tests.test_api_call import SERVER, USERNAME, PASSWORD, DATABASE, TRAILER_NAME
11+
from tests.test_api_call import SERVER, USERNAME, PASSWORD, DATABASE, CER_FILE, KEY_FILE, PEM_FILE, TRAILER_NAME
1212

1313
ASYNC_TRAILER_NAME = "async {name}".format(name=TRAILER_NAME)
1414

@@ -20,8 +20,13 @@
2020

2121
@pytest.fixture(scope="session")
2222
def async_populated_api():
23+
cert = None
24+
if CER_FILE and KEY_FILE:
25+
cert = (CER_FILE, KEY_FILE)
26+
elif PEM_FILE:
27+
cert = PEM_FILE
2328
if USERNAME and PASSWORD:
24-
session = API(USERNAME, password=PASSWORD, database=DATABASE, server=SERVER)
29+
session = API(USERNAME, password=PASSWORD, database=DATABASE, server=SERVER, cert=cert)
2530
try:
2631
session.authenticate()
2732
except MyGeotabException as exception:

tests/test_api_call.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
PASSWORD = os.environ.get("MYGEOTAB_PASSWORD")
1212
DATABASE = os.environ.get("MYGEOTAB_DATABASE")
1313
SERVER = os.environ.get("MYGEOTAB_SERVER")
14+
CER_FILE = os.environ.get("MYGEOTAB_CERTIFICATE_CER")
15+
KEY_FILE = os.environ.get("MYGEOTAB_CERTIFICATE_KEY")
16+
PEM_FILE = os.environ.get("MYGEOTAB_CERTIFICATE_PEM")
1417
TRAILER_NAME = "mygeotab-python test trailer"
1518

1619
FAKE_USERNAME = "fakeusername"
@@ -21,8 +24,13 @@
2124

2225
@pytest.fixture(scope="session")
2326
def populated_api():
27+
cert = None
28+
if CER_FILE and KEY_FILE:
29+
cert = (CER_FILE, KEY_FILE)
30+
elif PEM_FILE:
31+
cert = PEM_FILE
2432
if USERNAME and PASSWORD:
25-
session = api.API(USERNAME, password=PASSWORD, database=DATABASE, server=SERVER)
33+
session = api.API(USERNAME, password=PASSWORD, database=DATABASE, server=SERVER, cert=cert)
2634
try:
2735
session.authenticate()
2836
except api.MyGeotabException as exception:

0 commit comments

Comments
 (0)