Skip to content

Commit 2e6c979

Browse files
committed
Release 0.20.0
2 parents 59c880d + c6b667f commit 2e6c979

34 files changed

+830
-173
lines changed

.devcontainer/entrypoint.sh

+19-12
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ function check_tactical_ready {
3333
}
3434

3535
function django_setup {
36-
until (echo > /dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &> /dev/null; do
36+
until (echo >/dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &>/dev/null; do
3737
echo "waiting for postgresql container to be ready..."
3838
sleep 5
3939
done
4040

41-
until (echo > /dev/tcp/"${MESH_SERVICE}"/4443) &> /dev/null; do
41+
until (echo >/dev/tcp/"${MESH_SERVICE}"/4443) &>/dev/null; do
4242
echo "waiting for meshcentral container to be ready..."
4343
sleep 5
4444
done
@@ -49,8 +49,11 @@ function django_setup {
4949
MESH_TOKEN="$(cat ${TACTICAL_DIR}/tmp/mesh_token)"
5050

5151
DJANGO_SEKRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 80 | head -n 1)
52-
53-
localvars="$(cat << EOF
52+
53+
BASE_DOMAIN=$(echo "import tldextract; no_fetch_extract = tldextract.TLDExtract(suffix_list_urls=()); extracted = no_fetch_extract('${API_HOST}'); print(f'{extracted.domain}.{extracted.suffix}')" | python)
54+
55+
localvars="$(
56+
cat <<EOF
5457
SECRET_KEY = '${DJANGO_SEKRET}'
5558
5659
DEBUG = True
@@ -64,12 +67,17 @@ KEY_FILE = '${CERT_PRIV_PATH}'
6467
6568
SCRIPTS_DIR = '/community-scripts'
6669
67-
ALLOWED_HOSTS = ['${API_HOST}', '*']
68-
6970
ADMIN_URL = 'admin/'
7071
71-
CORS_ORIGIN_ALLOW_ALL = True
72-
CORS_ORIGIN_WHITELIST = ['https://${API_HOST}']
72+
ALLOWED_HOSTS = ['${API_HOST}', '${APP_HOST}', '*']
73+
74+
CORS_ORIGIN_WHITELIST = ['https://${APP_HOST}']
75+
76+
SESSION_COOKIE_DOMAIN = '${BASE_DOMAIN}'
77+
CSRF_COOKIE_DOMAIN = '${BASE_DOMAIN}'
78+
CSRF_TRUSTED_ORIGINS = ['https://${API_HOST}', 'https://${APP_HOST}']
79+
80+
HEADLESS_FRONTEND_URLS = {'socialaccount_login_error': 'https://${APP_HOST}/account/provider/callback'}
7381
7482
DATABASES = {
7583
'default': {
@@ -101,9 +109,9 @@ MESH_WS_URL = '${MESH_WS_URL}'
101109
ADMIN_ENABLED = True
102110
TRMM_INSECURE = True
103111
EOF
104-
)"
112+
)"
105113

106-
echo "${localvars}" > ${WORKSPACE_DIR}/api/tacticalrmm/tacticalrmm/local_settings.py
114+
echo "${localvars}" >${WORKSPACE_DIR}/api/tacticalrmm/tacticalrmm/local_settings.py
107115

108116
# run migrations and init scripts
109117
"${VIRTUAL_ENV}"/bin/python manage.py pre_update_tasks
@@ -118,9 +126,8 @@ EOF
118126
"${VIRTUAL_ENV}"/bin/python manage.py create_natsapi_conf
119127
"${VIRTUAL_ENV}"/bin/python manage.py create_installer_user
120128
"${VIRTUAL_ENV}"/bin/python manage.py post_update_tasks
121-
122129

123-
# create super user
130+
# create super user
124131
echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', '[email protected]', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
125132
}
126133

api/tacticalrmm/accounts/management/commands/reset_2fa.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import subprocess
22

33
import pyotp
4+
from django.conf import settings
45
from django.core.management.base import BaseCommand
56

67
from accounts.models import User
7-
from tacticalrmm.helpers import get_webdomain
8+
from tacticalrmm.util_settings import get_webdomain
89

910

1011
class Command(BaseCommand):
@@ -26,7 +27,7 @@ def handle(self, *args, **kwargs):
2627
user.save(update_fields=["totp_key"])
2728

2829
url = pyotp.totp.TOTP(code).provisioning_uri(
29-
username, issuer_name=get_webdomain()
30+
username, issuer_name=get_webdomain(settings.CORS_ORIGIN_WHITELIST[0])
3031
)
3132
subprocess.run(f'qr "{url}"', shell=True)
3233
self.stdout.write(

api/tacticalrmm/accounts/models.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Optional
22

3+
from allauth.socialaccount.models import SocialAccount
34
from django.contrib.auth.models import AbstractUser
45
from django.core.cache import cache
56
from django.db import models
@@ -73,6 +74,10 @@ def mesh_username(self):
7374
# lower() needed for mesh api
7475
return f"{self.username.replace(' ', '').lower()}___{self.pk}"
7576

77+
@property
78+
def is_sso_user(self):
79+
return SocialAccount.objects.filter(user_id=self.pk).exists()
80+
7681
@staticmethod
7782
def serialize(user):
7883
# serializes the task and returns json

api/tacticalrmm/accounts/permissions.py

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from rest_framework import permissions
22

33
from tacticalrmm.permissions import _has_perm
4+
from tacticalrmm.utils import get_core_settings
45

56

67
class AccountsPerms(permissions.BasePermission):
@@ -40,3 +41,14 @@ def has_permission(self, r, view) -> bool:
4041
return _has_perm(r, "can_list_api_keys")
4142

4243
return _has_perm(r, "can_manage_api_keys")
44+
45+
46+
class LocalUserPerms(permissions.BasePermission):
47+
def has_permission(self, r, view) -> bool:
48+
settings = get_core_settings()
49+
return not settings.block_local_user_logon
50+
51+
52+
class SelfResetSSOPerms(permissions.BasePermission):
53+
def has_permission(self, r, view) -> bool:
54+
return not r.user.is_sso_user

api/tacticalrmm/accounts/serializers.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import pyotp
2+
from django.conf import settings
23
from rest_framework.serializers import (
34
ModelSerializer,
45
ReadOnlyField,
56
SerializerMethodField,
67
)
78

8-
from tacticalrmm.helpers import get_webdomain
9+
from tacticalrmm.util_settings import get_webdomain
910

1011
from .models import APIKey, Role, User
1112

@@ -63,7 +64,7 @@ class Meta:
6364

6465
def get_qr_url(self, obj):
6566
return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
66-
obj.username, issuer_name=get_webdomain()
67+
obj.username, issuer_name=get_webdomain(settings.CORS_ORIGIN_WHITELIST[0])
6768
)
6869

6970

api/tacticalrmm/accounts/tests.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
class TestAccounts(TacticalTestCase):
1313
def setUp(self):
14+
self.setup_coresettings()
1415
self.setup_client()
1516
self.bob = User(username="bob")
1617
self.bob.set_password("hunter2")

api/tacticalrmm/accounts/urls.py

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
urlpatterns = [
66
path("users/", views.GetAddUsers.as_view()),
77
path("<int:pk>/users/", views.GetUpdateDeleteUser.as_view()),
8+
path("sessions/<str:pk>/", views.DeleteActiveLoginSession.as_view()),
9+
path(
10+
"users/<int:pk>/sessions/", views.GetDeleteActiveLoginSessionsPerUser.as_view()
11+
),
812
path("users/reset/", views.UserActions.as_view()),
913
path("users/reset_totp/", views.UserActions.as_view()),
1014
path("users/setup_totp/", views.TOTPSetup.as_view()),

0 commit comments

Comments
 (0)