Skip to content

Commit 399b477

Browse files
committed
SN-612 Adding core functionality of the SDK
1 parent 79cd1d2 commit 399b477

File tree

9 files changed

+218
-0
lines changed

9 files changed

+218
-0
lines changed

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)
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)
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
if params is None:
24+
params = list()
25+
if isinstance(params, list):
26+
raise ValueError('custom params should be a list of CustomParams, i.e: [CustomParams(key, value), ...])')
27+
28+
if sn_cookie_value is not None:
29+
self.cid, self.fp = _parse_cookie(sn_cookie_value)
30+
31+
self.vid = uuid.uuid1()
32+
self.ts = time.time() * 1000
33+
34+
35+
class CustomParam:
36+
def __init__(self, key, value):
37+
self.key = key
38+
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, body=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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
base64_decoded = base64.b64decode(cookie_value)
15+
if base64_decoded is None:
16+
base64_decoded = u'{}'
17+
18+
json_obj = json.loads(base64_decoded)
19+
20+
if hasattr(json_obj, 'fp'):
21+
fp = json_obj['fp']
22+
23+
if hasattr(json_obj, 'cid'):
24+
cid = json_obj['cid']
25+
26+
return cid, fp
27+
28+
29+
def verify_signature(secret, text_body, header_signature):
30+
comparison_signature = hmac.new(secret, text_body, hashlib.sha3_512).hexdigest()
31+
return comparison_signature == header_signature

0 commit comments

Comments
 (0)