mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Merge pull request #621 from gameyfin/fix/sso-users
Fix various SSO issues Fixes #614
This commit is contained in:
@@ -3,8 +3,8 @@ import withConfigPage from "Frontend/components/administration/withConfigPage";
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
|
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
|
||||||
import Section from "Frontend/components/general/Section";
|
import Section from "Frontend/components/general/Section";
|
||||||
import {addToast, Button} from "@heroui/react";
|
import {addToast, Button, Checkbox, CheckboxGroup, Tooltip} from "@heroui/react";
|
||||||
import {MagicWand} from "@phosphor-icons/react";
|
import {MagicWand, Warning} from "@phosphor-icons/react";
|
||||||
|
|
||||||
function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
|
function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
|
||||||
|
|
||||||
@@ -50,8 +50,21 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
|
|||||||
|
|
||||||
<Section title="SSO user handling"/>
|
<Section title="SSO user handling"/>
|
||||||
<div className="flex flex-row items-baseline">
|
<div className="flex flex-row items-baseline">
|
||||||
<ConfigFormField configElement={getConfig("sso.oidc.auto-register-new-users")}
|
<CheckboxGroup className="flex flex-col flex-1 items-baseline gap-2"
|
||||||
isDisabled={!formik.values.sso.oidc.enabled}/>
|
value={["auto-register-new-users"]}>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<Checkbox className="items-baseline" value="auto-register-new-users" isDisabled>
|
||||||
|
Automatically create new users after registration
|
||||||
|
</Checkbox>
|
||||||
|
<Tooltip content={"Currently not configurable (always enabled)"} placement="right">
|
||||||
|
<Warning weight="fill"/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</CheckboxGroup>
|
||||||
|
{/*TODO: enable when the issues with unregistered SSO users are sorted
|
||||||
|
|
||||||
|
<ConfigFormField configElement={getConfig("sso.oidc.auto-register-new-users")} isDisabled={!formik.values.sso.oidc.enabled}/>
|
||||||
|
*/}
|
||||||
<ConfigFormField configElement={getConfig("sso.oidc.match-existing-users-by")}
|
<ConfigFormField configElement={getConfig("sso.oidc.match-existing-users-by")}
|
||||||
isDisabled={!formik.values.sso.oidc.enabled ||
|
isDisabled={!formik.values.sso.oidc.enabled ||
|
||||||
!formik.values.sso.oidc["auto-register-new-users"]}/>
|
!formik.values.sso.oidc["auto-register-new-users"]}/>
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.gameyfin.app.core.security
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.springframework.context.annotation.Conditional
|
||||||
|
import org.springframework.security.core.AuthenticationException
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint
|
||||||
|
|
||||||
|
@Conditional(SsoEnabledCondition::class)
|
||||||
|
class CustomAuthenticationEntryPoint : AuthenticationEntryPoint {
|
||||||
|
override fun commence(
|
||||||
|
request: HttpServletRequest,
|
||||||
|
response: HttpServletResponse,
|
||||||
|
authException: AuthenticationException?
|
||||||
|
) {
|
||||||
|
if (request.getParameter("direct") == "1") {
|
||||||
|
response.sendRedirect("/login")
|
||||||
|
} else {
|
||||||
|
response.sendRedirect("/oauth2/authorization/${SecurityConfig.SSO_PROVIDER_KEY}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,9 @@ class SecurityConfig(
|
|||||||
private val sessionRegistry: SessionRegistry
|
private val sessionRegistry: SessionRegistry
|
||||||
) : VaadinWebSecurity() {
|
) : VaadinWebSecurity() {
|
||||||
|
|
||||||
private val ssoProviderKey: String = "oidc"
|
companion object {
|
||||||
|
const val SSO_PROVIDER_KEY = "oidc"
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun configure(http: HttpSecurity) {
|
override fun configure(http: HttpSecurity) {
|
||||||
@@ -56,14 +58,18 @@ class SecurityConfig(
|
|||||||
|
|
||||||
super.configure(http)
|
super.configure(http)
|
||||||
|
|
||||||
|
setLoginView(http, "/login", "/")
|
||||||
|
|
||||||
if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) {
|
if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) {
|
||||||
setOAuth2LoginPage(http, "/oauth2/authorization/$ssoProviderKey")
|
|
||||||
// Use custom success handler to handle user registration
|
// Use custom success handler to handle user registration
|
||||||
http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) }
|
http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) }
|
||||||
// Prevent unnecessary redirects
|
// Prevent unnecessary redirects
|
||||||
http.logout { logout -> logout.logoutSuccessHandler((HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) }
|
http.logout { logout -> logout.logoutSuccessHandler((HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) }
|
||||||
} else {
|
|
||||||
setLoginView(http, "/login", "/")
|
// Custom authentication entry point to support SSO and direct login
|
||||||
|
http.exceptionHandling { exceptionHandling ->
|
||||||
|
exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +85,7 @@ class SecurityConfig(
|
|||||||
@Bean
|
@Bean
|
||||||
@Conditional(SsoEnabledCondition::class)
|
@Conditional(SsoEnabledCondition::class)
|
||||||
fun clientRegistrationRepository(): ClientRegistrationRepository? {
|
fun clientRegistrationRepository(): ClientRegistrationRepository? {
|
||||||
val clientRegistration = ClientRegistration.withRegistrationId(ssoProviderKey)
|
val clientRegistration = ClientRegistration.withRegistrationId(SSO_PROVIDER_KEY)
|
||||||
.clientId(config.get(ConfigProperties.SSO.OIDC.ClientId))
|
.clientId(config.get(ConfigProperties.SSO.OIDC.ClientId))
|
||||||
.clientSecret(config.get(ConfigProperties.SSO.OIDC.ClientSecret))
|
.clientSecret(config.get(ConfigProperties.SSO.OIDC.ClientSecret))
|
||||||
.scope("openid", "profile", "email")
|
.scope("openid", "profile", "email")
|
||||||
|
|||||||
+8
-6
@@ -1,12 +1,12 @@
|
|||||||
package org.gameyfin.app.core.security
|
package org.gameyfin.app.core.security
|
||||||
|
|
||||||
import org.gameyfin.app.config.ConfigService
|
|
||||||
import org.gameyfin.app.users.UserService
|
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import org.gameyfin.app.config.ConfigProperties
|
import org.gameyfin.app.config.ConfigProperties
|
||||||
|
import org.gameyfin.app.config.ConfigService
|
||||||
import org.gameyfin.app.config.MatchUsersBy
|
import org.gameyfin.app.config.MatchUsersBy
|
||||||
import org.gameyfin.app.users.RoleService
|
import org.gameyfin.app.users.RoleService
|
||||||
|
import org.gameyfin.app.users.UserService
|
||||||
import org.gameyfin.app.users.entities.User
|
import org.gameyfin.app.users.entities.User
|
||||||
import org.springframework.security.core.Authentication
|
import org.springframework.security.core.Authentication
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser
|
||||||
@@ -42,11 +42,13 @@ class SsoAuthenticationSuccessHandler(
|
|||||||
|
|
||||||
// User could not be found in the database
|
// User could not be found in the database
|
||||||
if (matchedUser == null) {
|
if (matchedUser == null) {
|
||||||
|
// TODO: User registration is currently forced, but this should be configurable.
|
||||||
|
// However, this causes conflict with user preferences and game entities (since both reference the user entity)
|
||||||
// Check if new user registration is enabled
|
// Check if new user registration is enabled
|
||||||
if (config.get(ConfigProperties.SSO.OIDC.AutoRegisterNewUsers) == false) {
|
//if (config.get(ConfigProperties.SSO.OIDC.AutoRegisterNewUsers) == false) {
|
||||||
response.sendRedirect("/")
|
// response.sendRedirect("/")
|
||||||
return
|
// return
|
||||||
}
|
//
|
||||||
|
|
||||||
// Register as new user
|
// Register as new user
|
||||||
matchedUser = User(oidcUser)
|
matchedUser = User(oidcUser)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.gameyfin.pluginapi.gamemetadata.*
|
|||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.core.userdetails.UserDetails
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
@@ -125,8 +126,12 @@ class GameService(
|
|||||||
val existingGame = gameRepository.findByIdOrNull(gameUpdateDto.id)
|
val existingGame = gameRepository.findByIdOrNull(gameUpdateDto.id)
|
||||||
?: throw IllegalArgumentException("Game with ID $gameUpdateDto.id not found")
|
?: throw IllegalArgumentException("Game with ID $gameUpdateDto.id not found")
|
||||||
|
|
||||||
val userDetails = SecurityContextHolder.getContext().authentication.principal as UserDetails
|
val userDetails = SecurityContextHolder.getContext().authentication.principal
|
||||||
val user = userService.getByUsernameNonNull(userDetails.username)
|
val user = when (userDetails) {
|
||||||
|
is UserDetails -> userService.getByUsernameNonNull(userDetails.username)
|
||||||
|
is OidcUser -> userService.getByUsernameNonNull(userDetails.preferredUsername)
|
||||||
|
else -> throw IllegalStateException("Unkown user type: ${userDetails::class.java.name}")
|
||||||
|
}
|
||||||
|
|
||||||
// Update only non-null fields
|
// Update only non-null fields
|
||||||
gameUpdateDto.title?.let {
|
gameUpdateDto.title?.let {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package org.gameyfin.app.users
|
package org.gameyfin.app.users
|
||||||
|
|
||||||
import com.vaadin.hilla.Endpoint
|
import com.vaadin.hilla.Endpoint
|
||||||
import org.gameyfin.app.users.dto.UserUpdateDto
|
|
||||||
import org.gameyfin.app.users.enums.RoleAssignmentResult
|
|
||||||
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.dto.UserInfoDto
|
import org.gameyfin.app.users.dto.UserInfoDto
|
||||||
|
import org.gameyfin.app.users.dto.UserUpdateDto
|
||||||
|
import org.gameyfin.app.users.enums.RoleAssignmentResult
|
||||||
import org.springframework.security.core.Authentication
|
import org.springframework.security.core.Authentication
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
|
||||||
@@ -23,8 +23,7 @@ class UserEndpoint(
|
|||||||
|
|
||||||
@PermitAll
|
@PermitAll
|
||||||
fun getUserInfo(): UserInfoDto {
|
fun getUserInfo(): UserInfoDto {
|
||||||
val auth: Authentication = SecurityContextHolder.getContext().authentication
|
return userService.getUserInfo()
|
||||||
return userService.getUserInfo(auth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PermitAll
|
@PermitAll
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
package org.gameyfin.app.users
|
package org.gameyfin.app.users
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.gameyfin.app.config.ConfigProperties
|
||||||
import org.gameyfin.app.config.ConfigService
|
import org.gameyfin.app.config.ConfigService
|
||||||
|
import org.gameyfin.app.core.Role
|
||||||
|
import org.gameyfin.app.core.Utils
|
||||||
|
import org.gameyfin.app.core.events.*
|
||||||
import org.gameyfin.app.games.entities.Image
|
import org.gameyfin.app.games.entities.Image
|
||||||
|
import org.gameyfin.app.media.ImageService
|
||||||
|
import org.gameyfin.app.users.dto.UserInfoDto
|
||||||
|
import org.gameyfin.app.users.dto.UserRegistrationDto
|
||||||
import org.gameyfin.app.users.dto.UserUpdateDto
|
import org.gameyfin.app.users.dto.UserUpdateDto
|
||||||
import org.gameyfin.app.users.emailconfirmation.EmailConfirmationService
|
import org.gameyfin.app.users.emailconfirmation.EmailConfirmationService
|
||||||
import org.gameyfin.app.users.enums.RoleAssignmentResult
|
import org.gameyfin.app.users.enums.RoleAssignmentResult
|
||||||
import org.gameyfin.app.users.persistence.UserRepository
|
import org.gameyfin.app.users.persistence.UserRepository
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.gameyfin.app.config.ConfigProperties
|
|
||||||
import org.gameyfin.app.core.Role
|
|
||||||
import org.gameyfin.app.core.Utils
|
|
||||||
import org.gameyfin.app.core.events.AccountDeletedEvent
|
|
||||||
import org.gameyfin.app.core.events.AccountStatusChangedEvent
|
|
||||||
import org.gameyfin.app.core.events.EmailNeedsConfirmationEvent
|
|
||||||
import org.gameyfin.app.core.events.RegistrationAttemptWithExistingEmailEvent
|
|
||||||
import org.gameyfin.app.core.events.UserRegistrationWaitingForApprovalEvent
|
|
||||||
import org.gameyfin.app.media.ImageService
|
|
||||||
import org.gameyfin.app.users.dto.UserInfoDto
|
|
||||||
import org.gameyfin.app.users.dto.UserRegistrationDto
|
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.security.core.Authentication
|
|
||||||
import org.springframework.security.core.GrantedAuthority
|
import org.springframework.security.core.GrantedAuthority
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
@@ -52,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,
|
||||||
@@ -66,7 +74,8 @@ class UserService(
|
|||||||
fun existsByUsername(username: String): Boolean = userRepository.existsByUsername(username)
|
fun existsByUsername(username: String): Boolean = userRepository.existsByUsername(username)
|
||||||
fun existsByEmail(email: String): Boolean = userRepository.existsByEmail(email)
|
fun existsByEmail(email: String): Boolean = userRepository.existsByEmail(email)
|
||||||
|
|
||||||
fun findByOidcProviderId(oidcProviderId: String): org.gameyfin.app.users.entities.User? = userRepository.findByOidcProviderId(oidcProviderId)
|
fun findByOidcProviderId(oidcProviderId: String): org.gameyfin.app.users.entities.User? =
|
||||||
|
userRepository.findByOidcProviderId(oidcProviderId)
|
||||||
|
|
||||||
fun getAllUsers(): List<UserInfoDto> {
|
fun getAllUsers(): List<UserInfoDto> {
|
||||||
return userRepository.findAll().map { u -> toUserInfo(u) }
|
return userRepository.findAll().map { u -> toUserInfo(u) }
|
||||||
@@ -84,7 +93,8 @@ class UserService(
|
|||||||
return userRepository.findByUsername(username) ?: throw UsernameNotFoundException("Unknown user '$username'")
|
return userRepository.findByUsername(username) ?: throw UsernameNotFoundException("Unknown user '$username'")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserInfo(auth: Authentication): UserInfoDto {
|
fun getUserInfo(): UserInfoDto {
|
||||||
|
val auth = SecurityContextHolder.getContext().authentication
|
||||||
val principal = auth.principal
|
val principal = auth.principal
|
||||||
|
|
||||||
if (principal is OidcUser) {
|
if (principal is OidcUser) {
|
||||||
@@ -99,6 +109,15 @@ class UserService(
|
|||||||
return toUserInfo(user)
|
return toUserInfo(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCurrentUser(): org.gameyfin.app.users.entities.User {
|
||||||
|
val auth = SecurityContextHolder.getContext().authentication
|
||||||
|
if (auth.principal is OidcUser) {
|
||||||
|
return userRepository.findByOidcProviderId((auth.principal as OidcUser).subject)
|
||||||
|
?: throw UsernameNotFoundException("OIDC user not found")
|
||||||
|
}
|
||||||
|
return getByUsernameNonNull(auth.name)
|
||||||
|
}
|
||||||
|
|
||||||
fun getAvatar(username: String): Image? {
|
fun getAvatar(username: String): Image? {
|
||||||
val user = getByUsernameNonNull(username)
|
val user = getByUsernameNonNull(username)
|
||||||
return user.avatar
|
return user.avatar
|
||||||
@@ -126,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +197,10 @@ class UserService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerUserFromInvitation(registration: UserRegistrationDto, email: String): org.gameyfin.app.users.entities.User {
|
fun registerUserFromInvitation(
|
||||||
|
registration: UserRegistrationDto,
|
||||||
|
email: String
|
||||||
|
): org.gameyfin.app.users.entities.User {
|
||||||
val user = org.gameyfin.app.users.entities.User(
|
val user = org.gameyfin.app.users.entities.User(
|
||||||
username = registration.username,
|
username = registration.username,
|
||||||
password = passwordEncoder.encode(registration.password),
|
password = passwordEncoder.encode(registration.password),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ server:
|
|||||||
servlet:
|
servlet:
|
||||||
session:
|
session:
|
||||||
tracking-modes: cookie
|
tracking-modes: cookie
|
||||||
|
forward-headers-strategy: framework
|
||||||
|
|
||||||
management:
|
management:
|
||||||
server:
|
server:
|
||||||
|
|||||||
Reference in New Issue
Block a user