Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import and configure the library with your Castle API secret.

.. code:: python

from castle.configuration import configuration
from castle.configuration import configuration, WHITELISTED

# Same as setting it through Castle.api_secret
configuration.api_secret = ':YOUR-API-SECRET'
Expand All @@ -33,15 +33,15 @@ import and configure the library with your Castle API secret.
configuration.request_timeout = 1000

# Whitelisted and Blacklisted headers are case insensitive and allow to use _ and - as a separator, http prefixes are removed
# By default all headers are passed, but some are automatically scrubbed.
# If you need to apply a whitelist, we recommend using the minimum set of
# standard headers that we've exposed in the `WHITELISTED` constant.
# Whitelisted headers
configuration.whitelisted = ['X_HEADER']
# or append to default
configuration.whitelisted = configuration.whitelisted + ['http-x-header']
configuration.whitelisted = WHITELISTED + ['X_HEADER']

# Blacklisted headers take advantage over whitelisted elements
# Blacklisted headers take advantage over whitelisted elements. Note that
# some headers are always scrubbed, for security reasons.
configuration.blacklisted = ['HTTP-X-header']
# or append to default
configuration.blacklisted = configuration.blacklisted + ['X_HEADER']

Tracking
--------
Expand Down
36 changes: 20 additions & 16 deletions castle/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@
from castle.headers_formatter import HeadersFormatter

WHITELISTED = [
'User-Agent',
'Accept-Language',
'Accept-Encoding',
'Accept-Charset',
'Accept',
'Accept-Datetime',
'Forwarded',
'X-Forwarded',
'X-Real-IP',
'REMOTE_ADDR',
'X-Forwarded-For',
'CF_CONNECTING_IP'
"Accept",
"Accept-Charset",
"Accept-Datetime",
"Accept-Encoding",
"Accept-Language",
"Cache-Control",
"Connection",
"Content-Length",
"Content-Type",
"Cookie",
"Host",
"Origin",
"Pragma",
"Referer",
"TE",
"Upgrade-Insecure-Requests",
"User-Agent",
"X-Castle-Client-Id",
]

BLACKLISTED = ['HTTP_COOKIE']

# 500 milliseconds
REQUEST_TIMEOUT = 500
FAILOVER_STRATEGIES = ['allow', 'deny', 'challenge', 'throw']
Expand All @@ -29,8 +33,8 @@ def __init__(self):
self.host = 'api.castle.io'
self.port = 443
self.url_prefix = '/v1'
self.whitelisted = WHITELISTED
self.blacklisted = BLACKLISTED
self.whitelisted = []
self.blacklisted = []
self.request_timeout = REQUEST_TIMEOUT
self.failover_strategy = 'allow'

Expand Down
10 changes: 8 additions & 2 deletions castle/extractors/headers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from castle.headers_formatter import HeadersFormatter
from castle.configuration import configuration

DEFAULT_BLACKLIST = ['Cookie', 'Authorization']
DEFAULT_WHITELIST = ['User-Agent']


class ExtractorsHeaders(object):
def __init__(self, environ):
Expand All @@ -9,12 +12,15 @@ def __init__(self, environ):

def call(self):
headers = dict()
has_whitelist = len(configuration.whitelisted) > 0

for key, value in self.environ.items():
name = self.formatter.call(key)
if name not in configuration.whitelisted:
if has_whitelist and name not in configuration.whitelisted and name not in DEFAULT_WHITELIST:
headers[name] = True
continue
if name in configuration.blacklisted:
if name in configuration.blacklisted or name in DEFAULT_BLACKLIST:
headers[name] = True
continue
headers[name] = value

