Skip to content

Commit 80cc908

Browse files
committed
feat(dev): disable blueprints, config validators;
- JSON string loading config validator. - Safe url came_from. - Disable JWT login windows by default.
1 parent 5e2ad8c commit 80cc908

File tree

6 files changed

+55
-57
lines changed

6 files changed

+55
-57
lines changed

ckanext/language_domains/assets/webassets.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ css:
22
contents:
33
- styles/language_domains.css
44
output: language_domains/%(version)s_language_domains.css
5-
filters: cache_bust
65

76
js_login_sender:
87
contents:
@@ -11,7 +10,6 @@ js_login_sender:
1110
preload:
1211
- base/main # need ckan JS and jQuery
1312
output: language_domains/%(version)s_language_login_sender.js
14-
filters: cache_bust
1513

1614
js_login_receiver:
1715
contents:
@@ -20,7 +18,6 @@ js_login_receiver:
2018
preload:
2119
- base/main # need ckan JS and jQuery
2220
output: language_domains/%(version)s_language_login_receiver.js
23-
filters: cache_bust
2421

2522
js_logout_sender:
2623
contents:
@@ -29,7 +26,6 @@ js_logout_sender:
2926
preload:
3027
- base/main # need ckan JS and jQuery
3128
output: language_domains/%(version)s_language_logout_sender.js
32-
filters: cache_bust
3329

3430
js_logout_receiver:
3531
contents:
@@ -38,4 +34,3 @@ js_logout_receiver:
3834
preload:
3935
- base/main # need ckan JS and jQuery
4036
output: language_domains/%(version)s_language_logout_receiver.js
41-
filters: cache_bust

ckanext/language_domains/blueprint.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from werkzeug.datastructures import ImmutableMultiDict
44
from urllib.parse import urlsplit
55
from logging import getLogger
6-
import json
76
import jwt
87
import datetime
98

@@ -56,17 +55,14 @@ def login_master():
5655
return h.redirect_to('user.login')
5756

5857
came_from = request.args.get('_came_from', '')
58+
if not h.url_is_local(came_from):
59+
came_from = ''
5960

6061
default_domain = config.get('ckan.site_url', '')
6162
uri_parts = urlsplit(default_domain)
6263
domain_scheme = uri_parts.scheme
6364

64-
language_domains = config.get('ckanext.language_domains.domain_map', '')
65-
if not language_domains:
66-
language_domains = {}
67-
else:
68-
language_domains = json.loads(language_domains)
69-
65+
language_domains = config.get('ckanext.language_domains.domain_map')
7066
current_domain = request.url
7167
uri_parts = urlsplit(current_domain)
7268
current_domain = uri_parts.netloc
@@ -109,11 +105,9 @@ def login():
109105
uri_parts = urlsplit(default_domain)
110106
domain_scheme = uri_parts.scheme
111107

112-
language_domains = config.get('ckanext.language_domains.domain_map', '')
108+
language_domains = config.get('ckanext.language_domains.domain_map')
113109
trusted_domains = []
114110
if language_domains:
115-
language_domains = json.loads(language_domains)
116-
117111
current_domain = request.url
118112
uri_parts = urlsplit(current_domain)
119113
current_domain = uri_parts.netloc
@@ -210,17 +204,14 @@ def logout_master():
210204
return h.redirect_to('user.login')
211205

212206
came_from = request.args.get('_came_from', '')
207+
if not h.url_is_local(came_from):
208+
came_from = ''
213209

214210
default_domain = config.get('ckan.site_url', '')
215211
uri_parts = urlsplit(default_domain)
216212
domain_scheme = uri_parts.scheme
217213

218-
language_domains = config.get('ckanext.language_domains.domain_map', '')
219-
if not language_domains:
220-
language_domains = {}
221-
else:
222-
language_domains = json.loads(language_domains)
223-
214+
language_domains = config.get('ckanext.language_domains.domain_map')
224215
current_domain = request.url
225216
uri_parts = urlsplit(current_domain)
226217
current_domain = uri_parts.netloc
@@ -314,11 +305,9 @@ def logout():
314305
uri_parts = urlsplit(default_domain)
315306
domain_scheme = uri_parts.scheme
316307

