Skip to content

Commit ff5aba4

Browse files
authored
Merge pull request #1 from securenative/dev
Dev
2 parents 79cd1d2 + 36a5f12 commit ff5aba4

File tree

16 files changed

+476
-0
lines changed

16 files changed

+476
-0
lines changed

securenative/__init__.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from securenative.event_options import Event
2+
from securenative.sdk_options import SecureNativeOptions
3+
from securenative.securenative import SecureNative
4+
import securenative.event_types
5+
6+
_sn_sdk: SecureNative = None
7+
8+
9+
def init(api_key, options=SecureNativeOptions()):
10+
global _sn_sdk
11+
if _sn_sdk is None:
12+
_sn_sdk = SecureNative(api_key, options)
13+
14+
15+
def track(event: Event):
16+
sdk = _get_or_throw()
17+
sdk.track(event)
18+
19+
20+
def verify(event: Event):
21+
sdk = _get_or_throw()
22+
return sdk.verify(event)
23+
24+
25+
def verify_webhook(hmac_header: str, body: str):
26+
sdk = _get_or_throw()
27+
sdk.verify_webhook(hmac_header=hmac_header, body=body)
28+
29+
30+
def flush():
31+
sdk = _get_or_throw()
32+
sdk.flush()
33+
34+
35+
def _get_or_throw():
36+
if _sn_sdk is None:
37+
raise ValueError(
38+
u'You should call securenative.init(api_key, options) before making any other sdk function call.')
39+
return _sn_sdk

securenative/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
sdk_version = '0.1'
2+
_max_allowed_params = 6

securenative/errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class MissingApiKeyError(ValueError):
2+
pass

