diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index cb1d51ae4..7b0fb7e52 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2") api("org.springframework.boot:spring-boot-configuration-processor") api("org.springframework.boot:spring-boot-starter-data-jpa") + api("org.springframework.session:spring-session-jdbc") api("org.springframework.boot:spring-boot-starter-oauth2-client") api("org.springframework.boot:spring-boot-starter-security") api("org.springframework.boot:spring-boot-starter-web") diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/AuthschLoginController.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/AuthschLoginController.kt index eb2ac565a..5b5ef71fd 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/AuthschLoginController.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/AuthschLoginController.kt @@ -1,12 +1,10 @@ package hu.bme.sch.cmsch.component.login import hu.bme.sch.cmsch.component.app.ApplicationComponent -import hu.bme.sch.cmsch.component.token.SESSION_TOKEN_COLLECTOR_ATTRIBUTE import hu.bme.sch.cmsch.config.StartupPropertyConfig import hu.bme.sch.cmsch.service.JwtTokenProvider import hu.bme.sch.cmsch.util.getUserOrNull import jakarta.servlet.http.Cookie -import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.condition.ConditionalOnBean @@ -37,11 +35,7 @@ class AuthschLoginController( } @GetMapping("/control/post-login") - fun postLogin(request: HttpServletRequest, httpResponse: HttpServletResponse, auth: Authentication?) { - if (request.getSession(true).getAttribute(SESSION_TOKEN_COLLECTOR_ATTRIBUTE) != null) { - httpResponse.sendRedirect("/api/token-after-login") - return - } + fun postLogin(httpResponse: HttpServletResponse, auth: Authentication?) { httpResponse.sendRedirect( if (auth != null && auth.isAuthenticated) "/control/open-site" @@ -51,21 +45,17 @@ class AuthschLoginController( } @GetMapping("/control/login") - fun loginDefault(request: HttpServletRequest): String { + fun loginDefault(): String { return "redirect:${applicationComponent.siteUrl.getValue()}login" } @GetMapping("/control/logout") - fun logout(request: HttpServletRequest, auth: Authentication?, httpResponse: HttpServletResponse): String { + fun logout(auth: Authentication?, response: HttpServletResponse): String { log.info("Logging out from user {}", auth?.getUserOrNull()?.internalId ?: "n/a") try { - httpResponse.addCookie(createJwtCookie(null).apply { maxAge = 0 }) + createJwtCookies(null).forEach { response.addCookie(it) } SecurityContextHolder.getContext().authentication = null - val session = request.getSession(false) - session?.invalidate() - request.changeSessionId() - } catch (e: Exception) { // It should be logged out anyway } @@ -80,8 +70,7 @@ class AuthschLoginController( return "redirect:${applicationComponent.siteUrl.getValue()}?error=cannot-generate-jwt" } val jwtToken = jwtTokenProvider.createToken(auth.principal as CmschUser) - - response.addCookie(createJwtCookie(jwtToken)) + createJwtCookies(jwtToken).forEach { response.addCookie(it) } return "redirect:${applicationComponent.siteUrl.getValue()}" } @@ -94,7 +83,7 @@ class AuthschLoginController( if (auth == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build() - response.addCookie(createJwtCookie(jwtTokenProvider.refreshToken(auth))) + createJwtCookies(jwtTokenProvider.refreshToken(auth)).forEach { response.addCookie(it) } return ResponseEntity.ok().build() } @@ -103,13 +92,22 @@ class AuthschLoginController( return URI(url).host } - private fun createJwtCookie(value: String?): Cookie { - return Cookie("jwt", value).apply { - isHttpOnly = true - path = "/" - maxAge = startupPropertyConfig.sessionValidityInMilliseconds.toInt() / 1000 - secure = true - domain = getDomainFromUrl(applicationComponent.siteUrl.getValue()) - } + private fun createJwtCookies(value: String?): List { + return listOf( + Cookie("jwt", value).apply { + isHttpOnly = true + path = "/" + maxAge = startupPropertyConfig.sessionValidityInMilliseconds.toInt() / 1000 + secure = true + domain = getDomainFromUrl(applicationComponent.siteUrl.getValue()) + }, + Cookie("jwt", value).apply { + isHttpOnly = true + path = "/" + maxAge = startupPropertyConfig.sessionValidityInMilliseconds.toInt() / 1000 + secure = true + domain = getDomainFromUrl(applicationComponent.adminSiteUrl.getValue()) + }, + ) } } diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/SessionFilterConfigurer.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/SessionFilterConfigurer.kt deleted file mode 100644 index 4106ff33c..000000000 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/SessionFilterConfigurer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package hu.bme.sch.cmsch.component.login - -import hu.bme.sch.cmsch.config.StartupPropertyConfig -import org.springframework.security.config.annotation.SecurityConfigurerAdapter -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.web.DefaultSecurityFilterChain -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - -class SessionFilterConfigurer( - private val startupPropertyConfig: StartupPropertyConfig -) : SecurityConfigurerAdapter() { - - override fun configure(http: HttpSecurity) { - http.addFilterAfter( - SessionIncreaseFilter(startupPropertyConfig), - UsernamePasswordAuthenticationFilter::class.java - ) - } - -} - diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/SessionIncreaseFilter.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/SessionIncreaseFilter.kt deleted file mode 100644 index 3b26b6a82..000000000 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/SessionIncreaseFilter.kt +++ /dev/null @@ -1,41 +0,0 @@ -package hu.bme.sch.cmsch.component.login - -import hu.bme.sch.cmsch.config.StartupPropertyConfig -import hu.bme.sch.cmsch.service.ControlPermissions -import hu.bme.sch.cmsch.util.getUserOrNull -import org.slf4j.LoggerFactory -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.web.filter.GenericFilterBean -import java.io.IOException -import jakarta.servlet.FilterChain -import jakarta.servlet.ServletException -import jakarta.servlet.ServletRequest -import jakarta.servlet.ServletResponse -import jakarta.servlet.http.HttpServletRequest - -class SessionIncreaseFilter( - private val startupPropertyConfig: StartupPropertyConfig -) : GenericFilterBean() { - - private val log = LoggerFactory.getLogger(javaClass) - - @Throws(IOException::class, ServletException::class) - override fun doFilter(servletRequest: ServletRequest, response: ServletResponse, filterChain: FilterChain) { - val request = servletRequest as HttpServletRequest - if (request.servletPath.startsWith("/admin/")) { - val session = request.getSession(true) - val user = SecurityContextHolder.getContext()?.authentication?.getUserOrNull() - if (user?.let { ControlPermissions.PERMISSION_INCREASED_SESSION_DURATION.validate(it) } == true) { - log.debug( - "Increasing session time for user {} is {}", - user.userName, - startupPropertyConfig.increasedSessionTime - ) - session.maxInactiveInterval = startupPropertyConfig.increasedSessionTime - } - } - - filterChain.doFilter(servletRequest, response) - } - -} diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/token/TokenApiController.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/token/TokenApiController.kt index 6e4e04f59..f116a7ef9 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/token/TokenApiController.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/token/TokenApiController.kt @@ -17,8 +17,6 @@ import org.springframework.web.bind.annotation.* import java.net.URLEncoder import java.nio.charset.StandardCharsets -const val SESSION_TOKEN_COLLECTOR_ATTRIBUTE = "TOKEN_COLLECTOR_ATTRIBUTE" - @Controller @RequestMapping("/api") @ConditionalOnBean(TokenComponent::class) @@ -56,23 +54,11 @@ class TokenApiController( } } - @GetMapping("/token-after-login") - fun submitTokenAfterLogin(request: HttpServletRequest, auth: Authentication): String { - val token = request.getSession(true).getAttribute(SESSION_TOKEN_COLLECTOR_ATTRIBUTE)?.toString() - request.getSession(true).setAttribute(SESSION_TOKEN_COLLECTOR_ATTRIBUTE, null) - return if (token == null) { - "redirect:${applicationComponent.siteUrl.getValue()}?error=failed-to-redeem" - } else { - collectToken(auth, token) - } - } - @GetMapping("/qr/{token}") - fun readQrManually(@PathVariable token: String, request: HttpServletRequest, auth: Authentication?): String { + fun readQrManually(@PathVariable token: String, auth: Authentication?): String { return try { val user = auth?.getUserOrNull() if (user == null) { - request.getSession(true).setAttribute(SESSION_TOKEN_COLLECTOR_ATTRIBUTE, token) "redirect:${applicationComponent.siteUrl.getValue()}login" } else { collectToken(auth, token) diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/config/SecurityConfig.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/config/SecurityConfig.kt index 1e5fb7ef2..6512a3276 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/config/SecurityConfig.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/config/SecurityConfig.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper import hu.bme.sch.cmsch.component.countdown.CountdownFilterConfigurer import hu.bme.sch.cmsch.component.login.LoginComponent import hu.bme.sch.cmsch.component.login.LoginService -import hu.bme.sch.cmsch.component.login.SessionFilterConfigurer import hu.bme.sch.cmsch.component.login.authsch.CmschAuthschUser import hu.bme.sch.cmsch.component.login.authsch.ProfileResponse import hu.bme.sch.cmsch.component.login.google.CmschGoogleUser @@ -22,11 +21,15 @@ import org.springframework.context.annotation.Configuration import org.springframework.core.Ordered import org.springframework.http.HttpHeaders import org.springframework.http.MediaType +import org.springframework.jdbc.core.JdbcTemplate import org.springframework.retry.annotation.EnableRetry import org.springframework.security.config.Customizer import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest @@ -34,11 +37,13 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser import org.springframework.security.oauth2.core.user.DefaultOAuth2User import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher +import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession import org.springframework.web.reactive.function.client.WebClient import java.util.* -@EnableWebSecurity @Configuration +@EnableJdbcHttpSession +@EnableWebSecurity @EnableRetry(order = Ordered.HIGHEST_PRECEDENCE) @ConditionalOnBean(LoginComponent::class) open class SecurityConfig( @@ -48,7 +53,6 @@ open class SecurityConfig( private val countdownConfigurer: Optional, private val authschLoginService: LoginService, private val loginComponent: LoginComponent, - private val startupPropertyConfig: StartupPropertyConfig, @Value("\${custom.keycloak.base-url:http://localhost:8081/auth/realms/master}") private val keycloakBaseUrl: String, private val auditLogService: AuditLogService ) { @@ -67,8 +71,18 @@ open class SecurityConfig( .defaultHeader(HttpHeaders.USER_AGENT, "AuthSchKotlinAPI") .build() + @Bean - fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + fun authorizedClientService( + jdbcTemplate: JdbcTemplate, + clientRegistrationRepository: ClientRegistrationRepository + ): OAuth2AuthorizedClientService = JdbcOAuth2AuthorizedClientService(jdbcTemplate, clientRegistrationRepository) + + @Bean + fun securityFilterChain( + http: HttpSecurity, + authorizedClientService: OAuth2AuthorizedClientService + ): SecurityFilterChain { http.authorizeHttpRequests { it.requestMatchers( antMatcher("/"), @@ -131,7 +145,6 @@ open class SecurityConfig( RoleType.ADMIN.name, RoleType.SUPERUSER.name ) - it.requestMatchers( antMatcher("/admin/**"), antMatcher("/cdn/**") @@ -144,16 +157,18 @@ open class SecurityConfig( http.formLogin { it.disable() } http.exceptionHandling { it.accessDeniedPage("/403") } http.with(JwtConfigurer(jwtTokenProvider), Customizer.withDefaults()) - http.with(SessionFilterConfigurer(startupPropertyConfig), Customizer.withDefaults()) http.oauth2Login { oauth2 -> - oauth2.loginPage("/oauth2/authorization") + oauth2 + .loginPage("/oauth2/authorization") .authorizationEndpoint { it.authorizationRequestResolver( CustomAuthorizationRequestResolver( clientRegistrationRepository, "/oauth2/authorization", loginComponent ) ) - }.userInfoEndpoint { userInfo -> + } + .authorizedClientService(authorizedClientService) + .userInfoEndpoint { userInfo -> userInfo .oidcUserService { if (it.clientRegistration.clientId.contains("google")) { @@ -163,7 +178,8 @@ open class SecurityConfig( } } .userService { resolveAuthschUser(it) } - }.defaultSuccessUrl("/control/post-login") + } + .defaultSuccessUrl("/control/post-login") } countdownConfigurer.ifPresent { http.with(it, Customizer.withDefaults()) } http.csrf { @@ -212,7 +228,7 @@ open class SecurityConfig( private fun resolveKeycloakUser(request: OidcUserRequest): DefaultOidcUser { val decodedPayload = String(Base64.getDecoder().decode(request.accessToken.tokenValue.split(".")[1])) val profile: KeycloakUserInfoResponse = objectMapper.readerFor(KeycloakUserInfoResponse::class.java) - .readValue(decodedPayload) + .readValue(decodedPayload) val userEntity = authschLoginService.fetchKeycloakUserEntity(profile) auditLogService.login(userEntity, "keycloak user login g:${userEntity.group} r:${userEntity.role}") diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/config/StartupPropertyConfig.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/config/StartupPropertyConfig.kt index 4a0d59fc9..0a6c2f9dc 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/config/StartupPropertyConfig.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/config/StartupPropertyConfig.kt @@ -33,9 +33,6 @@ data class StartupPropertyConfig @ConstructorBinding constructor( val challengeOwnershipMode: OwnershipType, val raceOwnershipMode: OwnershipType, - // Increased session - val increasedSessionTime: Int, - // Microservice val masterRole: Boolean, val riddleMicroserviceEnabled: Boolean, diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/controller/dashboard/InstanceInfoDashboard.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/controller/dashboard/InstanceInfoDashboard.kt index 0b627c7ab..1c4149f34 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/controller/dashboard/InstanceInfoDashboard.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/controller/dashboard/InstanceInfoDashboard.kt @@ -87,7 +87,6 @@ class InstanceInfoDashboard( listOf("Time zone id", startupPropertyConfig.zoneId), listOf("Mailgun token length", startupPropertyConfig.mailgunToken.length.toString()), listOf("Session validity (ms)", startupPropertyConfig.sessionValidityInMilliseconds.toString()), - listOf("Increased session time (ms)", startupPropertyConfig.increasedSessionTime.toString()), listOf("Profile QR enabled", startupPropertyConfig.profileQrEnabled.toString()), listOf("Profile QR prefix", startupPropertyConfig.profileQrPrefix), listOf("Profile generation target", startupPropertyConfig.profileGenerationTarget), diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/jwt/JwtTokenFilter.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/jwt/JwtTokenFilter.kt index e7a3637b6..7919655ff 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/jwt/JwtTokenFilter.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/jwt/JwtTokenFilter.kt @@ -1,16 +1,16 @@ package hu.bme.sch.cmsch.jwt import hu.bme.sch.cmsch.service.JwtTokenProvider -import org.slf4j.LoggerFactory -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.web.filter.GenericFilterBean -import java.io.IOException import jakarta.servlet.FilterChain import jakarta.servlet.ServletException import jakarta.servlet.ServletRequest import jakarta.servlet.ServletResponse import jakarta.servlet.http.HttpServletRequest +import org.slf4j.LoggerFactory +import org.springframework.security.core.Authentication +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.filter.GenericFilterBean +import java.io.IOException class JwtTokenFilter( @@ -22,17 +22,16 @@ class JwtTokenFilter( @Throws(IOException::class, ServletException::class) override fun doFilter(req: ServletRequest, res: ServletResponse, filterChain: FilterChain) { val httpRequest = req as HttpServletRequest - if (httpRequest.servletPath.startsWith("/api/")) { - val token: String? = jwtTokenProvider.resolveToken(httpRequest) - try { - if (token != null && jwtTokenProvider.validateToken(token)) { - val auth: Authentication = jwtTokenProvider.getAuthentication(token) - SecurityContextHolder.getContext().authentication = auth - } - } catch (e: Exception) { - log.warn("Invalid token: {} user cannot be resolved because: {}", token, e.message) + val token: String? = jwtTokenProvider.resolveToken(httpRequest) + try { + if (!token.isNullOrBlank() && jwtTokenProvider.validateToken(token)) { + val auth: Authentication = jwtTokenProvider.getAuthentication(token) + SecurityContextHolder.getContext().authentication = auth } + } catch (e: Exception) { + log.warn("Invalid token: {} user cannot be resolved because: {}", token, e.message) } + filterChain.doFilter(req, res) } diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/model/Oauth2AuthorizedClient.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/Oauth2AuthorizedClient.kt new file mode 100644 index 000000000..e3ab6cb18 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/Oauth2AuthorizedClient.kt @@ -0,0 +1,52 @@ +package hu.bme.sch.cmsch.model + +import jakarta.persistence.* +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.annotations.ColumnDefault +import java.time.Instant + +/** + * Created for JdbcOAuth2AuthorizedClientService + */ +@Entity +@Table(name = "OAUTH2_AUTHORIZED_CLIENT") +open class Oauth2AuthorizedClient { + @EmbeddedId + open var id: Oauth2AuthorizedClientId? = null + + @Size(max = 100) + @NotNull + @Column(name = "ACCESS_TOKEN_TYPE", nullable = false, length = 100) + open var accessTokenType: String? = null + + @NotNull + @Column(name = "ACCESS_TOKEN_ISSUED_AT", nullable = false) + open var accessTokenIssuedAt: Instant? = null + + @NotNull + @Column(name = "ACCESS_TOKEN_EXPIRES_AT", nullable = false) + open var accessTokenExpiresAt: Instant? = null + + @Size(max = 1000) + @ColumnDefault("NULL") + @Column(name = "ACCESS_TOKEN_SCOPES", length = 1000) + open var accessTokenScopes: String? = null + + @ColumnDefault("NULL") + @Column(name = "REFRESH_TOKEN_ISSUED_AT") + open var refreshTokenIssuedAt: Instant? = null + + @NotNull + @ColumnDefault("CURRENT_TIMESTAMP") + @Column(name = "CREATED_AT", nullable = false) + open var createdAt: Instant? = null + + @Column(name = "ACCESS_TOKEN_VALUE", length = Integer.MAX_VALUE) + open var accessTokenValue: ByteArray? = null + + @ColumnDefault("NULL") + @Column(name = "REFRESH_TOKEN_VALUE", length = Integer.MAX_VALUE) + open var refreshTokenValue: ByteArray? = null + +} diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/model/Oauth2AuthorizedClientId.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/Oauth2AuthorizedClientId.kt new file mode 100644 index 000000000..83eefcb26 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/Oauth2AuthorizedClientId.kt @@ -0,0 +1,39 @@ +package hu.bme.sch.cmsch.model + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.Hibernate +import java.io.Serializable +import java.util.* + +/** + * Created for JdbcOAuth2AuthorizedClientService + */ +@Embeddable +open class Oauth2AuthorizedClientId : Serializable { + @Size(max = 100) + @NotNull + @Column(name = "CLIENT_REGISTRATION_ID", nullable = false, length = 100) + open var clientRegistrationId: String? = null + + @Size(max = 200) + @NotNull + @Column(name = "PRINCIPAL_NAME", nullable = false, length = 200) + open var principalName: String? = null + override fun hashCode(): Int = Objects.hash(clientRegistrationId, principalName) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false + + other as Oauth2AuthorizedClientId + + return clientRegistrationId == other.clientRegistrationId && + principalName == other.principalName + } + + companion object { + private const val serialVersionUID = 0L + } +} diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSession.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSession.kt new file mode 100644 index 000000000..47c715a54 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSession.kt @@ -0,0 +1,42 @@ +package hu.bme.sch.cmsch.model + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size + +@Entity +@Table(name = "SPRING_SESSION") +open class SpringSession { + @Id + @Size(max = 36) + @Column(name = "PRIMARY_ID", nullable = false, length = 36) + open var primaryId: String? = null + + @Size(max = 36) + @NotNull + @Column(name = "SESSION_ID", nullable = false, length = 36) + open var sessionId: String? = null + + @NotNull + @Column(name = "CREATION_TIME", nullable = false) + open var creationTime: Long? = null + + @NotNull + @Column(name = "LAST_ACCESS_TIME", nullable = false) + open var lastAccessTime: Long? = null + + @NotNull + @Column(name = "MAX_INACTIVE_INTERVAL", nullable = false) + open var maxInactiveInterval: Int? = null + + @NotNull + @Column(name = "EXPIRY_TIME", nullable = false) + open var expiryTime: Long? = null + + @Size(max = 100) + @Column(name = "PRINCIPAL_NAME", length = 100) + open var principalName: String? = null +} diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSessionAttribute.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSessionAttribute.kt new file mode 100644 index 000000000..837abdf60 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSessionAttribute.kt @@ -0,0 +1,23 @@ +package hu.bme.sch.cmsch.model + +import jakarta.persistence.* +import jakarta.validation.constraints.NotNull +import org.hibernate.annotations.OnDelete +import org.hibernate.annotations.OnDeleteAction + +@Entity +@Table(name = "SPRING_SESSION_ATTRIBUTES") +open class SpringSessionAttribute { + @EmbeddedId + open var id: SpringSessionAttributeId? = null + + @MapsId("sessionPrimaryId") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "SESSION_PRIMARY_ID", nullable = false) + open var sessionPrimary: SpringSession? = null + + @NotNull + @Column(name = "ATTRIBUTE_BYTES", nullable = false, length = Integer.MAX_VALUE) + open var attributeBytes: ByteArray? = null +} diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSessionAttributeId.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSessionAttributeId.kt new file mode 100644 index 000000000..afb7d7ed6 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/model/SpringSessionAttributeId.kt @@ -0,0 +1,36 @@ +package hu.bme.sch.cmsch.model + +import jakarta.persistence.Column +import jakarta.persistence.Embeddable +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import org.hibernate.Hibernate +import java.io.Serializable +import java.util.* + +@Embeddable +open class SpringSessionAttributeId : Serializable { + @Size(max = 36) + @NotNull + @Column(name = "SESSION_PRIMARY_ID", nullable = false, length = 36) + open var sessionPrimaryId: String? = null + + @Size(max = 200) + @NotNull + @Column(name = "ATTRIBUTE_NAME", nullable = false, length = 200) + open var attributeName: String? = null + override fun hashCode(): Int = Objects.hash(sessionPrimaryId, attributeName) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false + + other as SpringSessionAttributeId + + return sessionPrimaryId == other.sessionPrimaryId && + attributeName == other.attributeName + } + + companion object { + private const val serialVersionUID = 0L + } +}