castle 7.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. castle/__init__.py +0 -0
  2. castle/api_request.py +18 -0
  3. castle/client.py +155 -0
  4. castle/client_id/__init__.py +0 -0
  5. castle/client_id/extract.py +7 -0
  6. castle/command.py +3 -0
  7. castle/commands/__init__.py +0 -0
  8. castle/commands/filter.py +18 -0
  9. castle/commands/list_items/__init__.py +0 -0
  10. castle/commands/list_items/archive.py +17 -0
  11. castle/commands/list_items/count.py +17 -0
  12. castle/commands/list_items/create.py +15 -0
  13. castle/commands/list_items/create_batch.py +15 -0
  14. castle/commands/list_items/get.py +17 -0
  15. castle/commands/list_items/query.py +19 -0
  16. castle/commands/list_items/unarchive.py +17 -0
  17. castle/commands/list_items/update.py +20 -0
  18. castle/commands/lists/__init__.py +0 -0
  19. castle/commands/lists/create.py +11 -0
  20. castle/commands/lists/delete.py +13 -0
  21. castle/commands/lists/get.py +13 -0
  22. castle/commands/lists/get_all.py +7 -0
  23. castle/commands/lists/query.py +14 -0
  24. castle/commands/lists/update.py +13 -0
  25. castle/commands/log.py +18 -0
  26. castle/commands/privacy/__init__.py +0 -0
  27. castle/commands/privacy/delete_data.py +11 -0
  28. castle/commands/privacy/request_data.py +11 -0
  29. castle/commands/risk.py +18 -0
  30. castle/configuration.py +188 -0
  31. castle/context/__init__.py +0 -0
  32. castle/context/get_default.py +52 -0
  33. castle/context/merge.py +12 -0
  34. castle/context/prepare.py +11 -0
  35. castle/context/sanitize.py +20 -0
  36. castle/core/__init__.py +0 -0
  37. castle/core/process_response.py +58 -0
  38. castle/core/process_webhook.py +19 -0
  39. castle/core/send_request.py +38 -0
  40. castle/errors.py +59 -0
  41. castle/failover/__init__.py +0 -0
  42. castle/failover/prepare_response.py +17 -0
  43. castle/failover/strategy.py +17 -0
  44. castle/headers/__init__.py +0 -0
  45. castle/headers/extract.py +31 -0
  46. castle/headers/filter.py +26 -0
  47. castle/headers/format.py +11 -0
  48. castle/ips/__init__.py +0 -0
  49. castle/ips/extract.py +62 -0
  50. castle/logger.py +20 -0
  51. castle/payload/__init__.py +0 -0
  52. castle/payload/prepare.py +22 -0
  53. castle/secure_mode.py +10 -0
  54. castle/session.py +20 -0
  55. castle/utils/__init__.py +0 -0
  56. castle/utils/clone.py +7 -0
  57. castle/utils/merge.py +23 -0
  58. castle/utils/secure_compare.py +22 -0
  59. castle/utils/timestamp.py +8 -0
  60. castle/validators/__init__.py +0 -0
  61. castle/validators/present.py +9 -0
  62. castle/verdict.py +11 -0
  63. castle/version.py +1 -0
  64. castle/webhooks/__init__.py +0 -0
  65. castle/webhooks/verify.py +31 -0
  66. castle-7.0.0.dist-info/METADATA +168 -0
  67. castle-7.0.0.dist-info/RECORD +70 -0
  68. castle-7.0.0.dist-info/WHEEL +5 -0
  69. castle-7.0.0.dist-info/licenses/LICENSE +21 -0
  70. castle-7.0.0.dist-info/top_level.txt +1 -0
