Skip to content

Commit

Permalink
sync mesh users/perms with trmm #182
Browse files Browse the repository at this point in the history
  • Loading branch information
wh1te909 committed Feb 23, 2024
1 parent 18bc74b commit 5dac1ef
Show file tree
Hide file tree
Showing 19 changed files with 352 additions and 14 deletions.
8 changes: 8 additions & 0 deletions api/tacticalrmm/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ class User(AbstractUser, BaseAuditModel):
on_delete=models.SET_NULL,
)

@property
def mesh_user_id(self):
return f"user//{self.mesh_username}"

@property
def mesh_username(self):
return f"{self.username}___{self.pk}"

@staticmethod
def serialize(user):
# serializes the task and returns json
Expand Down
6 changes: 6 additions & 0 deletions api/tacticalrmm/accounts/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import TYPE_CHECKING

from django.conf import settings

if TYPE_CHECKING:
from django.http import HttpRequest

from accounts.models import User


Expand All @@ -16,3 +18,7 @@ def is_root_user(*, request: "HttpRequest", user: "User") -> bool:
getattr(settings, "DEMO", False) and request.user.username == settings.ROOT_USER
)
return root or demo


def is_superuser(user: "User") -> bool:
return user.role and getattr(user.role, "is_superuser")
7 changes: 6 additions & 1 deletion api/tacticalrmm/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from rest_framework.views import APIView

from accounts.utils import is_root_user
from core.tasks import sync_mesh_perms_task
from logs.models import AuditLog
from tacticalrmm.helpers import notify_error

Expand Down Expand Up @@ -133,6 +134,7 @@ def post(self, request):
user.role = role

user.save()
sync_mesh_perms_task.delay()
return Response(user.username)


