Skip to content

Commit 4cec0c4

Browse files
committed
#331 added support for ip4 subnets in trusted_ips
1 parent bcc8de6 commit 4cec0c4

9 files changed

Lines changed: 81 additions & 27 deletions

File tree

src/auth/identification.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import tornado.websocket
66

7+
from model.trusted_ips import TrustedIpValidator
78
from utils import tornado_utils, date_utils, audit_utils
89
from utils.date_utils import days_to_ms
910

@@ -39,13 +40,13 @@ class IpBasedIdentification(Identification):
3940
COOKIE_KEY = 'client_id_token'
4041
EMPTY_TOKEN = (None, None)
4142

42-
def __init__(self, trusted_ips, user_header_name) -> None:
43-
self._trusted_ips = set(trusted_ips)
43+
def __init__(self, ip_validator: TrustedIpValidator, user_header_name) -> None:
44+
self._ip_validator = ip_validator
4445
self._user_header_name = user_header_name
4546

4647
def identify(self, request_handler):
4748
remote_ip = request_handler.request.remote_ip
48-
new_trusted = remote_ip in self._trusted_ips
49+
new_trusted = self._ip_validator.is_trusted(remote_ip)
4950

5051
if new_trusted:
5152
if request_handler.get_cookie(self.COOKIE_KEY):
@@ -77,7 +78,7 @@ def identify(self, request_handler):
7778

7879
def identify_for_audit(self, request_handler):
7980
remote_ip = request_handler.request.remote_ip
80-
if (remote_ip in self._trusted_ips) and (self._user_header_name):
81+
if self._ip_validator.is_trusted(remote_ip) and (self._user_header_name):
8182
return request_handler.request.headers.get(self._user_header_name, None)
8283
return None
8384

src/execution/logging.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ def find_history_entry(self, execution_id, user_id):
190190
LOGGER.warning('find_history_entry: cannot parse file for %s', execution_id)
191191

192192
elif not self._can_access_entry(entry, user_id):
193-
message = 'User ' + user_id + ' has not access to execution #' + str(execution_id)
194-
LOGGER.warning('%s. Original user: %s', message, execution_id)
193+
message = 'User ' + user_id + ' has no access to execution #' + str(execution_id)
194+
LOGGER.warning('%s. Original user: %s', message, entry.user_id)
195195
raise AccessProhibitedException(message)
196196

197197
return entry

src/model/__init__.py

Whitespace-only changes.

src/model/server_conf.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from auth.authorization import ANY_USER
77
from model import model_helper
88
from model.model_helper import read_list, read_int_from_config, read_bool_from_config
9+
from model.trusted_ips import TrustedIpValidator
910
from utils.string_utils import strip
1011

1112
LOGGER = logging.getLogger('server_conf')
@@ -25,7 +26,7 @@ def __init__(self) -> None:
2526
self.admin_config = None
2627
self.title = None
2728
self.enable_script_titles = None
28-
self.trusted_ips = []
29+
self.ip_validator = TrustedIpValidator([])
2930
self.user_groups = None
3031
self.admin_users = []
3132
self.full_history_users = []
@@ -113,11 +114,11 @@ def from_json(conf_path, temp_folder):
113114
def_admins = def_trusted_ips
114115

115116
if access_config:
116-
config.trusted_ips = strip(read_list(access_config, 'trusted_ips', default=def_trusted_ips))
117+
trusted_ips = strip(read_list(access_config, 'trusted_ips', default=def_trusted_ips))
117118
admin_users = _parse_admin_users(access_config, default_admins=def_admins)
118119
full_history_users = _parse_history_users(access_config)
119120
else:
120-
config.trusted_ips = def_trusted_ips
121+
trusted_ips = def_trusted_ips
121122
admin_users = def_admins
122123
full_history_users = []
123124

@@ -129,6 +130,7 @@ def from_json(conf_path, temp_folder):
129130
config.admin_users = admin_users
130131
config.full_history_users = full_history_users
131132
config.user_header_name = user_header_name
133+
config.ip_validator = TrustedIpValidator(trusted_ips)
132134

133135
config.max_request_size_mb = read_int_from_config('max_request_size', json_object, default=10)
134136

src/model/trusted_ips.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ipaddress
2+
3+
4+
class TrustedIpValidator:
5+
def __init__(self, trusted_ips) -> None:
6+
self._simple_ips = {ip for ip in trusted_ips if '/' not in ip}
7+
self._networks = [ipaddress.ip_network(ip) for ip in trusted_ips if '/' in ip]
8+
9+
def is_trusted(self, ip):
10+
if ip in self._simple_ips:
11+
return True
12+
13+
if self._networks:
14+
address = ipaddress.ip_address(ip)
15+
for network in self._networks:
16+
if address in network:
17+
return True
18+
19+
return False

