Skip to content

Commit

Permalink
Merge pull request #906 from weni-ai/feature/improving-organization-a…
Browse files Browse the repository at this point in the history
…uthorization-security

Feature: Validating before deleting organization permission if the user is the last admin of the organization
  • Loading branch information
ericosta-dev authored Dec 12, 2024
2 parents 0cc6da6 + e214e6f commit 197fb19
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 89 deletions.
165 changes: 94 additions & 71 deletions connect/api/v1/organization/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
OrganizationHasPermission,
OrganizationAdminManagerAuthorization,
IsCRMUser,
_is_orm_user
_is_orm_user,
)
from connect.api.v1.organization.serializers import (
OrganizationSeralizer,
Expand All @@ -51,15 +51,20 @@
from connect import billing
from connect.billing.gateways.stripe_gateway import StripeGateway
from connect.utils import count_contacts
from connect.api.v1.internal.intelligence.intelligence_rest_client import IntelligenceRESTClient
from connect.api.v1.internal.intelligence.intelligence_rest_client import (
IntelligenceRESTClient,
)
import pendulum
from connect.common import tasks
import logging
import stripe

from connect.internals.event_driven.producer.rabbitmq_publisher import RabbitmqPublisher
from connect.usecases.authorizations.update import UpdateAuthorizationUseCase
from connect.usecases.authorizations.dto import DeleteAuthorizationDTO, UpdateAuthorizationDTO
from connect.usecases.authorizations.dto import (
DeleteAuthorizationDTO,
UpdateAuthorizationDTO,
)
from connect.usecases.authorizations.delete import DeleteAuthorizationUseCase


Expand All @@ -76,7 +81,11 @@ class OrganizationViewSet(
):
queryset = Organization.objects.all()
serializer_class = OrganizationSeralizer
permission_classes = [IsAuthenticated, OrganizationHasPermission | IsCRMUser, Has2FA]
permission_classes = [
IsAuthenticated,
OrganizationHasPermission | IsCRMUser,
Has2FA,
]
lookup_field = "uuid"
metadata_class = Metadata

Expand All @@ -101,7 +110,9 @@ def list(self, request, *args, **kwargs):
page = self.paginate_queryset(
self.filter_queryset(self.get_queryset().order_by("name")),
)
organization_serializer = OrganizationSeralizer(page, many=True, context=self.get_serializer_context())
organization_serializer = OrganizationSeralizer(
page, many=True, context=self.get_serializer_context()
)
return self.get_paginated_response(organization_serializer.data)

def create(self, request, *args, **kwargs):
Expand All @@ -116,32 +127,23 @@ def create(self, request, *args, **kwargs):
try:
ai_client = IntelligenceRESTClient()
ai_org = ai_client.create_organization(
user_email=user.email,
organization_name=org_info.get("name")
)
org_info.update(
dict(
intelligence_organization=ai_org.get("id")
)
user_email=user.email, organization_name=org_info.get("name")
)
org_info.update(dict(intelligence_organization=ai_org.get("id")))
except Exception as error:
data.update({
"message": "Could not create organization in AI module",
"status": "FAILED"
})
data.update(
{
"message": "Could not create organization in AI module",
"status": "FAILED",
}
)
logger.error(error)
return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
# for testing purposes
org_info.update(
dict(
intelligence_organization=randint(1, 99)
)
)
org_info.update(dict(intelligence_organization=randint(1, 99)))

cycle = BillingPlan._meta.get_field(
"cycle"
).default
cycle = BillingPlan._meta.get_field("cycle").default

new_organization = Organization.objects.create(
name=org_info.get("name"),
Expand All @@ -158,27 +160,23 @@ def create(self, request, *args, **kwargs):
flows_info = tasks.create_template_project(
project_info.get("name"),
user.email,
project_info.get("timezone")
project_info.get("timezone"),
)
else:
flows_info = tasks.create_project(
project_name=project_info.get("name"),
user_email=user.email,
project_timezone=project_info.get("timezone")
project_timezone=project_info.get("timezone"),
)
except Exception as error:
data.update({
"message": "Could not create project",
"status": "FAILED"
})
data.update(
{"message": "Could not create project", "status": "FAILED"}
)
logger.error(error)
new_organization.delete()
return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
flows_info = {
"id": randint(1, 100),
"uuid": uuid.uuid4()
}
flows_info = {"id": randint(1, 100), "uuid": uuid.uuid4()}

project = Project.objects.create(
name=project_info.get("name"),
Expand All @@ -188,14 +186,14 @@ def create(self, request, *args, **kwargs):
organization=new_organization,
is_template=True if project_info.get("template") else False,
created_by=user,
template_type=project_info.get("template_type")
template_type=project_info.get("template_type"),
)

if len(Project.objects.filter(created_by=user)) == 1:
data = dict(
send_request_flow=settings.SEND_REQUEST_FLOW_PRODUCT,
flow_uuid=settings.FLOW_PRODUCT_UUID,
token_authorization=settings.TOKEN_AUTHORIZATION_FLOW_PRODUCT
token_authorization=settings.TOKEN_AUTHORIZATION_FLOW_PRODUCT,
)
user.send_request_flow_user_info(data)

Expand All @@ -204,7 +202,7 @@ def create(self, request, *args, **kwargs):
email=user.email,
organization=new_organization,
role=OrganizationRole.ADMIN.value,
created_by=user
created_by=user,
)

# Create user's organizations authorizations
Expand All @@ -213,27 +211,30 @@ def create(self, request, *args, **kwargs):
email=auth.get("user_email"),
organization=new_organization,
role=auth.get("role"),
created_by=user
created_by=user,
)

