Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0999d98225 | ||
| 
						 | 
					d8dd3e133f | ||
| 
						 | 
					528470c37f | ||
| 
						 | 
					c03cd53853 | ||
| 
						 | 
					b57fc8a29c | ||
| 
						 | 
					a04ed5c3ca | ||
| 
						 | 
					3ad1df14f6 | ||
| 
						 | 
					d8caf12fdc | ||
| 
						 | 
					5ca9d30d5f | ||
| 
						 | 
					a7a71b4a46 | ||
| 
						 | 
					638603ac6b | ||
| 
						 | 
					1d70c15027 | ||
| 
						 | 
					7a5f03d672 | ||
| 
						 | 
					39e97c5589 | ||
| 
						 | 
					1943d8367e | ||
| 
						 | 
					f91c5af9a1 | ||
| 
						 | 
					2be71fc877 | ||
| 
						 | 
					f5f5b4a8db | ||
| 
						 | 
					ac9cfd09ea | ||
| 
						 | 
					4cfc85dbfd | ||
| 
						 | 
					1f3d2f47b1 | ||
| 
						 | 
					653c482ff7 | ||
| 
						 | 
					4b069cc2b0 | ||
| 
						 | 
					c89349a43a | ||
| 
						 | 
					6e92d6c62c | ||
| 
						 | 
					5d3d3e9076 | ||
| 
						 | 
					b440c772d6 | ||
| 
						 | 
					2895560b30 | ||
| 
						 | 
					bedcecb2e1 | ||
| 
						 | 
					656ac829a4 | ||
| 
						 | 
					4d83debc0e | ||
| 
						 | 
					4ff5d19979 | 
							
								
								
									
										13
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							@@ -42,9 +42,7 @@ jobs:
 | 
			
		||||
          cd api/tacticalrmm
 | 
			
		||||
          source ../env/bin/activate
 | 
			
		||||
          rm -f .coverage coverage.lcov
 | 
			
		||||
          coverage run --concurrency=multiprocessing manage.py test -v 2 --parallel
 | 
			
		||||
          coverage combine
 | 
			
		||||
          coverage lcov
 | 
			
		||||
          pytest
 | 
			
		||||
          if [ $? -ne 0 ]; then
 | 
			
		||||
              exit 1
 | 
			
		||||
          fi
 | 
			
		||||
@@ -58,9 +56,8 @@ jobs:
 | 
			
		||||
              exit 1
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
      - name: Coveralls
 | 
			
		||||
        uses: coverallsapp/github-action@master
 | 
			
		||||
      - uses: codecov/codecov-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          path-to-lcov: ./api/tacticalrmm/coverage.lcov
 | 
			
		||||
          base-path: ./api/tacticalrmm
 | 
			
		||||
          directory: ./api/tacticalrmm
 | 
			
		||||
          files: ./api/tacticalrmm/coverage.xml
 | 
			
		||||
          verbose: true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -53,3 +53,5 @@ nats-api.conf
 | 
			
		||||
ignore/
 | 
			
		||||
coverage.lcov
 | 
			
		||||
daphne.sock.lock
 | 
			
		||||
.pytest_cache
 | 
			
		||||
