Skip to content
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
10 changes: 1 addition & 9 deletions backend/backend/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@ def create(self, validated_data: dict[str, Any]) -> Any:
if request:
validated_data[RequestKey.CREATED_BY] = request.user
validated_data[RequestKey.MODIFIED_BY] = request.user
instance = super().create(validated_data)

# Auto-add key owner as co-owner for resources created via API key
if request and hasattr(request, "platform_api_key"):
platform_api_key = request.platform_api_key
if hasattr(instance, "shared_users") and platform_api_key.created_by:
instance.shared_users.add(platform_api_key.created_by)

return instance
Comment thread
Deepak-Kesavan marked this conversation as resolved.
return super().create(validated_data)
Comment thread
chandrasekharan-zipstack marked this conversation as resolved.

def update(self, instance: Any, validated_data: dict[str, Any]) -> Any:
if self.context.get(RequestKey.REQUEST):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2729,30 +2729,6 @@ def create_tool_from_import_data(
organization=organization,
)

# When a service account creates a tool, add the API key owner
# as a shared user so they can see it in the UI.
if getattr(user, "is_service_account", False):
from platform_api.models import PlatformApiKey

try:
key = PlatformApiKey.objects.get(api_user=user)
if key.created_by:
tool.shared_users.add(key.created_by)
else:
logger.warning(
"PlatformApiKey for service account %s has no "
"created_by while creating tool %s",
user.id,
tool.tool_id,
)
except PlatformApiKey.DoesNotExist:
logger.warning(
"No PlatformApiKey found for service account %s "
"while creating tool %s",
user.id,
tool.tool_id,
)

return tool

@staticmethod
Expand Down
13 changes: 12 additions & 1 deletion backend/tenant_account_v2/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,21 @@ class UserInviteResponseSerializer(serializers.Serializer):
class OrganizationMemberSerializer(serializers.ModelSerializer):
email = serializers.CharField(source="user.email", read_only=True)
id = serializers.CharField(source="user.id", read_only=True)
is_admin = serializers.SerializerMethodField()

class Meta:
model = OrganizationMember
fields = ("id", "email", "role")
fields = ("id", "email", "role", "is_admin")

def get_is_admin(self, obj: OrganizationMember) -> bool:
# Admin determination is auth-plugin specific (OSS "admin" vs Auth0
# "unstract_admin"); defer to the same check used for access control.
auth_controller = self.context.get("auth_controller")
if auth_controller is None:
from account_v2.authentication_controller import AuthenticationController

auth_controller = AuthenticationController()
return auth_controller.is_admin_by_role(obj.role)