317-
language_domains = config.get('ckanext.language_domains.domain_map', '')
308+
language_domains = config.get('ckanext.language_domains.domain_map')
318309
trusted_domains = []
319310
if language_domains:
320-
language_domains = json.loads(language_domains)
321-
322311
current_domain = request.url
323312
uri_parts = urlsplit(current_domain)
324313
current_domain = uri_parts.netloc

ckanext/language_domains/config_declaration.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@ groups:
77
description: |
88
Dictionary of language locales to domain list
99
required: true
10+
validators: load_json_string
1011
- key: ckanext.language_domains.root_paths
1112
example: {"example.com": "/data", "exemple.com": "/data"}
1213
description: |
1314
Dictionary of domains and their CKAN root_paths
1415
required: false
16+
validators: load_json_string
1517
- key: ckanext.language_domains.secret
1618
example: 'thisisalegitsecret'
1719
description: |
1820
Secret key for JWT encoding.
1921
required: true
22+
- key: ckanext.language_domains.enable_jwt_login
23+
example: true
24+
default: false
25+
description: >
26+
Enables multi-domain login and logout via JS
27+
window messaging with JWT tokens. Requires
28+
ckanext.language_domains.secret to be set.
29+
required: false
30+
validators: ignore_missing boolean_validator

ckanext/language_domains/helpers.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
)
55
from urllib.parse import urlparse, urlunparse
66
from urllib.parse import urlsplit
7-
import json
87
import re
98

109
from typing import Any, cast, Union, Tuple, Dict
@@ -38,11 +37,7 @@ def _get_correct_language_domain() -> Tuple[str, str]:
3837
default_domain = config.get('ckan.site_url', '')
3938
uri_parts = urlsplit(default_domain)
4039
default_scheme = uri_parts.scheme
41-
language_domains = config.get('ckanext.language_domains.domain_map', '')
42-
if not language_domains:
43-
language_domains = {}
44-
else:
45-
language_domains = json.loads(language_domains)
40+
language_domains = config.get('ckanext.language_domains.domain_map')
4641
current_lang = h.lang()
4742
correct_lang_domain = current_domain
4843
domain_index_match = _get_domain_index(current_domain, language_domains)
@@ -79,11 +74,7 @@ def redirect_to(*args: Any, **kw: Any) -> Response:
7974
status_code = 301
8075
_url = _url[len(f'/{current_lang}'):]
8176
_scheme, _host = _get_correct_language_domain()
82-
root_paths = config.get('ckanext.language_domains.root_paths', '')
83-
if not root_paths:
84-
root_paths = {}
85-
else:
86-
root_paths = json.loads(root_paths)
77+
root_paths = config.get('ckanext.language_domains.root_paths')
8778
root_path = root_paths.get(_host, '').rstrip('/')
8879
root_path = root_path.replace('/{{LANG}}', '')
8980
if not _url.startswith(root_path):
@@ -146,11 +137,7 @@ def local_url(url_to_amend: str, **kw: Any):
146137

147138
# ckan.root_path is defined when we have none standard language
148139
# position in the url
149-
root_paths = config.get('ckanext.language_domains.root_paths', '')
150-
if not root_paths:
151-
root_paths = {}
152-
else:
153-
root_paths = json.loads(root_paths)
140+
root_paths = config.get('ckanext.language_domains.root_paths')
154141
root_path = root_paths.get(host, '').rstrip('/')
155142
if root_path:
156143
# FIXME this can be written better once

ckanext/language_domains/plugin.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
from logging import getLogger
2-
import json
32
from urllib.parse import urlsplit
43

54
from typing import Any, Optional, List, Tuple, Callable, Dict
65
from flask import Blueprint
7-
from ckan.types import CKANApp
6+
from ckan.types import CKANApp, Validator
87
from ckan.common import CKANConfig
98

