Skip to content

Commit

Permalink
sso init
Browse files Browse the repository at this point in the history
  • Loading branch information
sadnub committed Sep 15, 2024
1 parent 22c152f commit 0081474
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 10 deletions.
18 changes: 14 additions & 4 deletions .devcontainer/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ function django_setup {

DJANGO_SEKRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 80 | head -n 1)

BASE_DOMAIN=$(echo "$APP_HOST" | awk -F. '{print $(NF-1)"."$NF}')

localvars="$(cat << EOF
SECRET_KEY = '${DJANGO_SEKRET}'
Expand All @@ -64,12 +66,20 @@ KEY_FILE = '${CERT_PRIV_PATH}'
SCRIPTS_DIR = '/community-scripts'
ALLOWED_HOSTS = ['${API_HOST}', '*']
ADMIN_URL = 'admin/'
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = ['https://${API_HOST}']
ALLOWED_HOSTS = ['${API_HOST}', 'https://${APP_HOST}', '*']
CORS_ORIGIN_WHITELIST = ['https://${API_HOST}', 'https://${APP_HOST}']
CORS_ALLOW_CREDENTIALS = True
SESSION_COOKIE_DOMAIN = '${BASE_DOMAIN}'
CSRF_COOKIE_DOMAIN = '${BASE_DOMAIN}'
CSRF_TRUSTED_ORIGINS = ['https://${API_HOST}', 'https://${APP_HOST}']
HEADLESS_FRONTEND_URLS = {
'socialaccount_login_error': 'https://${APP_HOST}/account/provider/callback'
}
DATABASES = {
'default': {
Expand Down
4 changes: 4 additions & 0 deletions api/tacticalrmm/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from django.urls import path
from django.urls import include

from . import views

urlpatterns = [
path("", include("allauth.urls")),
path("users/", views.GetAddUsers.as_view()),
path("<int:pk>/users/", views.GetUpdateDeleteUser.as_view()),
path("users/reset/", views.UserActions.as_view()),
Expand All @@ -15,4 +17,6 @@
path("apikeys/<int:pk>/", views.GetUpdateDeleteAPIKey.as_view()),
path("resetpw/", views.ResetPass.as_view()),
path("reset2fa/", views.Reset2FA.as_view()),
path("ssoproviders/", views.GetAddSSOProvider.as_view()),
path("ssoproviders/<int:pk>/", views.GetUpdateDeleteSSOProvider.as_view()),
]
101 changes: 100 additions & 1 deletion api/tacticalrmm/accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import datetime

import re
import pyotp
from allauth.socialaccount.models import SocialApp
from django.conf import settings
from django.contrib.auth import login
from django.db import IntegrityError
from django.shortcuts import get_object_or_404
from knox.views import LoginView as KnoxLoginView
from python_ipware import IpWare
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.serializers import ModelSerializer, ReadOnlyField
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
Expand Down Expand Up @@ -398,3 +400,100 @@ def put(self, request):
user.totp_key = ""
user.save()
return Response("2FA was reset. Log out and back in to setup.")


# sso views
class SocialAppSerializer(ModelSerializer):
server_url = ReadOnlyField(source="settings.server_url")
class Meta:
model = SocialApp
fields = [
"id",
"name",
"provider",
"provider_id",
"client_id",
"secret",
"server_url",
"settings",
]


class GetAddSSOProvider(APIView):
permission_classes = [IsAuthenticated, AccountsPerms]

def get(self, request):
providers = SocialApp.objects.all()
return Response(SocialAppSerializer(providers, many=True).data)

class InputSerializer(ModelSerializer):
server_url = ReadOnlyField()
class Meta:
model = SocialApp
fields = [
"name",
"client_id",
"secret",
"server_url",
"provider",
"provider_id",
"settings"
]

# removed any special characters and replaces spaces with a hyphen
def generate_provider_id(self, string):
id = re.sub(r'[^A-Za-z0-9\s]', '', string)
id = id.replace(' ', '-')
return id

def post(self, request):
data = request.data

# need to move server_url into json settings
data["settings"] = {}
data["settings"]["server_url"] = data["server_url"]

# set provider to 'openid_connect'
data["provider"] = "openid_connect"

# generate a url friendly provider id from the name
data["provider_id"] = self.generate_provider_id(data["name"])

serializer = self.InputSerializer(data=data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response("ok")


class GetUpdateDeleteSSOProvider(APIView):
permission_classes = [IsAuthenticated, APIKeyPerms]

class InputSerialzer(ModelSerializer):
server_url = ReadOnlyField()
class Meta:
model = SocialApp
fields = [
"client_id",
"secret",
"server_url",
"settings"
]

def put(self, request, pk):
provider = get_object_or_404(SocialApp, pk=pk)
data = request.data

# need to move server_url into json settings
data["settings"] = {}
data["settings"]["server_url"] = data["server_url"]

serializer = self.InputSerialzer(instance=provider, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response("ok")

def delete(self, request, pk):
provider = get_object_or_404(SocialApp, pk=pk)
provider.delete()
return Response("ok")

3 changes: 2 additions & 1 deletion api/tacticalrmm/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ channels==4.1.0
channels_redis==4.2.0
cryptography==42.0.8
Django==4.2.16
django-allauth[socialaccount]==64.2.1
django-cors-headers==4.4.0
django-filter==24.3
django-rest-knox==4.2.0
Expand Down Expand Up @@ -44,4 +45,4 @@ jinja2==3.1.4
markdown==3.7
plotly==5.24.0
weasyprint==62.3
ocxsect==0.1.5
ocxsect==0.1.5
23 changes: 23 additions & 0 deletions api/tacticalrmm/tacticalrmm/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_AUTHENTICATION_CLASSES": (
"knox.auth.TokenAuthentication",
"allauth.account.auth_backends.AuthenticationBackend",
"tacticalrmm.auth.APIAuthentication",
),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
Expand Down Expand Up @@ -163,6 +164,10 @@
"rest_framework.authtoken",
"knox",
"corsheaders",
"allauth.account",
"allauth.headless",
"allauth.socialaccount",
"allauth.socialaccount.providers.openid_connect",
"accounts",
"apiv3",
"clients",
Expand All @@ -189,6 +194,23 @@
},
}

# settings for django all auth
HEADLESS_ONLY = True
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
ACCOUNT_EMAIL_VERIFICATION = 'none'
SOCIALACCOUNT_ONLY = True
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
SOCIALACCOUNT_EMAIL_VERIFICATION = True

SOCIALACCOUNT_PROVIDERS = {
"openid_connect": {
"OAUTH_PKCE_ENABLED": True
}
}

SESSION_COOKIE_SECURE = True

# silence cache key length warnings
import warnings # noqa

Expand Down Expand Up @@ -216,6 +238,7 @@
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"tacticalrmm.middleware.AuditMiddleware",
"allauth.account.middleware.AccountMiddleware",
]