castle/__init__.py ADDED
File without changes
castle/api_request.py ADDED
@@ -0,0 +1,18 @@
1
+ from castle.core.send_request import CoreSendRequest
2
+ from castle.core.process_response import CoreProcessResponse
3
+ from castle.configuration import configuration
4
+ from castle.errors import ConfigurationError
5
+
6
+
7
+ class APIRequest(object):
8
+ def __init__(self, config=configuration):
9
+ self.req = CoreSendRequest({'Content-Type': 'application/json'})
10
+ self.config = config
11
+
12
+ def request(self, command):
13
+ if not self.config.isValid():
14
+ raise ConfigurationError
15
+ return self.req.build_query(command.method, command.path, command.data, self.config)
16
+
17
+ def call(self, command):
18
+ return CoreProcessResponse(self.request(command)).call()
castle/client.py ADDED
@@ -0,0 +1,155 @@
1
+ from castle.api_request import APIRequest
2
+ from castle.commands.filter import CommandsFilter
3
+ from castle.commands.log import CommandsLog
4
+ from castle.commands.risk import CommandsRisk
5
+ from castle.commands.lists.create import CommandsListsCreate
6
+ from castle.commands.lists.get_all import CommandsListsGetAll
7
+ from castle.commands.lists.get import CommandsListsGet
8
+ from castle.commands.lists.query import CommandsListsQuery
9
+ from castle.commands.lists.update import CommandsListsUpdate
10
+ from castle.commands.lists.delete import CommandsListsDelete
11
+ from castle.commands.list_items.create import CommandsListItemsCreate
12
+ from castle.commands.list_items.create_batch import CommandsListItemsCreateBatch
13
+ from castle.commands.list_items.get import CommandsListItemsGet
14
+ from castle.commands.list_items.query import CommandsListItemsQuery
15
+ from castle.commands.list_items.count import CommandsListItemsCount
16
+ from castle.commands.list_items.update import CommandsListItemsUpdate
17
+ from castle.commands.list_items.archive import CommandsListItemsArchive
18
+ from castle.commands.list_items.unarchive import CommandsListItemsUnarchive
19
+ from castle.commands.privacy.request_data import CommandsPrivacyRequestData
20
+ from castle.commands.privacy.delete_data import CommandsPrivacyDeleteData
21
+ from castle.configuration import configuration
22
+ from castle.context.prepare import ContextPrepare
23
+ from castle.errors import InternalServerError, RequestError
24
+ from castle.failover.prepare_response import FailoverPrepareResponse
25
+ from castle.failover.strategy import FailoverStrategy
26
+
27
+
28
+ class Client(object):
29
+ @classmethod
30
+ def from_request(cls, request, options=None):
31
+ if options is None:
32
+ options = {}
33
+
34
+ options = options.copy()
35
+ options['context'] = ContextPrepare.call(request, options)
36
+ return cls(options)
37
+
38
+ @staticmethod
39
+ def failover_response_or_raise(user_id, exception):
40
+ if configuration.failover_strategy == FailoverStrategy.THROW.value:
41
+ raise exception
42
+ return FailoverPrepareResponse(user_id, None, exception.__class__.__name__).call()
43
+
44
+ def __init__(self, options=None):
45
+ if options is None:
46
+ options = {}
47
+ self.do_not_track = options.get('do_not_track', False)
48
+ self.timestamp = options.get('timestamp')
49
+ self.context = options.get('context')
50
+ self.api = APIRequest()
51
+
52
+ def _add_timestamp_if_necessary(self, options):
53
+ if self.timestamp:
54
+ options.setdefault('timestamp', self.timestamp)
55
+
56
+ def filter(self, options):
57
+ if self.tracked():
58
+ self._add_timestamp_if_necessary(options)
59
+ command = CommandsFilter(self.context).call(options)
60
+ try:
61
+ response = self.api.call(command)
62
+ response.update(failover=False, failover_reason=None)
63
+ return response
64
+ except (RequestError, InternalServerError) as exception:
65
+ return Client.failover_response_or_raise(self._failover_user_id(options), exception)
66
+ else:
67
+ return FailoverPrepareResponse(
68
+ self._failover_user_id(options), 'allow', 'Castle set to do not track.'
69
+ ).call()
70
+
71
+ def log(self, options):
72
+ if not self.tracked():
73
+ return None
74
+ self._add_timestamp_if_necessary(options)
75
+
76
+ return self.api.call(CommandsLog(self.context).call(options))
77
+
78
+ def risk(self, options):
79
+ if self.tracked():
80
+ self._add_timestamp_if_necessary(options)
81
+ command = CommandsRisk(self.context).call(options)
82
+ try:
83
+ response = self.api.call(command)
84
+ response.update(failover=False, failover_reason=None)
85
+ return response
86
+ except (RequestError, InternalServerError) as exception:
87
+ return Client.failover_response_or_raise(self._failover_user_id(options), exception)
88
+ else:
89
+ return FailoverPrepareResponse(
90
+ self._failover_user_id(options), 'allow', 'Castle set to do not track.'
91
+ ).call()
92
+
93
+ def create_list(self, options):
94
+ return self.api.call(CommandsListsCreate.call(options))
95
+
96
+ def get_all_lists(self, options=None):
97
+ return self.api.call(CommandsListsGetAll.call(options))
98
+
99
+ def get_list(self, options):
100
+ return self.api.call(CommandsListsGet.call(options))
101
+
102
+ def query_lists(self, options):
103
+ return self.api.call(CommandsListsQuery.call(options))
104
+
105
+ def update_list(self, options):
106
+ return self.api.call(CommandsListsUpdate.call(options))
107
+
108
+ def delete_list(self, options):
109
+ return self.api.call(CommandsListsDelete.call(options))
110
+
111
+ def create_list_item(self, options):
112
+ return self.api.call(CommandsListItemsCreate.call(options))
113
+
114
+ def create_batch_list_items(self, options):
115
+ return self.api.call(CommandsListItemsCreateBatch.call(options))
116
+
117
+ def get_list_item(self, options):
118
+ return self.api.call(CommandsListItemsGet.call(options))
119
+
120
+ def query_list_items(self, options):
121
+ return self.api.call(CommandsListItemsQuery.call(options))
122
+
123
+ def count_list_items(self, options):
124
+ return self.api.call(CommandsListItemsCount.call(options))
125
+
126
+ def update_list_item(self, options):
127
+ return self.api.call(CommandsListItemsUpdate.call(options))
128
+
129
+ def archive_list_item(self, options):
130
+ return self.api.call(CommandsListItemsArchive.call(options))
131
+
132
+ def unarchive_list_item(self, options):
133
+ return self.api.call(CommandsListItemsUnarchive.call(options))
134
+
135
+ def request_user_data(self, options):
136
+ return self.api.call(CommandsPrivacyRequestData.call(options))
137
+
138
+ def delete_user_data(self, options):
139
+ return self.api.call(CommandsPrivacyDeleteData.call(options))
140
+
141
+ def disable_tracking(self):
142
+ self.do_not_track = True
143
+
144
+ def enable_tracking(self):
145
+ self.do_not_track = False
146
+
147
+ def tracked(self):
148
+ return not self.do_not_track
149
+
150
+ @staticmethod
151
+ def _failover_user_id(options):
152
+ # `user` is optional on /v1/filter and may be omitted on /v1/log; fall
153
+ # back to `matching_user_id` then None instead of crashing.
154
+ user = options.get('user') or {}
155
+ return user.get('id') or options.get('matching_user_id')
File without changes
@@ -0,0 +1,7 @@
1
+ class ClientIdExtract(object):
2
+ def __init__(self, headers, cookies=None):
3
+ self.headers = headers
4
+ self.cookies = cookies or dict()
5
+
6
+ def call(self):
7
+ return self.headers.get('X-Castle-Client-Id', self.cookies.get('__cid', ''))
castle/command.py ADDED
@@ -0,0 +1,3 @@
1
+ from collections import namedtuple
2
+
3
+ Command = namedtuple('Command', ['path', 'data', 'method'])
File without changes
@@ -0,0 +1,18 @@
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
+
6
+
7
+ class CommandsFilter(object):
8
+ def __init__(self, context):
9
+ self.context = context
10
+
11
+ def call(self, options):
12
+ context = ContextMerge.call(self.context, options.get('context'))
13
+ context = ContextSanitize.call(context)
14
+ if context:
15
+ options.update({'context': context})
16
+ options.update({'sent_at': generate_timestamp.call()})
17
+
18
+ return Command(method='post', path='filter', data=options)
File without changes
@@ -0,0 +1,17 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsArchive(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id', 'list_item_id')
10
+
11
+ return Command(
12
+ method='delete',
13
+ path='lists/{list_id}/items/{list_item_id}/archive'.format(
14
+ list_id=options['list_id'], list_item_id=options['list_item_id']
15
+ ),
16
+ data=None,
17
+ )
@@ -0,0 +1,17 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsCount(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id')
10
+ for query_filter in options.get('filters') or []:
11
+ ValidatorsPresent.call(query_filter, 'field', 'op', 'value')
12
+
13
+ list_id = options.pop('list_id')
14
+
15
+ return Command(
16
+ method='post', path='lists/{list_id}/items/count'.format(list_id=list_id), data=options
17
+ )
@@ -0,0 +1,15 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsCreate(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id', 'author', 'primary_value')
10
+
11
+ list_id = options.pop('list_id')
12
+
13
+ return Command(
14
+ method='post', path='lists/{list_id}/items'.format(list_id=list_id), data=options
15
+ )
@@ -0,0 +1,15 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsCreateBatch(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id', 'items')
10
+
11
+ list_id = options.pop('list_id')
12
+
13
+ return Command(
14
+ method='post', path='lists/{list_id}/items/batch'.format(list_id=list_id), data=options
15
+ )
@@ -0,0 +1,17 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsGet(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id', 'list_item_id')
10
+
11
+ return Command(
12
+ method='get',
13
+ path='lists/{list_id}/items/{list_item_id}'.format(
14
+ list_id=options['list_id'], list_item_id=options['list_item_id']
15
+ ),
16
+ data=None,
17
+ )
@@ -0,0 +1,19 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsQuery(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id')
10
+ for query_filter in options.get('filters') or []:
11
+ ValidatorsPresent.call(query_filter, 'field', 'op', 'value')
12
+ if options.get('sort'):
13
+ ValidatorsPresent.call(options['sort'], 'field', 'order')
14
+
15
+ list_id = options.pop('list_id')
16
+
17
+ return Command(
18
+ method='post', path='lists/{list_id}/items/query'.format(list_id=list_id), data=options
19
+ )
@@ -0,0 +1,17 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsUnarchive(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id', 'list_item_id')
10
+
11
+ return Command(
12
+ method='put',
13
+ path='lists/{list_id}/items/{list_item_id}/unarchive'.format(
14
+ list_id=options['list_id'], list_item_id=options['list_item_id']
15
+ ),
16
+ data=None,
17
+ )
@@ -0,0 +1,20 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListItemsUpdate(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id', 'list_item_id', 'comment')
10
+
11
+ list_id = options.pop('list_id')
12
+ list_item_id = options.pop('list_item_id')
13
+
14
+ return Command(
15
+ method='put',
16
+ path='lists/{list_id}/items/{list_item_id}'.format(
17
+ list_id=list_id, list_item_id=list_item_id
18
+ ),
19
+ data=options,
20
+ )
File without changes
@@ -0,0 +1,11 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListsCreate(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'name', 'color', 'primary_field')
10
+
11
+ return Command(method='post', path='lists', data=options)
@@ -0,0 +1,13 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListsDelete(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id')
10
+
11
+ return Command(
12
+ method='delete', path='lists/{list_id}'.format(list_id=options['list_id']), data=None
13
+ )
@@ -0,0 +1,13 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListsGet(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id')
10
+
11
+ return Command(
12
+ method='get', path='lists/{list_id}'.format(list_id=options['list_id']), data=None
13
+ )
@@ -0,0 +1,7 @@
1
+ from castle.command import Command
2
+
3
+
4
+ class CommandsListsGetAll(object):
5
+ @staticmethod
6
+ def call(options=None):
7
+ return Command(method='get', path='lists', data=None)
@@ -0,0 +1,14 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListsQuery(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ for query_filter in options.get('filters') or []:
10
+ ValidatorsPresent.call(query_filter, 'field', 'op', 'value')
11
+ if options.get('sort'):
12
+ ValidatorsPresent.call(options['sort'], 'field', 'order')
13
+
14
+ return Command(method='post', path='lists/query', data=options)
@@ -0,0 +1,13 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsListsUpdate(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'list_id')
10
+
11
+ list_id = options.pop('list_id')
12
+
13
+ return Command(method='put', path='lists/{list_id}'.format(list_id=list_id), data=options)
castle/commands/log.py ADDED
@@ -0,0 +1,18 @@
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
+
6
+
7
+ class CommandsLog(object):
8
+ def __init__(self, context):
9
+ self.context = context
10
+
11
+ def call(self, options):
12
+ context = ContextMerge.call(self.context, options.get('context'))
13
+ context = ContextSanitize.call(context)
14
+ if context:
15
+ options.update({'context': context})
16
+ options.update({'sent_at': generate_timestamp.call()})
17
+
18
+ return Command(method='post', path='log', data=options)
File without changes
@@ -0,0 +1,11 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsPrivacyDeleteData(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'identifier', 'identifier_type')
10
+
11
+ return Command(method='delete', path='privacy/users', data=options)
@@ -0,0 +1,11 @@
1
+ from castle.command import Command
2
+ from castle.validators.present import ValidatorsPresent
3
+
4
+
5
+ class CommandsPrivacyRequestData(object):
6
+ @staticmethod
7
+ def call(options=None):
8
+ options = options or {}
9
+ ValidatorsPresent.call(options, 'identifier', 'identifier_type')
10
+
11
+ return Command(method='post', path='privacy/users', data=options)
@@ -0,0 +1,18 @@
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
+
6
+
7
+ class CommandsRisk(object):
8
+ def __init__(self, context):
9
+ self.context = context
10
+
11
+ def call(self, options):
12
+ context = ContextMerge.call(self.context, options.get('context'))
13
+ context = ContextSanitize.call(context)
14
+ if context:
15
+ options.update({'context': context})
16
+ options.update({'sent_at': generate_timestamp.call()})
17
+
18
+ return Command(method='post', path='risk', data=options)