Skip to content

Commit 9e7c5f1

Browse files
author
Dawid Libiszewski
authored
Add filter, risk and log endpoint clients (#104)
1 parent 4772130 commit 9e7c5f1

9 files changed

Lines changed: 549 additions & 12 deletions

File tree

README.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,10 @@ Here is a simple example of track event.
118118
.. code:: python
119119
120120
from castle.client import Client
121-
from castle import events
122121
123122
castle = Client.from_request(request)
124123
castle.track({
125-
'event': '$login',
124+
'event': '$login.succeeded',
126125
'user_id': 'user_id'
127126
})
128127
@@ -148,11 +147,10 @@ background worker you can generate data for a worker:
148147
.. code:: python
149148
150149
from castle.payload.prepare import PayloadPrepare
151-
from castle import events
152150
153151
payload = PayloadPrepare.call(
154152
{
155-
'event': $login,
153+
'event': '$login.succeeded',
156154
'user_id': user.id,
157155
'properties': { 'key': 'value' },
158156
'user_traits': { 'key': 'value' }

castle/client.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from castle.api_request import APIRequest
22
from castle.commands.authenticate import CommandsAuthenticate
3+
from castle.commands.filter import CommandsFilter
4+
from castle.commands.log import CommandsLog
5+
from castle.commands.risk import CommandsRisk
36
from castle.commands.start_impersonation import CommandsStartImpersonation
47
from castle.commands.end_impersonation import CommandsEndImpersonation
58
from castle.commands.track import CommandsTrack
@@ -21,11 +24,11 @@ def from_request(cls, request, options=None):
2124
return cls(options)
2225

2326
@staticmethod
24-
def failover_response_or_raise(options, exception):
27+
def failover_response_or_raise(user_id, exception):
2528
if configuration.failover_strategy == FailoverStrategy.THROW.value:
2629
raise exception
2730
return FailoverPrepareResponse(
28-
options.get('user_id'), None, exception.__class__.__name__
31+
user_id, None, exception.__class__.__name__
2932
).call()
3033

3134
def __init__(self, options=None):
@@ -49,14 +52,55 @@ def authenticate(self, options):
4952
response.update(failover=False, failover_reason=None)
5053
return response
5154
except (RequestError, InternalServerError) as exception:
52-
return Client.failover_response_or_raise(options, exception)
55+
return Client.failover_response_or_raise(options.get('user_id'), exception)
5356
else:
5457
return FailoverPrepareResponse(
5558
options.get('user_id'),
5659
'allow',
5760
'Castle set to do not track.'
5861
).call()
5962

63+
def filter(self, options):
64+
if self.tracked():
65+
self._add_timestamp_if_necessary(options)
66+
command = CommandsFilter(self.context).call(options)
67+
try:
68+
response = self.api.call(command)
69+
response.update(failover=False, failover_reason=None)
70+
return response
71+
except (RequestError, InternalServerError) as exception:
72+
return Client.failover_response_or_raise(options.get('user').get('id'), exception)
73+
else:
74+
return FailoverPrepareResponse(
75+
options.get('user').get('id'),
76+
'allow',
77+
'Castle set to do not track.'
78+
).call()
79+
80+
def log(self, options):
81+
if not self.tracked():
82+
return None
83+
self._add_timestamp_if_necessary(options)
84+
85+
return self.api.call(CommandsLog(self.context).call(options))
86+
87+
def risk(self, options):
88+
if self.tracked():
89+
self._add_timestamp_if_necessary(options)
90+
command = CommandsRisk(self.context).call(options)
91+
try:
92+
response = self.api.call(command)
93+
response.update(failover=False, failover_reason=None)
94+
return response
95+
except (RequestError, InternalServerError) as exception:
96+
return Client.failover_response_or_raise(options.get('user').get('id'), exception)
97+
else:
98+
return FailoverPrepareResponse(
99+
options.get('user').get('id'),
100+
'allow',
101+
'Castle set to do not track.'
102+
).call()
103+
60104
def start_impersonation(self, options):
61105
self._add_timestamp_if_necessary(options)
62106
response = self.api.call(CommandsStartImpersonation(self.context).call(options))

castle/commands/filter.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from castle.command import Command
2+
from castle.utils.timestamp import UtilsTimestamp as generate_timestamp
3+
from castle.context.merge import ContextMerge
4+
from castle.context.sanitize import ContextSanitize
5+
from castle.validators.present import ValidatorsPresent
6+
7+
8+
class CommandsFilter(object):
9+
def __init__(self, context):
10+
self.context = context
11+
12+
def call(self, options):
13+
ValidatorsPresent.call(options, 'event')
14+
context = ContextMerge.call(self.context, options.get('context'))
15+
context = ContextSanitize.call(context)
16+
if context:
17+
options.update({'context': context})
18+
options.update({'sent_at': generate_timestamp.call()})
19+
20+
return Command(method='post', path='filter', data=options)

castle/commands/log.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from castle.command import Command
2+
from castle.utils.timestamp import UtilsTimestamp as generate_timestamp
3+
from castle.context.merge import ContextMerge
4+
from castle.context.sanitize import ContextSanitize
5+
from castle.validators.present import ValidatorsPresent
6+
7+
8+
class CommandsLog(object):
9+
def __init__(self, context):
10+
self.context = context
11+
12+
def call(self, options):
13+
ValidatorsPresent.call(options, 'event')
14+
context = ContextMerge.call(self.context, options.get('context'))
15+
context = ContextSanitize.call(context)
16+
if context:
17+
options.update({'context': context})
18+
options.update({'sent_at': generate_timestamp.call()})
19+
20+
return Command(method='post', path='log', data=options)

castle/commands/risk.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from castle.command import Command
2+
from castle.utils.timestamp import UtilsTimestamp as generate_timestamp
3+
from castle.context.merge import ContextMerge
4+
from castle.context.sanitize import ContextSanitize
5+
from castle.validators.present import ValidatorsPresent
6+
7+
8+
class CommandsRisk(object):
9+
def __init__(self, context):
10+
self.context = context
11+
12+
def call(self, options):
13+
ValidatorsPresent.call(options, 'event')
14+
context = ContextMerge.call(self.context, options.get('context'))
15+
context = ContextSanitize.call(context)
16+
if context:
17+
options.update({'context': context})
18+
options.update({'sent_at': generate_timestamp.call()})
19+
20+
return Command(method='post', path='risk', data=options)

castle/test/client_test.py

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def test_authenticate_tracked_true(self):
109109
status=200
110110
)
111111
client = Client.from_request(request(), {})
112-
options = {'event': '$login.authenticate', 'user_id': '1234'}
112+
options = {'event': '$login.succeeded', 'user_id': '1234'}
113113
response_text.update(failover=False, failover_reason=None)
114114
self.assertEqual(client.authenticate(options), response_text)
115115

@@ -128,7 +128,7 @@ def test_authenticate_tracked_true_status_500(self):
128128
status=500
129129
)
130130
client = Client.from_request(request(), {})
131-
options = {'event': '$login.authenticate', 'user_id': '1234'}
131+
options = {'event': '$login.succeeded', 'user_id': '1234'}
132132
self.assertEqual(client.authenticate(options), response_text)
133133

134134
def test_authenticate_tracked_false(self):
@@ -140,7 +140,7 @@ def test_authenticate_tracked_false(self):
140140
}
141141
client = Client.from_request(request(), {})
142142
client.disable_tracking()
143-
options = {'event': '$login.authenticate', 'user_id': '1234'}
143+
options = {'event': '$login.succeeded', 'user_id': '1234'}
144144
self.assertEqual(client.authenticate(options), response_text)
145145

