-
-
Notifications
You must be signed in to change notification settings - Fork 294
[change] Added generic enforcement of deactivated devices #1338 #1365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -399,7 +399,9 @@ def post(self, request, *args, **kwargs): | |
| # (key is not None only if CONSISTENT_REGISTRATION is enabled) | ||
| new = False | ||
| try: | ||
| device = self.model.objects.get(key=key) | ||
| device = self.model.objects.select_related("config").get(key=key) | ||
| if device.is_deactivated(): | ||
| return ControllerResponse("error: device deactivated", status=403) | ||
|
Comment on lines
+402
to
+404
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the canonical strict deactivation helper here At Line 401, this guard should call the shared strict helper ( Suggested fix- if device.is_deactivated():
+ if device.is_fully_deactivated():
return ControllerResponse("error: device deactivated", status=403)🤖 Prompt for AI Agents |
||
| # update device info | ||
| for attr in self.UPDATABLE_FIELDS: | ||
| if attr in request.POST: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -141,6 +141,10 @@ def auto_add_to_devices(cls, credential_id, organization_id): | |
| DeviceConnection = load_model("connection", "DeviceConnection") | ||
| Device = load_model("config", "Device") | ||
|
|
||
| # Deactivated devices are intentionally included: attaching credentials | ||
| # is a database-only operation with no network activity (the connection | ||
| # cannot be used while the device is deactivated), and having the | ||
| # credentials ready avoids extra setup when the device is reactivated. | ||
| devices = Device.objects.exclude(config=None) | ||
| if organization_id: | ||
| devices = devices.filter(organization_id=organization_id) | ||
|
|
@@ -179,6 +183,9 @@ def auto_add_credentials_to_device(cls, instance, created, **kwargs): | |
| if not created: | ||
| return | ||
| device = instance.device | ||
| # Credentials are attached even when the device is deactivated: this only | ||
| # creates DeviceConnection rows (no network operation) and avoids extra | ||
| # setup on reactivation. See auto_add_to_devices for the same rationale. | ||
| # select credentials which | ||
| # - are flagged as auto_add | ||
| # - belong to the same organization of the device | ||
|
|
@@ -266,6 +273,11 @@ def __init__(self, *args, **kwargs): | |
| def get_working_connection( | ||
| cls, device, connector="openwisp_controller.connection.connectors.ssh.Ssh" | ||
| ): | ||
| # Deactivated devices are not filtered out here on purpose: a device that | ||
| # is still "deactivating" needs one last connection so the cleared | ||
| # configuration can be pushed to it. connect() below refuses fully | ||
| # deactivated devices, so only the legitimate deactivating push gets | ||
| # through. | ||
| qs = cls.objects.filter( | ||
| device=device, | ||
| enabled=True, | ||
|
|
@@ -345,6 +357,11 @@ def set_connector(self, connector): | |
|
|
||
| def connect(self): | ||
| try: | ||
| # Refuse fully deactivated devices (device deactivated and its config | ||
| # already "deactivated"). A device that is only "deactivating" is let | ||
| # through so the final cleared configuration can still be pushed. | ||
| if self.device.is_fully_deactivated(): | ||
| raise RuntimeError("Device is deactivated") | ||
|
Comment on lines
+363
to
+364
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make the deactivation failure message translatable. At Line 350, the new string is stored in Proposed fix- raise RuntimeError("Device is deactivated")
+ raise RuntimeError(gettext("Device is deactivated."))As per coding guidelines: "For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework". 🤖 Prompt for AI Agents |
||
| self.connector_instance.connect() | ||
| except Exception as e: | ||
| self.is_working = False | ||
|
|
@@ -378,6 +395,8 @@ def save(self, *args, **kwargs): | |
| self._initial_failure_reason = self.failure_reason | ||
|
|
||
| def send_is_working_changed_signal(self): | ||
| if self.device.is_fully_deactivated(): | ||
| return | ||
| is_working_changed.send( | ||
| sender=self.__class__, | ||
| is_working=self.is_working, | ||
|
|
@@ -462,6 +481,8 @@ def __str__(self): | |
| return f'«{command}» {sent} {created.strftime("%d %b %Y at %I:%M %p")}' | ||
|
|
||
| def clean(self): | ||
| if self.device.is_deactivated(): | ||
| raise ValidationError({"device": _("Device is deactivated.")}) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| self._verify_command_type_allowed() | ||
| self._verify_connection() | ||
| try: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,12 +11,12 @@ def _create_organization(self, **kwargs): | |||||
| options = dict(name="org1") | ||||||
| options.update(kwargs) | ||||||
| options.setdefault("slug", slugify(options["name"])) | ||||||
| if not Organization.objects.filter(**kwargs).count(): | ||||||
| if not Organization.objects.filter(**options).count(): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Prefer Line 14 uses ⚡ Suggested refactor- if not Organization.objects.filter(**options).count():
+ if not Organization.objects.filter(**options).exists():📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| org = Organization(**options) | ||||||
| org.full_clean() | ||||||
| org.save() | ||||||
| else: | ||||||
| org = Organization.objects.get(**kwargs) | ||||||
| org = Organization.objects.get(**options) | ||||||
| return org | ||||||
|
|
||||||
| def _add_default_org(self, kwargs): | ||||||
|
|
@@ -44,7 +44,10 @@ def _create_floorplan(self, **kwargs): | |||||
|
|
||||||
| def _create_object_location(self, **kwargs): | ||||||
| if "location" not in kwargs: | ||||||
| kwargs["location"] = self._create_location() | ||||||
| location_kwargs = {} | ||||||
| if "content_object" in kwargs: | ||||||
| location_kwargs["organization"] = kwargs["content_object"].organization | ||||||
| kwargs["location"] = self._create_location(**location_kwargs) | ||||||
| kwargs["organization"] = kwargs["location"].organization | ||||||
| if "content_object" not in kwargs: | ||||||
| kwargs["content_object"] = self._create_object( | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the canonical operability criteria for device filtering.
At Line 113, filtering only on
_is_deactivated=Truecan still include devices whose config is already non-operable (for example, deactivating/deactivated), so group template updates may run on devices that should be skipped.Suggested change
🤖 Prompt for AI Agents