Skip to content

[fix] Do not allow applying more than 1 templates of same VPN server #832#1020

Merged
nemesifier merged 3 commits into
masterfrom
issues/832-multiple-template-same-vpn
May 28, 2025
Merged

[fix] Do not allow applying more than 1 templates of same VPN server #832#1020
nemesifier merged 3 commits into
masterfrom
issues/832-multiple-template-same-vpn

Conversation

@pandafy
Copy link
Copy Markdown
Member

@pandafy pandafy commented May 2, 2025

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • N/A I have updated the documentation.

Reference to Existing Issue

Fixes #832

Screenshot

Screenshot from 2025-05-02 19-51-13

Validates if there are duplicate templates for the same VPN server.
Raises a ValidationError if duplicates are found.
"""
if action != 'pre_add':
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check makes the following test fail - when users switches the template of the device and both the templates belongs to the same VPN.

It happens because pre_add action is sent before pre_clear, thus the db still has old template assigned to the config and this logic raises an error.

def test_vpn_template_switch(self):
"""
Test switching between two VPN templates that use the same VPN server
Verifies that:
1. Only one VpnClient exists at a time
2. VPN config variables are correctly resolved
3. Switching back and forth works properly
"""
vpn = self._create_vpn()
template1 = self._create_template(
name='vpn-test-1',
type='vpn',
vpn=vpn,
config={},
auto_cert=True,
)
template1.config['openvpn'][0]['dev'] = 'tun0'
template1.full_clean()
template1.save()
template2 = self._create_template(
name='vpn-test-2',
type='vpn',
vpn=vpn,
config={},
auto_cert=True,
)
template2.config['openvpn'][0]['dev'] = 'tun1'
template2.full_clean()
template2.save()
# Add device with default template (template1)
path = reverse(f'admin:{self.app_label}_device_add')
params = self._get_device_params(org=self._get_org())
response = self.client.post(path, data=params, follow=True)
self.assertEqual(response.status_code, 200)
config = Config.objects.get(device__name=params['name'])
# Add template1 to the device
path = reverse(f'admin:{self.app_label}_device_change', args=[config.device_id])
params.update(
{
'config-0-id': str(config.pk),
'config-0-device': str(config.device_id),
'config-0-templates': str(template1.pk),
'config-INITIAL_FORMS': 1,
'_continue': True,
}
)
response = self.client.post(path, data=params, follow=True)
self.assertEqual(response.status_code, 200)
config.refresh_from_db()
# Ensure all works as expected
self.assertEqual(config.templates.count(), 1)
self.assertEqual(config.vpnclient_set.count(), 1)
self.assertEqual(
config.backend_instance.config['openvpn'][0]['cert'],
f'/etc/x509/client-{vpn.pk.hex}.pem',
)
self.assertEqual(
config.backend_instance.config['openvpn'][0]['dev'],
'tun0',
)
with self.subTest('Switch device to template2'):
path = reverse(
f'admin:{self.app_label}_device_change', args=[config.device_id]
)
params.update(
{
'config-0-templates': str(template2.pk),
}
)
response = self.client.post(path, data=params, follow=True)
self.assertEqual(response.status_code, 200)
config.refresh_from_db()
self.assertEqual(config.vpnclient_set.count(), 1)
del config.backend_instance
self.assertEqual(
config.backend_instance.config['openvpn'][0]['cert'],
f'/etc/x509/client-{vpn.pk.hex}.pem',
)
self.assertEqual(
config.backend_instance.config['openvpn'][0]['dev'],
'tun1',
)
with self.subTest('Switch device back to template1'):
params.update(
{
'config-0-templates': str(template1.pk),
}
)
response = self.client.post(path, data=params, follow=True)
self.assertEqual(response.status_code, 200)
config.refresh_from_db()
del config.backend_instance
self.assertEqual(
config.backend_instance.config['openvpn'][0]['cert'],
f'/etc/x509/client-{vpn.pk.hex}.pem',
)
self.assertEqual(
config.backend_instance.config['openvpn'][0]['dev'],
'tun0',
)
self.assertEqual(config.vpnclient_set.count(), 1)

@pandafy pandafy force-pushed the issues/832-multiple-template-same-vpn branch from f6b1a44 to a130437 Compare May 6, 2025 14:17
@coveralls
Copy link
Copy Markdown

coveralls commented May 6, 2025

Coverage Status

coverage: 98.816% (-0.01%) from 98.827%
when pulling 257732f on issues/832-multiple-template-same-vpn
into ba733cf on master.

@pandafy pandafy force-pushed the issues/832-multiple-template-same-vpn branch 2 times, most recently from a946356 to dc499d2 Compare May 6, 2025 15:19
Copy link
Copy Markdown
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried replicating #832 on the current master and I couldn't do so, I get an uncaught exception instead:

Screenshot from 2025-05-22 17-02-41

Are you sure all the new logic is 100% needed? It seems recent changes already half-handled this.

Trying the same operation on this branch gives me the following result which is a lot better and matches your testing:

Screenshot from 2025-05-22 16-59-16

I have other observations below.

Comment thread openwisp_controller/config/base/config.py Outdated
Comment thread openwisp_controller/config/base/config.py Outdated
Comment thread openwisp_controller/config/base/config.py Outdated
Comment thread openwisp_controller/config/base/config.py Outdated
Comment thread openwisp_controller/config/tests/test_config.py Outdated
Comment thread openwisp_controller/config/tests/test_config.py Outdated
Comment thread openwisp_controller/config/tests/test_config.py Outdated
Comment thread openwisp_controller/config/tests/test_api.py
Comment thread openwisp_controller/config/tests/test_api.py Outdated
Comment thread openwisp_controller/config/tests/test_api.py Outdated
@github-project-automation github-project-automation Bot moved this from To do (general) to In progress in OpenWISP Contributor's Board May 22, 2025
@pandafy
Copy link
Copy Markdown
Member Author

pandafy commented May 23, 2025

I tried replicating #832 on the current master and I couldn't do so, I get an uncaught exception instead:

Screenshot from 2025-05-22 17-02-41

Are you sure all the new logic is 100% needed? It seems recent changes already half-handled this.

The ValidationError you saw is raised by Config.manage_vpn_clients (due to client.full_clean()):

if action == 'post_add':
for template in templates.filter(type='vpn'):
# Create VPN client if needed
if not vpn_client_model.objects.filter(
config=instance, vpn=template.vpn, template=template
).exists():
client = vpn_client_model(
config=instance,
vpn=template.vpn,
template=template,
auto_cert=template.auto_cert,
)
client.full_clean()
client.save()

But, this is called in post_add (i.e after executing Config.save()). Django does not expect errors to be called such late in the flow (Django Admin does not handle errors raised from save()), thus it returns HTTP 500 response.

This PR performs validation of VPN Client templates and ensures the error is caught before save().

@pandafy pandafy force-pushed the issues/832-multiple-template-same-vpn branch 2 times, most recently from ee6a49e to 5b8f9df Compare May 27, 2025 10:04
@pandafy pandafy force-pushed the issues/832-multiple-template-same-vpn branch from 5b8f9df to 2e24377 Compare May 27, 2025 11:08
@nemesifier nemesifier added the bug label May 28, 2025
@nemesifier nemesifier merged commit d50a4c0 into master May 28, 2025
19 checks passed
@nemesifier nemesifier deleted the issues/832-multiple-template-same-vpn branch May 28, 2025 20:41
@github-project-automation github-project-automation Bot moved this from In progress to Done in OpenWISP Contributor's Board May 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[change] Do not allow applying two (or more) templates of same VPN server on a device

3 participants