secure sso token a little more and allow for disabling sso feature.

This commit is contained in:
sadnub
2024-10-18 11:06:55 -04:00
parent 9edb848947
commit ce11685371
10 changed files with 136 additions and 54 deletions

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.14 on 2024-10-15 15:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0048_coresettings_block_local_user_logon'),
]
operations = [
migrations.AddField(
model_name='coresettings',
name='disable_sso',
field=models.BooleanField(default=True),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.14 on 2024-10-15 20:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0049_coresettings_disable_sso'),
]
operations = [
migrations.RemoveField(
model_name='coresettings',
name='disable_sso',
),
migrations.AddField(
model_name='coresettings',
name='sso_enabled',
field=models.BooleanField(default=False),
),
]

View File

@@ -112,6 +112,7 @@ class CoreSettings(BaseAuditModel):
notify_on_warning_alerts = models.BooleanField(default=True)
block_local_user_logon = models.BooleanField(default=True)
sso_enabled = models.BooleanField(default=False)
def save(self, *args, **kwargs) -> None:
from alerts.tasks import cache_agents_alert_template

View File

@@ -4,40 +4,19 @@ from allauth.account.utils import user_email, user_field, user_username
from allauth.utils import valid_email_or_none
from accounts.models import Role
class TacticalSocialAdapter(DefaultSocialAccountAdapter):
def populate_user(self, request, sociallogin, data):
"""
Hook that can be used to further populate the user instance.
For convenience, we populate several common fields.
Note that the user instance being populated represents a
suggested User instance that represents the social user that is
in the process of being logged in.
The User instance need not be completely valid and conflict
free. For example, verifying whether or not the username
already exists, is not a responsibility.
"""
username = data.get("username")
first_name = data.get("first_name")
last_name = data.get("last_name")
email = data.get("email")
name = data.get("name")
user = sociallogin.user
user_username(user, username or "")
user_email(user, valid_email_or_none(email) or "")
name_parts = (name or "").partition(" ")
user_field(user, "first_name", first_name or name_parts[0])
user_field(user, "last_name", last_name or name_parts[2])
user = super().populate_user(request, sociallogin, data)
try:
provider = sociallogin.account.get_provider()
provider_settings = SocialApp.objects.get(provider_id=provider).settings
user.role = Role.objects.get(pk=provider_settings["role"])
print(provider, provider_settings)
except:
print("Provider settings or Role not found. Continuing with blank permissions.")
print(
"Provider settings or Role not found. Continuing with blank permissions."
)
return user

View File

@@ -0,0 +1,8 @@
from rest_framework import permissions
from allauth.socialaccount.models import SocialAccount
class SSOLoginPerms(permissions.BasePermission):
def has_permission(self, r, view):
connected_apps = SocialAccount.objects.filter(user=r.user)
return len(connected_apps) > 0

View File

@@ -4,15 +4,75 @@ This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
"""
from django.urls import path
from django.urls import include
from django.urls import path, include, re_path
from allauth.socialaccount.providers.openid_connect.views import callback
from allauth.headless.socialaccount.views import (
RedirectToProviderView,
ManageProvidersView,
)
from allauth.headless.base.views import ConfigView
from . import views
urlpatterns = [
path("", include("allauth.urls")),
re_path(
r"^oidc/(?P<provider_id>[^/]+)/",
include(
[
path(
"login/callback/",
callback,
name="openid_connect_callback",
),
]
),
),
path("ssoproviders/", views.GetAddSSOProvider.as_view()),
path("ssoproviders/<int:pk>/", views.GetUpdateDeleteSSOProvider.as_view()),
path("ssoproviders/token/", views.GetAccessToken.as_view()),
path("ssoproviders/settings/", views.GetUpdateSSOSettings.as_view()),
]
allauth_urls = [
path(
"browser/v1/",
include(
(
[
path(
"config",
ConfigView.as_api_view(client="browser"),
name="config",
),
path(
"",
include(
(
[
path(
"auth/provider/redirect",
RedirectToProviderView.as_api_view(
client="browser"
),
name="redirect_to_provider",
),
path(
"providers",
ManageProvidersView.as_api_view(
client="browser"
),
name="manage_providers",
),
],
"headless",
),
namespace="socialaccount",
),
),
],
"headless",
),
namespace="browser",
),
)
]

View File

@@ -21,7 +21,7 @@ from python_ipware import IpWare
from accounts.permissions import AccountsPerms
from logs.models import AuditLog
from tacticalrmm.utils import get_core_settings
from .permissions import SSOLoginPerms
class SocialAppSerializer(ModelSerializer):
server_url = ReadOnlyField(source="settings.server_url")
@@ -126,13 +126,17 @@ class GetUpdateDeleteSSOProvider(APIView):
class GetAccessToken(KnoxLoginView):
permission_classes = [IsAuthenticated]
permission_classes = [SSOLoginPerms]
authentication_classes = [SessionAuthentication]
def post(self, request, format=None):
core = get_core_settings()
# check for auth method before signing in
if (
"account_authentication_methods" in request.session
core.sso_enabled
and "account_authentication_methods" in request.session
and len(request.session["account_authentication_methods"]) > 0
):
login_method = request.session["account_authentication_methods"][0]
@@ -158,10 +162,9 @@ class GetAccessToken(KnoxLoginView):
return Response(response.data)
else:
AuditLog.audit_user_login_failed_sso(request.user.username)
logout(request)
return Response(
"The credentials supplied were invalid", status.HTTP_403_FORBIDDEN
"No pending login session found", status.HTTP_403_FORBIDDEN
)
@@ -173,7 +176,10 @@ class GetUpdateSSOSettings(APIView):
core_settings = get_core_settings()
return Response(
{"block_local_user_logon": core_settings.block_local_user_logon}
{
"block_local_user_logon": core_settings.block_local_user_logon,
"sso_enabled": core_settings.sso_enabled
}
)
def post(self, request):
@@ -183,6 +189,7 @@ class GetUpdateSSOSettings(APIView):
core_settings = get_core_settings()
core_settings.block_local_user_logon = data["block_local_user_logon"]
core_settings.save(update_fields=["block_local_user_logon"])
core_settings.sso_enabled = data["sso_enabled"]
core_settings.save(update_fields=["block_local_user_logon", "sso_enabled"])
return Response("ok")

View File

@@ -225,18 +225,6 @@ class AuditLog(models.Model):
debug_info=debug_info,
)
@staticmethod
def audit_user_login_failed_sso(
username: str, debug_info: Dict[Any, Any] = {}
) -> None:
AuditLog.objects.create(
username=username,
object_type=AuditObjType.USER,
action=AuditActionType.LOGIN,
message=f"{username} failed to login through unknown sso provider",
debug_info=debug_info,
)
@staticmethod
def audit_url_action(
username: str,

View File

@@ -208,7 +208,6 @@ SOCIALACCOUNT_EMAIL_VERIFICATION = True
SOCIALACCOUNT_PROVIDERS = {"openid_connect": {"OAUTH_PKCE_ENABLED": True}}
AUTHENTICATION_BACKENDS = ("allauth.account.auth_backends.AuthenticationBackend",)
SESSION_COOKIE_SECURE = True
# silence cache key length warnings

View File

@@ -3,12 +3,12 @@ from django.urls import include, path, register_converter
from knox import views as knox_views
from accounts.views import CheckCreds, CheckCredsV2, LoginView, LoginViewV2
from ee.sso.urls import allauth_urls
# from agents.consumers import SendCMD
from core.consumers import DashInfo, TerminalConsumer
from core.views import home
class AgentIDConverter:
regex = "[^/]{20}[^/]+"
@@ -24,7 +24,7 @@ register_converter(AgentIDConverter, "agent")
urlpatterns = [
path("", home),
# all auth urls
path("_allauth/", include("allauth.headless.urls")),
path("_allauth/", include(allauth_urls)),
path("v2/checkcreds/", CheckCredsV2.as_view()),
path("v2/login/", LoginViewV2.as_view()),
path("checkcreds/", CheckCreds.as_view()), # DEPRECATED AS OF 0.19.0