Expand Down
12 changes: 10 additions & 2 deletions castle/test/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ def test_init(self):
context = {
'active': True,
'client_id': '1234',
'headers': {'User-Agent': 'test', 'X-Forwarded-For': '217.144.192.112'},
'headers': {
'User-Agent': 'test',
'X-Forwarded-For': '217.144.192.112',
'X-Castle-Client-Id': '1234'
},
'ip': '217.144.192.112',
'library': {'name': 'castle-python', 'version': VERSION},
'origin': 'web',
Expand Down Expand Up @@ -189,7 +193,11 @@ def test_to_context(self):
context = {
'active': True,
'client_id': '1234',
'headers': {'User-Agent': 'test', 'X-Forwarded-For': '217.144.192.112'},
'headers': {
'User-Agent': 'test',
'X-Forwarded-For': '217.144.192.112',
'X-Castle-Client-Id': '1234'
},
'ip': '217.144.192.112',
'library': {'name': 'castle-python', 'version': VERSION},
'origin': 'web',
Expand Down
8 changes: 3 additions & 5 deletions castle/test/configuration_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from castle.test import unittest
from castle.exceptions import ConfigurationError
from castle.configuration import Configuration, WHITELISTED, BLACKLISTED
from castle.configuration import Configuration
from castle.headers_formatter import HeadersFormatter


Expand All @@ -11,10 +11,8 @@ def test_default_values(self):
self.assertEqual(config.host, 'api.castle.io')
self.assertEqual(config.port, 443)
self.assertEqual(config.url_prefix, '/v1')
self.assertEqual(config.whitelisted, [
HeadersFormatter.call(v) for v in WHITELISTED])
self.assertEqual(config.blacklisted, [
HeadersFormatter.call(v) for v in BLACKLISTED])
self.assertEqual(config.whitelisted, [])
self.assertEqual(config.blacklisted, [])
self.assertEqual(config.request_timeout, 500)
self.assertEqual(config.failover_strategy, 'allow')

Expand Down
10 changes: 7 additions & 3 deletions castle/test/context/default_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_default_context(self):
self.assertEqual(context['client_id'], client_id())
self.assertEqual(context['active'], True)
self.assertEqual(context['origin'], 'web')
self.assertEqual(context['headers'], {'X-Forwarded-For': request_ip()})
self.assertEqual(context['headers'], {'X-Forwarded-For': request_ip(), 'Cookie': True})
self.assertEqual(context['ip'], request_ip())
self.assertDictEqual(context['library'], {
'name': 'castle-python', 'version': __version__})
Expand All @@ -59,8 +59,12 @@ def test_default_context_with_extras(self):
self.assertEqual(context['origin'], 'web')
self.assertEqual(
context['headers'],
{'X-Forwarded-For': request_ip(), 'Accept-Language': 'en',
'User-Agent': 'test'}
{
'X-Forwarded-For': request_ip(),
'Accept-Language': 'en',
'User-Agent': 'test',
'Cookie': True
}
)
self.assertEqual(context['ip'], request_ip())
self.assertDictEqual(
Expand Down
15 changes: 8 additions & 7 deletions castle/test/extractors/headers_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from castle.test import unittest
from castle.configuration import configuration
from castle.configuration import configuration, WHITELISTED
from castle.extractors.headers import ExtractorsHeaders


Expand All @@ -9,7 +9,7 @@ def client_id():

def environ():
return {
'HTTP_X_FORWARDED_FOR': '1.2.3.4',
'HTTP_USER_AGENT': 'requests',
'HTTP_OK': 'OK',
'TEST': '1',
'HTTP_COOKIE': "__cid={client_id};other=efgh".format(client_id=client_id)
Expand All @@ -18,13 +18,14 @@ def environ():

class ExtractorsHeadersTestCase(unittest.TestCase):
def test_extract_headers(self):
configuration.whitelisted = []
self.assertEqual(ExtractorsHeaders(environ()).call(),
{'X-Forwarded-For': '1.2.3.4'})
{'User-Agent': 'requests', 'Ok': 'OK', 'Test': '1', 'Cookie': True})

def test_extend_whitelisted_headers(self):
configuration.whitelisted += ['TEST']
def test_add_whitelisted_headers(self):
configuration.whitelisted = WHITELISTED + ['TEST']
self.assertEqual(
ExtractorsHeaders(environ()).call(),
{'X-Forwarded-For': '1.2.3.4', 'Test': '1'}
{'User-Agent': 'requests', 'Test': '1', 'Cookie': True, 'Ok': True}
)
configuration.whitelisted.remove('Test')
configuration.whitelisted = []