diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index d5f4617505..cb06c9b17c 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -14,18 +14,18 @@ jobs: name: Tests strategy: matrix: - python-version: ['3.10.4'] + python-version: ["3.10.4"] steps: - uses: actions/checkout@v3 - uses: harmon758/postgresql-action@v1 with: - postgresql version: '14' - postgresql db: 'pipeline' - postgresql user: 'pipeline' - postgresql password: 'pipeline123456' - + postgresql version: "14" + postgresql db: "pipeline" + postgresql user: "pipeline" + postgresql password: "pipeline123456" + - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: @@ -49,13 +49,13 @@ jobs: pip install -r requirements.txt -r requirements-test.txt - name: Codestyle black - working-directory: api/tacticalrmm + working-directory: api run: | black --exclude migrations/ --check tacticalrmm if [ $? -ne 0 ]; then exit 1 fi - + - name: Run django tests env: GHACTIONS: "yes" diff --git a/.vscode/settings.json b/.vscode/settings.json index 7eebc2f2a0..977027a5b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,10 @@ "editor.bracketPairColorization.enabled": true, "editor.guides.bracketPairs": true, "editor.formatOnSave": true, + "files.associations": { + "**/ansible/**/*.yml": "ansible", + "**/docker/**/docker-compose*.yml": "dockercompose" + }, "files.watcherExclude": { "files.watcherExclude": { "**/.git/objects/**": true, diff --git a/api/tacticalrmm/agents/models.py b/api/tacticalrmm/agents/models.py index 1256812e2c..30a299d99f 100644 --- a/api/tacticalrmm/agents/models.py +++ b/api/tacticalrmm/agents/models.py @@ -532,12 +532,17 @@ def run_script( wait: bool = False, run_on_any: bool = False, history_pk: int = 0, + run_as_user: bool = False, ) -> Any: from scripts.models import Script script = Script.objects.get(pk=scriptpk) + # always override if set on script model + if script.run_as_user: + run_as_user = True + parsed_args = script.parse_script_args(self, script.shell, args) data = { @@ -548,6 +553,7 @@ def run_script( "code": script.code, "shell": script.shell, }, + "run_as_user": run_as_user, } if history_pk != 0: diff --git a/api/tacticalrmm/agents/tasks.py b/api/tacticalrmm/agents/tasks.py index b306de63c3..8a0e05ea78 100644 --- a/api/tacticalrmm/agents/tasks.py +++ b/api/tacticalrmm/agents/tasks.py @@ -153,6 +153,7 @@ def run_script_email_results_task( emails: list[str], args: list[str] = [], history_pk: int = 0, + run_as_user: bool = False, ): agent = Agent.objects.get(pk=agentpk) script = Script.objects.get(pk=scriptpk) @@ -163,6 +164,7 @@ def run_script_email_results_task( timeout=nats_timeout, wait=True, history_pk=history_pk, + run_as_user=run_as_user, ) if r == "timeout": DebugLog.error( diff --git a/api/tacticalrmm/agents/tests/test_agents.py b/api/tacticalrmm/agents/tests/test_agents.py index 9656d714f0..61c429c6cc 100644 --- a/api/tacticalrmm/agents/tests/test_agents.py +++ b/api/tacticalrmm/agents/tests/test_agents.py @@ -403,6 +403,7 @@ def test_send_raw_cmd(self, mock_ret): "cmd": "ipconfig", "shell": "cmd", "timeout": 30, + "run_as_user": False, } mock_ret.return_value = "nt authority\\system" r = self.client.post(url, data, format="json") @@ -538,6 +539,7 @@ def test_run_script(self, run_script, email_task): "output": "wait", "args": [], "timeout": 15, + "run_as_user": False, } r = self.client.post(url, data, format="json") @@ -547,7 +549,12 @@ def test_run_script(self, run_script, email_task): raise AgentHistory.DoesNotExist run_script.assert_called_with( - scriptpk=script.pk, args=[], timeout=18, wait=True, history_pk=hist.pk + scriptpk=script.pk, + args=[], + timeout=18, + wait=True, + history_pk=hist.pk, + run_as_user=False, ) run_script.reset_mock() @@ -559,6 +566,7 @@ def test_run_script(self, run_script, email_task): "timeout": 15, "emailMode": "default", "emails": ["admin@example.com", "bob@example.com"], + "run_as_user": False, } r = self.client.post(url, data, format="json") self.assertEqual(r.status_code, 200) @@ -568,6 +576,7 @@ def test_run_script(self, run_script, email_task): nats_timeout=18, emails=[], args=["abc", "123"], + run_as_user=False, ) email_task.reset_mock() @@ -581,6 +590,7 @@ def test_run_script(self, run_script, email_task): nats_timeout=18, emails=["admin@example.com", "bob@example.com"], args=["abc", "123"], + run_as_user=False, ) # test fire and forget @@ -589,6 +599,7 @@ def test_run_script(self, run_script, email_task): "output": "forget", "args": ["hello", "world"], "timeout": 22, + "run_as_user": True, } r = self.client.post(url, data, format="json") @@ -598,7 +609,11 @@ def test_run_script(self, run_script, email_task): raise AgentHistory.DoesNotExist run_script.assert_called_with( - scriptpk=script.pk, args=["hello", "world"], timeout=25, history_pk=hist.pk + scriptpk=script.pk, + args=["hello", "world"], + timeout=25, + history_pk=hist.pk, + run_as_user=True, ) run_script.reset_mock() @@ -613,6 +628,7 @@ def test_run_script(self, run_script, email_task): "timeout": 22, "custom_field": custom_field.pk, "save_all_output": True, + "run_as_user": False, } r = self.client.post(url, data, format="json") @@ -627,6 +643,7 @@ def test_run_script(self, run_script, email_task): timeout=25, wait=True, history_pk=hist.pk, + run_as_user=False, ) run_script.reset_mock() @@ -644,6 +661,7 @@ def test_run_script(self, run_script, email_task): "timeout": 22, "custom_field": custom_field.pk, "save_all_output": False, + "run_as_user": False, } r = self.client.post(url, data, format="json") @@ -658,6 +676,7 @@ def test_run_script(self, run_script, email_task): timeout=25, wait=True, history_pk=hist.pk, + run_as_user=False, ) run_script.reset_mock() @@ -677,6 +696,7 @@ def test_run_script(self, run_script, email_task): "timeout": 22, "custom_field": custom_field.pk, "save_all_output": False, + "run_as_user": False, } r = self.client.post(url, data, format="json") @@ -691,6 +711,7 @@ def test_run_script(self, run_script, email_task): timeout=25, wait=True, history_pk=hist.pk, + run_as_user=False, ) run_script.reset_mock() @@ -707,6 +728,7 @@ def test_run_script(self, run_script, email_task): "output": "note", "args": ["hello", "world"], "timeout": 22, + "run_as_user": False, } r = self.client.post(url, data, format="json") @@ -721,6 +743,7 @@ def test_run_script(self, run_script, email_task): timeout=25, wait=True, history_pk=hist.pk, + run_as_user=False, ) run_script.reset_mock() diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 01c12ebc49..ddcfd3f07c 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -415,6 +415,7 @@ def send_raw_cmd(request, agent_id): "command": request.data["cmd"], "shell": shell, }, + "run_as_user": request.data["run_as_user"], } hist = AgentHistory.objects.create( @@ -691,6 +692,7 @@ def run_script(request, agent_id): script = get_object_or_404(Script, pk=request.data["script"]) output = request.data["output"] args = request.data["args"] + run_as_user: bool = request.data["run_as_user"] req_timeout = int(request.data["timeout"]) + 3 AuditLog.audit_script_run( @@ -715,6 +717,7 @@ def run_script(request, agent_id): timeout=req_timeout, wait=True, history_pk=history_pk, + run_as_user=run_as_user, ) return Response(r) @@ -728,6 +731,7 @@ def run_script(request, agent_id): nats_timeout=req_timeout, emails=emails, args=args, + run_as_user=run_as_user, ) elif output == "collector": from core.models import CustomField @@ -738,6 +742,7 @@ def run_script(request, agent_id): timeout=req_timeout, wait=True, history_pk=history_pk, + run_as_user=run_as_user, ) custom_field = CustomField.objects.get(pk=request.data["custom_field"]) @@ -766,13 +771,18 @@ def run_script(request, agent_id): timeout=req_timeout, wait=True, history_pk=history_pk, + run_as_user=run_as_user, ) Note.objects.create(agent=agent, user=request.user, note=r) return Response(r) else: agent.run_script( - scriptpk=script.pk, args=args, timeout=req_timeout, history_pk=history_pk + scriptpk=script.pk, + args=args, + timeout=req_timeout, + history_pk=history_pk, + run_as_user=run_as_user, ) return Response(f"{script.name} will now be run on {agent.hostname}") @@ -907,7 +917,7 @@ def bulk(request): shell, request.data["timeout"], request.user.username[:50], - run_on_offline=request.data["offlineAgents"], + request.data["run_as_user"], ) return Response(f"Command will now be run on {len(agents)} agents") @@ -919,6 +929,7 @@ def bulk(request): request.data["args"], request.data["timeout"], request.user.username[:50], + request.data["run_as_user"], ) return Response(f"{script.name} will now be run on {len(agents)} agents") diff --git a/api/tacticalrmm/alerts/models.py b/api/tacticalrmm/alerts/models.py index a0db7040e7..df034a76c1 100644 --- a/api/tacticalrmm/alerts/models.py +++ b/api/tacticalrmm/alerts/models.py @@ -469,6 +469,7 @@ def handle_alert_failure( wait=True, full=True, run_on_any=True, + run_as_user=False, ) # command was successful @@ -591,6 +592,7 @@ def handle_alert_resolve( wait=True, full=True, run_on_any=True, + run_as_user=False, ) # command was successful diff --git a/api/tacticalrmm/alerts/tests.py b/api/tacticalrmm/alerts/tests.py index badf8488a3..5896496644 100644 --- a/api/tacticalrmm/alerts/tests.py +++ b/api/tacticalrmm/alerts/tests.py @@ -1424,6 +1424,7 @@ def test_alert_actions( "timeout": 30, "script_args": [], "payload": {"code": failure_action.code, "shell": failure_action.shell}, + "run_as_user": False, } nats_cmd.assert_called_with(data, timeout=30, wait=True) @@ -1452,6 +1453,7 @@ def test_alert_actions( "timeout": 35, "script_args": ["nice_arg"], "payload": {"code": resolved_action.code, "shell": resolved_action.shell}, + "run_as_user": False, } nats_cmd.assert_called_with(data, timeout=35, wait=True) diff --git a/api/tacticalrmm/autotasks/serializers.py b/api/tacticalrmm/autotasks/serializers.py index 67851b7cea..c68caa733a 100644 --- a/api/tacticalrmm/autotasks/serializers.py +++ b/api/tacticalrmm/autotasks/serializers.py @@ -241,6 +241,7 @@ def get_task_actions(self, obj): ), "shell": script.shell, "timeout": action["timeout"], + "run_as_user": script.run_as_user, } ) if actions_to_remove: diff --git a/api/tacticalrmm/core/tasks.py b/api/tacticalrmm/core/tasks.py index 7d7b7cf5b5..0cf51e2578 100644 --- a/api/tacticalrmm/core/tasks.py +++ b/api/tacticalrmm/core/tasks.py @@ -174,7 +174,7 @@ def _get_failing_data(agents: "QuerySet[Any]") -> Dict[str, bool]: and task.task_result.status == TaskStatus.FAILING and task.alert_severity == AlertSeverity.WARNING ): - data["warning"] + data["warning"] = True return data diff --git a/api/tacticalrmm/scripts/migrations/0018_script_run_as_user.py b/api/tacticalrmm/scripts/migrations/0018_script_run_as_user.py new file mode 100644 index 0000000000..37720768df --- /dev/null +++ b/api/tacticalrmm/scripts/migrations/0018_script_run_as_user.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.6 on 2022-07-30 21:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scripts', '0017_auto_20220311_0100'), + ] + + operations = [ + migrations.AddField( + model_name='script', + name='run_as_user', + field=models.BooleanField(default=False), + ), + ] diff --git a/api/tacticalrmm/scripts/models.py b/api/tacticalrmm/scripts/models.py index e36480337c..d17ede5121 100644 --- a/api/tacticalrmm/scripts/models.py +++ b/api/tacticalrmm/scripts/models.py @@ -40,6 +40,7 @@ class Script(BaseAuditModel): supported_platforms = ArrayField( models.CharField(max_length=20), null=True, blank=True, default=list ) + run_as_user = models.BooleanField(default=False) def __str__(self): return self.name diff --git a/api/tacticalrmm/scripts/serializers.py b/api/tacticalrmm/scripts/serializers.py index ea8be32e1d..0cdb27ae5f 100644 --- a/api/tacticalrmm/scripts/serializers.py +++ b/api/tacticalrmm/scripts/serializers.py @@ -20,6 +20,7 @@ class Meta: "filename", "hidden", "supported_platforms", + "run_as_user", ] @@ -43,16 +44,17 @@ class Meta: "filename", "hidden", "supported_platforms", + "run_as_user", ] class ScriptCheckSerializer(ModelSerializer): code = ReadOnlyField() - script_hash = ReadOnlyField + script_hash = ReadOnlyField() class Meta: model = Script - fields = ["code", "shell", "script_hash"] + fields = ["code", "shell", "run_as_user", "script_hash"] class ScriptSnippetSerializer(ModelSerializer): diff --git a/api/tacticalrmm/scripts/tasks.py b/api/tacticalrmm/scripts/tasks.py index 4e662a8b96..64226301fe 100644 --- a/api/tacticalrmm/scripts/tasks.py +++ b/api/tacticalrmm/scripts/tasks.py @@ -9,7 +9,12 @@ @app.task def handle_bulk_command_task( - agentpks, cmd, shell, timeout, username, run_on_offline=False + agentpks: list[int], + cmd: str, + shell: str, + timeout, + username, + run_as_user: bool = False, ) -> None: nats_data = { "func": "rawcmd", @@ -18,7 +23,9 @@ def handle_bulk_command_task( "command": cmd, "shell": shell, }, + "run_as_user": run_as_user, } + agent: "Agent" for agent in Agent.objects.filter(pk__in=agentpks): hist = AgentHistory.objects.create( agent=agent, @@ -33,9 +40,15 @@ def handle_bulk_command_task( @app.task def handle_bulk_script_task( - scriptpk: int, agentpks: List[int], args: List[str], timeout: int, username: str + scriptpk: int, + agentpks: List[int], + args: List[str], + timeout: int, + username: str, + run_as_user: bool = False, ) -> None: script = Script.objects.get(pk=scriptpk) + agent: "Agent" for agent in Agent.objects.filter(pk__in=agentpks): hist = AgentHistory.objects.create( agent=agent, @@ -44,5 +57,9 @@ def handle_bulk_script_task( username=username, ) agent.run_script( - scriptpk=script.pk, args=args, timeout=timeout, history_pk=hist.pk + scriptpk=script.pk, + args=args, + timeout=timeout, + history_pk=hist.pk, + run_as_user=run_as_user, ) diff --git a/api/tacticalrmm/scripts/tests.py b/api/tacticalrmm/scripts/tests.py index c47c05d8a1..a2b4c97d14 100644 --- a/api/tacticalrmm/scripts/tests.py +++ b/api/tacticalrmm/scripts/tests.py @@ -145,6 +145,7 @@ def test_test_script(self, run_script): "timeout": 90, "args": [], "shell": ScriptShell.POWERSHELL, + "run_as_user": False, } resp = self.client.post(url, data, format="json") diff --git a/api/tacticalrmm/scripts/views.py b/api/tacticalrmm/scripts/views.py index 37a30ab186..2361a52544 100644 --- a/api/tacticalrmm/scripts/views.py +++ b/api/tacticalrmm/scripts/views.py @@ -160,6 +160,7 @@ def post(self, request, agent_id): "code": Script.replace_with_snippets(request.data["code"]), "shell": request.data["shell"], }, + "run_as_user": request.data["run_as_user"], } r = asyncio.run( diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index a9f2e570a8..716007b868 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -17,17 +17,17 @@ AUTH_USER_MODEL = "accounts.User" # latest release -TRMM_VERSION = "0.14.3" +TRMM_VERSION = "0.14.4" # https://github.com/amidaware/tacticalrmm-web -WEB_VERSION = "0.100.6" +WEB_VERSION = "0.100.7" # bump this version everytime vue code is changed # to alert user they need to manually refresh their browser -APP_VER = "0.0.167" +APP_VER = "0.0.168" # https://github.com/amidaware/rmmagent -LATEST_AGENT_VER = "2.1.2" +LATEST_AGENT_VER = "2.2.0" MESH_VER = "1.0.60"