if SWAGGER_ENABLED:
Expand Down
4 changes: 4 additions & 0 deletions api/tacticalrmm/tacticalrmm/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def to_url(self, value):

urlpatterns = [
path("", home),

# all auth urls
path("_allauth/", include("allauth.headless.urls")),

path("v2/checkcreds/", CheckCredsV2.as_view()),
path("v2/login/", LoginViewV2.as_view()),
path("checkcreds/", CheckCreds.as_view()), # DEPRECATED AS OF 0.19.0
Expand Down
15 changes: 11 additions & 4 deletions docker/containers/tactical/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,20 @@ LOG_DIR = '/opt/tactical/api/tacticalrmm/private/log'
SCRIPTS_DIR = '/opt/tactical/community-scripts'
ALLOWED_HOSTS = ['${API_HOST}', 'tactical-backend']
ALLOWED_HOSTS = ['${API_HOST}', '${APP_HOST}', 'tactical-backend']
ADMIN_URL = '${ADMINURL}/'
CORS_ORIGIN_WHITELIST = [
'https://${APP_HOST}'
]
CORS_ORIGIN_WHITELIST = ['https://${API_HOST}', 'https://${APP_HOST}']
CORS_ALLOW_CREDENTIALS = True
SESSION_COOKIE_DOMAIN = '${BASE_DOMAIN}'
CSRF_COOKIE_DOMAIN = '${BASE_DOMAIN}'
CSRF_TRUSTED_ORIGINS = ['https://${API_HOST}', 'https://${APP_HOST}']
HEADLESS_FRONTEND_URLS = {
'socialaccount_login_error': 'https://${APP_HOST}/account/provider/callback'
}
DATABASES = {
'default': {
Expand Down

0 comments on commit 0081474

Please sign in to comment.