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
19 changes: 19 additions & 0 deletions ckanext/unaids/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,25 @@ def get_support_url():
return None


def get_freshdesk_widget_id():
raw = (os.environ.get('CKAN_UNAIDS_FRESHDESK_WIDGET_ID') or '').strip() \
or toolkit.config.get('ckanext.unaids.freshdesk_widget_id', None)
try:
widget_id = int(str(raw).strip())
except (TypeError, ValueError):
return None
# id is emitted into a JS Number; reject ids that would lose precision
if 0 < widget_id <= 2 ** 53 - 1:
return widget_id
return None


def get_freshdesk_app_name():
name = (os.environ.get('CKAN_UNAIDS_FRESHDESK_APP_NAME') or '').strip() \
or (toolkit.config.get('ckanext.unaids.freshdesk_app_name') or '')
return name.strip() or None


def is_an_estimates_dataset(dataset_type_name):
return 'estimates' in dataset_type_name.lower()

Expand Down
4 changes: 4 additions & 0 deletions ckanext/unaids/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
build_pages_nav_main,
get_google_analytics_id,
get_support_url,
get_freshdesk_widget_id,
get_freshdesk_app_name,
is_an_estimates_dataset,
url_encode,
get_profile_editor_url,
Expand Down Expand Up @@ -154,6 +156,8 @@ def get_helpers(self):
"build_nav_main": build_pages_nav_main,
"get_google_analytics_id": get_google_analytics_id,
"get_support_url": get_support_url,
"get_freshdesk_widget_id": get_freshdesk_widget_id,
"get_freshdesk_app_name": get_freshdesk_app_name,
"is_an_estimates_dataset": is_an_estimates_dataset,
"url_encode": url_encode,
"get_profile_editor_url": get_profile_editor_url,
Expand Down
108 changes: 107 additions & 1 deletion ckanext/unaids/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import pytest
from ckan.tests import factories
from ckan.plugins import toolkit
from ckanext.unaids.helpers import get_support_url
from ckanext.unaids.helpers import (
get_support_url,
get_freshdesk_widget_id,
get_freshdesk_app_name,
)


class TestGetSupportUrl(object):
Expand Down Expand Up @@ -43,6 +47,108 @@ def test_env_takes_precedence_over_config(self, monkeypatch):
assert get_support_url() == 'https://env.example.org/new'


class TestGetFreshdeskWidgetId(object):
@pytest.mark.parametrize('value,expected', [
('157000000691', 157000000691),
(' 12345 ', 12345),
('0', None),
('-5', None),
('abc', None),
('12ab', None),
('0};alert(1)//', None),
('', None),
('9007199254740991', 9007199254740991),
('9007199254740992', None),
])
def test_only_positive_int_is_exposed(self, monkeypatch, value, expected):
monkeypatch.setenv('CKAN_UNAIDS_FRESHDESK_WIDGET_ID', value)
assert get_freshdesk_widget_id() == expected

def test_blank_env_falls_back_to_config(self, monkeypatch):
monkeypatch.setenv('CKAN_UNAIDS_FRESHDESK_WIDGET_ID', ' ')
monkeypatch.setitem(
toolkit.config, 'ckanext.unaids.freshdesk_widget_id', '111')
assert get_freshdesk_widget_id() == 111

def test_none_when_unset(self, monkeypatch):
monkeypatch.delenv('CKAN_UNAIDS_FRESHDESK_WIDGET_ID', raising=False)
monkeypatch.delitem(
toolkit.config, 'ckanext.unaids.freshdesk_widget_id', raising=False)
assert get_freshdesk_widget_id() is None

def test_falls_back_to_config(self, monkeypatch):
monkeypatch.delenv('CKAN_UNAIDS_FRESHDESK_WIDGET_ID', raising=False)
monkeypatch.setitem(
toolkit.config, 'ckanext.unaids.freshdesk_widget_id', '157000000691')
assert get_freshdesk_widget_id() == 157000000691

def test_env_takes_precedence_over_config(self, monkeypatch):
monkeypatch.setitem(
toolkit.config, 'ckanext.unaids.freshdesk_widget_id', '111')
monkeypatch.setenv('CKAN_UNAIDS_FRESHDESK_WIDGET_ID', '222')
assert get_freshdesk_widget_id() == 222


