Skip to content

Commit

Permalink
[feature] Added configurable setting for API host
Browse files Browse the repository at this point in the history
Added "OPENWISP_CONTROLLER_API_HOST" for configuring
different host for API endpoints.
  • Loading branch information
pandafy committed Jun 2, 2021
1 parent a2cdd44 commit ce86118
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"gettext": true,
"JSONEditor": true,
"ReconnectingWebSocket": true,
"userLanguage": true
"userLanguage": true,
"owControllerApiHost": true
},
"browser": true
}
51 changes: 51 additions & 0 deletions openwisp_controller/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from django.conf import settings
from django.core import checks

from . import settings as app_settings


def check_cors_configuration(app_configs, **kwargs):
errors = []
if not app_settings.OPENWISP_CONTROLLER_API_HOST:
return errors

if not (
'corsheaders' in settings.INSTALLED_APPS
and 'corsheaders.middleware.CorsMiddleware' in settings.MIDDLEWARE
):
errors.append(
checks.Warning(
msg='Improperly Configured',
hint=(
'"django-cors-headers" is either not installed or improperly '
'configured. CORS configuration is required for using '
'"OPENWISP_CONTROLLER_API_HOST" settings. '
' Configure equivalent CORS rules on your server '
'if you are not using "django-cors-headers".'
),
obj='Settings',
)
)
return errors


def check_openwisp_controller_ctx_processor(app_config, **kwargs):
errors = []
ctx_processor = 'openwisp_controller.context_processors.controller_api_settings'

if not app_settings.OPENWISP_CONTROLLER_API_HOST:
return errors

if not (ctx_processor in settings.TEMPLATES[0]['OPTIONS']['context_processors']):
errors.append(
checks.Warning(
msg='Improperly Configured',
hint=(
f'"{ctx_processor} is absent from context processors.'
'It is required to be added in TEMPLATES["context_processor"] '
'for "OPENWISP_CONTROLLER_API_HOST" to work properly.'
),
obj='Settings',
)
)
return errors
11 changes: 11 additions & 0 deletions openwisp_controller/config/templates/admin/config/change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
{% endif %}
{% endblock %}

{% block extrahead %}
<script>
{% if OPENWISP_CONTROLLER_API_HOST %}
const owControllerApiHost = new URL('{{ OPENWISP_CONTROLLER_API_HOST }}');
{% else %}
const owControllerApiHost = window.location;
{% endif %}
</script>
{{ block.super}}
{% endblock extrahead %}

