Skip to content

Commit

Permalink
Release 0.19.0
Browse files Browse the repository at this point in the history
  • Loading branch information
wh1te909 committed Jul 12, 2024
2 parents de0a69e + 50af28b commit 442f09d
Show file tree
Hide file tree
Showing 52 changed files with 1,510 additions and 312 deletions.
1 change: 1 addition & 0 deletions .devcontainer/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ ALLOWED_HOSTS = ['${API_HOST}', '*']
ADMIN_URL = 'admin/'
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = ['https://${API_HOST}']
DATABASES = {
'default': {
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"python.analysis.diagnosticSeverityOverrides": {
"reportUnusedImport": "error",
"reportDuplicateImport": "error",
"reportGeneralTypeIssues": "none"
"reportGeneralTypeIssues": "none",
"reportOptionalMemberAccess": "none",
},
"python.analysis.typeCheckingMode": "basic",
"editor.bracketPairColorization.enabled": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.13 on 2024-06-28 20:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0036_remove_role_can_ping_agents"),
]

operations = [
migrations.AddField(
model_name="role",
name="can_run_server_scripts",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="role",
name="can_use_webterm",
field=models.BooleanField(default=False),
),
]
2 changes: 2 additions & 0 deletions api/tacticalrmm/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ class Role(BaseAuditModel):
can_run_urlactions = models.BooleanField(default=False)
can_view_customfields = models.BooleanField(default=False)
can_manage_customfields = models.BooleanField(default=False)
can_run_server_scripts = models.BooleanField(default=False)
can_use_webterm = models.BooleanField(default=False)

# checks
can_list_checks = models.BooleanField(default=False)
Expand Down
10 changes: 5 additions & 5 deletions api/tacticalrmm/accounts/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ def setUp(self):
self.bob.save()

def test_check_creds(self):
url = "/checkcreds/"
url = "/v2/checkcreds/"

data = {"username": "bob", "password": "hunter2"}
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertIn("totp", r.data.keys())
self.assertEqual(r.data["totp"], "totp not set")
self.assertEqual(r.data["totp"], False)

data = {"username": "bob", "password": "a3asdsa2314"}
r = self.client.post(url, data, format="json")
Expand All @@ -40,7 +40,7 @@ def test_check_creds(self):
data = {"username": "bob", "password": "hunter2"}
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "ok")
self.assertEqual(r.data["totp"], True)

# test user set to block dashboard logins
self.bob.block_dashboard_login = True
Expand All @@ -50,7 +50,7 @@ def test_check_creds(self):

@patch("pyotp.TOTP.verify")
def test_login_view(self, mock_verify):
url = "/login/"
url = "/v2/login/"

mock_verify.return_value = True
data = {"username": "bob", "password": "hunter2", "twofactor": "123456"}
Expand Down Expand Up @@ -404,7 +404,7 @@ def test_post_totp_set(self):

r = self.client.post(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "totp token already set")
self.assertEqual(r.data, False)


class TestAPIAuthentication(TacticalTestCase):
Expand Down
87 changes: 86 additions & 1 deletion api/tacticalrmm/accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datetime

import pyotp
from django.conf import settings
from django.contrib.auth import login
Expand Down Expand Up @@ -26,7 +28,87 @@
)


class CheckCredsV2(KnoxLoginView):
permission_classes = (AllowAny,)

# restrict time on tokens issued by this view to 3 min
def get_token_ttl(self):
return datetime.timedelta(seconds=180)

def post(self, request, format=None):
# check credentials
serializer = AuthTokenSerializer(data=request.data)
if not serializer.is_valid():
AuditLog.audit_user_failed_login(
request.data["username"], debug_info={"ip": request._client_ip}
)
return notify_error("Bad credentials")

user = serializer.validated_data["user"]

if user.block_dashboard_login:
return notify_error("Bad credentials")

# if totp token not set modify response to notify frontend
if not user.totp_key:
login(request, user)
response = super().post(request, format=None)
response.data["totp"] = False
return response

return Response({"totp": True})


