Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement GDPR export for users #8741

Merged
merged 2 commits into from
Apr 4, 2023
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
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-gdpr-export
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: GDPR export

Users can now request a GDPR export on their account page. Note that this is only supported when running oCIS as backend.

https://github.com/owncloud/web/issues/8738
https://github.com/owncloud/web/pull/8741
1 change: 1 addition & 0 deletions changelog/unreleased/enhancement-update-libregraph
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ libre-graph-api has been updated to v1.0
https://github.com/owncloud/web/pull/8132
https://github.com/owncloud/web/pull/8171
https://github.com/owncloud/web/pull/8250
https://github.com/owncloud/web/pull/8741
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.3.0-SNAPSHOT
6.5.0-SNAPSHOT
701 changes: 518 additions & 183 deletions packages/web-client/src/generated/api.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/web-client/src/generated/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable */
/**
* Libre Graph API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
* Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
*
* The version of the OpenAPI document: v1.0.1
*
Expand Down
2 changes: 1 addition & 1 deletion packages/web-client/src/generated/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable */
/**
* Libre Graph API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
* Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
*
* The version of the OpenAPI document: v1.0.1
*
Expand Down
2 changes: 1 addition & 1 deletion packages/web-client/src/generated/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable */
/**
* Libre Graph API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
* Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
*
* The version of the OpenAPI document: v1.0.1
*
Expand Down
2 changes: 1 addition & 1 deletion packages/web-client/src/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable */
/**
* Libre Graph API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
* Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
*
* The version of the OpenAPI document: v1.0.1
*
Expand Down
18 changes: 9 additions & 9 deletions packages/web-client/src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
CollectionOfApplications,
ApplicationsApiFactory,
UserAppRoleAssignmentApiFactory,
AppRoleAssignment
AppRoleAssignment,
ExportPersonalDataRequest
} from './generated'

export interface Graph {
Expand Down Expand Up @@ -55,6 +56,10 @@ export interface Graph {
userId: string,
appRoleAssignment: AppRoleAssignment
) => AxiosPromise<AppRoleAssignment>
exportPersonalData: (
userId: string,
exportPersonalDataRequest?: ExportPersonalDataRequest
) => AxiosPromise<void>
}
groups: {
listGroups: (orderBy?: string) => AxiosPromise<CollectionOfGroup>
Expand Down Expand Up @@ -133,17 +138,16 @@ export const graph = (baseURI: string, axiosClient: AxiosInstance): Graph => {
deleteUser: (userId: string) => userApiFactory.deleteUser(userId),
listUsers: (orderBy?: any, filter?: string) =>
usersApiFactory.listUsers(
0,
0,
Comment on lines -136 to -137
Copy link
Contributor Author

@JammingBen JammingBen Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but some parameters seem to have gone after updating the graph client.

'',
filter,
false,
new Set<any>([orderBy]),
new Set<any>([]),
new Set<any>(['memberOf', 'appRoleAssignments'])
),
createUserAppRoleAssignment: (userId: string, appRoleAssignment: AppRoleAssignment) =>
userAppRoleAssignmentApiFactory.userCreateAppRoleAssignments(userId, appRoleAssignment)
userAppRoleAssignmentApiFactory.userCreateAppRoleAssignments(userId, appRoleAssignment),
exportPersonalData: (userId: string, exportPersonalDataRequest?: ExportPersonalDataRequest) =>
userApiFactory.exportPersonalData(userId, exportPersonalDataRequest)
},
groups: {
createGroup: (group: Group) => groupsApiFactory.createGroup(group),
Expand All @@ -152,11 +156,7 @@ export const graph = (baseURI: string, axiosClient: AxiosInstance): Graph => {
deleteGroup: (groupId: string) => groupApiFactory.deleteGroup(groupId),
listGroups: (orderBy?: any) =>
groupsApiFactory.listGroups(
0,
0,
'',
'',
false,
new Set<any>([orderBy]),
new Set<any>([]),
new Set<any>(['members'])
Expand Down
5 changes: 4 additions & 1 deletion packages/web-pkg/src/composables/capability/useCapability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export const useCapabilityCoreSupportUrlSigning = createCapabilityComposable(
'core.support-url-signing',
false
)

export const useCapabilityGraphPersonalDataExport = createCapabilityComposable(
'graph.personal-data-export',
false
)
export const useCapabilityFilesSharingQuickLinkDefaultRole = createCapabilityComposable(
'files_sharing.quick_link.default_role',
'viewer'
Expand Down
139 changes: 139 additions & 0 deletions packages/web-runtime/src/components/Account/GdprExport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<template>
<span v-if="loading">
<oc-spinner />
</span>
<span v-else-if="exportInProgress" class="oc-flex oc-flex-middle" data-testid="export-in-process">
<oc-icon name="time" fill-type="line" size="small" class="oc-mr-s" />
<span v-text="$gettext('Export is being processed. This can take up to 24 hours.')" />
</span>
<div v-else>
<oc-button
appearance="raw"
variation="primary"
data-testid="request-export-btn"
@click="requestExport"
>
<span v-text="$gettext('Request new export')" />
</oc-button>
<div v-if="exportFile" class="oc-flex oc-flex-middle">
<oc-button
appearance="raw"
variation="primary"
data-testid="download-export-btn"
@click="downloadExport"
>
<oc-icon name="download" fill-type="line" size="small" />
<span v-text="$gettext('Download export')" />
</oc-button>
<span class="oc-ml-s" v-text="`(${exportDate})`" />
</div>
</div>
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, onUnmounted, ref, unref } from 'vue'
import { useTask } from 'vue-concurrency'
import { useGettext } from 'vue3-gettext'
import { Resource } from 'web-client'
import { useClientService, useStore } from 'web-pkg/src/composables'
import { useDownloadFile } from 'web-pkg/src/composables/download'
import { formatDateFromJSDate } from 'web-pkg'
import { isPersonalSpaceResource } from 'web-client/src/helpers'

const GDPR_EXPORT_FILE_NAME = '.personal_data_export.json'
const POLLING_INTERVAL = 30000

export default defineComponent({
name: 'GdprExport',
setup() {
const store = useStore()
const { $gettext, current: currentLanguage } = useGettext()
const { webdav, graphAuthenticated } = useClientService()
const { downloadFile } = useDownloadFile()

const loading = ref(true)
const checkInterval = ref()
const exportFile = ref<Resource>()
const exportInProgress = ref(false)

const personalSpace = computed(() => {
return store.getters['runtime/spaces/spaces'].find((s) => isPersonalSpaceResource(s))
})

const loadExportTask = useTask(function* () {
try {
const resource = yield webdav.getFileInfo(unref(personalSpace), {
path: `/${GDPR_EXPORT_FILE_NAME}`
})

if (resource.processing) {
exportInProgress.value = true
if (!unref(checkInterval)) {
checkInterval.value = setInterval(() => {
loadExportTask.perform()
}, POLLING_INTERVAL)
}
return
}

exportFile.value = resource
exportInProgress.value = false
if (unref(checkInterval)) {
clearInterval(unref(checkInterval))
}
} catch (e) {
if (e.statusCode !== 404) {
// resource seems to exist, but something else went wrong
console.error(e)
}
} finally {
loading.value = false
}
}).restartable()

const requestExport = async () => {
try {
await graphAuthenticated.users.exportPersonalData(store.getters.user.uuid, {
storageLocation: `/${GDPR_EXPORT_FILE_NAME}`
})
await loadExportTask.perform()
return store.dispatch('showMessage', {
title: $gettext('GDPR export has been requested')
})
} catch (e) {
return store.dispatch('showMessage', {
title: $gettext('GDPR export could not be requested. Please contact an administrator.'),
status: 'danger'
})
}
}
const downloadExport = () => {
return downloadFile(unref(exportFile))
}

const exportDate = computed(() => {
return formatDateFromJSDate(new Date(unref(exportFile).mdate), currentLanguage)
})

onMounted(() => {
loadExportTask.perform()
})

onUnmounted(() => {
if (unref(checkInterval)) {
clearInterval(unref(checkInterval))
}
})

return {
loading,
loadExportTask,
exportFile,
exportInProgress,
requestExport,
downloadExport,
exportDate
}
}
})
</script>
24 changes: 22 additions & 2 deletions packages/web-runtime/src/pages/account.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@
</oc-button>
</dd>
</div>
<div v-if="showGdprExport" class="account-page-gdpr-export oc-mb oc-width-1-2@s">
<dt class="oc-text-normal oc-text-muted" v-text="$gettext('GDPR export')" />
<dd data-testid="gdpr-export">
<gdpr-export />
</dd>
</div>
</dl>
</main>
</template>
Expand All @@ -104,6 +110,7 @@ import EditPasswordModal from '../components/EditPasswordModal.vue'
import { computed, defineComponent, onMounted, unref } from 'vue'
import {
useAccessToken,
useCapabilityGraphPersonalDataExport,
useCapabilitySpacesEnabled,
useClientService,
useStore
Expand All @@ -113,26 +120,38 @@ import axios from 'axios'
import { v4 as uuidV4 } from 'uuid'
import { useGettext } from 'vue3-gettext'
import { setCurrentLanguage } from 'web-runtime/src/helpers/language'
import { configurationManager } from 'web-pkg/src/configuration'
import GdprExport from 'web-runtime/src/components/Account/GdprExport.vue'
import { useConfigurationManager } from 'web-pkg/src/composables/configuration'
import { isPersonalSpaceResource } from 'web-client/src/helpers'

export default defineComponent({
name: 'Personal',
components: {
EditPasswordModal
EditPasswordModal,
GdprExport
},
setup() {
const store = useStore()
const accessToken = useAccessToken({ store })
const language = useGettext()
const clientService = useClientService()
const configurationManager = useConfigurationManager()

// FIXME: Use graph capability when we have it
const isLanguageSupported = useCapabilitySpacesEnabled()
const isChangePasswordEnabled = useCapabilitySpacesEnabled()
const isPersonalDataExportEnabled = useCapabilityGraphPersonalDataExport()
const user = computed(() => {
return store.getters.user
})

const showGdprExport = computed(() => {
return (
unref(isPersonalDataExportEnabled) &&
store.getters['runtime/spaces/spaces'].some((s) => isPersonalSpaceResource(s))
)
})

const loadAccountBundleTask = useTask(function* () {
try {
const {
Expand Down Expand Up @@ -235,6 +254,7 @@ export default defineComponent({
updateSelectedLanguage,
accountEditLink,
isChangePasswordEnabled,
showGdprExport,
isLanguageSupported,
groupNames,
user,
Expand Down
Loading