if project_info.get("template"):
data = {
"project": project,
"organization": new_organization
}
data = {"project": project, "organization": new_organization}
project_data = TemplateProjectSerializer().create(data, request)
if project_data.get("status") == "FAILED":
new_organization.delete()
project.delete()
return Response(project_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(
project_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)

serializer = OrganizationSeralizer(new_organization, context={"request": request})
project_serializer = ProjectSerializer(project, context={"request": request})
serializer = OrganizationSeralizer(
new_organization, context={"request": request}
)
project_serializer = ProjectSerializer(
project, context={"request": request}
)
response_data = dict(
project=project_serializer.data,
status="SUCCESS",
message="",
organization=serializer.data
organization=serializer.data,
)

except Exception as exception:
Expand All @@ -247,15 +248,20 @@ def perform_destroy(self, instance):
instance.send_email_delete_organization()
instance.delete()
ai_client = IntelligenceRESTClient()
ai_client.delete_organization(organization_id=intelligence_organization, user_email=self.request.user.email)
ai_client.delete_organization(
organization_id=intelligence_organization,
user_email=self.request.user.email,
)

def update(self, request, *args, **kwargs):
data = request.data
partial = kwargs.pop('partial', False)
partial = kwargs.pop("partial", False)
instance = self.get_object()

if data.get("name"):
instance.send_email_change_organization_name(instance.name, data.get("name"))
instance.send_email_change_organization_name(
instance.name, data.get("name")
)

serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
Expand Down Expand Up @@ -363,7 +369,9 @@ def get_contact_active(
"uuid": project.uuid,
"name": project.name,
"flow_organization": project.flow_organization,
"active_contacts": count_contacts(project=project, before=str(before), after=str(after)),
"active_contacts": count_contacts(
project=project, before=str(before), after=str(after)
),
}
)

Expand Down Expand Up @@ -665,12 +673,7 @@ def set_2fa_required(self, request, organization_uuid):
data = {"2fa_required": organization.enforce_2fa}
return JsonResponse(data=data, status=status.HTTP_200_OK)

@action(
detail=True,
methods=['POST'],
url_path="billing/validate-customer-card"
)
@action(detail=True, methods=["POST"], url_path="billing/validate-customer-card")
def validate_customer_card(self, request):
customer = request.data.get("customer")
if customer:
Expand All @@ -682,17 +685,21 @@ def validate_customer_card(self, request):
response["charge"] = gateway.card_verification_charge(customer)