class LoginViewV2(KnoxLoginView):
permission_classes = (AllowAny,)

def post(self, request, format=None):
valid = False

serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]

if user.block_dashboard_login:
return notify_error("Bad credentials")

token = request.data["twofactor"]
totp = pyotp.TOTP(user.totp_key)

if settings.DEBUG and token == "sekret":
valid = True
elif getattr(settings, "DEMO", False):
valid = True
elif totp.verify(token, valid_window=10):
valid = True

if valid:
login(request, user)

# save ip information
ipw = IpWare()
client_ip, _ = ipw.get_client_ip(request.META)
if client_ip:
user.last_login_ip = str(client_ip)
user.save()

AuditLog.audit_user_login_successful(
request.data["username"], debug_info={"ip": request._client_ip}
)
response = super().post(request, format=None)
response.data["username"] = request.user.username
return Response(response.data)
else:
AuditLog.audit_user_failed_twofactor(
request.data["username"], debug_info={"ip": request._client_ip}
)
return notify_error("Bad credentials")


class CheckCreds(KnoxLoginView):
# TODO
# This view is deprecated as of 0.19.0
# Needed for the initial update to 0.19.0 so frontend code doesn't break on login
permission_classes = (AllowAny,)

def post(self, request, format=None):
Expand Down Expand Up @@ -54,6 +136,9 @@ def post(self, request, format=None):


class LoginView(KnoxLoginView):
# TODO
# This view is deprecated as of 0.19.0
# Needed for the initial update to 0.19.0 so frontend code doesn't break on login
permission_classes = (AllowAny,)

def post(self, request, format=None):
Expand Down Expand Up @@ -207,7 +292,7 @@ def post(self, request):
user.save(update_fields=["totp_key"])
return Response(TOTPSetupSerializer(user).data)

return Response("totp token already set")
return Response(False)


class UserUI(APIView):
Expand Down
24 changes: 14 additions & 10 deletions api/tacticalrmm/agents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
PAAction,
PAStatus,
)
from tacticalrmm.helpers import setup_nats_options
from tacticalrmm.helpers import has_script_actions, has_webhook, setup_nats_options
from tacticalrmm.models import PermissionQuerySet

if TYPE_CHECKING:
Expand Down Expand Up @@ -950,18 +950,22 @@ def delete_superseded_updates(self) -> None:
def should_create_alert(
self, alert_template: "Optional[AlertTemplate]" = None
) -> bool:
return bool(
has_agent_notification = (
self.overdue_dashboard_alert
or self.overdue_email_alert
or self.overdue_text_alert
or (
alert_template
and (
alert_template.agent_always_alert
or alert_template.agent_always_email
or alert_template.agent_always_text
)
)
)
has_alert_template_notification = alert_template and (
alert_template.agent_always_alert
or alert_template.agent_always_email
or alert_template.agent_always_text
)

return bool(
has_agent_notification
or has_alert_template_notification
or has_webhook(alert_template)
or has_script_actions(alert_template)
)

def send_outage_email(self) -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 4.2.13 on 2024-06-28 20:21

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("core", "0045_coresettings_enable_server_scripts_and_more"),
("alerts", "0013_alerttemplate_action_env_vars_and_more"),
]

operations = [
migrations.AddField(
model_name="alerttemplate",
name="action_rest",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="url_action_alert_template",
to="core.urlaction",
),
),
migrations.AddField(
model_name="alerttemplate",
name="action_type",
field=models.CharField(
choices=[("script", "Script"), ("server", "Server"), ("rest", "Rest")],
default="script",
max_length=10,
),
),
migrations.AddField(
model_name="alerttemplate",
name="resolved_action_rest",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="resolved_url_action_alert_template",
to="core.urlaction",
),
),
migrations.AddField(
model_name="alerttemplate",
name="resolved_action_type",
field=models.CharField(
choices=[("script", "Script"), ("server", "Server"), ("rest", "Rest")],
default="script",
max_length=10,
),
),
]
Loading

0 comments on commit 442f09d

Please sign in to comment.