src/tests/ip_idenfication_test.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from auth.identification import IpBasedIdentification
44
from auth.tornado_auth import TornadoAuth
5+
from model.trusted_ips import TrustedIpValidator
56
from tests.test_utils import mock_object
67
from utils import date_utils
78

@@ -13,7 +14,7 @@ def mock_request_handler(ip=None, x_forwarded_for=None, x_real_ip=None, saved_to
1314

1415
handler_mock.application = mock_object()
1516
handler_mock.application.auth = TornadoAuth(None)
16-
handler_mock.application.identification = IpBasedIdentification(['127.0.0.1'], user_header_name)
17+
handler_mock.application.identification = IpBasedIdentification(TrustedIpValidator(['127.0.0.1']), user_header_name)
1718

1819
handler_mock.request = mock_object()
1920
handler_mock.request.headers = {}
@@ -55,22 +56,22 @@ def clear_cookie(key):
5556
class IpIdentificationTest(unittest.TestCase):
5657

5758
def test_localhost_ip_trusted_identification(self):
58-
identification = IpBasedIdentification(['127.0.0.1'], None)
59+
identification = IpBasedIdentification(TrustedIpValidator(['127.0.0.1']), None)
5960
id = identification.identify(mock_request_handler(ip='127.0.0.1'))
6061
self.assertEqual('127.0.0.1', id)
6162

6263
def test_some_ip_trusted_identification(self):
63-
identification = IpBasedIdentification(['192.168.21.13'], None)
64+
identification = IpBasedIdentification(TrustedIpValidator(['192.168.21.13']), None)
6465
id = identification.identify(mock_request_handler(ip='192.168.21.13'))
6566
self.assertEqual('192.168.21.13', id)
6667

6768
def test_ip_untrusted_identification(self):
68-
identification = IpBasedIdentification([], None)
69+
identification = IpBasedIdentification(TrustedIpValidator([]), None)
6970
id = identification.identify(mock_request_handler(ip='192.168.21.13'))
7071
self.assertNotEqual('192.168.21.13', id)
7172

7273
def test_ip_untrusted_identification_for_different_connections(self):
73-
identification = IpBasedIdentification([], None)
74+
identification = IpBasedIdentification(TrustedIpValidator([]), None)
7475

7576
ids = set()
7677
for _ in range(0, 100):
@@ -79,22 +80,22 @@ def test_ip_untrusted_identification_for_different_connections(self):
7980
self.assertEqual(100, len(ids))
8081

8182
def test_ip_untrusted_identification_same_connection(self):
82-
identification = IpBasedIdentification([], None)
83+
identification = IpBasedIdentification(TrustedIpValidator([]), None)
8384

8485
request_handler = mock_request_handler(ip='192.168.21.13')
8586
id1 = identification.identify(request_handler)
8687
id2 = identification.identify(request_handler)
8788
self.assertEqual(id1, id2)
8889

8990
def test_proxied_ip_behind_trusted(self):
90-
identification = IpBasedIdentification(['127.0.0.1'], None)
91+
identification = IpBasedIdentification(TrustedIpValidator(['127.0.0.1']), None)
9192

9293
request_handler = mock_request_handler(ip='127.0.0.1', x_forwarded_for='192.168.21.13')
9394
id = identification.identify(request_handler)
9495
self.assertEqual('192.168.21.13', id)
9596

9697
def test_proxied_ip_behind_untrusted(self):
97-
identification = IpBasedIdentification([], None)
98+
identification = IpBasedIdentification(TrustedIpValidator([]), None)
9899

99100
request_handler = mock_request_handler(ip='127.0.0.1', x_forwarded_for='192.168.21.13')
100101
id = identification.identify(request_handler)
@@ -104,9 +105,9 @@ def test_proxied_ip_behind_untrusted(self):
104105
def test_change_to_trusted(self):
105106
request_handler = mock_request_handler(ip='192.168.21.13')
106107

107-
old_id = IpBasedIdentification([], None).identify(request_handler)
108+
old_id = IpBasedIdentification(TrustedIpValidator([]), None).identify(request_handler)
108109

109-
trusted_identification = IpBasedIdentification(['192.168.21.13'], None)
110+
trusted_identification = IpBasedIdentification(TrustedIpValidator(['192.168.21.13']), None)
110111
new_id = trusted_identification.identify(request_handler)
111112

112113
self.assertNotEqual(old_id, new_id)
@@ -116,10 +117,10 @@ def test_change_to_trusted(self):
116117
def test_change_to_untrusted(self):
117118
request_handler = mock_request_handler(ip='192.168.21.13')
118119

119-
trusted_identification = IpBasedIdentification(['192.168.21.13'], None)
120+
trusted_identification = IpBasedIdentification(TrustedIpValidator(['192.168.21.13']), None)
120121
old_id = trusted_identification.identify(request_handler)
121122

122-
new_id = IpBasedIdentification([], None).identify(request_handler)
123+
new_id = IpBasedIdentification(TrustedIpValidator([]), None).identify(request_handler)
123124

124125
self.assertNotEqual(old_id, new_id)
125126
self.assertNotEqual(new_id, '192.168.21.13')
@@ -128,7 +129,7 @@ def test_change_to_untrusted(self):
128129
def test_no_cookie_change_for_same_user(self):
129130
request_handler = mock_request_handler(ip='192.168.21.13')
130131

131-
identification = IpBasedIdentification([], None)
132+
identification = IpBasedIdentification(TrustedIpValidator([]), None)
132133

133134
identification.identify(request_handler)
134135
cookie1 = request_handler.get_cookie(COOKIE_KEY)
@@ -140,7 +141,7 @@ def test_no_cookie_change_for_same_user(self):
140141
def test_refresh_old_cookie_with_same_id(self):
141142
request_handler = mock_request_handler(ip='192.168.21.13')
142143

143-
identification = IpBasedIdentification([], None)
144+
identification = IpBasedIdentification(TrustedIpValidator([]), None)
144145

145146
id = '1234567'
146147
token_expiry = str(date_utils.get_current_millis() + date_utils.days_to_ms(2))
@@ -157,7 +158,7 @@ def test_broken_token_structure(self):
157158
request_handler = mock_request_handler(ip='192.168.21.13')
158159
request_handler.set_secure_cookie(COOKIE_KEY, 'something')
159160

160-
IpBasedIdentification([], None).identify(request_handler)
161+
IpBasedIdentification(TrustedIpValidator([]), None).identify(request_handler)
161162

162163
new_token = request_handler.get_cookie(COOKIE_KEY)
163164

@@ -167,7 +168,7 @@ def test_broken_token_timestamp(self):
167168
request_handler = mock_request_handler(ip='192.168.21.13')
168169
request_handler.set_secure_cookie(COOKIE_KEY, 'something&hello')
169170

170-
id = IpBasedIdentification([], None).identify(request_handler)
171+
id = IpBasedIdentification(TrustedIpValidator([]), None).identify(request_handler)
171172

172173
new_token = request_handler.get_cookie(COOKIE_KEY)
173174

@@ -178,7 +179,7 @@ def test_old_token_timestamp(self):
178179
request_handler = mock_request_handler(ip='192.168.21.13')
179180
request_handler.set_secure_cookie(COOKIE_KEY, 'something&100000')
180181

181-
id = IpBasedIdentification([], None).identify(request_handler)
182+
id = IpBasedIdentification(TrustedIpValidator([]), None).identify(request_handler)
182183

183184
new_token = request_handler.get_cookie(COOKIE_KEY)
184185

src/tests/model/__init__.py

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from unittest import TestCase
2+
3+
from parameterized import parameterized
4+
5+
from model.trusted_ips import TrustedIpValidator
6+
7+
8+
class TestTrustedIpValidator(TestCase):
9+
@parameterized.expand([
10+
(['192.168.0.15'], '192.168.0.15', True),
11+
(['192.168.0.0'], '192.168.0.15', False),
12+
(['192.168.0.1'], '192.168.0.15', False),
13+
(['127.0.0.1'], '127.0.0.1', True),
14+
(['::1'], '::1', True),
15+
(['192.168.0.15/32'], '192.168.0.15', True),
16+
(['192.168.0.16/32'], '192.168.0.15', False),
17+
(['192.168.0.14/31'], '192.168.0.15', True),
18+
(['192.168.0.16/31'], '192.168.0.15', False),
19+
(['192.168.0.0/28'], '192.168.0.15', True),
20+
(['192.168.0.0/28'], '192.168.0.15', True),
21+
(['192.168.0.0/28'], '192.168.0.16', False),
22+
(['192.168.0.16/28'], '192.168.0.15', False),
23+
(['192.168.0.16/28'], '192.168.0.16', True),
24+
(['192.168.0.0/24'], '192.168.0.16', True),
25+
(['192.168.0.0/16'], '192.168.32.127', True),
26+
])
27+
def test_is_trusted(self, configured_ips, user_ip, expected_result):
28+
validator = TrustedIpValidator(configured_ips)
29+
trusted = validator.is_trusted(user_ip)
30+
self.assertEqual(expected_result, trusted, user_ip + ' is trusted=' + str(trusted)
31+
+ ' but should be ' + str(expected_result) + ' for ' + str(configured_ips))

src/web/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ def init(server_config: ServerConfig,
931931
if auth.is_enabled():
932932
identification = AuthBasedIdentification(auth)
933933
else:
934-
identification = IpBasedIdentification(server_config.trusted_ips, server_config.user_header_name)
934+
identification = IpBasedIdentification(server_config.ip_validator, server_config.user_header_name)
935935

936936
downloads_folder = file_download_feature.get_result_files_folder()
937937

0 commit comments

Comments
 (0)