From b3f6ac86c12f7bdf2090f1d2e93c53dfc3972d9a Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 6 Dec 2024 11:59:19 -0700 Subject: [PATCH] Permissions Cleanup Squeezing in: https://github.com/jellyfin/jellyfin-web/issues/6361 --- Shared/Objects/UserPermissions.swift | 41 ++++++++++++++++ Shared/Strings/Strings.swift | 2 + .../StoredValue/StoredValues+User.swift | 14 ++++-- .../SwiftinStore+UserState.swift | 38 +------------- Swiftfin.xcodeproj/project.pbxproj | 6 +++ .../Views/ItemEditorView/ItemEditorView.swift | 2 +- Swiftfin/Views/ItemView/ItemView.swift | 24 ++++++--- .../Components/Sections/ItemSection.swift | 49 ++++++++++--------- .../SettingsView/SettingsView.swift | 2 +- Translations/en.lproj/Localizable.strings | 4 ++ 10 files changed, 111 insertions(+), 71 deletions(-) create mode 100644 Shared/Objects/UserPermissions.swift diff --git a/Shared/Objects/UserPermissions.swift b/Shared/Objects/UserPermissions.swift new file mode 100644 index 000000000..706f5c3e5 --- /dev/null +++ b/Shared/Objects/UserPermissions.swift @@ -0,0 +1,41 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024 Jellyfin & Jellyfin Contributors +// + +import JellyfinAPI + +struct UserPermissions { + + let isAdministrator: Bool + let items: UserItemPermissions + + init(_ policy: UserPolicy?) { + self.isAdministrator = policy?.isAdministrator ?? false + self.items = UserItemPermissions(policy, isAdministrator: isAdministrator) + } + + struct UserItemPermissions { + + let canDelete: Bool + let canDownload: Bool + let canEditMetadata: Bool + let canManageSubtitles: Bool + let canManageCollections: Bool + let canManageLyrics: Bool + + init(_ policy: UserPolicy?, isAdministrator: Bool) { + self.canDelete = policy?.enableContentDeletion ?? false || policy?.enableContentDeletionFromFolders != [] + self.canDownload = policy?.enableContentDownloading ?? false + self.canEditMetadata = isAdministrator + // TODO: SDK 10.9 Enable Comments + self.canManageSubtitles = isAdministrator // || policy?.enableSubtitleManagement ?? false + self.canManageCollections = isAdministrator // || policy?.enableCollectionManagement ?? false + // TODO: SDK 10.10 Enable Comments + self.canManageLyrics = isAdministrator // || policy?.enableSubtitleManagement ?? false + } + } +} diff --git a/Shared/Strings/Strings.swift b/Shared/Strings/Strings.swift index 99f1adaad..8957324b8 100644 --- a/Shared/Strings/Strings.swift +++ b/Shared/Strings/Strings.swift @@ -64,6 +64,8 @@ internal enum L10n { internal static let allGenres = L10n.tr("Localizable", "allGenres", fallback: "All Genres") /// All Media internal static let allMedia = L10n.tr("Localizable", "allMedia", fallback: "All Media") + /// Allow collection management + internal static let allowCollectionManagement = L10n.tr("Localizable", "allowCollectionManagement", fallback: "Allow collection management") /// Allow media item deletion internal static let allowItemDeletion = L10n.tr("Localizable", "allowItemDeletion", fallback: "Allow media item deletion") /// Allow media item editing diff --git a/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift b/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift index 13f5cc6db..476b9a4e8 100644 --- a/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift +++ b/Shared/SwiftfinStore/StoredValue/StoredValues+User.swift @@ -149,10 +149,10 @@ extension StoredValues.Keys { ) } - static var enableItemEditor: Key { + static var enableItemEditing: Key { CurrentUserKey( - "enableItemEditor", - domain: "enableItemEditor", + "enableItemEditing", + domain: "enableItemEditing", default: false ) } @@ -164,5 +164,13 @@ extension StoredValues.Keys { default: false ) } + + static var enableCollectionManagement: Key { + CurrentUserKey( + "enableCollectionManagement", + domain: "enableCollectionManagement", + default: false + ) + } } } diff --git a/Shared/SwiftfinStore/SwiftinStore+UserState.swift b/Shared/SwiftfinStore/SwiftinStore+UserState.swift index 8eabaa295..91d53211a 100644 --- a/Shared/SwiftfinStore/SwiftinStore+UserState.swift +++ b/Shared/SwiftfinStore/SwiftinStore+UserState.swift @@ -64,42 +64,8 @@ extension UserState { } } - /// User has administrator permissions - var isAdministrator: Bool { - data.policy?.isAdministrator ?? false - } - - /// User has permission to delete something whether from a folder or all folders - var hasItemDeletionPermissions: Bool { - data.policy?.enableContentDeletion ?? false || data.policy?.enableContentDeletionFromFolders != [] - } - - /// User has permission to download items - var hasItemDownloadPermissions: Bool { - data.policy?.enableContentDownloading ?? false - } - - /// User has permission to edit items - var hasMetadataEditingPermissions: Bool { - isAdministrator - } - - /// User has permission to edit item subtitles - var hasSubtitleManagementPermissions: Bool { - // TODO: SDK 10.9, enable enableSubtitleManagement - isAdministrator // || data.policy?.enableSubtitleManagement ?? false - } - - /// User has permission to edit collections - var hasCollectionManagementPermissions: Bool { - // TODO: SDK 10.9, enable enableCollectionManagement - isAdministrator // || data.policy?.enableCollectionManagement ?? false - } - - /// User has permission to edit item lyrics - var hasLyricManagementPermissions: Bool { - // TODO: SDK 10.10, enable enableSubtitleManagement - isAdministrator // || data.policy?.enableSubtitleManagement ?? false + var permissions: UserPermissions { + UserPermissions(data.policy) } var pinHint: String { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index ec2ec3cce..da6e05af3 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -91,6 +91,8 @@ 4E5071DB2CFCEC1D003FA2AD /* GenreEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5071D92CFCEC0E003FA2AD /* GenreEditorViewModel.swift */; }; 4E5071E42CFCEFD3003FA2AD /* AddItemElementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5071E32CFCEFD1003FA2AD /* AddItemElementView.swift */; }; 4E5334A22CD1A28700D59FA8 /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5334A12CD1A28400D59FA8 /* ActionButton.swift */; }; + 4E556AB02D036F6900733377 /* UserPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E556AAF2D036F5E00733377 /* UserPermissions.swift */; }; + 4E556AB12D036F6900733377 /* UserPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E556AAF2D036F5E00733377 /* UserPermissions.swift */; }; 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; }; 4E63B9FA2C8A5BEF00C25378 /* AdminDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9F42C8A5BEF00C25378 /* AdminDashboardView.swift */; }; 4E63B9FC2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */; }; @@ -1202,6 +1204,7 @@ 4E5071D92CFCEC0E003FA2AD /* GenreEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenreEditorViewModel.swift; sourceTree = ""; }; 4E5071E32CFCEFD1003FA2AD /* AddItemElementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddItemElementView.swift; sourceTree = ""; }; 4E5334A12CD1A28400D59FA8 /* ActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = ""; }; + 4E556AAF2D036F5E00733377 /* UserPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPermissions.swift; sourceTree = ""; }; 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; 4E63B9F42C8A5BEF00C25378 /* AdminDashboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdminDashboardView.swift; sourceTree = ""; }; 4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionsViewModel.swift; sourceTree = ""; }; @@ -2789,6 +2792,7 @@ E1C8CE7B28FF015000DF5D7B /* TrailingTimestampType.swift */, 4E01446B2D0292E000193038 /* Trie.swift */, E1EA09682BED78BB004CDE76 /* UserAccessPolicy.swift */, + 4E556AAF2D036F5E00733377 /* UserPermissions.swift */, DFB7C3DE2C7AA42700CE7CDC /* UserSignInState.swift */, E1D8429229340B8300D1041A /* Utilities.swift */, E1BDF2E42951475300CC0294 /* VideoPlayerActionButton.swift */, @@ -5185,6 +5189,7 @@ E133328929538D8D00EE76AB /* Files.swift in Sources */, E154967A296CB4B000C4EF88 /* VideoPlayerSettingsView.swift in Sources */, C46008742A97DFF2002B1C7A /* LiveLoadingOverlay.swift in Sources */, + 4E556AB12D036F6900733377 /* UserPermissions.swift in Sources */, E1575EA0293E7B1E001665B1 /* CGPoint.swift in Sources */, E1C926132887565C002A7A66 /* EpisodeSelector.swift in Sources */, E12CC1CD28D135C700678D5D /* NextUpView.swift in Sources */, @@ -5796,6 +5801,7 @@ E1D27EE72BBC955F00152D16 /* UnmaskSecureField.swift in Sources */, E1CAF65D2BA345830087D991 /* MediaType.swift in Sources */, E1AD105F26D9ADDD003E4A08 /* NameGuidPair.swift in Sources */, + 4E556AB02D036F6900733377 /* UserPermissions.swift in Sources */, E18A8E7D28D606BE00333B9A /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, 4EC2B19B2CC96E7400D866BE /* ServerUsersView.swift in Sources */, E18E01F1288747230022598C /* PlayButton.swift in Sources */, diff --git a/Swiftfin/Views/ItemEditorView/ItemEditorView.swift b/Swiftfin/Views/ItemEditorView/ItemEditorView.swift index 90208dda0..7a3920041 100644 --- a/Swiftfin/Views/ItemEditorView/ItemEditorView.swift +++ b/Swiftfin/Views/ItemEditorView/ItemEditorView.swift @@ -51,7 +51,7 @@ struct ItemEditorView: View { private var refreshButtonView: some View { Section { RefreshMetadataButton(item: viewModel.item) - .environment(\.isEnabled, userSession?.user.isAdministrator ?? false) + .environment(\.isEnabled, userSession?.user.permissions.isAdministrator ?? false) } footer: { LearnMoreButton(L10n.metadata) { TextPair( diff --git a/Swiftfin/Views/ItemView/ItemView.swift b/Swiftfin/Views/ItemView/ItemView.swift index 2bd959d4b..78aaf8b4f 100644 --- a/Swiftfin/Views/ItemView/ItemView.swift +++ b/Swiftfin/Views/ItemView/ItemView.swift @@ -32,21 +32,31 @@ struct ItemView: View { @StoredValue(.User.enableItemDeletion) private var enableItemDeletion: Bool - @StoredValue(.User.enableItemEditor) - private var enableItemEditor: Bool + @StoredValue(.User.enableItemEditing) + private var enableItemEditing: Bool + @StoredValue(.User.enableCollectionManagement) + private var enableCollectionManagement: Bool private var canDelete: Bool { - enableItemDeletion && viewModel.item.canDelete ?? false + if viewModel.item.type == .boxSet { + return enableCollectionManagement && viewModel.item.canDelete ?? false + } else { + return enableItemDeletion && viewModel.item.canDelete ?? false + } } - private var canDownload: Bool { - viewModel.item.canDownload ?? false + private var canEdit: Bool { + if viewModel.item.type == .boxSet { + return enableCollectionManagement + } else { + return enableItemEditing + } } // Use to hide the menu button when not needed. // Add more checks as needed. For example, canDownload. private var enableMenu: Bool { - canDelete || enableItemEditor + canDelete || canEdit } private static func typeViewModel(for item: BaseItemDto) -> ItemViewModel { @@ -132,7 +142,7 @@ struct ItemView: View { isLoading: viewModel.backgroundStates.contains(.refresh), isHidden: !enableMenu ) { - if enableItemEditor { + if canEdit { Button(L10n.edit, systemImage: "pencil") { router.route(to: \.itemEditor, viewModel) } diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift index c7e32f644..11180c0b3 100644 --- a/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings/Components/Sections/ItemSection.swift @@ -17,36 +17,39 @@ extension CustomizeViewsSettings { @Injected(\.currentUserSession) private var userSession - @StoredValue(.User.enableItemEditor) - private var enableItemEditor + @StoredValue(.User.enableItemEditing) + private var enableItemEditing @StoredValue(.User.enableItemDeletion) private var enableItemDeletion + @StoredValue(.User.enableCollectionManagement) + private var enableCollectionManagement var body: some View { Section(L10n.items) { - /* if userSession?.user.hasItemDownloadPermissions ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) - } */ - - if userSession?.user.hasMetadataEditingPermissions ?? false { - Toggle(L10n.allowItemEditing, isOn: $enableItemEditor) + /// Enable Editing Items from All Visible LIbraries + if userSession?.user.permissions.items.canEditMetadata ?? false { + Toggle(L10n.allowItemEditing, isOn: $enableItemEditing) } - - /* if userSession?.user.hasCollectionManagementPermissions ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) - }*/ - - if userSession?.user.hasItemDeletionPermissions ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) + /// Enable Downloading All Items + /* if userSession?.user.permissions.items.canDownload ?? false { + Toggle(L10n.allowItemDownloading, isOn: $enableItemDownloads) + } */ + /// Enable Deleting or Editing Collections + if userSession?.user.permissions.items.canManageCollections ?? false { + Toggle(L10n.allowCollectionManagement, isOn: $enableCollectionManagement) } - - /* if userSession?.user.hasLyricManagementPermissions ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) - } - - if userSession?.user.hasSubtitleManagementPermissions ?? false { - Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) - }*/ + /// Manage Item Lyrics + /* if userSession?.user.permissions.items.canManageLyrics ?? false { + Toggle(L10n.allowLyricsManagement isOn: $enableLyricsManagement) + } */ + /// Manage Item Subtitles + /* if userSession?.user.items.canManageSubtitles ?? false { + Toggle(L10n.allowSubtitleManagement, isOn: $enableSubtitleManagement) + } */ + } + /// Enable Deleting Items from Approved Libraries + if userSession?.user.permissions.items.canDelete ?? false { + Toggle(L10n.allowItemDeletion, isOn: $enableItemDeletion) } } } diff --git a/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift index c807c0b40..1d95a2429 100644 --- a/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView/SettingsView.swift @@ -43,7 +43,7 @@ struct SettingsView: View { router.route(to: \.serverConnection, viewModel.userSession.server) } - if viewModel.userSession.user.isAdministrator { + if viewModel.userSession.user.permissions.isAdministrator { ChevronButton(L10n.dashboard) .onSelect { router.route(to: \.adminDashboard) diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 4e5faac77..ee9cc9e5b 100644 --- a/Translations/en.lproj/Localizable.strings +++ b/Translations/en.lproj/Localizable.strings @@ -1453,6 +1453,10 @@ // Toggle option for enabling media item editing "allowItemEditing" = "Allow media item editing"; +// Allow Collection Management - Toggle +// Toggle option for enabling collection editing / deletion +"allowCollectionManagement" = "Allow collection management"; + // Allow Media Item Deletion - Toggle // Toggle option for enabling media item deletion "allowItemDeletion" = "Allow media item deletion";