Expand All @@ -153,6 +155,7 @@ def put(self, request, pk):
serializer = UserSerializer(instance=user, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
sync_mesh_perms_task.delay()

return Response("ok")

Expand All @@ -162,7 +165,7 @@ def delete(self, request, pk):
return notify_error("The root user cannot be deleted from the UI")

user.delete()

sync_mesh_perms_task.delay()
return Response("ok")


Expand Down Expand Up @@ -243,11 +246,13 @@ def put(self, request, pk):
serializer = RoleSerializer(instance=role, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
sync_mesh_perms_task.delay()
return Response("Role was edited")

def delete(self, request, pk):
role = get_object_or_404(Role, pk=pk)
role.delete()
sync_mesh_perms_task.delay()
return Response("Role was removed")


Expand Down
6 changes: 5 additions & 1 deletion api/tacticalrmm/agents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from agents.utils import get_agent_url
from checks.models import CheckResult
from core.models import TZ_CHOICES
from core.utils import get_core_settings, send_command_with_mesh
from core.utils import _b64_to_hex, get_core_settings, send_command_with_mesh
from logs.models import BaseAuditModel, DebugLog, PendingAction
from tacticalrmm.constants import (
AGENT_STATUS_OFFLINE,
Expand Down Expand Up @@ -452,6 +452,10 @@ def serial_number(self) -> str:
except:
return ""

@property
def hex_mesh_node_id(self) -> str:
return _b64_to_hex(self.mesh_node_id)

@classmethod
def online_agents(cls, min_version: str = "") -> "List[Agent]":
if min_version:
Expand Down
10 changes: 8 additions & 2 deletions api/tacticalrmm/agents/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from core.tasks import sync_mesh_perms_task
from core.utils import (
get_core_settings,
get_mesh_ws_url,
Expand Down Expand Up @@ -258,6 +259,7 @@ def put(self, request, agent_id):
serializer.is_valid(raise_exception=True)
serializer.save()

sync_mesh_perms_task.delay()
return Response("The agent was updated successfully")

# uninstall agent
Expand All @@ -283,6 +285,7 @@ def delete(self, request, agent_id):
message=f"Unable to remove agent {name} from meshcentral database: {e}",
log_type=DebugLogType.AGENT_ISSUES,
)
sync_mesh_perms_task.delay()
return Response(f"{name} will now be uninstalled.")


Expand Down Expand Up @@ -326,9 +329,12 @@ def get(self, request, agent_id):
core = get_core_settings()

if not core.mesh_disable_auto_login:
token = get_login_token(
key=core.mesh_token, user=f"user//{core.mesh_username}"
user = (
request.user.mesh_user_id
if core.sync_mesh_with_trmm
else f"user//{core.mesh_username}"
)
token = get_login_token(key=core.mesh_token, user=user)
token_param = f"login={token}&"
else:
token_param = ""
Expand Down
2 changes: 2 additions & 0 deletions api/tacticalrmm/apiv3/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from checks.constants import CHECK_DEFER, CHECK_RESULT_DEFER
from checks.models import Check, CheckResult
from checks.serializers import CheckRunnerGetSerializer
from core.tasks import sync_mesh_perms_task
from core.utils import (
download_mesh_agent,
get_core_settings,
Expand Down Expand Up @@ -481,6 +482,7 @@ def post(self, request):
)

ret = {"pk": agent.pk, "token": token.key}
sync_mesh_perms_task.delay()
return Response(ret)


Expand Down
2 changes: 1 addition & 1 deletion api/tacticalrmm/automation/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def test_update_policy(self, cache_alert_template):
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)

cache_alert_template.called_once()
cache_alert_template.assert_called_once()

self.check_not_authenticated("put", url)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
AGENT_OUTAGES_LOCK,
ORPHANED_WIN_TASK_LOCK,
RESOLVE_ALERTS_LOCK,
SYNC_MESH_PERMS_TASK_LOCK,
SYNC_SCHED_TASK_LOCK,
)

Expand All @@ -18,5 +19,6 @@ def handle(self, *args, **kwargs):
ORPHANED_WIN_TASK_LOCK,
RESOLVE_ALERTS_LOCK,
SYNC_SCHED_TASK_LOCK,
SYNC_MESH_PERMS_TASK_LOCK,
):
cache.delete(key)
105 changes: 105 additions & 0 deletions api/tacticalrmm/core/mesh_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import asyncio
import json
from typing import TYPE_CHECKING, Any

import websockets

from accounts.utils import is_superuser
from tacticalrmm.helpers import make_random_password
from tacticalrmm.logger import logger

if TYPE_CHECKING:
from accounts.models import User


def mesh_action(
*, payload: dict[str, Any], uri: str, wait=True
) -> dict[str, Any] | None:
async def _do(payload, uri: str):
async with websockets.connect(uri) as ws:
await ws.send(json.dumps(payload))
if wait:
async for message in ws:
r = json.loads(message)
if r["action"] == payload["action"]:
return r
else:
return None

payload["responseid"] = "meshctrl"
logger.debug(payload)

return asyncio.run(_do(payload, uri))


def update_mesh_displayname(*, user_info: dict[str, Any], uri: str) -> None:
payload = {
"action": "edituser",
"id": user_info["_id"],
"realname": user_info["full_name"],
}
mesh_action(payload=payload, uri=uri, wait=False)
logger.debug(
f"Updating user {user_info['username']} display name to: {user_info['full_name']}"
)


def add_user_to_mesh(*, user_info: dict[str, Any], uri: str) -> None:
payload = {
"action": "adduser",
"username": user_info["username"],
"email": user_info["email"],
"pass": make_random_password(len=30),
"resetNextLogin": False,
"randomPassword": False,
"removeEvents": False,
}
mesh_action(payload=payload, uri=uri, wait=False)
logger.debug(f"Adding user {user_info['username']} to mesh")
if user_info["full_name"]:
update_mesh_displayname(user_info=user_info, uri=uri)


def delete_user_from_mesh(*, mesh_user_id: str, uri: str) -> None:
logger.debug(f"Deleting {mesh_user_id} from mesh")
payload = {
"action": "deleteuser",
"userid": mesh_user_id,
}
mesh_action(payload=payload, uri=uri, wait=False)


def add_agent_to_user(*, user_id: str, node_id: str, hostname: str, uri: str) -> None:
logger.debug(f"Adding agent {hostname} to {user_id}")
payload = {
"action": "adddeviceuser",
"nodeid": node_id,
"userids": [user_id],
"rights": 72,
"remove": False,
}
mesh_action(payload=payload, uri=uri, wait=False)


def remove_agent_from_user(*, user_id: str, node_id: str, uri: str) -> None:
logger.debug(f"Removing agent {node_id} from {user_id}")
payload = {
"action": "adddeviceuser",
"nodeid": node_id,
"userids": [user_id],
"rights": 0,
"remove": True,
}
mesh_action(payload=payload, uri=uri, wait=False)


def has_mesh_perms(*, user: "User") -> bool:
if user.is_superuser or is_superuser(user):
return True

return user.role and getattr(user.role, "can_use_mesh")


def get_mesh_users(*, uri: str) -> dict[str, Any] | None:
payload = {"action": "users"}
return mesh_action(payload=payload, uri=uri, wait=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-02-20 02:51

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("core", "0041_auto_20240128_0301"),
]

operations = [
migrations.AddField(
model_name="coresettings",
name="mesh_company_name",
field=models.CharField(blank=True, max_length=255, null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-02-23 19:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("core", "0042_coresettings_mesh_company_name"),
]

operations = [
migrations.AddField(
model_name="coresettings",
name="sync_mesh_with_trmm",
field=models.BooleanField(default=True),
),
]
4 changes: 3 additions & 1 deletion api/tacticalrmm/core/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import smtplib
from contextlib import suppress
from email.message import EmailMessage
from email.headerregistry import Address
from email.message import EmailMessage
from typing import TYPE_CHECKING, List, Optional, cast

import requests
Expand Down Expand Up @@ -75,6 +75,8 @@ class CoreSettings(BaseAuditModel):
max_length=255, null=True, blank=True, default="TacticalRMM"
)
mesh_disable_auto_login = models.BooleanField(default=False)
mesh_company_name = models.CharField(max_length=255, null=True, blank=True)
sync_mesh_with_trmm = models.BooleanField(default=True)
agent_auto_update = models.BooleanField(default=True)
workstation_policy = models.ForeignKey(
"automation.Policy",
Expand Down
Loading

0 comments on commit 5dac1ef

Please sign in to comment.