{% block object-tools-items %}
<li>
{% if download_url %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var interpolate = interpolate || function(){};

const deviceId = getObjectIdFromUrl();
const commandWebSocket = new ReconnectingWebSocket(
`${getWebSocketProtocol()}${location.host}/ws/device/${getObjectIdFromUrl()}/command`,
`${getWebSocketProtocol()}${owControllerApiHost.host}/ws/device/${getObjectIdFromUrl()}/command`,
null, {
debug: false
}
Expand Down Expand Up @@ -326,7 +326,7 @@ function initCommandOverlay($) {
};
$.ajax({
type: 'POST',
url: `/api/v1/device/${deviceId}/command/`,
url: `${owControllerApiHost.origin}/api/v1/device/${deviceId}/command/`,
headers: {
'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val()
},
Expand Down
13 changes: 0 additions & 13 deletions openwisp_controller/connection/templates/admin/change_form.html

This file was deleted.

19 changes: 18 additions & 1 deletion openwisp_controller/connection/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import json
from unittest.mock import patch

from django.test import TestCase
from django.test import TestCase, override_settings
from django.urls import reverse
from swapper import load_model

from ... import settings as module_settings
from ...config.tests.test_admin import TestAdmin as TestConfigAdmin
from ...tests import _get_updated_templates_settings
from ...tests.utils import TestAdminMixin
from ..connectors.ssh import Ssh
from ..widgets import CredentialsSchemaWidget
Expand Down Expand Up @@ -176,5 +179,19 @@ def test_commands_schema_view(self):
self.assertIn('change_password', result)
self.assertIn('reboot', result)

@patch.object(
module_settings, 'OPENWISP_CONTROLLER_API_HOST', 'https://example.com',
)
def test_notification_host_setting(self, ctx_processors=[]):
url = reverse(
f'admin:{self.config_app_label}_device_change', args=(self.device.id,)
)
with override_settings(
TEMPLATES=_get_updated_templates_settings(ctx_processors)
):
response = self.client.get(url)
self.assertContains(response, 'https://example.com')
self.assertNotContains(response, 'owControllerApiHost = window.location')


del TestConfigAdmin
7 changes: 7 additions & 0 deletions openwisp_controller/context_processors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import settings as app_settings


def controller_api_settings(request):
return {
'OPENWISP_CONTROLLER_API_HOST': app_settings.OPENWISP_CONTROLLER_API_HOST,
}
3 changes: 3 additions & 0 deletions openwisp_controller/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.conf import settings

OPENWISP_CONTROLLER_API_HOST = getattr(settings, 'OPENWISP_CONTROLLER_API_HOST', None)
14 changes: 14 additions & 0 deletions openwisp_controller/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.conf import settings

from openwisp_utils.utils import deepcopy


def _get_updated_templates_settings(context_processors=[]):
template_settings = deepcopy(settings.TEMPLATES[0])
if len(context_processors):
template_settings['OPTIONS']['context_processors'].extend(context_processors)
else:
template_settings['OPTIONS']['context_processors'].append(
'openwisp_controller.context_processors.controller_api_settings'
)
return [template_settings]
47 changes: 47 additions & 0 deletions openwisp_controller/tests/test_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from unittest.mock import patch

from django.test import TestCase, override_settings

from .. import checks, context_processors
from .. import settings as app_settings
from . import _get_updated_templates_settings


class TestUtilities(TestCase):
@patch.object(
app_settings, 'OPENWISP_CONTROLLER_API_HOST', 'https://example.com',
)
def test_cors_not_configured_check(self):
error_message = '"django-cors-headers" is either not installed or improperly'

def run_check():
return checks.check_cors_configuration(None).pop()

with self.subTest('Test "django-cors-headers" absent in INSTALLED_APPS'):
error = run_check()
self.assertIn(error_message, error.hint)

with self.subTest('Test "django-cors-headers" middleware not configured'):
error = run_check()
self.assertIn(error_message, error.hint)

@patch.object(
app_settings, 'OPENWISP_CONTROLLER_API_HOST', 'https://example.com',
)
def test_openwisp_controller_context_processor_check(self):
def runcheck():
return checks.check_openwisp_controller_ctx_processor(None).pop()

error_message = 'absent from context processor'
error = runcheck()
self.assertIn(error_message, error.hint)

@patch.object(
app_settings, 'OPENWISP_CONTROLLER_API_HOST', 'https://example.com',
)
def test_openwisp_controller_context_processor(self):
with override_settings(TEMPLATES=_get_updated_templates_settings()):
context = {
'OPENWISP_CONTROLLER_API_HOST': 'https://example.com',
}
self.assertEqual(context_processors.controller_api_settings(None), context)
6 changes: 0 additions & 6 deletions tests/openwisp2/sample_connection/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ class TestConnectionAdmin(BaseTestConnectionAdmin):
class TestCommandInlines(BaseTestCommandInlines):
config_app_label = 'sample_config'

def test_notification_host_setting(self):
# TODO: Fix this failing test
# ctx_processor = 'openwisp2.context_processors.controller_api_settings'
# super().test_notification_host_setting([ctx_processor])
pass


class TestModels(BaseTestModels):
app_label = 'sample_connection'
Expand Down

0 comments on commit ce86118

Please sign in to comment.