diff options
Diffstat (limited to 'apps/files_sharing/src/utils')
-rw-r--r-- | apps/files_sharing/src/utils/AccountIcon.spec.ts | 40 | ||||
-rw-r--r-- | apps/files_sharing/src/utils/AccountIcon.ts | 28 | ||||
-rw-r--r-- | apps/files_sharing/src/utils/GeneratePassword.ts | 66 | ||||
-rw-r--r-- | apps/files_sharing/src/utils/NodeShareUtils.ts | 58 | ||||
-rw-r--r-- | apps/files_sharing/src/utils/SharedWithMe.js | 65 |
5 files changed, 257 insertions, 0 deletions
diff --git a/apps/files_sharing/src/utils/AccountIcon.spec.ts b/apps/files_sharing/src/utils/AccountIcon.spec.ts new file mode 100644 index 00000000000..bbc7f031774 --- /dev/null +++ b/apps/files_sharing/src/utils/AccountIcon.spec.ts @@ -0,0 +1,40 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { describe, expect, it, afterEach } from 'vitest' +import { generateAvatarSvg } from './AccountIcon' +describe('AccountIcon', () => { + + afterEach(() => { + delete document.body.dataset.themes + }) + + it('should generate regular account avatar svg', () => { + const svg = generateAvatarSvg('admin') + expect(svg).toContain('/avatar/admin/32') + expect(svg).not.toContain('dark') + expect(svg).toContain('?guestFallback=true') + }) + + it('should generate guest account avatar svg', () => { + const svg = generateAvatarSvg('admin', true) + expect(svg).toContain('/avatar/guest/admin/32') + expect(svg).not.toContain('dark') + expect(svg).not.toContain('?guestFallback=true') + }) + + it('should generate dark mode account avatar svg', () => { + document.body.dataset.themes = 'dark' + const svg = generateAvatarSvg('admin') + expect(svg).toContain('/avatar/admin/32/dark') + expect(svg).toContain('?guestFallback=true') + }) + + it('should generate dark mode guest account avatar svg', () => { + document.body.dataset.themes = 'dark' + const svg = generateAvatarSvg('admin', true) + expect(svg).toContain('/avatar/guest/admin/32/dark') + expect(svg).not.toContain('?guestFallback=true') + }) +}) diff --git a/apps/files_sharing/src/utils/AccountIcon.ts b/apps/files_sharing/src/utils/AccountIcon.ts new file mode 100644 index 00000000000..21732f08f68 --- /dev/null +++ b/apps/files_sharing/src/utils/AccountIcon.ts @@ -0,0 +1,28 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { generateUrl } from '@nextcloud/router' + +const isDarkMode = () => { + return window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches === true + || document.querySelector('[data-themes*=dark]') !== null +} + +export const generateAvatarSvg = (userId: string, isGuest = false) => { + // normal avatar url: /avatar/{userId}/32?guestFallback=true + // dark avatar url: /avatar/{userId}/32/dark?guestFallback=true + // guest avatar url: /avatar/guest/{userId}/32 + // guest dark avatar url: /avatar/guest/{userId}/32/dark + const basePath = isGuest ? `/avatar/guest/${userId}` : `/avatar/${userId}` + const darkModePath = isDarkMode() ? '/dark' : '' + const guestFallback = isGuest ? '' : '?guestFallback=true' + + const url = `${basePath}/32${darkModePath}${guestFallback}` + const avatarUrl = generateUrl(url, { userId }) + + return `<svg width="32" height="32" viewBox="0 0 32 32" + xmlns="http://www.w3.org/2000/svg" class="sharing-status__avatar"> + <image href="${avatarUrl}" height="32" width="32" /> + </svg>` +} diff --git a/apps/files_sharing/src/utils/GeneratePassword.ts b/apps/files_sharing/src/utils/GeneratePassword.ts new file mode 100644 index 00000000000..82efaaa69d4 --- /dev/null +++ b/apps/files_sharing/src/utils/GeneratePassword.ts @@ -0,0 +1,66 @@ +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import axios from '@nextcloud/axios' +import Config from '../services/ConfigService.ts' +import { showError, showSuccess } from '@nextcloud/dialogs' +import { translate as t } from '@nextcloud/l10n' + +const config = new Config() +// note: some chars removed on purpose to make them human friendly when read out +const passwordSet = 'abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789' + +/** + * Generate a valid policy password or request a valid password if password_policy is enabled + * + * @param {boolean} verbose If enabled the the status is shown to the user via toast + */ +export default async function(verbose = false): Promise<string> { + // password policy is enabled, let's request a pass + if (config.passwordPolicy.api && config.passwordPolicy.api.generate) { + try { + const request = await axios.get(config.passwordPolicy.api.generate) + if (request.data.ocs.data.password) { + if (verbose) { + showSuccess(t('files_sharing', 'Password created successfully')) + } + return request.data.ocs.data.password + } + } catch (error) { + console.info('Error generating password from password_policy', error) + if (verbose) { + showError(t('files_sharing', 'Error generating password from password policy')) + } + } + } + + const array = new Uint8Array(10) + const ratio = passwordSet.length / 255 + getRandomValues(array) + let password = '' + for (let i = 0; i < array.length; i++) { + password += passwordSet.charAt(array[i] * ratio) + } + return password +} + +/** + * Fills the given array with cryptographically secure random values. + * If the crypto API is not available, it falls back to less secure Math.random(). + * Crypto API is available in modern browsers on secure contexts (HTTPS). + * + * @param {Uint8Array} array - The array to fill with random values. + */ +function getRandomValues(array: Uint8Array): void { + if (self?.crypto?.getRandomValues) { + self.crypto.getRandomValues(array) + return + } + + let len = array.length + while (len--) { + array[len] = Math.floor(Math.random() * 256) + } +} diff --git a/apps/files_sharing/src/utils/NodeShareUtils.ts b/apps/files_sharing/src/utils/NodeShareUtils.ts new file mode 100644 index 00000000000..f14f981e2ad --- /dev/null +++ b/apps/files_sharing/src/utils/NodeShareUtils.ts @@ -0,0 +1,58 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { getCurrentUser } from '@nextcloud/auth' +import type { Node } from '@nextcloud/files' +import { ShareType } from '@nextcloud/sharing' + +type Share = { + /** The recipient display name */ + 'display-name': string + /** The recipient user id */ + id: string + /** The share type */ + type: ShareType +} + +const getSharesAttribute = function(node: Node) { + return Object.values(node.attributes.sharees).flat() as Share[] +} + +export const isNodeSharedWithMe = function(node: Node) { + const uid = getCurrentUser()?.uid + const shares = getSharesAttribute(node) + + // If you're the owner, you can't share with yourself + if (node.owner === uid) { + return false + } + + return shares.length > 0 && ( + // If some shares are shared with you as a direct user share + shares.some(share => share.id === uid && share.type === ShareType.User) + // Or of the file is shared with a group you're in + // (if it's returned by the backend, we assume you're in it) + || shares.some(share => share.type === ShareType.Group) + ) +} + +export const isNodeSharedWithOthers = function(node: Node) { + const uid = getCurrentUser()?.uid + const shares = getSharesAttribute(node) + + // If you're NOT the owner, you can't share with yourself + if (node.owner === uid) { + return false + } + + return shares.length > 0 + // If some shares are shared with you as a direct user share + && shares.some(share => share.id !== uid && share.type !== ShareType.Group) +} + +export const isNodeShared = function(node: Node) { + const shares = getSharesAttribute(node) + return shares.length > 0 +} diff --git a/apps/files_sharing/src/utils/SharedWithMe.js b/apps/files_sharing/src/utils/SharedWithMe.js new file mode 100644 index 00000000000..2f63932bfbe --- /dev/null +++ b/apps/files_sharing/src/utils/SharedWithMe.js @@ -0,0 +1,65 @@ +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { ShareType } from '@nextcloud/sharing' + +const shareWithTitle = function(share) { + if (share.type === ShareType.Group) { + return t( + 'files_sharing', + 'Shared with you and the group {group} by {owner}', + { + group: share.shareWithDisplayName, + owner: share.ownerDisplayName, + }, + undefined, + { escape: false }, + ) + } else if (share.type === ShareType.Team) { + return t( + 'files_sharing', + 'Shared with you and {circle} by {owner}', + { + circle: share.shareWithDisplayName, + owner: share.ownerDisplayName, + }, + undefined, + { escape: false }, + ) + } else if (share.type === ShareType.Room) { + if (share.shareWithDisplayName) { + return t( + 'files_sharing', + 'Shared with you and the conversation {conversation} by {owner}', + { + conversation: share.shareWithDisplayName, + owner: share.ownerDisplayName, + }, + undefined, + { escape: false }, + ) + } else { + return t( + 'files_sharing', + 'Shared with you in a conversation by {owner}', + { + owner: share.ownerDisplayName, + }, + undefined, + { escape: false }, + ) + } + } else { + return t( + 'files_sharing', + 'Shared with you by {owner}', + { owner: share.ownerDisplayName }, + undefined, + { escape: false }, + ) + } +} + +export { shareWithTitle } |