return JsonResponse(data=response, status=status.HTTP_200_OK)
return JsonResponse(data={"response": "no customer"}, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse(
data={"response": "no customer"}, status=status.HTTP_400_BAD_REQUEST
)

@action(
detail=True,
methods=["POST"],
url_name="organization-retrieve",
url_path="internal/retrieve"
url_path="internal/retrieve",
)
def retrieve_organization(self, request):
flow_organization_uuid = request.uuid
organization = Organization.objects.get(project__flow_organization=flow_organization_uuid)
organization = Organization.objects.get(
project__flow_organization=flow_organization_uuid
)
return {
"status": status.HTTP_200_OK,
"response": {
Expand All @@ -702,7 +709,7 @@ def retrieve_organization(self, request):
"inteligence_organization": organization.inteligence_organization,
"extra_integration": organization.extra_integration,
"is_suspended": organization.is_suspended,
}
},
}

@action(
Expand All @@ -718,7 +725,10 @@ def upgrade_plan(self, request, organization_uuid):

self.check_object_permissions(self.request, organization)
if not organization.organization_billing.stripe_customer:
return JsonResponse(data={"status": "FAILURE", "message": "Empty customer"}, status=status.HTTP_304_NOT_MODIFIED)
return JsonResponse(
data={"status": "FAILURE", "message": "Empty customer"},
status=status.HTTP_304_NOT_MODIFIED,
)

org_billing = organization.organization_billing
old_plan = organization.organization_billing.plan
Expand All @@ -727,13 +737,19 @@ def upgrade_plan(self, request, organization_uuid):

if not plan_info["valid"]:
return JsonResponse(
data={"status": "FAILURE", "message": "Invalid plan choice"}, status=status.HTTP_400_BAD_REQUEST
data={"status": "FAILURE", "message": "Invalid plan choice"},
status=status.HTTP_400_BAD_REQUEST,
)

price = BillingPlan.plan_info(plan)["price"]

if settings.TESTING:
p_intent = stripe.PaymentIntent(amount_received=price, id="pi_test_id", amount=price, charges={"amount": price, "amount_captured": price})
p_intent = stripe.PaymentIntent(
amount_received=price,
id="pi_test_id",
amount=price,
charges={"amount": price, "amount_captured": price},
)
purchase_result = {"status": "SUCCESS", "response": p_intent}
if request.data.get("stripe_failure"):
data["status"] = "FAILURE"
Expand Down Expand Up @@ -762,17 +778,21 @@ def upgrade_plan(self, request, organization_uuid):
old_plan,
)
return JsonResponse(
data={"status": "SUCCESS", "old_plan": old_plan, "plan": org_billing.plan},
status=status.HTTP_200_OK
data={
"status": "SUCCESS",
"old_plan": old_plan,
"plan": org_billing.plan,
},
status=status.HTTP_200_OK,
)
return JsonResponse(
data={"status": "FAILURE", "message": "Invalid plan choice"},
status=status.HTTP_400_BAD_REQUEST
status=status.HTTP_400_BAD_REQUEST,
)

return JsonResponse(
data={"status": "FAILURE", "message": "Stripe error"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)


Expand Down Expand Up @@ -843,13 +863,15 @@ def update(self, *args, **kwargs):
id=self.kwargs.get("user__id"),
org_uuid=self.kwargs.get("organization__uuid"),
role=int(data.get("role")),
request_user=self.request.user
request_user=self.request.user,
)

usecase = UpdateAuthorizationUseCase(message_publisher=RabbitmqPublisher())
authorization = usecase.update_authorization(auth_dto)

instance.organization.send_email_permission_change(instance.user, old_permission, new_permission)
instance.organization.send_email_permission_change(
instance.user, old_permission, new_permission
)

return Response(data={"role": authorization.role})

Expand All @@ -862,12 +884,13 @@ def destroy(self, request, *args, **kwargs):
IsAuthenticated,
OrganizationAdminManagerAuthorization,
]
obj = self.get_object()
self.filter_class = None
self.lookup_field = "user__id"

auth_dto = DeleteAuthorizationDTO(
id=self.kwargs.get("user__id"),
org_uuid=self.kwargs.get("organization__uuid"),
id=obj.user.id,
org_uuid=obj.organization.uuid,
request_user=self.request.user,
)

Expand Down
Loading

0 comments on commit 197fb19

Please sign in to comment.