109
import ckan.plugins as plugins
1110
import ckan.lib.helpers as core_helpers
1211

13-
from ckanext.language_domains import helpers
12+
from ckanext.language_domains import helpers, validators
1413
from ckanext.language_domains.blueprint import language_domain_views
1514

1615

@@ -21,6 +20,7 @@
2120
class LanguageDomainsPlugin(plugins.SingletonPlugin):
2221
plugins.implements(plugins.IMiddleware, inherit=True)
2322
plugins.implements(plugins.IConfigurer)
23+
plugins.implements(plugins.IValidators)
2424
plugins.implements(plugins.ITemplateHelpers)
2525
plugins.implements(plugins.IBlueprint)
2626

@@ -31,14 +31,23 @@ def make_middleware(self, app: CKANApp, config: 'CKANConfig') -> CKANApp:
3131
# IConfigurer
3232
def update_config(self, config: 'CKANConfig'):
3333
# NOTE: monkey patch these core helpers as the other helpers call them directly
34+
# TODO: fix upstream helpers to always use h object helpers!!!
3435
core_helpers.redirect_to = helpers.redirect_to
3536
core_helpers.get_site_protocol_and_host = helpers.get_site_protocol_and_host
3637
core_helpers._local_url = helpers.local_url
3738

38-
plugins.toolkit.add_template_directory(config, 'templates')
39-
plugins.toolkit.add_resource('assets', 'language_domain_assets')
39+
if plugins.toolkit.config.get('ckanext.language_domains.'
40+
'enable_jwt_login', False):
41+
plugins.toolkit.add_template_directory(config, 'templates')
42+
plugins.toolkit.add_resource('assets', 'language_domain_assets')
4043

41-
config['ckan.auth.route_after_login'] = 'language_domains.login_master'
44+
config['ckan.auth.route_after_login'] = 'language_domains.login_master'
45+
46+
# IValidators
47+
def get_validators(self) -> Dict[str, Validator]:
48+
return {
49+
'load_json_string': validators.load_json_string,
50+
}
4251

4352
# ITemplateHelpers
4453
def get_helpers(self) -> Dict[str, Callable[..., Any]]:
@@ -47,26 +56,22 @@ def get_helpers(self) -> Dict[str, Callable[..., Any]]:
4756

4857
# IBlueprint
4958
def get_blueprint(self) -> List[Blueprint]:
50-
return [language_domain_views]
59+
if plugins.toolkit.config.get('ckanext.language_domains.'
60+
'enable_jwt_login', False):
61+
return [language_domain_views]
62+
return []
63+
5164

5265

5366
class LanguageDomainMiddleware(object):
5467
def __init__(self, app: Any, config: 'CKANConfig'):
5568
self.app = app
56-
language_domains = config.get('ckanext.language_domains.domain_map', '')
57-
if not language_domains:
58-
self.language_domains = {}
59-
else:
60-
self.language_domains = json.loads(language_domains)
69+
self.language_domains = config.get('ckanext.language_domains.domain_map')
6170
default_domain = config.get('ckan.site_url', '')
6271
uri_parts = urlsplit(default_domain)
6372
self.default_domain = uri_parts.netloc
6473
self.domain_scheme = uri_parts.scheme
65-
root_paths = config.get('ckanext.language_domains.root_paths', '')
66-
if not root_paths:
67-
self.root_paths = {}
68-
else:
69-
self.root_paths = json.loads(root_paths)
74+
self.root_paths = config.get('ckanext.language_domains.root_paths')
7075

7176
def __call__(self, environ: Any, start_response: Any) -> Any:
7277
extra_response_headers = []
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import json
2+
3+
from typing import Any
4+
5+
6+
def load_json_string(value: Any):
7+
if isinstance(value, dict):
8+
return value
9+
if not value:
10+
return {}
11+
return json.loads(value)

0 commit comments

Comments
 (0)