Skip to content

Commit

Permalink
Limit recording visibility by Role (#5614)
Browse files Browse the repository at this point in the history
* initial work

* more work

* Final work

* Remove unneeded changes

* Fixes to versions
  • Loading branch information
farhatahmad authored Dec 13, 2023
1 parent acfd54f commit 31fa9ab
Show file tree
Hide file tree
Showing 15 changed files with 7,352 additions and 10,406 deletions.
3 changes: 2 additions & 1 deletion app/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,8 @@
"manage_roles": "Allow users with this role to edit other roles",
"shared_list": "Include users with this role in the dropdown for sharing rooms",
"room_limit": "Room Limit",
"email_on_signup": "Receive an email when a new user signs up"
"email_on_signup": "Receive an email when a new user signs up",
"allowed_recording_visibility": "Allowed recording visibilities"
}
}
},
Expand Down
15 changes: 15 additions & 0 deletions app/assets/stylesheets/application.bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,21 @@ input.search-bar {
}
}

.custom-select {
.select-brand-control {
border-color: var(--brand-color) !important;
box-shadow: 0 0 0 1px var(--brand-color) !important;
}

.select-brand-option {
background-color: whitesmoke;
color: var(--brand-color) !important;
&:active {
background-color: var(--brand-color-light) !important;
}
}
}