146146
@responses.activate
@@ -153,14 +153,155 @@ def test_track_tracked_true(self):
153153
status=200
154154
)
155155
client = Client.from_request(request(), {})
156-
options = {'event': '$login.authenticate', 'user_id': '1234'}
156+
options = {'event': '$login.succeeded', 'user_id': '1234'}
157157
self.assertEqual(client.track(options), response_text)
158158

159159
def test_track_tracked_false(self):
160160
client = Client.from_request(request(), {})
161161
client.disable_tracking()
162162
self.assertEqual(client.track({}), None)
163163

164+
@responses.activate
165+
def test_filter_tracked_true(self):
166+
response_text = {'action': Verdict.ALLOW.value, 'user_id': '1234'}
167+
responses.add(
168+
responses.POST,
169+
'https://api.castle.io/v1/filter',
170+
json=response_text,
171+
status=200
172+
)
173+
client = Client.from_request(request(), {})
174+
options = {
175+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
176+
'event': '$login',
177+
'status': '$succeeded',
178+
'user': {'id': '1234'}
179+
}
180+
response_text.update(failover=False, failover_reason=None)
181+
self.assertEqual(client.filter(options), response_text)
182+
183+
@responses.activate
184+
def test_filter_tracked_true_status_500(self):
185+
response_text = {
186+
'action': Verdict.ALLOW.value,
187+
'user_id': '1234',
188+
'failover': True,
189+
'failover_reason': 'InternalServerError'
190+
}
191+
responses.add(
192+
responses.POST,
193+
'https://api.castle.io/v1/filter',
194+
json='filter',
195+
status=500
196+
)
197+
client = Client.from_request(request(), {})
198+
options = {
199+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
200+
'event': '$login',
201+
'status': '$succeeded',
202+
'user': {'id': '1234'}
203+
}
204+
self.assertEqual(client.filter(options), response_text)
205+
206+
def test_filter_tracked_false(self):
207+
response_text = {
208+
'action': Verdict.ALLOW.value,
209+
'user_id': '1234',
210+
'failover': True,
211+
'failover_reason': 'Castle set to do not track.'
212+
}
213+
client = Client.from_request(request(), {})
214+
client.disable_tracking()
215+
options = {
216+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
217+
'event': '$login',
218+
'status': '$succeeded',
219+
'user': {'id': '1234'}
220+
}
221+
self.assertEqual(client.filter(options), response_text)
222+
223+
@responses.activate
224+
def test_log_tracked_true(self):
225+
response_text = 'log'
226+
responses.add(
227+
responses.POST,
228+
'https://api.castle.io/v1/log',
229+
json=response_text,
230+
status=200
231+
)
232+
client = Client.from_request(request(), {})
233+
options = {
234+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
235+
'event': '$login',
236+
'status': '$succeeded',
237+
'user': {'id': '1234'}
238+
}
239+
self.assertEqual(client.log(options), response_text)
240+
241+
def test_log_tracked_false(self):
242+
client = Client.from_request(request(), {})
243+
client.disable_tracking()
244+
self.assertEqual(client.log({}), None)
245+
246+
@responses.activate
247+
def test_risk_tracked_true(self):
248+
response_text = {'action': Verdict.ALLOW.value, 'user_id': '1234'}
249+
responses.add(
250+
responses.POST,
251+
'https://api.castle.io/v1/risk',
252+
json=response_text,
253+
status=200
254+
)
255+
client = Client.from_request(request(), {})
256+
options = {
257+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
258+
'event': '$login',
259+
'status': '$succeeded',
260+
'user': {'id': '1234'}
261+
}
262+
response_text.update(failover=False, failover_reason=None)
263+
self.assertEqual(client.risk(options), response_text)
264+
265+
@responses.activate
266+
def test_risk_tracked_true_status_500(self):
267+
response_text = {
268+
'action': Verdict.ALLOW.value,
269+
'user_id': '1234',
270+
'failover': True,
271+
'failover_reason': 'InternalServerError'
272+
}
273+
responses.add(
274+
responses.POST,
275+
'https://api.castle.io/v1/risk',
276+
json='risk',
277+
status=500
278+
)
279+
client = Client.from_request(request(), {})
280+
options = {
281+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
282+
'event': '$login',
283+
'status': '$succeeded',
284+
'user': {'id': '1234'}
285+
}
286+
self.assertEqual(client.risk(options), response_text)
287+
288+
def test_risk_tracked_false(self):
289+
response_text = {
290+
'action': Verdict.ALLOW.value,
291+
'user_id': '1234',
292+
'failover': True,
293+
'failover_reason': 'Castle set to do not track.'
294+
}
295+
client = Client.from_request(request(), {})
296+
client.disable_tracking()
297+
options = {
298+
'request_token': '7e51335b-f4bc-4bc7-875d-b713fb61eb23-bf021a3022a1a302',
299+
'event': '$login',
300+
'status': '$succeeded',
301+
'user': {'id': '1234'}
302+
}
303+
self.assertEqual(client.risk(options), response_text)
304+
164305
def test_disable_tracking(self):
165306
client = Client.from_request(request(), {})
166307
client.disable_tracking()
@@ -184,7 +325,7 @@ def test_tracked_when_do_not_track_true(self):
184325
def test_failover_strategy_not_throw(self):
185326
options = {'user_id': '1234'}
186327
self.assertEqual(
187-
Client.failover_response_or_raise(options, Exception()),
328+
Client.failover_response_or_raise(options.get('user_id'), Exception()),
188329
{
189330
'action': Verdict.ALLOW.value,
190331
'user_id': '1234',

0 commit comments

Comments
 (0)