Skip to content

Commit 2aa5ad1

Browse files
authored
Merge pull request #543 from splitio/kerberos-refactor-client
refactored httpclient for kerberos auth
2 parents 38bd316 + dfd430d commit 2aa5ad1

File tree

3 files changed

+182
-82
lines changed

3 files changed

+182
-82
lines changed

splitio/api/client.py

Lines changed: 137 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
TELEMETRY_URL = 'https://telemetry.split.io/api'
2020

2121
_LOGGER = logging.getLogger(__name__)
22-
22+
_EXC_MSG = '{source} library is throwing exceptions'
2323

2424
HttpResponse = namedtuple('HttpResponse', ['status_code', 'body', 'headers'])
2525

@@ -122,7 +122,7 @@ def _get_headers(self, extra_headers, sdk_key):
122122
class HttpClient(HttpClientBase):
123123
"""HttpClient wrapper."""
124124

125-
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, authentication_scheme=None, authentication_params=None):
125+
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None):
126126
"""
127127
Class constructor.
128128
@@ -140,8 +140,6 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, t
140140
_LOGGER.debug("Initializing httpclient")
141141
self._timeout = timeout/1000 if timeout else None # Convert ms to seconds.
142142
self._urls = _construct_urls(sdk_url, events_url, auth_url, telemetry_url)
143-
self._authentication_scheme = authentication_scheme
144-
self._authentication_params = authentication_params
145143
self._lock = threading.RLock()
146144

147145
def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments
@@ -162,22 +160,19 @@ def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint:
162160
:return: Tuple of status_code & response text
163161
:rtype: HttpResponse
164162
"""
165-
with self._lock:
166-
start = get_current_epoch_time_ms()
167-
with requests.Session() as session:
168-
self._set_authentication(session)
169-
try:
170-
response = session.get(
171-
_build_url(server, path, self._urls),
172-
params=query,
173-
headers=self._get_headers(extra_headers, sdk_key),
174-
timeout=self._timeout
175-
)
176-
self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
177-
return HttpResponse(response.status_code, response.text, response.headers)
163+
start = get_current_epoch_time_ms()
164+
try:
165+
response = requests.get(
166+
_build_url(server, path, self._urls),
167+
params=query,
168+
headers=self._get_headers(extra_headers, sdk_key),
169+
timeout=self._timeout
170+
)
171+
self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
172+
return HttpResponse(response.status_code, response.text, response.headers)
178173

179-
except Exception as exc: # pylint: disable=broad-except
180-
raise HttpClientException('requests library is throwing exceptions') from exc
174+
except Exception as exc: # pylint: disable=broad-except
175+
raise HttpClientException(_EXC_MSG.format(source='request')) from exc
181176

182177
def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
183178
"""
@@ -199,37 +194,19 @@ def post(self, server, path, sdk_key, body, query=None, extra_headers=None): #
199194
:return: Tuple of status_code & response text
200195
:rtype: HttpResponse
201196
"""
202-
with self._lock:
203-
start = get_current_epoch_time_ms()
204-
with requests.Session() as session:
205-
self._set_authentication(session)
206-
try:
207-
response = session.post(
208-
_build_url(server, path, self._urls),
209-
json=body,
210-
params=query,
211-
headers=self._get_headers(extra_headers, sdk_key),
212-
timeout=self._timeout,
213-
)
214-
self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
215-
return HttpResponse(response.status_code, response.text, response.headers)
216-
except Exception as exc: # pylint: disable=broad-except
217-
raise HttpClientException('requests library is throwing exceptions') from exc
218-
219-
def _set_authentication(self, session):
220-
if self._authentication_scheme == AuthenticateScheme.KERBEROS_SPNEGO:
221-
_LOGGER.debug("Using Kerberos Spnego Authentication")
222-
if self._authentication_params != [None, None]:
223-
session.auth = HTTPKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1], mutual_authentication=OPTIONAL)
224-
else:
225-
session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
226-
elif self._authentication_scheme == AuthenticateScheme.KERBEROS_PROXY:
227-
_LOGGER.debug("Using Kerberos Proxy Authentication")
228-
if self._authentication_params != [None, None]:
229-
session.mount('https://', HTTPAdapterWithProxyKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1]))
230-
else:
231-
session.mount('https://', HTTPAdapterWithProxyKerberosAuth())
232-
197+
start = get_current_epoch_time_ms()
198+
try:
199+
response = requests.post(
200+
_build_url(server, path, self._urls),
201+
json=body,
202+
params=query,
203+
headers=self._get_headers(extra_headers, sdk_key),
204+
timeout=self._timeout,
205+
)
206+
self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
207+
return HttpResponse(response.status_code, response.text, response.headers)
208+
except Exception as exc: # pylint: disable=broad-except
209+
raise HttpClientException(_EXC_MSG.format(source='request')) from exc
233210