//Brand
:root {
--brand-color: '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def update
private

def role_params
params.require(:role).permit(:role_id, :name, :value)
params.require(:role).permit(:role_id, :name, :value, value: [])
end

def create_default_room
Expand Down
8 changes: 6 additions & 2 deletions app/controllers/api/v1/recordings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,13 @@ def destroy
def update_visibility
new_visibility = params[:visibility].to_s

new_visibility_params = visibility_params_of(new_visibility)
allowed_visibilities = JSON.parse(RolePermission.joins(:permission)
.find_by(role_id: current_user.role_id, permission: { name: 'AccessToVisibilities' })
.value)

return render_error status: :forbidden unless allowed_visibilities.include?(new_visibility)

return render_error status: :bad_request if new_visibility_params.nil?
new_visibility_params = visibility_params_of(new_visibility)

bbb_api = BigBlueButtonApi.new(provider: current_provider)

Expand Down
34 changes: 34 additions & 0 deletions app/javascript/components/admin/roles/forms/EditRoleForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button, Stack } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import Select from 'react-select';
import Form from '../../../shared_components/forms/Form';
import FormControl from '../../../shared_components/forms/FormControl';
import useUpdateRole from '../../../../hooks/mutations/admin/roles/useUpdateRole';
Expand All @@ -44,6 +45,14 @@ export default function EditRoleForm({ role }) {

const { methods: methodsName, fields: fieldsName } = useEditRoleNameForm({ defaultValues: { name: role?.name } });

const visibilityOptions = [
{ value: 'Published', label: 'Published' },
{ value: 'Unpublished', label: 'Unpublished' },
{ value: 'Protected', label: 'Protected' },
{ value: 'Public', label: 'Public' },
{ value: 'Public/Protected', label: 'Public/Protected' },
];

const {
methods: methodsLimit,
fields: fieldsLimit,
Expand Down Expand Up @@ -137,6 +146,31 @@ export default function EditRoleForm({ role }) {
defaultValue={rolePermissions?.EmailOnSignup === 'true'}
/>

<Form className="pb-3">
<Stack direction="horizontal">
<div className="text-muted me-auto">
{t('admin.roles.edit.allowed_recording_visibility')}
</div>
<div>
<Select
className="custom-select float-end"
isMulti
isClearable={false}
isSearchable={false}
defaultValue={visibilityOptions?.filter((vis) => JSON.parse(rolePermissions?.AccessToVisibilities)?.includes(vis.value))}
options={visibilityOptions}
onChange={(value) => {
updatePermissionAPI.mutate({ role_id: role?.id, name: 'AccessToVisibilities', value: value.map((v) => v.value) });
}}
classNames={{
control: (state) => (state.isFocused ? 'select-brand-control' : ''),
option: (state) => (state.isFocused ? 'select-brand-option' : ''),
}}
/>
</div>
</Stack>
</Form>

<Form methods={methodsLimit} onBlur={methodsLimit.handleSubmit(updatePermissionAPI.mutate)}>
<Stack direction="horizontal">
<div className="text-muted me-auto">
Expand Down
85 changes: 50 additions & 35 deletions app/javascript/components/recordings/RecordingRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default function RecordingRow({
const currentUser = useAuth();
const redirectRecordingUrl = useRedirectRecordingUrl();
const copyRecordingUrl = useCopyRecordingUrl();
const allowedVisibilities = JSON.parse(currentUser.permissions?.AccessToVisibilities);

const localizedTime = localizeDateTimeString(recording?.recorded_at, currentUser?.language);
const formats = recording.formats.sort(
Expand Down Expand Up @@ -106,41 +107,55 @@ export default function RecordingRow({
defaultValue={recording.visibility}
dropUp={dropUp}
>
<Dropdown.Item
key="Public/Protected"
value="Public/Protected"
onClick={() => visibilityAPI.mutate({ visibility: 'Public/Protected', id: recording.record_id })}
>
{t('recording.public_protected')}
</Dropdown.Item>
<Dropdown.Item
key="Public"
value="Public"
onClick={() => visibilityAPI.mutate({ visibility: 'Public', id: recording.record_id })}
>
{t('recording.public')}
</Dropdown.Item>
<Dropdown.Item
key="Protected"
value="Protected"
onClick={() => visibilityAPI.mutate({ visibility: 'Protected', id: recording.record_id })}
>
{t('recording.protected')}
</Dropdown.Item>
<Dropdown.Item
key="Published"
value="Published"
onClick={() => visibilityAPI.mutate({ visibility: 'Published', id: recording.record_id })}
>
{t('recording.published')}
</Dropdown.Item>
<Dropdown.Item
key="Unpublished"
value="Unpublished"
onClick={() => visibilityAPI.mutate({ visibility: 'Unpublished', id: recording.record_id })}
>
{t('recording.unpublished')}
</Dropdown.Item>
{ (allowedVisibilities.includes('Public/Protected') || recording.visibility === 'Public/Protected') && (
<Dropdown.Item
key="Public/Protected"
value="Public/Protected"
onClick={() => visibilityAPI.mutate({ visibility: 'Public/Protected', id: recording.record_id })}
>
{t('recording.public_protected')}
</Dropdown.Item>
)}

{ (allowedVisibilities.includes('Public') || recording.visibility === 'Public') && (
<Dropdown.Item
key="Public"
value="Public"
onClick={() => visibilityAPI.mutate({ visibility: 'Public', id: recording.record_id })}
>
{t('recording.public')}
</Dropdown.Item>
)}

{ (allowedVisibilities.includes('Protected') || recording.visibility === 'Protected') && (
<Dropdown.Item
key="Protected"
value="Protected"
onClick={() => visibilityAPI.mutate({ visibility: 'Protected', id: recording.record_id })}
>
{t('recording.protected')}
</Dropdown.Item>
)}

{ (allowedVisibilities.includes('Published') || recording.visibility === 'Published') && (
<Dropdown.Item
key="Published"
value="Published"
onClick={() => visibilityAPI.mutate({ visibility: 'Published', id: recording.record_id })}
>
{t('recording.published')}
</Dropdown.Item>
)}

{ (allowedVisibilities.includes('Unpublished') || recording.visibility === 'Unpublished') && (
<Dropdown.Item
key="Unpublished"
value="Unpublished"
onClick={() => visibilityAPI.mutate({ visibility: 'Unpublished', id: recording.record_id })}
>
{t('recording.unpublished')}
</Dropdown.Item>
)}
</SimpleSelect>
</td>
<td className="border-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ChevronDownIcon } from '@heroicons/react/20/solid';

export default function SimpleSelect({ defaultValue, dropUp, children }) {
// Get the currently selected option and set the dropdown toggle to that value
const defaultString = children?.filter((item) => item.props.value === defaultValue)[0];
const defaultString = children?.filter(Boolean)?.filter((item) => item.props.value === defaultValue)[0];

return (
<Dropdown className="simple-select" drop={dropUp ? 'up' : undefined}>
Expand Down
6 changes: 5 additions & 1 deletion app/services/tenant_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def create_role_permissions
shared_list = Permission.find_by(name: 'SharedList')
can_record = Permission.find_by(name: 'CanRecord')
room_limit = Permission.find_by(name: 'RoomLimit')
access_to_visbilities = Permission.find_by(name: 'AccessToVisibilities')

RolePermission.create! [
{ role: admin, permission: create_room, value: 'true' },
Expand All @@ -94,6 +95,7 @@ def create_role_permissions
{ role: admin, permission: shared_list, value: 'true' },
{ role: admin, permission: can_record, value: 'true' },
{ role: admin, permission: room_limit, value: '100' },
{ role: admin, permission: access_to_visbilities, value: Recording::VISIBILITIES.values },

{ role: user, permission: create_room, value: 'true' },
{ role: user, permission: manage_users, value: 'false' },
Expand All @@ -104,6 +106,7 @@ def create_role_permissions
{ role: user, permission: shared_list, value: 'true' },
{ role: user, permission: can_record, value: 'true' },
{ role: user, permission: room_limit, value: '100' },
{ role: user, permission: access_to_visbilities, value: Recording::VISIBILITIES.values },

{ role: guest, permission: create_room, value: 'false' },
{ role: guest, permission: manage_users, value: 'false' },
Expand All @@ -113,7 +116,8 @@ def create_role_permissions
{ role: guest, permission: manage_roles, value: 'false' },
{ role: guest, permission: shared_list, value: 'true' },
{ role: guest, permission: can_record, value: 'true' },
{ role: guest, permission: room_limit, value: '100' }
{ role: guest, permission: room_limit, value: '100' },
{ role: guest, permission: access_to_visbilities, value: Recording::VISIBILITIES.values }
]
end
end
15 changes: 15 additions & 0 deletions db/data/20231210154647_add_visibility_to_role_permissions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class AddVisibilityToRolePermissions < ActiveRecord::Migration[7.1]
def up
visibility_permission = Permission.create!(name: 'AccessToVisibilities')

Role.all.each do |role|
RolePermission.create!(role:, permission: visibility_permission, value: Recording::VISIBILITIES.values)
end
end

def down
raise ActiveRecord::IrreversibleMigration
end
end
2 changes: 1 addition & 1 deletion db/data_schema.rb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
DataMigrate::Data.define(version: 20231117151542)
DataMigrate::Data.define(version: 20231210154647)
Loading

0 comments on commit 31fa9ab

Please sign in to comment.