coverage.xml
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
# Tactical RMM
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
[](https://coveralls.io/github/amidaware/tacticalrmm?branch=develop)
 | 
			
		||||
[](https://codecov.io/gh/amidaware/tacticalrmm)
 | 
			
		||||
[](https://github.com/python/black)
 | 
			
		||||
 | 
			
		||||
Tactical RMM is a remote monitoring & management tool, built with Django and Vue.\
 | 
			
		||||
 
 | 
			
		||||
@@ -69,17 +69,17 @@ class TestAccounts(TacticalTestCase):
 | 
			
		||||
        self.assertEqual(r.status_code, 400)
 | 
			
		||||
        self.assertIn("non_field_errors", r.data.keys())
 | 
			
		||||
 | 
			
		||||
    @override_settings(DEBUG=True)
 | 
			
		||||
    @patch("pyotp.TOTP.verify")
 | 
			
		||||
    def test_debug_login_view(self, mock_verify):
 | 
			
		||||
        url = "/login/"
 | 
			
		||||
        mock_verify.return_value = True
 | 
			
		||||
    # @override_settings(DEBUG=True)
 | 
			
		||||
    # @patch("pyotp.TOTP.verify")
 | 
			
		||||
    # def test_debug_login_view(self, mock_verify):
 | 
			
		||||
    #     url = "/login/"
 | 
			
		||||
    #     mock_verify.return_value = True
 | 
			
		||||
 | 
			
		||||
        data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
 | 
			
		||||
        r = self.client.post(url, data, format="json")
 | 
			
		||||
        self.assertEqual(r.status_code, 200)
 | 
			
		||||
        self.assertIn("expiry", r.data.keys())
 | 
			
		||||
        self.assertIn("token", r.data.keys())
 | 
			
		||||
    #     data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
 | 
			
		||||
    #     r = self.client.post(url, data, format="json")
 | 
			
		||||
    #     self.assertEqual(r.status_code, 200)
 | 
			
		||||
    #     self.assertIn("expiry", r.data.keys())
 | 
			
		||||
    #     self.assertIn("token", r.data.keys())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGetAddUsers(TacticalTestCase):
 | 
			
		||||
@@ -338,7 +338,7 @@ class TestAPIKeyViews(TacticalTestCase):
 | 
			
		||||
        resp = self.client.put(url, data, format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        apikey = APIKey.objects.get(pk=apikey.pk)
 | 
			
		||||
        self.assertEquals(apikey.name, "New Name")
 | 
			
		||||
        self.assertEqual(apikey.name, "New Name")
 | 
			
		||||
 | 
			
		||||
        self.check_not_authenticated("put", url)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ def agent_update(agent_id: str, force: bool = False) -> str:
 | 
			
		||||
 | 
			
		||||
@app.task
 | 
			
		||||
def force_code_sign(agent_ids: list[str]) -> None:
 | 
			
		||||
    chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
 | 
			
		||||
    chunks = (agent_ids[i : i + 70] for i in range(0, len(agent_ids), 70))
 | 
			
		||||
    for chunk in chunks:
 | 
			
		||||
        for agent_id in chunk:
 | 
			
		||||
            agent_update(agent_id=agent_id, force=True)
 | 
			
		||||
@@ -77,7 +77,7 @@ def force_code_sign(agent_ids: list[str]) -> None:
 | 
			
		||||
 | 
			
		||||
@app.task
 | 
			
		||||
def send_agent_update_task(agent_ids: list[str]) -> None:
 | 
			
		||||
    chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
 | 
			
		||||
    chunks = (agent_ids[i : i + 70] for i in range(0, len(agent_ids), 70))
 | 
			
		||||
    for chunk in chunks:
 | 
			
		||||
        for agent_id in chunk:
 | 
			
		||||
            agent_update(agent_id)
 | 
			
		||||
@@ -97,7 +97,7 @@ def auto_self_agent_update_task() -> None:
 | 
			
		||||
        if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    chunks = (agent_ids[i : i + 30] for i in range(0, len(agent_ids), 30))
 | 
			
		||||
    chunks = (agent_ids[i : i + 70] for i in range(0, len(agent_ids), 70))
 | 
			
		||||
    for chunk in chunks:
 | 
			
		||||
        for agent_id in chunk:
 | 
			
		||||
            agent_update(agent_id)
 | 
			
		||||
 
 | 
			
		||||
@@ -245,7 +245,10 @@ class TestAgentViews(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_get_agent_versions(self):
 | 
			
		||||
        url = "/agents/versions/"
 | 
			
		||||
 | 
			
		||||
        with self.assertNumQueries(1):
 | 
			
		||||
            r = self.client.get(url)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(r.status_code, 200)
 | 
			
		||||
        assert any(i["hostname"] == self.agent.hostname for i in r.json()["agents"])
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -136,10 +136,10 @@ class GetAgents(APIView):
 | 
			
		||||
        else:
 | 
			
		||||
            agents = (
 | 
			
		||||
                Agent.objects.filter_by_role(request.user)  # type: ignore
 | 
			
		||||
                .select_related("site")
 | 
			
		||||
                .defer(*AGENT_DEFER)
 | 
			
		||||
                .select_related("site__client")
 | 
			
		||||
                .filter(monitoring_type_filter)
 | 
			
		||||
                .filter(client_site_filter)
 | 
			
		||||
                .only("agent_id", "hostname", "site")
 | 
			
		||||
            )
 | 
			
		||||
            serializer = AgentHostnameSerializer(agents, many=True)
 | 
			
		||||
 | 
			
		||||
@@ -297,9 +297,9 @@ class AgentMeshCentral(APIView):
 | 
			
		||||
@permission_classes([IsAuthenticated, AgentPerms])
 | 
			
		||||
def get_agent_versions(request):
 | 
			
		||||
    agents = (
 | 
			
		||||
        Agent.objects.filter_by_role(request.user)  # type: ignore
 | 
			
		||||
        .prefetch_related("site")
 | 
			
		||||
        .only("pk", "hostname")
 | 
			
		||||
        Agent.objects.defer(*AGENT_DEFER)
 | 
			
		||||
        .filter_by_role(request.user)  # type: ignore
 | 
			
		||||
        .select_related("site__client")
 | 
			
		||||
    )
 | 
			
		||||
    return Response(
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -70,8 +70,8 @@ class TestAlertsViews(TacticalTestCase):
 | 
			
		||||
        data = {"top": 3}
 | 
			
		||||
        resp = self.client.patch(url, data, format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        self.assertEquals(resp.data["alerts"], AlertSerializer(alerts, many=True).data)
 | 
			
		||||
        self.assertEquals(resp.data["alerts_count"], 10)
 | 
			
		||||
        self.assertEqual(resp.data["alerts"], AlertSerializer(alerts, many=True).data)
 | 
			
		||||
        self.assertEqual(resp.data["alerts_count"], 10)
 | 
			
		||||
 | 
			
		||||
        # test filter data
 | 
			
		||||
        # test data and result counts
 | 
			
		||||
@@ -409,15 +409,15 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        core.server_policy = policy
 | 
			
		||||
        core.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
 | 
			
		||||
        # assign second Alert Template to as default alert template
 | 
			
		||||
        core.alert_template = alert_templates[1]
 | 
			
		||||
        core.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
 | 
			
		||||
        # assign third Alert Template to client
 | 
			
		||||
        workstation.client.alert_template = alert_templates[2]
 | 
			
		||||
@@ -425,8 +425,8 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        workstation.client.save()
 | 
			
		||||
        server.client.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
 | 
			
		||||
        # apply policy to client and should override
 | 
			
		||||
        workstation.client.workstation_policy = policy
 | 
			
		||||
@@ -434,8 +434,8 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        workstation.client.save()
 | 
			
		||||
        server.client.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
 | 
			
		||||
        # assign fouth Alert Template to site
 | 
			
		||||
        workstation.site.alert_template = alert_templates[3]
 | 
			
		||||
@@ -443,8 +443,8 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        workstation.site.save()
 | 
			
		||||
        server.site.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
 | 
			
		||||
        # apply policy to site
 | 
			
		||||
        workstation.site.workstation_policy = policy
 | 
			
		||||
@@ -452,8 +452,8 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        workstation.site.save()
 | 
			
		||||
        server.site.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
 | 
			
		||||
        # apply policy to agents
 | 
			
		||||
        workstation.policy = policy
 | 
			
		||||
@@ -461,35 +461,35 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        workstation.save()
 | 
			
		||||
        server.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[0].pk)
 | 
			
		||||
 | 
			
		||||
        # test disabling alert template
 | 
			
		||||
        alert_templates[0].is_active = False
 | 
			
		||||
        alert_templates[0].save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
 | 
			
		||||
        # test policy exclusions
 | 
			
		||||
        alert_templates[3].excluded_agents.set([workstation.pk])
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
 | 
			
		||||
        # test workstation exclusions
 | 
			
		||||
        alert_templates[2].exclude_workstations = True
 | 
			
		||||
        alert_templates[2].save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[3].pk)
 | 
			
		||||
 | 
			
		||||
        # test server exclusions
 | 
			
		||||
        alert_templates[3].exclude_servers = True
 | 
			
		||||
        alert_templates[3].save()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(workstation.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEquals(server.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
        self.assertEqual(workstation.set_alert_template().pk, alert_templates[1].pk)
 | 
			
		||||
        self.assertEqual(server.set_alert_template().pk, alert_templates[2].pk)
 | 
			
		||||
 | 
			
		||||
    @patch("agents.tasks.sleep")
 | 
			
		||||
    @patch("core.models.CoreSettings.send_mail")
 | 
			
		||||
@@ -523,7 +523,7 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        # call outages task and no alert should be created
 | 
			
		||||
        agent_outages_task()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(Alert.objects.count(), 0)
 | 
			
		||||
        self.assertEqual(Alert.objects.count(), 0)
 | 
			
		||||
 | 
			
		||||
        # set overdue_dashboard_alert and alert should be created
 | 
			
		||||
        agent_dashboard_alert.overdue_dashboard_alert = True
 | 
			
		||||
@@ -574,22 +574,22 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        agent_outages_task()
 | 
			
		||||
 | 
			
		||||
        # should have created 6 alerts
 | 
			
		||||
        self.assertEquals(Alert.objects.count(), 6)
 | 
			
		||||
        self.assertEqual(Alert.objects.count(), 6)
 | 
			
		||||
 | 
			
		||||
        # other specific agents should have created alerts
 | 
			
		||||
        self.assertEquals(Alert.objects.filter(agent=agent_dashboard_alert).count(), 1)
 | 
			
		||||
        self.assertEquals(Alert.objects.filter(agent=agent_text_alert).count(), 1)
 | 
			
		||||
        self.assertEquals(Alert.objects.filter(agent=agent_email_alert).count(), 1)
 | 
			
		||||
        self.assertEquals(Alert.objects.filter(agent=agent_template_email).count(), 1)
 | 
			
		||||
        self.assertEquals(
 | 
			
		||||
        self.assertEqual(Alert.objects.filter(agent=agent_dashboard_alert).count(), 1)
 | 
			
		||||
        self.assertEqual(Alert.objects.filter(agent=agent_text_alert).count(), 1)
 | 
			
		||||
        self.assertEqual(Alert.objects.filter(agent=agent_email_alert).count(), 1)
 | 
			
		||||
        self.assertEqual(Alert.objects.filter(agent=agent_template_email).count(), 1)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            Alert.objects.filter(agent=agent_template_dashboard).count(), 1
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEquals(Alert.objects.filter(agent=agent_template_text).count(), 1)
 | 
			
		||||
        self.assertEquals(Alert.objects.filter(agent=agent_template_blank).count(), 0)
 | 
			
		||||
        self.assertEqual(Alert.objects.filter(agent=agent_template_text).count(), 1)
 | 
			
		||||
        self.assertEqual(Alert.objects.filter(agent=agent_template_blank).count(), 0)
 | 
			
		||||
 | 
			
		||||
        # check if email and text tasks were called
 | 
			
		||||
        self.assertEquals(outage_email.call_count, 2)
 | 
			
		||||
        self.assertEquals(outage_sms.call_count, 2)
 | 
			
		||||
        self.assertEqual(outage_email.call_count, 2)
 | 
			
		||||
        self.assertEqual(outage_sms.call_count, 2)
 | 
			
		||||
 | 
			
		||||
        outage_sms.assert_any_call(
 | 
			
		||||
            pk=Alert.objects.get(agent=agent_text_alert).pk, alert_interval=None
 | 
			
		||||
@@ -630,7 +630,7 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
        # calling agent outage task again shouldn't create duplicate alerts and won't send alerts
 | 
			
		||||
        agent_outages_task()
 | 
			
		||||
        self.assertEquals(Alert.objects.count(), 6)
 | 
			
		||||
        self.assertEqual(Alert.objects.count(), 6)
 | 
			
		||||
 | 
			
		||||
        # test periodic notification
 | 
			
		||||
        # change email/text sent to sometime in the past
 | 
			
		||||
@@ -942,7 +942,7 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        send_email.assert_not_called()
 | 
			
		||||
        send_sms.assert_not_called()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            Alert.objects.filter(assigned_check=check_template_email).count(), 1
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -1244,7 +1244,7 @@ class TestAlertTasks(TacticalTestCase):
 | 
			
		||||
        send_email.assert_not_called()
 | 
			
		||||
        send_sms.assert_not_called()
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            Alert.objects.filter(assigned_task=task_template_email).count(), 1
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ class TestAPIv3(TacticalTestCase):
 | 
			
		||||
        r = self.client.get(url)
 | 
			
		||||
        self.assertEqual(r.status_code, 200)
 | 
			
		||||
        self.assertEqual(r.data["check_interval"], 20)
 | 
			
		||||
        self.assertEquals(len(r.data["checks"]), 2)
 | 
			
		||||
        self.assertEqual(len(r.data["checks"]), 2)
 | 
			
		||||
 | 
			
		||||
        url = "/api/v3/Maj34ACb324j234asdj2n34kASDjh34-DESKTOPTEST123/checkrunner/"
 | 
			
		||||
        r = self.client.get(url)
 | 
			
		||||
 
 | 
			
		||||
@@ -266,9 +266,9 @@ class TaskRunner(APIView):
 | 
			
		||||
    permission_classes = [IsAuthenticated]
 | 
			
		||||
 | 
			
		||||
    def get(self, request, pk, agentid):
 | 
			
		||||
        _ = get_object_or_404(Agent, agent_id=agentid)
 | 
			
		||||
        agent = get_object_or_404(Agent, agent_id=agentid)
 | 
			
		||||
        task = get_object_or_404(AutomatedTask, pk=pk)
 | 
			
		||||
        return Response(TaskGOGetSerializer(task).data)
 | 
			
		||||
        return Response(TaskGOGetSerializer(task, context={"agent": agent}).data)
 | 
			
		||||
 | 
			
		||||
    def patch(self, request, pk, agentid):
 | 
			
		||||
        from alerts.models import Alert
 | 
			
		||||
 
 | 
			
		||||
@@ -410,11 +410,11 @@ class TestPolicyTasks(TacticalTestCase):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        self.assertEquals(len(resp.data["server_clients"]), 1)
 | 
			
		||||
        self.assertEquals(len(resp.data["server_sites"]), 0)
 | 
			
		||||
        self.assertEquals(len(resp.data["workstation_clients"]), 1)
 | 
			
		||||
        self.assertEquals(len(resp.data["workstation_sites"]), 0)
 | 
			
		||||
        self.assertEquals(len(resp.data["agents"]), 0)
 | 
			
		||||
        self.assertEqual(len(resp.data["server_clients"]), 1)
 | 
			
		||||
        self.assertEqual(len(resp.data["server_sites"]), 0)
 | 
			
		||||
        self.assertEqual(len(resp.data["workstation_clients"]), 1)
 | 
			
		||||
        self.assertEqual(len(resp.data["workstation_sites"]), 0)
 | 
			
		||||
        self.assertEqual(len(resp.data["agents"]), 0)
 | 
			
		||||
 | 
			
		||||
        # Add Site to Policy
 | 
			
		||||
        policy.server_sites.add(server_agents[10].site)
 | 
			
		||||
@@ -422,9 +422,9 @@ class TestPolicyTasks(TacticalTestCase):
 | 
			
		||||
        resp = self.client.get(
 | 
			
		||||
            f"/automation/policies/{policy.pk}/related/", format="json"
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEquals(len(resp.data["server_sites"]), 1)
 | 
			
		||||
        self.assertEquals(len(resp.data["workstation_sites"]), 1)
 | 
			
		||||
        self.assertEquals(len(resp.data["agents"]), 0)
 | 
			
		||||
        self.assertEqual(len(resp.data["server_sites"]), 1)
 | 
			
		||||
        self.assertEqual(len(resp.data["workstation_sites"]), 1)
 | 
			
		||||
        self.assertEqual(len(resp.data["agents"]), 0)
 | 
			
		||||
 | 
			
		||||
        # Add Agent to Policy
 | 
			
		||||
        policy.agents.add(server_agents[2])
 | 
			
		||||
@@ -432,7 +432,7 @@ class TestPolicyTasks(TacticalTestCase):
 | 
			
		||||
        resp = self.client.get(
 | 
			
		||||
            f"/automation/policies/{policy.pk}/related/", format="json"
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEquals(len(resp.data["agents"]), 2)
 | 
			
		||||
        self.assertEqual(len(resp.data["agents"]), 2)
 | 
			
		||||
 | 
			
		||||
    def test_getting_agent_policy_checks(self):
 | 
			
		||||
 | 
			
		||||
@@ -442,7 +442,7 @@ class TestPolicyTasks(TacticalTestCase):
 | 
			
		||||
        agent = baker.make_recipe("agents.agent", policy=policy)
 | 
			
		||||
 | 
			
		||||
        # test policy assigned to agent
 | 
			
		||||
        self.assertEquals(len(agent.get_checks_from_policies()), 7)
 | 
			
		||||
        self.assertEqual(len(agent.get_checks_from_policies()), 7)
 | 
			
		||||
 | 
			
		||||
    def test_getting_agent_policy_checks_with_enforced(self):
 | 
			
		||||
        # setup data
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,9 @@ class GetAddPolicies(APIView):
 | 
			
		||||
    permission_classes = [IsAuthenticated, AutomationPolicyPerms]
 | 
			
		||||
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
        policies = Policy.objects.all()
 | 
			
		||||
        policies = Policy.objects.select_related("alert_template").prefetch_related(
 | 
			
		||||
            "excluded_agents", "excluded_sites", "excluded_clients"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return Response(
 | 
			
		||||
            PolicyTableSerializer(
 | 
			
		||||
 
 | 
			
		||||
@@ -198,13 +198,14 @@ class TaskGOGetSerializer(serializers.ModelSerializer):
 | 
			
		||||
    def get_task_actions(self, obj):
 | 
			
		||||
        tmp = []
 | 
			
		||||
        actions_to_remove = []
 | 
			
		||||
        agent = self.context["agent"] if "agent" in self.context.keys() else obj.agent
 | 
			
		||||
        for action in obj.actions:
 | 
			
		||||
            if action["type"] == "cmd":
 | 
			
		||||
                tmp.append(
 | 
			
		||||
                    {
 | 
			
		||||
                        "type": "cmd",
 | 
			
		||||
                        "command": Script.parse_script_args(
 | 
			
		||||
                            agent=obj.agent,
 | 
			
		||||
                            agent=agent,
 | 
			
		||||
                            shell=action["shell"],
 | 
			
		||||
                            args=[action["command"]],
 | 
			
		||||
                        )[0],
 | 
			
		||||
@@ -225,7 +226,7 @@ class TaskGOGetSerializer(serializers.ModelSerializer):
 | 
			
		||||
                        "script_name": script.name,
 | 
			
		||||
                        "code": script.code,
 | 
			
		||||
                        "script_args": Script.parse_script_args(
 | 
			
		||||
                            agent=obj.agent,
 | 
			
		||||
                            agent=agent,
 | 
			
		||||
                            shell=script.shell,
 | 
			
		||||
                            args=action["script_args"],
 | 
			
		||||
                        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -842,8 +842,8 @@ class TestCheckTasks(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
        check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(check.alert_severity, "warning")
 | 
			
		||||
        self.assertEquals(check_result.status, "failing")
 | 
			
		||||
        self.assertEqual(check.alert_severity, "warning")
 | 
			
		||||
        self.assertEqual(check_result.status, "failing")
 | 
			
		||||
 | 
			
		||||
        # test passing when contains
 | 
			
		||||
        resp = self.client.patch(url, no_logs_data, format="json")
 | 
			
		||||
@@ -851,7 +851,7 @@ class TestCheckTasks(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
        check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(check_result.status, "passing")
 | 
			
		||||
        self.assertEqual(check_result.status, "passing")
 | 
			
		||||
 | 
			
		||||
        # test failing when not contains and message and source
 | 
			
		||||
        check.fail_when = "not_contains"
 | 
			
		||||
@@ -863,8 +863,8 @@ class TestCheckTasks(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
        check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(check_result.status, "failing")
 | 
			
		||||
        self.assertEquals(check.alert_severity, "error")
 | 
			
		||||
        self.assertEqual(check_result.status, "failing")
 | 
			
		||||
        self.assertEqual(check.alert_severity, "error")
 | 
			
		||||
 | 
			
		||||
        # test passing when contains with source and message
 | 
			
		||||
        resp = self.client.patch(url, data, format="json")
 | 
			
		||||
@@ -872,7 +872,7 @@ class TestCheckTasks(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
        check_result = CheckResult.objects.get(assigned_check=check, agent=self.agent)
 | 
			
		||||
 | 
			
		||||
        self.assertEquals(check_result.status, "passing")
 | 
			
		||||
        self.assertEqual(check_result.status, "passing")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCheckPermissions(TacticalTestCase):
 | 
			
		||||
 
 | 
			
		||||
@@ -32,9 +32,8 @@ class GetAddClients(APIView):
 | 
			
		||||
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
        clients = (
 | 
			
		||||
            Client.objects.select_related(
 | 
			
		||||
                "workstation_policy", "server_policy", "alert_template"
 | 
			
		||||
            )
 | 
			
		||||
            Client.objects.order_by("name")
 | 
			
		||||
            .select_related("workstation_policy", "server_policy", "alert_template")
 | 
			
		||||
            .filter_by_role(request.user)  # type: ignore
 | 
			
		||||
            .prefetch_related(
 | 
			
		||||
                Prefetch(
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
# Generated by Django 4.0.4 on 2022-04-23 14:26
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('core', '0033_coresettings_mesh_disable_auto_login'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='customfield',
 | 
			
		||||
            name='name',
 | 
			
		||||
            field=models.CharField(max_length=100),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -175,12 +175,12 @@ class CoreSettings(BaseAuditModel):
 | 
			
		||||
        body: str,
 | 
			
		||||
        alert_template: "Optional[AlertTemplate]" = None,
 | 
			
		||||
        test: bool = False,
 | 
			
		||||
    ) -> Union[bool, str]:
 | 
			
		||||
    ) -> tuple[str, bool]:
 | 
			
		||||
        if test and not self.email_is_configured:
 | 
			
		||||
            return "There needs to be at least one email recipient configured"
 | 
			
		||||
            return ("There needs to be at least one email recipient configured", False)
 | 
			
		||||
        # return since email must be configured to continue
 | 
			
		||||
        elif not self.email_is_configured:
 | 
			
		||||
            return "SMTP messaging not configured."
 | 
			
		||||
            return ("SMTP messaging not configured.", False)
 | 
			
		||||
 | 
			
		||||
        # override email from if alert_template is passed and is set
 | 
			
		||||
        if alert_template and alert_template.email_from:
 | 
			
		||||
@@ -194,7 +194,7 @@ class CoreSettings(BaseAuditModel):
 | 
			
		||||
        elif self.email_alert_recipients:
 | 
			
		||||
            email_recipients = ", ".join(cast(List[str], self.email_alert_recipients))
 | 
			
		||||
        else:
 | 
			
		||||
            return "There needs to be at least one email recipient configured"
 | 
			
		||||
            return ("There needs to be at least one email recipient configured", False)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            msg = EmailMessage()
 | 
			
		||||
@@ -221,18 +221,21 @@ class CoreSettings(BaseAuditModel):
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            DebugLog.error(message=f"Sending email failed with error: {e}")
 | 
			
		||||
            if test:
 | 
			
		||||
                return str(e)
 | 
			
		||||
        finally:
 | 
			
		||||
            return True
 | 
			
		||||
                return (str(e), False)
 | 
			
		||||
 | 
			
		||||
        if test:
 | 
			
		||||
            return ("Email test ok!", True)
 | 
			
		||||
 | 
			
		||||
        return ("ok", True)
 | 
			
		||||
 | 
			
		||||
    def send_sms(
 | 
			
		||||
        self,
 | 
			
		||||
        body: str,
 | 
			
		||||
        alert_template: "Optional[AlertTemplate]" = None,
 | 
			
		||||
        test: bool = False,
 | 
			
		||||
    ) -> Union[str, bool]:
 | 
			
		||||
    ) -> tuple[str, bool]:
 | 
			
		||||
        if not self.sms_is_configured:
 | 
			
		||||
            return "Sms alerting is not setup correctly."
 | 
			
		||||
            return ("Sms alerting is not setup correctly.", False)
 | 
			
		||||
 | 
			
		||||
        # override email recipients if alert_template is passed and is set
 | 
			
		||||
        if alert_template and alert_template.text_recipients:
 | 
			
		||||
@@ -240,7 +243,7 @@ class CoreSettings(BaseAuditModel):
 | 
			
		||||
        elif self.sms_alert_recipients:
 | 
			
		||||
            text_recipients = cast(List[str], self.sms_alert_recipients)
 | 
			
		||||
        else:
 | 
			
		||||
            return "No sms recipients found"
 | 
			
		||||
            return ("No sms recipients found", False)
 | 
			
		||||
 | 
			
		||||
        tw_client = TwClient(self.twilio_account_sid, self.twilio_auth_token)
 | 
			
		||||
        for num in text_recipients:
 | 
			
		||||
@@ -249,9 +252,12 @@ class CoreSettings(BaseAuditModel):
 | 
			
		||||
            except TwilioRestException as e:
 | 
			
		||||
                DebugLog.error(message=f"SMS failed to send: {e}")
 | 
			
		||||
                if test:
 | 
			
		||||
                    return str(e)
 | 
			
		||||
                    return (str(e), False)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
        if test:
 | 
			
		||||
            return ("SMS Test sent successfully!", True)
 | 
			
		||||
 | 
			
		||||
        return ("ok", True)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def serialize(core):
 | 
			
		||||
@@ -284,7 +290,7 @@ class CustomField(BaseAuditModel):
 | 
			
		||||
        blank=True,
 | 
			
		||||
        default=list,
 | 
			
		||||
    )
 | 
			
		||||
    name = models.CharField(max_length=30)
 | 
			
		||||
    name = models.CharField(max_length=100)
 | 
			
		||||
    required = models.BooleanField(blank=True, default=False)
 | 
			
		||||
    default_value_string = models.TextField(null=True, blank=True)
 | 
			
		||||
    default_value_bool = models.BooleanField(default=False)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ from clients.models import Client, Site
 | 
			
		||||
from checks.models import Check, CheckResult
 | 
			
		||||
from core.utils import get_core_settings
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db.models import Prefetch, Exists, OuterRef
 | 
			
		||||
from django.db.models import Prefetch
 | 
			
		||||
from logs.models import PendingAction
 | 
			
		||||
from logs.tasks import prune_audit_log, prune_debug_log
 | 
			
		||||
from packaging import version as pyver
 | 
			
		||||
@@ -53,6 +53,23 @@ def core_maintenance_tasks() -> None:
 | 
			
		||||
 | 
			
		||||
@app.task
 | 
			
		||||
def handle_resolved_stuff() -> None:
 | 
			
		||||
 | 
			
		||||
    # change agent update pending status to completed if agent has just updated
 | 
			
		||||
    actions = (
 | 
			
		||||
        PendingAction.objects.select_related("agent")
 | 
			
		||||
        .defer("agent__services", "agent__wmi_detail")
 | 
			
		||||
        .filter(action_type="agentupdate", status="pending")
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    to_update = [
 | 
			
		||||
        action.id
 | 
			
		||||
        for action in actions
 | 
			
		||||
        if pyver.parse(action.agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
 | 
			
		||||
        and action.agent.status == "online"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    PendingAction.objects.filter(pk__in=to_update).update(status="completed")
 | 
			
		||||
 | 
			
		||||
    agent_queryset = (
 | 
			
		||||
        Agent.objects.defer(*AGENT_DEFER)
 | 
			
		||||
        .select_related(
 | 
			
		||||
@@ -80,26 +97,11 @@ def handle_resolved_stuff() -> None:
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for agent in agent_queryset.annotate(
 | 
			
		||||
        has_pending_actions=Exists(
 | 
			
		||||
            PendingAction.objects.filter(
 | 
			
		||||
                pk=OuterRef("pk"), action_type="agent_update", status="pending"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    ):
 | 
			
		||||
    for agent in agent_queryset:
 | 
			
		||||
        if (
 | 
			
		||||
            pyver.parse(agent.version) >= pyver.parse("1.6.0")
 | 
			
		||||
            and agent.status == "online"
 | 
			
		||||
        ):
 | 
			
		||||
            # change agent update pending status to completed if agent has just updated
 | 
			
		||||
            if (
 | 
			
		||||
                pyver.parse(agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
 | 
			
		||||
                and agent.has_pending_actions
 | 
			
		||||
            ):
 | 
			
		||||
                agent.pendingactions.filter(
 | 
			
		||||
                    action_type="agent_update", status="pending"
 | 
			
		||||
                ).update(status="completed")
 | 
			
		||||
 | 
			
		||||
            # sync scheduled tasks
 | 
			
		||||
            for task in agent.get_tasks_with_policies():
 | 
			
		||||
                if not task.task_result or task.task_result.sync_status == "initial":
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from channels.db import database_sync_to_async
 | 
			
		||||
from channels.testing import WebsocketCommunicator
 | 
			
		||||
from model_bakery import baker
 | 
			
		||||
@@ -11,7 +12,9 @@ from core.utils import get_core_settings
 | 
			
		||||
from .consumers import DashInfo
 | 
			
		||||
from .models import CustomField, GlobalKVStore, URLAction
 | 
			
		||||
from .serializers import CustomFieldSerializer, KeyStoreSerializer, URLActionSerializer
 | 
			
		||||
from .tasks import core_maintenance_tasks
 | 
			
		||||
from .tasks import core_maintenance_tasks, handle_resolved_stuff
 | 
			
		||||
from logs.models import PendingAction
 | 
			
		||||
from agents.models import Agent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCodeSign(TacticalTestCase):
 | 
			
		||||
@@ -393,6 +396,29 @@ class TestCoreTasks(TacticalTestCase):
 | 
			
		||||
 | 
			
		||||
        self.check_not_authenticated("get", url)
 | 
			
		||||
 | 
			
		||||
    def test_resolved_pending_agentupdate_task(self):
 | 
			
		||||
        online = baker.make_recipe("agents.online_agent", version="2.0.0", _quantity=20)
 | 
			
		||||
        offline = baker.make_recipe(
 | 
			
		||||
            "agents.offline_agent", version="2.0.0", _quantity=20
 | 
			
		||||
        )
 | 
			
		||||
        agents = online + offline
 | 
			
		||||
        for agent in agents:
 | 
			
		||||
            baker.make_recipe("logs.pending_agentupdate_action", agent=agent)
 | 
			
		||||
 | 
			
		||||
        Agent.objects.update(version=settings.LATEST_AGENT_VER)
 | 
			
		||||
 | 
			
		||||
        handle_resolved_stuff()
 | 
			
		||||
 | 
			
		||||
        complete = PendingAction.objects.filter(
 | 
			
		||||
            action_type="agentupdate", status="completed"
 | 
			
		||||
        ).count()
 | 
			
		||||
        old = PendingAction.objects.filter(
 | 
			
		||||
            action_type="agentupdate", status="pending"
 | 
			
		||||
        ).count()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(complete, 20)
 | 
			
		||||
        self.assertEqual(old, 20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCorePermissions(TacticalTestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -93,14 +93,14 @@ def dashboard_info(request):
 | 
			
		||||
@permission_classes([IsAuthenticated, CoreSettingsPerms])
 | 
			
		||||
def email_test(request):
 | 
			
		||||
    core = get_core_settings()
 | 
			
		||||
    r = core.send_mail(
 | 
			
		||||
 | 
			
		||||
    msg, ok = core.send_mail(
 | 
			
		||||
        subject="Test from Tactical RMM", body="This is a test message", test=True
 | 
			
		||||
    )
 | 
			
		||||
    if not ok:
 | 
			
		||||
        return notify_error(msg)
 | 
			
		||||
 | 
			
		||||
    if not isinstance(r, bool) and isinstance(r, str):
 | 
			
		||||
        return notify_error(r)
 | 
			
		||||
 | 
			
		||||
    return Response("Email Test OK!")
 | 
			
		||||
    return Response(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@api_view(["POST"])
 | 
			
		||||
@@ -388,9 +388,8 @@ class TwilioSMSTest(APIView):
 | 
			
		||||
                "All fields are required, including at least 1 recipient"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        r = core.send_sms("TacticalRMM Test SMS", test=True)
 | 
			
		||||
        msg, ok = core.send_sms("TacticalRMM Test SMS", test=True)
 | 
			
		||||
        if not ok:
 | 
			
		||||
            return notify_error(msg)
 | 
			
		||||
 | 
			
		||||
        if not isinstance(r, bool) and isinstance(r, str):
 | 
			
		||||
            return notify_error(r)
 | 
			
		||||
 | 
			
		||||
        return Response("SMS Test sent successfully!")
 | 
			
		||||
        return Response(msg)
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
default_app_config = "logs.apps.LogsConfig"
 | 
			
		||||
 
 | 
			
		||||
@@ -26,3 +26,7 @@ object_logs = Recipe(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
login_logs = Recipe("logs.AuditLog", action=cycle(login_actions), object_type="user")
 | 
			
		||||
 | 
			
		||||
pending_agentupdate_action = Recipe(
 | 
			
		||||
    "logs.PendingAction", action_type="agentupdate", status="pending"
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -96,9 +96,14 @@ class PendingActions(APIView):
 | 
			
		||||
    def get(self, request, agent_id=None):
 | 
			
		||||
        if agent_id:
 | 
			
		||||
            agent = get_object_or_404(
 | 
			
		||||
                Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id
 | 
			
		||||
                Agent.objects.defer(*AGENT_DEFER).prefetch_related("pendingactions"),
 | 
			
		||||
                agent_id=agent_id,
 | 
			
		||||
            )
 | 
			
		||||
            actions = (
 | 
			
		||||
                PendingAction.objects.filter(agent=agent)
 | 
			
		||||
                .select_related("agent__site", "agent__site__client")
 | 
			
		||||
                .defer("agent__services", "agent__wmi_detail")
 | 
			
		||||
            )
 | 
			
		||||
            actions = PendingAction.objects.filter(agent=agent)
 | 
			
		||||
        else:
 | 
			
		||||
            actions = (
 | 
			
		||||
                PendingAction.objects.filter_by_role(request.user)  # type: ignore
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tacticalrmm/pytest.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tacticalrmm/pytest.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
[pytest]
 | 
			
		||||
DJANGO_SETTINGS_MODULE = tacticalrmm.settings
 | 
			
		||||
python_files = tests.py
 | 
			
		||||
addopts = -vvv --cov --cov-config=.coveragerc --cov-report=xml -n auto
 | 
			
		||||
 | 
			
		||||
filterwarnings =
 | 
			
		||||
    ignore::django.core.cache.CacheKeyWarning
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
coverage
 | 
			
		||||
coveralls
 | 
			
		||||
model_bakery
 | 
			
		||||
black
 | 
			
		||||
tblib
 | 
			
		||||
pytest
 | 
			
		||||
pytest-django
 | 
			
		||||
pytest-xdist
 | 
			
		||||
pytest-cov
 | 
			
		||||
codecov
 | 
			
		||||
@@ -80,7 +80,7 @@ class TestScriptViews(TacticalTestCase):
 | 
			
		||||
        resp = self.client.put(url, data, format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        script = Script.objects.get(pk=script.pk)
 | 
			
		||||
        self.assertEquals(script.description, "Description Change")
 | 
			
		||||
        self.assertEqual(script.description, "Description Change")
 | 
			
		||||
 | 
			
		||||
        # correct_hash = hmac.new(
 | 
			
		||||
        #     settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
 | 
			
		||||
@@ -467,7 +467,7 @@ class TestScriptSnippetViews(TacticalTestCase):
 | 
			
		||||
        resp = self.client.put(url, data, format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        snippet = ScriptSnippet.objects.get(pk=snippet.pk)
 | 
			
		||||
        self.assertEquals(snippet.name, "New Name")
 | 
			
		||||
        self.assertEqual(snippet.name, "New Name")
 | 
			
		||||
 | 
			
		||||
        self.check_not_authenticated("put", url)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ class TestServiceViews(TacticalTestCase):
 | 
			
		||||
        resp = self.client.get(url, format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        nats_cmd.assert_called_with(data={"func": "winservices"}, timeout=10)
 | 
			
		||||
        self.assertEquals(Agent.objects.get(pk=agent.pk).services, nats_return)
 | 
			
		||||
        self.assertEqual(Agent.objects.get(pk=agent.pk).services, nats_return)
 | 
			
		||||
 | 
			
		||||
        self.check_not_authenticated("get", url)
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +141,7 @@ class TestServiceViews(TacticalTestCase):
 | 
			
		||||
        nats_cmd.assert_called_with(
 | 
			
		||||
            {"func": "winsvcdetail", "payload": {"name": "alg"}}, timeout=10
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEquals(resp.data, nats_return)
 | 
			
		||||
        self.assertEqual(resp.data, nats_return)
 | 
			
		||||
 | 
			
		||||
        self.check_not_authenticated("get", url)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ class TestSoftwareViews(TacticalTestCase):
 | 
			
		||||
        # test without agent software
 | 
			
		||||
        resp = self.client.get(url, format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        self.assertEquals(resp.data, [])
 | 
			
		||||
        self.assertEqual(resp.data, [])
 | 
			
		||||
 | 
			
		||||
        # make some software
 | 
			
		||||
        software = baker.make(
 | 
			
		||||
@@ -60,7 +60,7 @@ class TestSoftwareViews(TacticalTestCase):
 | 
			
		||||
        serializer = InstalledSoftwareSerializer([software], many=True)
 | 
			
		||||
        resp = self.client.get(f"{base_url}/", format="json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        self.assertEquals(resp.data, serializer.data)
 | 
			
		||||
        self.assertEqual(resp.data, serializer.data)
 | 
			
		||||
 | 
			
		||||
        self.check_not_authenticated("get", url)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,11 +17,11 @@ LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh"
 | 
			
		||||
AUTH_USER_MODEL = "accounts.User"
 | 
			
		||||
 | 
			
		||||
# latest release
 | 
			
		||||
TRMM_VERSION = "0.13.0"
 | 
			
		||||
TRMM_VERSION = "0.13.2"
 | 
			
		||||
 | 
			
		||||
# bump this version everytime vue code is changed
 | 
			
		||||
# to alert user they need to manually refresh their browser
 | 
			
		||||
APP_VER = "0.0.161"
 | 
			
		||||
APP_VER = "0.0.163"
 | 
			
		||||
 | 
			
		||||
# https://github.com/amidaware/rmmagent
 | 
			
		||||
LATEST_AGENT_VER = "2.0.3"
 | 
			
		||||
@@ -49,7 +49,6 @@ ASGI_APPLICATION = "tacticalrmm.asgi.application"
 | 
			
		||||
LANGUAGE_CODE = "en-us"
 | 
			
		||||
TIME_ZONE = "UTC"
 | 
			
		||||
USE_I18N = True
 | 
			
		||||
USE_L10N = True
 | 
			
		||||
USE_TZ = True
 | 
			
		||||
 | 
			
		||||
STATIC_URL = "/static/"
 | 
			
		||||
@@ -96,23 +95,6 @@ if not DEBUG:
 | 
			
		||||
        {"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)}
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
    "django.middleware.security.SecurityMiddleware",
 | 
			
		||||
    "django.contrib.sessions.middleware.SessionMiddleware",
 | 
			
		||||
    "corsheaders.middleware.CorsMiddleware",  ##
 | 
			
		||||
    "tacticalrmm.middleware.LogIPMiddleware",
 | 
			
		||||
    "django.middleware.common.CommonMiddleware",
 | 
			
		||||
    "django.middleware.csrf.CsrfViewMiddleware",
 | 
			
		||||
    "django.contrib.auth.middleware.AuthenticationMiddleware",
 | 
			
		||||
    "tacticalrmm.middleware.AuditMiddleware",
 | 
			
		||||
    "tacticalrmm.middleware.LinuxMiddleware",
 | 
			
		||||
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
if DEMO:
 | 
			
		||||
    MIDDLEWARE += ("tacticalrmm.middleware.DemoMiddleware",)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
INSTALLED_APPS = [
 | 
			
		||||
    "django.contrib.auth",
 | 
			
		||||
    "django.contrib.contenttypes",
 | 
			
		||||
@@ -257,11 +239,9 @@ LOGGING = {
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if "AZPIPELINE" in os.environ:
 | 
			
		||||
    ADMIN_ENABLED = False
 | 
			
		||||
 | 
			
		||||
if "GHACTIONS" in os.environ:
 | 
			
		||||
    print("-----------------------PIPELINE----------------------------")
 | 
			
		||||
    print("-----------------------GHACTIONS----------------------------")
 | 
			
		||||
    DATABASES = {
 | 
			
		||||
        "default": {
 | 
			
		||||
            "ENGINE": "django.db.backends.postgresql",
 | 
			
		||||
 
 | 
			
		||||
@@ -65,9 +65,11 @@ services:
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - tactical-postgres
 | 
			
		||||
      - tactical-meshcentral
 | 
			
		||||
      - tactical-redis
 | 
			
		||||
    networks:
 | 
			
		||||
      - api-db
 | 
			
		||||
      - proxy
 | 
			
		||||
      - redis
 | 
			
		||||
    volumes:
 | 
			
		||||
      - tactical_data:/opt/tactical
 | 
			
		||||
      - mesh_data:/meshcentral-data
 | 
			
		||||
 
 | 
			
		||||
@@ -328,7 +328,12 @@ else
 | 
			
		||||
    echo -ne ${RED} SSL Certificate has expired or doesnt exist for $domain  | tee -a checklog.log
 | 
			
		||||
	printf >&2 "\n\n"
 | 
			
		||||
fi
 | 
			
		||||
	echo -ne ${YELLOW} Getting summary output of logs | tee -a checklog.log  
 | 
			
		||||
 | 
			
		||||
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log  | tee -a checklog.log
 | 
			
		||||
	printf >&2 "\n\n"
 | 
			
		||||
tail /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log  | tee -a checklog.log
 | 
			
		||||
	printf >&2 "\n\n"
 | 
			
		||||
 | 
			
		||||
printf >&2 "\n\n"
 | 
			
		||||
echo -ne ${YELLOW} 
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ export default function () {
 | 
			
		||||
        showCommunityScripts: false,
 | 
			
		||||
        agentDblClickAction: "",
 | 
			
		||||
        agentUrlAction: null,
 | 
			
		||||
        defaultAgentTblTab: "server",
 | 
			
		||||
        defaultAgentTblTab: null,
 | 
			
		||||
        clientTreeSort: "alphafail",
 | 
			
		||||
        clientTreeSplitter: 20,
 | 
			
		||||
        noCodeSign: false,
 | 
			
		||||
@@ -253,14 +253,15 @@ export default function () {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          const sorted = output.sort((a, b) => a.label.localeCompare(b.label));
 | 
			
		||||
          if (state.clientTreeSort === "alphafail") {
 | 
			
		||||
            // move failing clients to the top
 | 
			
		||||
            const failing = output.filter(i => i.color === "negative" || i.color === "warning");
 | 
			
		||||
            const ok = output.filter(i => i.color !== "negative" && i.color !== "warning");
 | 
			
		||||
            const failing = sorted.filter(i => i.color === "negative" || i.color === "warning");
 | 
			
		||||
            const ok = sorted.filter(i => i.color !== "negative" && i.color !== "warning");
 | 
			
		||||
            const sortedByFailing = [...failing, ...ok];
 | 
			
		||||
            commit("loadTree", sortedByFailing);
 | 
			
		||||
          } else {
 | 
			
		||||
            commit("loadTree", output);
 | 
			
		||||
            commit("loadTree", sorted);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        })
 | 
			
		||||
 
 | 
			
		||||
@@ -453,7 +453,6 @@ export default {
 | 
			
		||||
          field: "last_seen",
 | 
			
		||||
          sortable: true,
 | 
			
		||||
          align: "left",
 | 
			
		||||
          sort: (a, b) => this.dateStringToUnix(a) - this.dateStringToUnix(b),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: "boot_time",
 | 
			
		||||
@@ -491,10 +490,12 @@ export default {
 | 
			
		||||
    selectedTree(newVal, oldVal) {
 | 
			
		||||
      if (this.clearSearchWhenSwitching) this.clearFilter();
 | 
			
		||||
    },
 | 
			
		||||
    tab(newVal, oldVal) {
 | 
			
		||||
      this.$store.dispatch("loadAgents");
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    getTree() {
 | 
			
		||||
      this.$store.dispatch("loadAgents");
 | 
			
		||||
      this.$store.dispatch("loadTree");
 | 
			
		||||
    },
 | 
			
		||||
    clearTreeSelected() {
 | 
			
		||||
@@ -708,7 +709,6 @@ export default {
 | 
			
		||||
      },
 | 
			
		||||
      set(newVal) {
 | 
			
		||||
        this.$store.commit("SET_DEFAULT_AGENT_TBL_TAB", newVal);
 | 
			
		||||
        this.$store.dispatch("loadAgents");
 | 
			
		||||
        this.$store.commit("destroySubTable");
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user