234211
def _record_telemetry(self, status_code, elapsed):
235212
"""
@@ -306,7 +283,7 @@ async def get(self, server, path, apikey, query=None, extra_headers=None): # py
306283
return HttpResponse(response.status, body, response.headers)
307284

308285
except aiohttp.ClientError as exc: # pylint: disable=broad-except
309-
raise HttpClientException('aiohttp library is throwing exceptions') from exc
286+
raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc
310287

311288
async def post(self, server, path, apikey, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
312289
"""
@@ -350,7 +327,7 @@ async def post(self, server, path, apikey, body, query=None, extra_headers=None)
350327
return HttpResponse(response.status, body, response.headers)
351328

352329
except aiohttp.ClientError as exc: # pylint: disable=broad-except
353-
raise HttpClientException('aiohttp library is throwing exceptions') from exc
330+
raise HttpClientException(_EXC_MSG.format(source='aiohttp')) from exc
354331

355332
async def _record_telemetry(self, status_code, elapsed):
356333
"""
@@ -372,3 +349,111 @@ async def _record_telemetry(self, status_code, elapsed):
372349
async def close_session(self):
373350
if not self._session.closed:
374351
await self._session.close()
352+
353+
class HttpClientKerberos(HttpClient):
354+
"""HttpClient wrapper."""
355+
356+
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, authentication_scheme=None, authentication_params=None):
357+
"""
358+
Class constructor.
359+
360+
:param timeout: How many milliseconds to wait until the server responds.
361+
:type timeout: int
362+
:param sdk_url: Optional alternative sdk URL.
363+
:type sdk_url: str
364+
:param events_url: Optional alternative events URL.
365+
:type events_url: str
366+
:param auth_url: Optional alternative auth URL.
367+
:type auth_url: str
368+
:param telemetry_url: Optional alternative telemetry URL.
369+
:type telemetry_url: str
370+
"""
371+
_LOGGER.debug("Initializing httpclient for Kerberos auth")
372+
HttpClient.__init__(self, timeout=timeout, sdk_url=sdk_url, events_url=events_url, auth_url=auth_url, telemetry_url=telemetry_url)
373+
self._authentication_scheme = authentication_scheme
374+
self._authentication_params = authentication_params
375+
376+
def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint: disable=too-many-arguments
377+
"""
378+
Issue a get request.
379+
:param server: Whether the request is for SDK server, Events server or Auth server.
380+
:typee server: str
381+
:param path: path to append to the host url.
382+
:type path: str
383+
:param sdk_key: sdk key.
384+
:type sdk_key: str
385+
:param query: Query string passed as dictionary.
386+
:type query: dict
387+
:param extra_headers: key/value pairs of possible extra headers.
388+
:type extra_headers: dict
389+
390+
:return: Tuple of status_code & response text
391+
:rtype: HttpResponse
392+
"""
393+
with self._lock:
394+
start = get_current_epoch_time_ms()
395+
with requests.Session() as session:
396+
self._set_authentication(session)
397+
try:
398+
response = session.get(
399+
_build_url(server, path, self._urls),
400+
headers=self._get_headers(extra_headers, sdk_key),
401+
params=query,
402+
timeout=self._timeout
403+
)
404+
self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
405+
return HttpResponse(response.status_code, response.text, response.headers)
406+
407+
except Exception as exc: # pylint: disable=broad-except
408+
raise HttpClientException(_EXC_MSG.format(source='request')) from exc
409+
410+
def post(self, server, path, sdk_key, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
411+
"""
412+
Issue a POST request.
413+
414+
:param server: Whether the request is for SDK server or Events server.
415+
:typee server: str
416+
:param path: path to append to the host url.
417+
:type path: str
418+
:param sdk_key: sdk key.
419+
:type sdk_key: str
420+
:param body: body sent in the request.
421+
:type body: str
422+
:param query: Query string passed as dictionary.
423+
:type query: dict
424+
:param extra_headers: key/value pairs of possible extra headers.
425+
:type extra_headers: dict
426+
427+
:return: Tuple of status_code & response text
428+
:rtype: HttpResponse
429+
"""
430+
with self._lock:
431+
start = get_current_epoch_time_ms()
432+
with requests.Session() as session:
433+
self._set_authentication(session)
434+
try:
435+
response = session.post(
436+
_build_url(server, path, self._urls),
437+
params=query,
438+
headers=self._get_headers(extra_headers, sdk_key),
439+
json=body,
440+
timeout=self._timeout,
441+
)
442+
self._record_telemetry(response.status_code, get_current_epoch_time_ms() - start)
443+
return HttpResponse(response.status_code, response.text, response.headers)
444+
except Exception as exc: # pylint: disable=broad-except
445+
raise HttpClientException(_EXC_MSG.format(source='request')) from exc
446+
447+
def _set_authentication(self, session):
448+
if self._authentication_scheme == AuthenticateScheme.KERBEROS_SPNEGO:
449+
_LOGGER.debug("Using Kerberos Spnego Authentication")
450+
if self._authentication_params != [None, None]:
451+
session.auth = HTTPKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1], mutual_authentication=OPTIONAL)
452+
else:
453+
session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
454+
elif self._authentication_scheme == AuthenticateScheme.KERBEROS_PROXY:
455+
_LOGGER.debug("Using Kerberos Proxy Authentication")
456+
if self._authentication_params != [None, None]:
457+
session.mount('https://', HTTPAdapterWithProxyKerberosAuth(principal=self._authentication_params[0], password=self._authentication_params[1]))
458+
else:
459+
session.mount('https://', HTTPAdapterWithProxyKerberosAuth())

splitio/client/factory.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
PluggableImpressionsStorageAsync, PluggableSegmentStorageAsync, PluggableSplitStorageAsync
3434

3535
# APIs
36-
from splitio.api.client import HttpClient, HttpClientAsync
36+
from splitio.api.client import HttpClient, HttpClientAsync, HttpClientKerberos
3737
from splitio.api.splits import SplitsAPI, SplitsAPIAsync
3838
from splitio.api.segments import SegmentsAPI, SegmentsAPIAsync
3939
from splitio.api.impressions import ImpressionsAPI, ImpressionsAPIAsync
@@ -512,16 +512,23 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
512512
if cfg.get("httpAuthenticateScheme") in [AuthenticateScheme.KERBEROS_SPNEGO, AuthenticateScheme.KERBEROS_PROXY]:
513513
authentication_params = [cfg.get("kerberosPrincipalUser"),
514514
cfg.get("kerberosPrincipalPassword")]
515-
516-
http_client = HttpClient(
517-
sdk_url=sdk_url,
518-
events_url=events_url,
519-
auth_url=auth_api_base_url,
520-
telemetry_url=telemetry_api_base_url,
521-
timeout=cfg.get('connectionTimeout'),
522-
authentication_scheme = cfg.get("httpAuthenticateScheme"),
523-
authentication_params = authentication_params
524-
)
515+
http_client = HttpClientKerberos(
516+
sdk_url=sdk_url,
517+
events_url=events_url,
518+
auth_url=auth_api_base_url,
519+
telemetry_url=telemetry_api_base_url,
520+
timeout=cfg.get('connectionTimeout'),
521+
authentication_scheme = cfg.get("httpAuthenticateScheme"),
522+
authentication_params = authentication_params
523+
)
524+
else:
525+
http_client = HttpClient(
526+
sdk_url=sdk_url,
527+
events_url=events_url,
528+
auth_url=auth_api_base_url,
529+
telemetry_url=telemetry_api_base_url,
530+
timeout=cfg.get('connectionTimeout'),
531+
)
525532

526533
sdk_metadata = util.get_metadata(cfg)
527534
apis = {

0 commit comments

Comments
 (0)