Skip to content

Commit

Permalink
Merge pull request #17166 from jmchilton/permissions_overhaul
Browse files Browse the repository at this point in the history
Much simpler default dataset permissions for typical users.
  • Loading branch information
martenson authored Feb 10, 2024
2 parents cc94611 + 0319676 commit aca50e1
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 16 deletions.
58 changes: 58 additions & 0 deletions client/src/components/Dataset/DatasetPermissionsForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script lang="ts" setup>
import { ref, watch } from "vue";
import FormGeneric from "@/components/Form/FormGeneric.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
interface DatasetPermissionsFormProps {
loading: boolean;
simplePermissions: boolean;
title: string;
checked: boolean;
formConfig: object;
}
const props = defineProps<DatasetPermissionsFormProps>();
const selectedAdvancedForm = ref(false);
const checkedInForm = ref(props.checked);
const emit = defineEmits<{
(e: "change", value: boolean): void;
}>();
async function change(value: boolean) {
emit("change", value);
}
watch(props, () => {
checkedInForm.value = props.checked;
});
</script>

<template>
<div>
<LoadingSpan v-if="loading" message="Loading permission information" />
<div v-else-if="simplePermissions && !selectedAdvancedForm">
<div class="ui-portlet-section">
<div class="portlet-header">
<span class="portlet-title"
><span class="portlet-title-icon fa mr-1 fa-users"></span>
<b itemprop="name" class="portlet-title-text">{{ title }}</b>
</span>
</div>
<div class="portlet-content">
<div class="mb-3 mt-3">
<b-form-checkbox v-model="checkedInForm" name="check-button" switch @change="change">
Make new datasets private
</b-form-checkbox>
</div>
<a href="#" @click="selectedAdvancedForm = true">Show advanced options.</a>
</div>
</div>
</div>
<div v-else>
<FormGeneric v-bind="formConfig" />
</div>
</div>
</template>
71 changes: 71 additions & 0 deletions client/src/components/History/HistoryDatasetPermissions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts" setup>
import axios from "axios";
import { computed, ref } from "vue";
import { initRefs, updateRefs, useCallbacks } from "@/composables/datasetPermissions";
import { withPrefix } from "@/utils/redirect";
import DatasetPermissionsForm from "@/components/Dataset/DatasetPermissionsForm.vue";
interface HistoryDatasetPermissionsProps {
historyId: string;
}
const props = defineProps<HistoryDatasetPermissionsProps>();
const loading = ref(true);
const {
managePermissionsOptions,
accessPermissionsOptions,
managePermissions,
accessPermissions,
simplePermissions,
checked,
} = initRefs();
const inputsUrl = computed(() => {
return `/history/permissions?id=${props.historyId}`;
});
const title = "Change default dataset permissions for history";
const formConfig = computed(() => {
return {
title: title,
url: inputsUrl.value,
submitTitle: "Save Permissions",
redirect: "/histories/list",
};
});
async function change(value: unknown) {
const managePermissionValue: number = managePermissions.value[0] as number;
let access: number[] = [] as number[];
if (value) {
access = [managePermissionValue];
}
const formValue = {
DATASET_MANAGE_PERMISSIONS: [managePermissionValue],
DATASET_ACCESS: access,
};
axios.put(withPrefix(inputsUrl.value), formValue).then(onSuccess).catch(onError);
}
async function init() {
const { data } = await axios.get(withPrefix(inputsUrl.value));
updateRefs(data.inputs, managePermissionsOptions, accessPermissionsOptions, managePermissions, accessPermissions);
loading.value = false;
}
const { onSuccess, onError } = useCallbacks(init);
</script>

<template>
<DatasetPermissionsForm
:loading="loading"
:simple-permissions="simplePermissions"
:title="title"
:form-config="formConfig"
:checked="checked"
@change="change" />
</template>
75 changes: 75 additions & 0 deletions client/src/components/User/UserDatasetPermissions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<script lang="ts" setup>
import axios from "axios";
import { computed, ref } from "vue";
import { initRefs, updateRefs, useCallbacks } from "@/composables/datasetPermissions";
import { withPrefix } from "@/utils/redirect";
import DatasetPermissionsForm from "@/components/Dataset/DatasetPermissionsForm.vue";
interface UserDatasetPermissionsProps {
userId: string;
}
const props = defineProps<UserDatasetPermissionsProps>();
const loading = ref(true);
const {
managePermissionsOptions,
accessPermissionsOptions,
managePermissions,
accessPermissions,
simplePermissions,
checked,
} = initRefs();
const inputsUrl = computed(() => {
return `/api/users/${props.userId}/permissions/inputs`;
});
async function init() {
const { data } = await axios.get(withPrefix(inputsUrl.value));
updateRefs(data.inputs, managePermissionsOptions, accessPermissionsOptions, managePermissions, accessPermissions);
loading.value = false;
}
const title = "Set Dataset Permissions for New Histories";
const formConfig = computed(() => {
return {
title: title,
id: "edit-preferences-permissions",
description:
"Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored.",
url: inputsUrl.value,
icon: "fa-users",
submitTitle: "Save Permissions",
redirect: "/user",
};
});
async function change(value: unknown) {
const managePermissionValue: number = managePermissions.value[0] as number;
let access: number[] = [] as number[];
if (value) {
access = [managePermissionValue];
}
const formValue = {
DATASET_MANAGE_PERMISSIONS: [managePermissionValue],
DATASET_ACCESS: access,
};
axios.put(withPrefix(inputsUrl.value), formValue).then(onSuccess).catch(onError);
}
const { onSuccess, onError } = useCallbacks(init);
</script>