class LimitedUserEmailListSerializer(serializers.ListSerializer):
Expand Down
4 changes: 3 additions & 1 deletion backend/tenant_account_v2/users_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ def get_organization_members(self, request: Request) -> Response:
members: list[OrganizationMember] = (
auth_controller.get_organization_members_by_org_id()
)
serialized_members = OrganizationMemberSerializer(members, many=True).data
serialized_members = OrganizationMemberSerializer(
members, many=True, context={"auth_controller": auth_controller}
).data
return Response(
status=status.HTTP_200_OK,
data={"message": "success", "members": serialized_members},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/custom-tools/header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ function Header({
users.map((user) => ({
id: user?.id,
email: user?.email,
is_admin: user?.is_admin,
})),
);
return users;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ function ListOfTools({ segmentOptions, segmentValue, onSegmentChange }) {
users.map((user) => ({
id: user?.id,
email: user?.email,
is_admin: user?.is_admin,
})),
);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ function ToolSettings({ type }) {
users.map((user) => ({
id: user?.id,
email: user?.email,
is_admin: user?.is_admin,
})),
);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import PropTypes from "prop-types";
import { useEffect, useState } from "react";

import { useSessionStore } from "../../../store/session-store";
import { SpinnerLoader } from "../spinner-loader/SpinnerLoader";

function SharePermission({
Expand All @@ -28,31 +29,48 @@
onApply,
isSharableToOrg = false,
}) {
const { sessionDetails } = useSessionStore();
const currentUserId = sessionDetails?.id?.toString();
const [selectedUsers, setSelectedUsers] = useState([]);
const [filteredUsers, setFilteredUsers] = useState([]);
const [shareWithEveryone, setShareWithEveryone] = useState(false);

useEffect(() => {
if (permissionEdit && adapter && adapter?.shared_users) {
// If permissionEdit is true, and adapter is available,
// set the selectedUsers to the IDs of shared users
const creatorId = (
adapter?.created_by?.id ?? adapter?.created_by
)?.toString();
const users = allUsers.filter((user) => {
if (adapter?.created_by?.id !== undefined) {
return isSharableToOrg
? !selectedUsers.includes(user?.id?.toString())
: user?.id !== adapter?.created_by?.id?.toString() &&
!selectedUsers.includes(user?.id?.toString());
} else {
return isSharableToOrg
? !selectedUsers.includes(user?.id?.toString())
: user?.id !== adapter?.created_by?.toString() &&
!selectedUsers.includes(user?.id?.toString());
const userId = user?.id?.toString();
// Already-selected users shouldn't reappear as options
if (selectedUsers.includes(userId)) {
return false;
}
// Can't share a resource with yourself
if (userId === currentUserId) {
return false;
}
// Admins already have access to everything — sharing is a no-op
if (user?.is_admin) {
return false;
}
// The creator already owns it, unless we're sharing org-wide
if (!isSharableToOrg && userId === creatorId) {
return false;
}
return true;
});
setFilteredUsers(users);
setShareWithEveryone(adapter?.shared_to_org || false);
}
}, [permissionEdit, adapter, allUsers, selectedUsers]);
}, [
permissionEdit,
adapter,
allUsers,
selectedUsers,
currentUserId,
isSharableToOrg,
]);

useEffect(() => {
if (adapter?.shared_users) {
Expand Down Expand Up @@ -80,25 +98,30 @@
setShareWithEveryone(checked);
};

// Admins have org-wide access, so they aren't listed as explicit shares.
// This also hides legacy admin rows created before the auto-share removal.
const sharedUsersToDisplay = selectedUsers
.map((userId) => {
const user = allUsers.find((u) =>
u?.id !== undefined

Check warning on line 106 in frontend/src/components/widgets/share-permission/SharePermission.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=Zipstack_unstract&issues=AZ6CihGaTOuqbzHSyNMw&open=AZ6CihGaTOuqbzHSyNMw&pullRequest=2000
? u?.id.toString() === userId.toString()
: u?.toString() === userId.toString(),
);
return {
id: user?.id,
email: user?.email,
is_admin: user?.is_admin,
};
})
.filter((user) => !user.is_admin);

let sharedWithContent;
if (shareWithEveryone) {
sharedWithContent = <Typography.Text>Shared with everyone</Typography.Text>;
} else if (selectedUsers.length > 0) {
} else if (sharedUsersToDisplay.length > 0) {
sharedWithContent = (
<List
dataSource={selectedUsers.map((userId) => {
const user = allUsers.find((u) => {
if (u?.id !== undefined) {
return u?.id.toString() === userId.toString();
} else {
return u?.toString() === userId.toString();
}
});
return {
id: user?.id,
email: user?.email,
};
})}
dataSource={sharedUsersToDisplay}
renderItem={(item) => (
<List.Item
extra={
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/hooks/useShareModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@ function useShareModal({
userList = responseData.map((user) => ({
id: user.id,
email: user.email,
is_admin: user.is_admin,
}));
} else if (responseData?.members && Array.isArray(responseData.members)) {
userList = responseData.members.map((member) => ({
id: member.id,
email: member.email,
is_admin: member.is_admin,
}));
} else if (responseData?.users && Array.isArray(responseData.users)) {
userList = responseData.users.map((user) => ({
id: user.id,
email: user.email,
is_admin: user.is_admin,
}));
}

Expand Down
Loading