Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding capture for 2fa enforcements at org level #26660

Merged
merged 2 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion posthog/api/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
from django.shortcuts import get_object_or_404
from rest_framework import exceptions, permissions, serializers, viewsets
from rest_framework.request import Request
from rest_framework.response import Response
import posthoganalytics

from posthog import settings
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import ProjectBasicSerializer, TeamBasicSerializer
from posthog.auth import PersonalAPIKeyAuthentication
from posthog.cloud_utils import is_cloud
from posthog.constants import INTERNAL_BOT_EMAIL_SUFFIX, AvailableFeature
from posthog.event_usage import report_organization_deleted
from posthog.event_usage import report_organization_deleted, groups
from posthog.models import Organization, User
from posthog.models.async_deletion import AsyncDeletion, DeletionType
from posthog.rbac.user_access_control import UserAccessControlSerializerMixin
Expand Down Expand Up @@ -240,3 +242,24 @@ def get_serializer_context(self) -> dict[str, Any]:
**super().get_serializer_context(),
"user_permissions": UserPermissions(cast(User, self.request.user)),
}

def update(self, request: Request, *args: Any, **kwargs: Any) -> Response:
if "enforce_2fa" in request.data:
enforce_2fa_value = request.data["enforce_2fa"]
organization = self.get_object()
user = cast(User, request.user)

# Add capture event for 2FA enforcement change
posthoganalytics.capture(
str(user.distinct_id),
"organization 2fa enforcement toggled",
properties={
"enabled": enforce_2fa_value,
"organization_id": str(organization.id),
"organization_name": organization.name,
"user_role": user.organization_memberships.get(organization=organization).level,
},
groups=groups(organization),
)

return super().update(request, *args, **kwargs)
17 changes: 16 additions & 1 deletion posthog/api/test/test_organization.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from rest_framework import status
from unittest.mock import patch, ANY

from posthog.models import Organization, OrganizationMembership, Team
from posthog.models.personal_api_key import PersonalAPIKey, hash_key_value
Expand Down Expand Up @@ -128,7 +129,8 @@ def test_cant_update_plugins_access_level(self):
self.organization.refresh_from_db()
self.assertEqual(self.organization.plugins_access_level, 3)

def test_enforce_2fa_for_everyone(self):
@patch("posthoganalytics.capture")
def test_enforce_2fa_for_everyone(self, mock_capture):
# Only admins should be able to enforce 2fa
response = self.client.patch(f"/api/organizations/{self.organization.id}/", {"enforce_2fa": True})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
Expand All @@ -142,6 +144,19 @@ def test_enforce_2fa_for_everyone(self):
self.organization.refresh_from_db()
self.assertEqual(self.organization.enforce_2fa, True)

# Verify the capture event was called correctly
mock_capture.assert_any_call(
self.user.distinct_id,
"organization 2fa enforcement toggled",
properties={
"enabled": True,
"organization_id": str(self.organization.id),
"organization_name": self.organization.name,
"user_role": OrganizationMembership.Level.ADMIN,
},
groups={"instance": ANY, "organization": str(self.organization.id)},
)

def test_projects_outside_personal_api_key_scoped_organizations_not_listed(self):
other_org, _, _ = Organization.objects.bootstrap(self.user)
personal_api_key = generate_random_token_personal()
Expand Down
Loading