<template>
<DatasetPermissionsForm
:loading="loading"
:simple-permissions="simplePermissions"
:title="title"
:form-config="formConfig"
:checked="checked"
@change="change" />
</template>
7 changes: 7 additions & 0 deletions client/src/components/User/UserPreferences.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
:title="link.title"
:description="link.description"
:to="`/user/${index}`" />
<UserPreferencesElement
v-if="isConfigLoaded && !config.single_user"
id="edit-preferences-permissions"
icon="fa-users"
title="Set Dataset Permissions for New Histories"
description="Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored."
to="/user/permissions" />
<UserPreferencesElement
id="edit-preferences-api-key"
icon="fa-key"
Expand Down
12 changes: 0 additions & 12 deletions client/src/components/User/UserPreferencesModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,6 @@ export const getUserPreferencesModel = (user_id) => {
redirect: "/user",
disabled: config.use_remote_user || !config.enable_account_interface,
},
permissions: {
title: _l("Set Dataset Permissions for New Histories"),
id: "edit-preferences-permissions",
description: _l(
"Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored."
),
url: `/api/users/${user_id}/permissions/inputs`,
icon: "fa-users",
submitTitle: "Save Permissions",
redirect: "/user",
disabled: config.single_user,
},
toolbox_filters: {
title: _l("Manage Toolbox Filters"),
id: "edit-preferences-toolbox-filters",
Expand Down
84 changes: 84 additions & 0 deletions client/src/composables/datasetPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { AxiosResponse } from "axios";
import type { Ref } from "vue";
import { computed, ref } from "vue";

import { useToast } from "@/composables/toast";
import { errorMessageAsString } from "@/utils/simple-error";

interface InputOption {
roleName: string;
roleValue: number;
}

interface Input {
value: number[];
options: [string, number][];
}

export function initRefs() {
const managePermissionsOptions = ref<InputOption[]>([]);
const accessPermissionsOptions = ref<InputOption[]>([]);
const managePermissions = ref<number[]>([]);
const accessPermissions = ref<number[]>([]);

const simplePermissions = computed(() => {
// we have one manage permission and read access can be that or not that but nothing else
const hasOneManagePermission = managePermissions.value.length == 1;
const hasAtMostOneAccessPermissions = accessPermissions.value.length < 2;
if (!hasOneManagePermission || !hasAtMostOneAccessPermissions) {
return false;
}
const managePermissionValue = managePermissions.value[0];
return accessPermissions.value.length == 0 || accessPermissions.value[0] == managePermissionValue;
});

const checked = computed(() => {
return accessPermissions.value.length > 0;
});

return {
managePermissionsOptions,
accessPermissionsOptions,
managePermissions,
accessPermissions,
simplePermissions,
checked,
};
}

export function updateRefs(
inputs: Input[],
managePermissionsOptions: Ref<InputOption[]>,
accessPermissionsOptions: Ref<InputOption[]>,
managePermissions: Ref<number[]>,
accessPermissions: Ref<number[]>
) {
const manageInput: Input = inputs[0] as Input;
const accessInput: Input = inputs[1] as Input;
managePermissionsOptions.value = manageInput.options.map((v: [string, number]) => {
return <InputOption>{ roleName: v[0], roleValue: v[1] };
});
accessPermissionsOptions.value = accessInput.options.map((v: [string, number]) => {
return <InputOption>{ roleName: v[0], roleValue: v[1] };
});

managePermissions.value = manageInput.value;
accessPermissions.value = accessInput.value;
}

export function useCallbacks(init: () => void) {
const toast = useToast();

async function onError(e: unknown) {
toast.error(errorMessageAsString(e));
}

async function onSuccess(data: AxiosResponse) {
toast.success(data.data.message);
init();
}

init();

return { onSuccess, onError };
}
15 changes: 12 additions & 3 deletions client/src/entry/analysis/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ import { patchRouterPush } from "./router-push";
import AboutGalaxy from "@/components/AboutGalaxy.vue";
import HistoryArchive from "@/components/History/Archiving/HistoryArchive.vue";
import HistoryArchiveWizard from "@/components/History/Archiving/HistoryArchiveWizard.vue";
import HistoryDatasetPermissions from "@/components/History/HistoryDatasetPermissions.vue";
import NotificationsList from "@/components/Notifications/NotificationsList.vue";
import Sharing from "@/components/Sharing/SharingPage.vue";
import HistoryStorageOverview from "@/components/User/DiskUsage/Visualizations/HistoryStorageOverview.vue";
import UserDatasetPermissions from "@/components/User/UserDatasetPermissions.vue";
import WorkflowPublished from "@/components/Workflow/Published/WorkflowPublished.vue";

Vue.use(VueRouter);
Expand Down Expand Up @@ -261,10 +263,9 @@ export function getRouter(Galaxy) {
},
{
path: "histories/permissions",
component: FormGeneric,
component: HistoryDatasetPermissions,
props: (route) => ({
url: `/history/permissions?id=${route.query.id}`,
redirect: "/histories/list",
historyId: route.query.id,
}),
},
{
Expand Down Expand Up @@ -442,6 +443,14 @@ export function getRouter(Galaxy) {
component: NotificationsPreferences,
redirect: redirectAnon(),
},
{
path: "user/permissions",
component: UserDatasetPermissions,
redirect: redirectAnon(),
props: (route) => ({
userId: Galaxy.user.id,
}),
},
{
path: "user/:formId",
component: UserPreferencesForm,
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class RBACAgent:
),
DATASET_ACCESS=Action(
"access",
"Users having associated role can import this dataset into their history for analysis.",
"Users having all associated roles can import this dataset into their history for analysis.",
"restrict",
),
LIBRARY_ACCESS=Action(
Expand Down

0 comments on commit aca50e1

Please sign in to comment.