Skip to content

Commit aaea525

Browse files
authored
Add support for HMAC auth to client API (#203)
* Add support for HMAC auth to client API
1 parent 2151ea7 commit aaea525

7 files changed

Lines changed: 39 additions & 16 deletions

File tree

.isort.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[settings]
2-
known_third_party = click,pytz,requests,requests_mock,setuptools,tabulate
2+
known_third_party = click,pytz,requests,requests_hawk,requests_mock,setuptools,tabulate

alertaclient/api.py

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

99
import requests
1010
from requests.auth import AuthBase, HTTPBasicAuth
11+
from requests_hawk import HawkAuth
1112

13+
from alertaclient.auth.utils import merge
1214
from alertaclient.exceptions import UnknownError
1315
from alertaclient.models.alert import Alert
1416
from alertaclient.models.blackout import Blackout
@@ -29,18 +31,19 @@ class Client:
2931

3032
DEFAULT_ENDPOINT = 'http://localhost:8080'
3133

32-
def __init__(self, endpoint=None, key=None, token=None, username=None, password=None, timeout=5.0, ssl_verify=True, debug=False):
34+
def __init__(self, endpoint=None, key=None, secret=None, token=None, username=None, password=None, timeout=5.0, ssl_verify=True, headers=None, debug=False):
3335
self.endpoint = endpoint or os.environ.get('ALERTA_ENDPOINT', self.DEFAULT_ENDPOINT)
3436

3537
if debug:
3638
HTTPConnection.debuglevel = 1
3739

3840
key = key or os.environ.get('ALERTA_API_KEY', '')
39-
self.http = HTTPClient(self.endpoint, key, token, username, password, timeout, ssl_verify, debug)
41+
self.http = HTTPClient(self.endpoint, key, secret, token, username, password, timeout, ssl_verify, headers, debug)
4042

4143
# Alerts
4244
def send_alert(self, resource, event, **kwargs):
4345
data = {
46+
'id': kwargs.get('id'),
4447
'resource': resource,
4548
'event': event,
4649
'environment': kwargs.get('environment'),
@@ -467,25 +470,30 @@ def __call__(self, r):
467470

468471
class HTTPClient:
469472

470-
def __init__(self, endpoint, key=None, token=None, username=None, password=None, timeout=30.0, ssl_verify=True, debug=False):
473+
def __init__(self, endpoint, key=None, secret=None, token=None, username=None, password=None, timeout=30.0, ssl_verify=True, headers=None, debug=False):
471474
self.endpoint = endpoint
472475
self.auth = None
473476

474477
if username:
475478
self.auth = HTTPBasicAuth(username, password)
479+
elif secret:
480+
self.auth = HawkAuth(id=key, key=secret) # HMAC
476481
elif key:
477-
self.auth = ApiKeyAuth(key)
482+
self.auth = ApiKeyAuth(api_key=key)
478483
elif token:
479484
self.auth = TokenAuth(token)
480485

481486
self.timeout = timeout
482487
self.session = requests.Session()
483488
self.session.verify = ssl_verify # or use REQUESTS_CA_BUNDLE env var
484489

490+
self.headers = headers or dict()
491+
merge(self.headers, self.default_headers())
492+
485493
self.debug = debug
486494

487495
@staticmethod
488-
def headers():
496+
def default_headers():
489497
return {
490498
'X-Request-ID': str(uuid.uuid4()),
491499
'Content-Type': 'application/json'
@@ -500,7 +508,7 @@ def get(self, path, query=None, **kwargs):
500508

501509
url = self.endpoint + path + '?' + urlencode(query, doseq=True)
502510
try:
503-
response = self.session.get(url, headers=self.headers(), auth=self.auth, timeout=self.timeout)
511+
response = self.session.get(url, headers=self.headers, auth=self.auth, timeout=self.timeout)
504512
except requests.exceptions.RequestException:
505513
raise
506514
return self._handle_error(response)
@@ -509,7 +517,7 @@ def post(self, path, data=None):
509517
url = self.endpoint + path
510518
try:
511519
response = self.session.post(url, data=json.dumps(data, cls=CustomJsonEncoder),
512-
headers=self.headers(), auth=self.auth, timeout=self.timeout)
520+
headers=self.headers, auth=self.auth, timeout=self.timeout)
513521
except requests.exceptions.RequestException:
514522
raise
515523
return self._handle_error(response)
@@ -518,7 +526,7 @@ def put(self, path, data=None):
518526
url = self.endpoint + path
519527
try:
520528
response = self.session.put(url, data=json.dumps(data, cls=CustomJsonEncoder),
521-
headers=self.headers(), auth=self.auth, timeout=self.timeout)
529+
headers=self.headers, auth=self.auth, timeout=self.timeout)
522530
except requests.exceptions.RequestException:
523531
raise
524532
return self._handle_error(response)
@@ -527,7 +535,7 @@ def delete(self, path):
527535
url = self.endpoint + path
528536

529537
try:
530-
response = self.session.delete(url, headers=self.headers(), auth=self.auth, timeout=self.timeout)
538+
response = self.session.delete(url, headers=self.headers, auth=self.auth, timeout=self.timeout)
531539
except requests.exceptions.RequestException:
532540
raise
533541
return self._handle_error(response)

alertaclient/auth/hmac.py

Whitespace-only changes.

alertaclient/auth/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,17 @@ def dump_netrc(self):
6464
rep = rep + line
6565
rep = rep + '\n'
6666
return rep
67+
68+
69+
def merge(dict1, dict2):
70+
"""
71+
Merge two dictionaries.
72+
:param dict1:
73+
:param dict2:
74+
:return:
75+
"""
76+
for k in dict2:
77+
if k in dict1 and isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
78+
merge(dict1[k], dict2[k])
79+
else:
80+
dict1[k] = dict2[k]

mypy.ini

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
[mypy]
2-
3-
[mypy-tabulate.*]
42
ignore_missing_imports = True

requirements.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
click
2-
pytz
3-
requests
4-
tabulate
1+
Click==7.0
2+
pytz==2019.3
3+
PyYAML==5.3
4+
requests==2.22.0
5+
requests-hawk==1.0.1
6+
tabulate==0.8.6

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def read(filename):
2323
install_requires=[
2424
'Click',
2525
'requests',
26+
'requests_hawk',
2627
'tabulate',
2728
'pytz'
2829
],

0 commit comments

Comments
 (0)