mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-30 05:38:59 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 97a07ba74d |
@@ -198,3 +198,37 @@ class IsPresentInMeeting(permissions.BasePermission):
|
||||
return False
|
||||
except ParticipantsManagementException:
|
||||
return False
|
||||
|
||||
|
||||
class RoomCapabilityPermission(permissions.BasePermission):
|
||||
"""Wip."""
|
||||
|
||||
capability = None
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
"""Wip."""
|
||||
user = request.user
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return False
|
||||
|
||||
if obj.is_administrator_or_owner(user):
|
||||
return True
|
||||
|
||||
if not obj.configuration.get(self.capability, True):
|
||||
return False
|
||||
|
||||
try:
|
||||
return ParticipantsManagement().check_if_in_meeting(
|
||||
room_name=str(obj.pk), identity=str(user.sub)
|
||||
)
|
||||
except ParticipantNotFoundException:
|
||||
return False
|
||||
except ParticipantsManagementException:
|
||||
return False
|
||||
|
||||
|
||||
class CanRecord(RoomCapabilityPermission):
|
||||
"""Wip."""
|
||||
|
||||
capability = "authenticated_can_record"
|
||||
|
||||
@@ -330,6 +330,7 @@ class RoomConfiguration(BaseModel):
|
||||
|
||||
can_publish_sources: list[TrackSource] | None = None
|
||||
everyone_can_mute: bool | None = None
|
||||
authenticated_can_record: bool | None = None
|
||||
|
||||
model_config = {"extra": "forbid"}
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ class RoomViewSet(
|
||||
methods=["post"],
|
||||
url_path="start-recording",
|
||||
permission_classes=[
|
||||
permissions.HasPrivilegesOnRoom,
|
||||
permissions.CanRecord,
|
||||
],
|
||||
)
|
||||
@FeatureFlag.require("recording")
|
||||
@@ -394,11 +394,30 @@ class RoomViewSet(
|
||||
mode=mode,
|
||||
options=options.model_dump(exclude_none=True) if options else {},
|
||||
)
|
||||
models.RecordingAccess.objects.create(
|
||||
user=self.request.user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
recording=recording,
|
||||
)
|
||||
|
||||
if room.is_administrator_or_owner(request.user):
|
||||
models.RecordingAccess.objects.create(
|
||||
user=self.request.user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
recording=recording,
|
||||
)
|
||||
else:
|
||||
# todo - encapsulate it in a clear method which replicate privileges role on obj
|
||||
accesses = models.ResourceAccess.objects.filter(
|
||||
resource=room,
|
||||
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
||||
).values_list("user_id", "role")
|
||||
|
||||
models.RecordingAccess.objects.bulk_create(
|
||||
[
|
||||
models.RecordingAccess(
|
||||
user_id=user_id,
|
||||
role=role,
|
||||
recording=recording,
|
||||
)
|
||||
for user_id, role in accesses
|
||||
]
|
||||
)
|
||||
|
||||
except (DjangoValidationError, IntegrityError):
|
||||
# DjangoValidationError covers the Python-level check (full_clean);
|
||||
@@ -442,7 +461,7 @@ class RoomViewSet(
|
||||
methods=["post"],
|
||||
url_path="stop-recording",
|
||||
permission_classes=[
|
||||
permissions.HasPrivilegesOnRoom,
|
||||
permissions.CanRecord,
|
||||
],
|
||||
)
|
||||
@FeatureFlag.require("recording")
|
||||
|
||||
@@ -98,6 +98,7 @@ export const NoAccessView = ({
|
||||
)}
|
||||
</Text>
|
||||
</VStack>
|
||||
{/*todo - update the message here to adapt to room configuration */}
|
||||
{!isLoggedIn && (
|
||||
<LoginPrompt
|
||||
heading={t(`${i18nKey}.login.heading`)}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
|
||||
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
|
||||
import { FeatureFlags } from '@/features/analytics/enums'
|
||||
import { LimitDescription } from './LimitDescription'
|
||||
import { useCanRecord } from '@/features/recording/hooks/useCanRecord'
|
||||
|
||||
export const ScreenRecordingSidePanel = () => {
|
||||
const { data } = useConfig()
|
||||
@@ -38,6 +39,7 @@ export const ScreenRecordingSidePanel = () => {
|
||||
|
||||
const [includeTranscript, setIncludeTranscript] = useState(false)
|
||||
|
||||
const canRecord = useCanRecord()
|
||||
const isAdminOrOwner = useIsAdminOrOwner()
|
||||
|
||||
const hasScreenRecordingAccess = useHasRecordingAccess(
|
||||
@@ -110,7 +112,7 @@ export const ScreenRecordingSidePanel = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAdminOrOwner) {
|
||||
if (!canRecord) {
|
||||
return (
|
||||
<NoAccessView
|
||||
i18nKeyPrefix={keyPrefix}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useRoomContext } from '@livekit/components-react'
|
||||
import {
|
||||
RecordingMode,
|
||||
useHasRecordingAccess,
|
||||
useHasFeatureWithoutAdminRights,
|
||||
useHasFeatureWithoutRecordingRights,
|
||||
useRecordingStatuses,
|
||||
} from '../index'
|
||||
import { useState } from 'react'
|
||||
@@ -53,7 +53,7 @@ export const TranscriptSidePanel = () => {
|
||||
FeatureFlags.Transcript
|
||||
)
|
||||
|
||||
const hasFeatureWithoutAdminRights = useHasFeatureWithoutAdminRights(
|
||||
const hasFeatureWithoutAdminRights = useHasFeatureWithoutRecordingRights(
|
||||
RecordingMode.Transcript,
|
||||
FeatureFlags.Transcript
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
|
||||
import { useRoomData } from '@/features/rooms/livekit/hooks/useRoomData'
|
||||
import { useUser } from '@/features/auth/api/useUser'
|
||||
|
||||
export const useCanRecord = () => {
|
||||
const apiRoomData = useRoomData()
|
||||
const isAdminOrOwner = useIsAdminOrOwner()
|
||||
const { isLoggedIn } = useUser()
|
||||
|
||||
return (
|
||||
isAdminOrOwner ||
|
||||
(!!isLoggedIn &&
|
||||
apiRoomData?.configuration?.authenticated_can_record !== false)
|
||||
)
|
||||
}
|
||||
+4
-4
@@ -2,21 +2,21 @@ import { useFeatureFlagEnabled } from 'posthog-js/react'
|
||||
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
|
||||
import type { RecordingMode } from '../types'
|
||||
import { useIsRecordingModeEnabled } from './useIsRecordingModeEnabled'
|
||||
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
|
||||
import { useCanRecord } from './useCanRecord'
|
||||
import type { FeatureFlags } from '@/features/analytics/enums'
|
||||
|
||||
export const useHasFeatureWithoutAdminRights = (
|
||||
export const useHasFeatureWithoutRecordingRights = (
|
||||
mode: RecordingMode,
|
||||
featureFlag: FeatureFlags
|
||||
) => {
|
||||
const featureEnabled = useFeatureFlagEnabled(featureFlag)
|
||||
const isAnalyticsEnabled = useIsAnalyticsEnabled()
|
||||
const isRecordingModeEnabled = useIsRecordingModeEnabled(mode)
|
||||
const isAdminOrOwner = useIsAdminOrOwner()
|
||||
const canRecord = useCanRecord()
|
||||
|
||||
return (
|
||||
(featureEnabled || !isAnalyticsEnabled) &&
|
||||
isRecordingModeEnabled &&
|
||||
!isAdminOrOwner
|
||||
!canRecord
|
||||
)
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { useFeatureFlagEnabled } from 'posthog-js/react'
|
||||
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
|
||||
import type { RecordingMode } from '../types'
|
||||
import { useIsRecordingModeEnabled } from './useIsRecordingModeEnabled'
|
||||
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
|
||||
import type { FeatureFlags } from '@/features/analytics/enums'
|
||||
import { useCanRecord } from './useCanRecord'
|
||||
|
||||
export const useHasRecordingAccess = (
|
||||
mode: RecordingMode,
|
||||
@@ -12,11 +12,11 @@ export const useHasRecordingAccess = (
|
||||
const featureEnabled = useFeatureFlagEnabled(featureFlag)
|
||||
const isAnalyticsEnabled = useIsAnalyticsEnabled()
|
||||
const isRecordingModeEnabled = useIsRecordingModeEnabled(mode)
|
||||
const isAdminOrOwner = useIsAdminOrOwner()
|
||||
const canRecord = useCanRecord()
|
||||
|
||||
return (
|
||||
(featureEnabled || !isAnalyticsEnabled) &&
|
||||
isAdminOrOwner &&
|
||||
canRecord &&
|
||||
isRecordingModeEnabled
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// hooks
|
||||
export { useIsRecordingModeEnabled } from './hooks/useIsRecordingModeEnabled'
|
||||
export { useHasRecordingAccess } from './hooks/useHasRecordingAccess'
|
||||
export { useHasFeatureWithoutAdminRights } from './hooks/useHasFeatureWithoutAdminRights'
|
||||
export { useHasFeatureWithoutRecordingRights } from './hooks/useHasFeatureWithoutRecordingRights.ts'
|
||||
export { useRecordingStatuses } from './hooks/useRecordingStatuses'
|
||||
|
||||
// api
|
||||
|
||||
@@ -16,6 +16,7 @@ export enum ApiAccessLevel {
|
||||
export type RoomConfiguration = {
|
||||
can_publish_sources?: Source[] | null
|
||||
everyone_can_mute?: boolean | null
|
||||
authenticated_can_record?: boolean | null
|
||||
}
|
||||
|
||||
export type ApiRoom = {
|
||||
|
||||
@@ -50,7 +50,12 @@ export const Admin = () => {
|
||||
isScreenShareEnabled,
|
||||
} = usePublishSourcesManager()
|
||||
|
||||
const { toggleMuting, isMutingEnabled } = usePermissionsManager()
|
||||
const {
|
||||
toggleMuting,
|
||||
isMutingEnabled,
|
||||
isAuthenticatedCanRecordEnabled,
|
||||
toggleAuthenticatedCanRecord,
|
||||
} = usePermissionsManager()
|
||||
|
||||
return (
|
||||
<Div
|
||||
@@ -155,6 +160,17 @@ export const Admin = () => {
|
||||
fullWidth: true,
|
||||
}}
|
||||
/>
|
||||
<Field
|
||||
type="switch"
|
||||
label={'Authenticated can record'}
|
||||
description={'Authenticated can record'}
|
||||
isSelected={isAuthenticatedCanRecordEnabled}
|
||||
onChange={toggleAuthenticatedCanRecord}
|
||||
wrapperProps={{
|
||||
noMargin: true,
|
||||
fullWidth: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -12,6 +12,8 @@ export const usePermissionsManager = () => {
|
||||
const roomId = data?.slug
|
||||
|
||||
const isMutingEnabled = configuration?.everyone_can_mute ?? true
|
||||
const isAuthenticatedCanRecordEnabled =
|
||||
configuration?.authenticated_can_record ?? true
|
||||
|
||||
const toggleMuting = useCallback(
|
||||
async (enabled: boolean) => {
|
||||
@@ -39,8 +41,36 @@ export const usePermissionsManager = () => {
|
||||
[configuration, roomId, patchRoom]
|
||||
)
|
||||
|
||||
const toggleAuthenticatedCanRecord = useCallback(
|
||||
async (enabled: boolean) => {
|
||||
if (!roomId) return
|
||||
|
||||
try {
|
||||
const newConfiguration = {
|
||||
...configuration,
|
||||
authenticated_can_record: enabled,
|
||||
}
|
||||
|
||||
const room = await patchRoom({
|
||||
roomId,
|
||||
room: { configuration: newConfiguration },
|
||||
})
|
||||
|
||||
queryClient.setQueryData([keys.room, roomId], room)
|
||||
|
||||
return { configuration: newConfiguration }
|
||||
} catch (error) {
|
||||
console.error('Failed to update recording permission:', error)
|
||||
return { success: false, error }
|
||||
}
|
||||
},
|
||||
[configuration, roomId, patchRoom]
|
||||
)
|
||||
|
||||
return {
|
||||
toggleMuting,
|
||||
isMutingEnabled,
|
||||
isAuthenticatedCanRecordEnabled,
|
||||
toggleAuthenticatedCanRecord,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user