Fix various issues with SSO users

This commit is contained in:
GRIMSIM
2025-07-11 14:05:00 +02:00
parent 0e8b0b70e2
commit 739c883bf1
8 changed files with 41 additions and 22 deletions
@@ -56,7 +56,7 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
<Checkbox className="items-baseline" value="auto-register-new-users" isDisabled> <Checkbox className="items-baseline" value="auto-register-new-users" isDisabled>
Automatically create new users after registration Automatically create new users after registration
</Checkbox> </Checkbox>
<Tooltip content={"Currently not configurable due to a bug"} placement="right"> <Tooltip content={"Currently not configurable (always enabled)"} placement="right">
<Warning weight="fill"/> <Warning weight="fill"/>
</Tooltip> </Tooltip>
</div> </div>
@@ -13,7 +13,6 @@ import {UserPreferenceService} from "Frontend/util/user-preference-service";
import SearchBar from "Frontend/components/general/SearchBar"; import SearchBar from "Frontend/components/general/SearchBar";
import {useSnapshot} from "valtio/react"; import {useSnapshot} from "valtio/react";
import {gameState} from "Frontend/state/GameState"; import {gameState} from "Frontend/state/GameState";
import {scanState} from "Frontend/state/ScanState";
import ScanProgressPopover from "Frontend/components/general/ScanProgressPopover"; import ScanProgressPopover from "Frontend/components/general/ScanProgressPopover";
import {isAdmin} from "Frontend/util/utils"; import {isAdmin} from "Frontend/util/utils";
@@ -27,7 +26,6 @@ export default function MainLayout() {
const isHomePage = location.pathname === "/"; const isHomePage = location.pathname === "/";
const [isExploding, setIsExploding] = useState(false); const [isExploding, setIsExploding] = useState(false);
const games = useSnapshot(gameState).games; const games = useSnapshot(gameState).games;
const scans = useSnapshot(scanState);
useEffect(() => { useEffect(() => {
let newTitle = `Gameyfin - ${routeMetadata?.title}`; let newTitle = `Gameyfin - ${routeMetadata?.title}`;
@@ -7,15 +7,15 @@ import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.config.dto.ConfigEntryDto import org.gameyfin.app.config.dto.ConfigEntryDto
import org.gameyfin.app.config.dto.ConfigUpdateDto import org.gameyfin.app.config.dto.ConfigUpdateDto
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.users.UserService
import org.gameyfin.app.users.util.isAdmin import org.gameyfin.app.users.util.isAdmin
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
@Endpoint @Endpoint
@RolesAllowed(Role.Names.ADMIN) @RolesAllowed(Role.Names.ADMIN)
class ConfigEndpoint( class ConfigEndpoint(
private val configService: ConfigService private val configService: ConfigService,
private val userService: UserService,
) { ) {
companion object { companion object {
val log = KotlinLogging.logger { } val log = KotlinLogging.logger { }
@@ -25,7 +25,7 @@ class ConfigEndpoint(
@PermitAll @PermitAll
fun subscribe(): Flux<List<ConfigUpdateDto>> { fun subscribe(): Flux<List<ConfigUpdateDto>> {
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails val user = userService.getCurrentUser()
return if (user.isAdmin()) ConfigService.subscribe() return if (user.isAdmin()) ConfigService.subscribe()
else Flux.empty() else Flux.empty()
} }
@@ -4,15 +4,15 @@ import com.vaadin.hilla.Endpoint
import jakarta.annotation.security.PermitAll import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.users.UserService
import org.gameyfin.app.users.util.isAdmin import org.gameyfin.app.users.util.isAdmin
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
@Endpoint @Endpoint
@RolesAllowed(Role.Names.ADMIN) @RolesAllowed(Role.Names.ADMIN)
class LogEndpoint( class LogEndpoint(
private val logService: LogService private val logService: LogService,
private val userService: UserService,
) { ) {
fun reloadLogConfig() { fun reloadLogConfig() {
@@ -21,7 +21,7 @@ class LogEndpoint(
@PermitAll @PermitAll
fun getApplicationLogs(): Flux<String> { fun getApplicationLogs(): Flux<String> {
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails val user = userService.getCurrentUser()
return if (user.isAdmin()) logService.streamLogs() return if (user.isAdmin()) logService.streamLogs()
else Flux.empty() else Flux.empty()
} }
@@ -5,21 +5,21 @@ import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.core.plugins.dto.PluginUpdateDto import org.gameyfin.app.core.plugins.dto.PluginUpdateDto
import org.gameyfin.app.users.UserService
import org.gameyfin.app.users.util.isAdmin import org.gameyfin.app.users.util.isAdmin
import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
@Endpoint @Endpoint
@RolesAllowed(Role.Names.ADMIN) @RolesAllowed(Role.Names.ADMIN)
class PluginEndpoint( class PluginEndpoint(
private val pluginService: PluginService private val pluginService: PluginService,
private val userService: UserService,
) { ) {
@PermitAll @PermitAll
fun subscribe(): Flux<List<PluginUpdateDto>> { fun subscribe(): Flux<List<PluginUpdateDto>> {
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails val user = userService.getCurrentUser()
return if (user.isAdmin()) PluginService.subscribe() return if (user.isAdmin()) PluginService.subscribe()
else Flux.empty() else Flux.empty()
} }
@@ -1,23 +1,23 @@
package org.gameyfin.app.libraries package org.gameyfin.app.libraries
import com.vaadin.hilla.Endpoint import com.vaadin.hilla.Endpoint
import org.gameyfin.app.libraries.dto.LibraryDto
import org.gameyfin.app.libraries.dto.LibraryEvent
import jakarta.annotation.security.PermitAll import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.libraries.dto.LibraryDto
import org.gameyfin.app.libraries.dto.LibraryEvent
import org.gameyfin.app.libraries.dto.LibraryScanProgress import org.gameyfin.app.libraries.dto.LibraryScanProgress
import org.gameyfin.app.libraries.dto.LibraryUpdateDto import org.gameyfin.app.libraries.dto.LibraryUpdateDto
import org.gameyfin.app.libraries.enums.ScanType import org.gameyfin.app.libraries.enums.ScanType
import org.gameyfin.app.users.UserService
import org.gameyfin.app.users.util.isAdmin import org.gameyfin.app.users.util.isAdmin
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
@Endpoint @Endpoint
@PermitAll @PermitAll
class LibraryEndpoint( class LibraryEndpoint(
private val libraryService: LibraryService private val libraryService: LibraryService,
private val userService: UserService,
) { ) {
fun subscribeToLibraryEvents(): Flux<List<LibraryEvent>> { fun subscribeToLibraryEvents(): Flux<List<LibraryEvent>> {
return LibraryService.subscribeToLibraryEvents() return LibraryService.subscribeToLibraryEvents()
@@ -27,7 +27,7 @@ class LibraryEndpoint(
fun subscribeToScanProgressEvents(): Flux<List<LibraryScanProgress>> { fun subscribeToScanProgressEvents(): Flux<List<LibraryScanProgress>> {
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails val user = userService.getCurrentUser()
return if (user.isAdmin()) LibraryService.subscribeToScanProgressEvents() return if (user.isAdmin()) LibraryService.subscribeToScanProgressEvents()
else Flux.empty() else Flux.empty()
} }
@@ -47,6 +47,19 @@ class UserService(
override fun loadUserByUsername(username: String): UserDetails { override fun loadUserByUsername(username: String): UserDetails {
val user = getByUsernameNonNull(username) val user = getByUsernameNonNull(username)
if (user.oidcProviderId != null && user.password == null) {
// If the user is an OIDC user, we return a UserDetails with no password
return User(
user.username,
"", // OIDC users do not have a password
user.enabled,
true,
true,
true,
toAuthorities(user.roles)
)
}
return User( return User(
user.username, user.username,
user.password, user.password,
@@ -132,7 +145,11 @@ class UserService(
} }
fun registerOrUpdateUser(user: org.gameyfin.app.users.entities.User): org.gameyfin.app.users.entities.User { fun registerOrUpdateUser(user: org.gameyfin.app.users.entities.User): org.gameyfin.app.users.entities.User {
user.password = passwordEncoder.encode(user.password) // OIDC users can have null passwords, so we only encode if a password is provided
if (user.password != null) {
user.password = passwordEncoder.encode(user.password)
}
return userRepository.save(user) return userRepository.save(user)
} }
@@ -15,3 +15,7 @@ fun UserDetails.hasRole(role: Role): Boolean {
fun UserDetails.isAdmin(): Boolean { fun UserDetails.isAdmin(): Boolean {
return hasRole(Role.SUPERADMIN) || hasRole(Role.ADMIN) return hasRole(Role.SUPERADMIN) || hasRole(Role.ADMIN)
} }
fun User.isAdmin(): Boolean {
return hasRole(Role.SUPERADMIN) || hasRole(Role.ADMIN)
}