securenative/event_manager.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import json
2+
import threading
3+
import copy
4+
5+
from securenative.errors import MissingApiKeyError
6+
from securenative.http_client import HttpClient
7+
from securenative.sdk_options import SecureNativeOptions
8+
9+
10+
class QueueItem:
11+
def __init__(self, url, body):
12+
self.url = url
13+
self.body = body
14+
15+
16+
class EventManager:
17+
def __init__(self, api_key, options=SecureNativeOptions(), http_client=HttpClient()):
18+
if api_key is None:
19+
raise MissingApiKeyError()
20+
21+
self.http_client = http_client
22+
self.api_key = api_key
23+
self.options = options
24+
self.queue = list()
25+
26+
if self.options.auto_send:
27+
interval_seconds = max(options.interval // 1000, 1)
28+
threading.Timer(interval_seconds, self.flush).start()
29+
30+
def send_async(self, event, resource_path):
31+
item = QueueItem(
32+
self._build_url(resource_path),
33+
json.dumps(event.as_dict())
34+
)
35+
36+
self.queue.insert(0, item)
37+
if self._is_queue_full():
38+
self.queue = self.queue[:len(self.queue - 1)]
39+
40+
def flush(self):
41+
queue_copy = copy.copy(self.queue)
42+
self.queue = list()
43+
44+
for item in queue_copy:
45+
self.http_client.post(item.url, self.api_key, item.body)
46+
47+
def send_sync(self, event, resources_path):
48+
return self.http_client.post(
49+
self._build_url(resources_path),
50+
self.api_key,
51+
json.dumps(event.as_dict())
52+
)
53+
54+
def _build_url(self, resource_path):
55+
return self.options.api_url + "/" + resource_path
56+
57+
def _is_queue_full(self):
58+
return len(self.queue) > self.options.max_events

securenative/event_options.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import uuid
2+
import time
3+
4+
from securenative.utils import _parse_cookie
5+
6+
7+
class User:
8+
def __init__(self, user_id=u'', user_email=u'', user_name=u''):
9+
self.user_id = user_id
10+
self.user_name = user_name
11+
self.user_email = user_email
12+
13+
14+
class Event:
15+
def __init__(self, event_type, user=User(), ip=u'127.0.0.1', remote_ip=u'127.0.0.1', user_agent=u'unknown',
16+
sn_cookie_value=None, params=None):
17+
self.event_type = event_type
18+
self.user = user
19+
self.remote_ip = remote_ip
20+
self.ip = ip
21+
self.user_agent = user_agent
22+
self.params = params
23+
self.cid = ''
24+
self.fp = ''
25+
self.params = list()
26+
27+
if params is not None:
28+
if not isinstance(params, list):
29+
raise ValueError(
30+
'custom params should be a list of CustomParams, i.e: [CustomParams(key, value), ...])')
31+
if len(params) > 0 and not isinstance(params[0], CustomParam):
32+
raise ValueError(
33+
'custom params should be a list of CustomParams, i.e: [CustomParams(key, value), ...])')
34+
35+
self.params = params
36+
37+
if sn_cookie_value is not None:
38+
self.cid, self.fp = _parse_cookie(sn_cookie_value)
39+
40+
self.vid = str(uuid.uuid4())
41+
self.ts = int(time.time())*1000
42+
43+
def as_dict(self):
44+
return {
45+
"eventType": self.event_type,
46+
"user": {
47+
"id": self.user.user_id,
48+
"email": self.user.user_email,
49+
"name": self.user.user_name
50+
},
51+
"remoteIP": self.remote_ip,
52+
"ip": self.ip,
53+
"cid": self.cid,
54+
"fp": self.fp,
55+
"ts": self.ts,
56+
"vid": self.vid,
57+
"userAgent": self.user_agent,
58+
"device": {},
59+
"params": [{p.key: p.value} for p in self.params]
60+
}
61+
62+
63+
class CustomParam:
64+
def __init__(self, key, value):
65+
self.key = key
66+
self.value = value

securenative/event_types.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
login = "sn.user.login"
2+
login_challenge = "sn.user.login.challenge"
3+
login_failure = "sn.user.login.failure"
4+
log_out = "sn.user.logout"
5+
sign_up = "sn.user.signup"
6+
auth_challenge = "sn.user.auth.challange"
7+
auth_challenge_success = "sn.user.auth.challange.success"
8+
auth_challenge_failure = "sn.user.auth.challange.failure"
9+
two_factor_disable = "sn.user.2fa.disable"
10+
email_update = "sn.user.email.update"
11+
password_reset = "sn.user.password.reset"
12+
password_reset_success = "sn.user.password.reset.success"
13+
password_update = "sn.user.password.update"
14+
password_reset_failure = "sn.user.password.reset.failure"
15+
user_invite = "sn.user.invite"
16+
role_update = "sn.user.role.update"
17+
profile_update = "sn.user.profile.update"
18+
page_view = "sn.user.page.view"

securenative/http_client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import requests
2+
3+
from securenative.config import sdk_version
4+
5+
6+
class HttpClient:
7+
def _headers(self, api_key):
8+
return {
9+
'Content-Type': 'application/json',
10+
'User-Agent': 'SecureNative-python',
11+
'Sn-Version': sdk_version,
12+
'Authorization': api_key
13+
}
14+
15+
def post(self, url, api_key, body):
16+
return requests.post(url=url, data=body, headers=self._headers(api_key))

securenative/sdk_options.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
_securenative_prod = u"https://api.securenative.com"
2+
_securenative_stg = u"https://api.securenative-stg.com"
3+
4+
5+
class SecureNativeOptions:
6+
def __init__(self, api_url=_securenative_prod, interval=1000, max_events=1000, timeout=1500, auto_send=True):
7+
super().__init__()
8+
self.timeout = timeout
9+
self.max_events = max_events
10+
self.api_url = api_url
11+
self.interval = interval
12+
self.auto_send = auto_send

securenative/securenative.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import json
2+
3+
from securenative.config import _max_allowed_params
4+
from securenative.errors import MissingApiKeyError
5+
from securenative.event_manager import EventManager
6+
from securenative.sdk_options import SecureNativeOptions
7+
from securenative.utils import verify_signature
8+
9+
10+
class SecureNative:
11+
def __init__(self, api_key, options=SecureNativeOptions()):
12+
if api_key is None:
13+
raise MissingApiKeyError()
14+
15+
self._api_key = api_key
16+
self._options = options
17+
self._event_manager = EventManager(self._api_key, self._options)
18+
19+
def track(self, event):
20+
_validate_event(event)
21+
self._event_manager.send_async(event, 'collector/api/v1/track')
22+
23+
def verify(self, event):
24+
_validate_event(event)
25+
response = self._event_manager.send_sync(event, 'collector/api/v1/verify')
26+
if response.status_code == 200:
27+
json_result = json.loads(response.text)
28+
return json_result
29+
else:
30+
return None
31+
32+
def verify_webhook(self, hmac_header, body):
33+
return verify_signature(self._api_key, body, hmac_header)
34+
35+
def flush(self):
36+
self._event_manager.flush()
37+
38+
39+
def _validate_event(event):
40+
if len(event.params) > _max_allowed_params:
41+
event.params = event.params[:_max_allowed_params]

securenative/utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import base64
2+
import json
3+
import hmac
4+
import hashlib
5+
6+
7+
def _parse_cookie(cookie_value=None):
8+
fp = u''
9+
cid = u''
10+
11+
if cookie_value is None:
12+
return cid, fp
13+
14+
try:
15+
base64_decoded = base64.b64decode(cookie_value)
16+
if base64_decoded is None:
17+
base64_decoded = u'{}'
18+
19+
json_obj = json.loads(base64_decoded)
20+
21+
if u'fp' in json_obj:
22+
fp = json_obj['fp']
23+
24+
if u'cid' in json_obj:
25+
cid = json_obj['cid']
26+
except Exception as ex:
27+
pass
28+
29+
return cid, fp
30+
31+
32+
def verify_signature(secret, text_body, header_signature):
33+
try:
34+
key = secret.encode('utf-8')
35+
body = text_body.encode('utf-8')
36+
comparison_signature = hmac.new(key, body, hashlib.sha512).hexdigest()
37+
return hmac.compare_digest(comparison_signature, header_signature)
38+
except Exception as ex:
39+
return False

0 commit comments

Comments
 (0)