class TestGetFreshdeskAppName(object):
@pytest.mark.parametrize('value,expected', [
('AIDS Data Repository (ADR)', 'AIDS Data Repository (ADR)'),
(' AIDS Data Repository (ADR) ', 'AIDS Data Repository (ADR)'),
('', None),
(' ', None),
])
def test_env_value(self, monkeypatch, value, expected):
monkeypatch.setenv('CKAN_UNAIDS_FRESHDESK_APP_NAME', value)
assert get_freshdesk_app_name() == expected

def test_none_when_unset(self, monkeypatch):
monkeypatch.delenv('CKAN_UNAIDS_FRESHDESK_APP_NAME', raising=False)
monkeypatch.delitem(
toolkit.config, 'ckanext.unaids.freshdesk_app_name', raising=False)
assert get_freshdesk_app_name() is None

def test_blank_env_falls_back_to_config(self, monkeypatch):
monkeypatch.setenv('CKAN_UNAIDS_FRESHDESK_APP_NAME', ' ')
monkeypatch.setitem(
toolkit.config, 'ckanext.unaids.freshdesk_app_name', 'ADR')
assert get_freshdesk_app_name() == 'ADR'

def test_env_takes_precedence_over_config(self, monkeypatch):
monkeypatch.setitem(
toolkit.config, 'ckanext.unaids.freshdesk_app_name', 'FromConfig')
monkeypatch.setenv('CKAN_UNAIDS_FRESHDESK_APP_NAME', 'FromEnv')
assert get_freshdesk_app_name() == 'FromEnv'


@pytest.mark.ckan_config(
'ckan.plugins', 'activity ytp_request unaids pages scheming_datasets')
@pytest.mark.usefixtures('with_plugins')
class TestFreshdeskWidgetTemplate(object):
@pytest.mark.ckan_config(
'ckanext.unaids.freshdesk_widget_id', '156000001542')
@pytest.mark.ckan_config(
'ckanext.unaids.freshdesk_app_name', '</script><script>alert(1)</script>')
def test_widget_and_prefill_render_escaped(self, app):
body = app.get('/', follow_redirects=False).body
assert 'widget.freshworks.com/widgets/156000001542.js' in body
assert "FreshworksWidget('prefill', 'ticketForm'" in body
# app name is emitted via tojson: value present but escaped, so it
# can't break out of the inline <script>
assert 'alert(1)' in body
assert '</script><script>alert(1)</script>' not in body

@pytest.mark.ckan_config(
'ckanext.unaids.freshdesk_widget_id', '156000001542')
def test_widget_without_prefill_when_app_name_unset(self, app):
body = app.get('/', follow_redirects=False).body
assert 'widget.freshworks.com/widgets/156000001542.js' in body
assert "FreshworksWidget('prefill'" not in body

def test_no_widget_when_id_unset(self, app):
body = app.get('/', follow_redirects=False).body
assert 'widget.freshworks.com' not in body
assert 'fwSettings' not in body


@pytest.mark.ckan_config('ckan.plugins', 'activity ytp_request unaids scheming_datasets versions')
@pytest.mark.usefixtures('with_plugins')
class TestDatasetLockHelper(object):
Expand Down
12 changes: 12 additions & 0 deletions ckanext/unaids/theme/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
gtag('config', '{{ h.get_google_analytics_id() }}');
</script>
{% endif %}
{% set fw_id = h.get_freshdesk_widget_id() %}
{% if fw_id %}
{% set fw_app_name = h.get_freshdesk_app_name() %}
<script>
window.fwSettings = {'widget_id': {{ fw_id | tojson }}};
!function(){if("function"!=typeof window.FreshworksWidget){var n=function(){n.q.push(arguments)};n.q=[],window.FreshworksWidget=n}}()
{% if fw_app_name %}
FreshworksWidget('prefill', 'ticketForm', {custom_fields: {cf_select_model: {{ fw_app_name | tojson }}}});
{% endif %}
</script>
<script type="text/javascript" src="https://widget.freshworks.com/widgets/{{ fw_id | tojson }}.js" async defer></script>
{% endif %}
{% endblock %}

{% block scripts %}
Expand Down