DataTransferPublicSession.ktAnnotationen: param, Suppress, Service, JsonIgnore, Autowired
Klassen: DataTransferPublicSession, TransferAreaData, CheckAccessResult
Funktionen (9): checkLogin, login, checkDataBaseEntry, register, unregister, logout, isOwnerOfFile, registerFileAsOwner, getSessionMap
Eigenschaften (28): id, accessToken, password, userInfo, ownedFiles, dataTransferArea, failedAccessMessage, dataTransferAreaDao, data, area, errorMessage, loginProtection, clientIpAddress, offset, seconds, numberOfFailedAttempts, loginResultStatus, dbo, errorMessage, map, map, id, data, map, data...
Importe: 12 Pakete
Paket: org.projectforge.plugins.datatransfer.restPublic
package org.projectforge.plugins.datatransfer.restPublic
import com.fasterxml.jackson.annotation.JsonIgnore
import mu.KotlinLogging
import org.projectforge.business.login.LoginProtection
import org.projectforge.business.login.LoginResultStatus
import org.projectforge.framework.ToStringUtil
import org.projectforge.framework.i18n.translate
import org.projectforge.plugins.datatransfer.DataTransferAreaDO
import org.projectforge.plugins.datatransfer.DataTransferAreaDao
import org.projectforge.rest.config.RestUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import jakarta.servlet.http.HttpServletRequest
private val log = KotlinLogging.logger {}
/**
* Eine minimale Sitzungsverwaltung, um lästige erneute Anmeldungen für externe Benutzer des Datentransfer-Tools zu vermeiden.
*/
@Service
class DataTransferPublicSession {
internal class TransferAreaData(
var id: Long,
var accessToken: String,
@JsonIgnore var password: String?,
var userInfo: String?,
var ownedFiles: MutableList<String> = mutableListOf()
)
internal class CheckAccessResult(
val dataTransferArea: DataTransferAreaDO? = null,
val failedAccessMessage: String? = null
)
@Autowired
private lateinit var dataTransferAreaDao: DataTransferAreaDao
/**
* Prüft, ob der Benutzer einen gültigen Eintrag (accessToken/Passwort) in seiner Sitzung hat.
* @param areaId Suchbereich nach ID in der Benutzersitzung.
* @param accessToken Suchbereich nach accessToken in der Benutzersitzung.
*/
internal fun checkLogin(
request: HttpServletRequest,
areaId: Long? = null,
accessToken: String? = null
): Pair<DataTransferAreaDO, TransferAreaData>? {
check(areaId != null || !accessToken.isNullOrBlank())
val data = if (areaId != null) {
getSessionMap(request)?.get(areaId)
} else {
getSessionMap(request)?.entries?.find { it.value.accessToken == accessToken }?.value
} ?: return null
val area = dataTransferAreaDao.getAnonymousArea(data.accessToken) ?: return null
val errorMessage = checkDataBaseEntry(request, area, data.id, data.accessToken, data.password, data.userInfo)
if (errorMessage != null) {
unregister(request, data.id) // Abmelden, erneute Anmeldung erzwingen.
return null
}
log.info {
"Externe Benutzerinfo aus Sitzung wiederhergestellt: ${ToStringUtil.toJsonString(data)}, ip=${
RestUtils.getClientIp(
request
)
}"
}
return Pair(area, data)
}
/**
* Versucht, den Benutzer anzumelden. Verwendet LoginProtection. Prüft nicht, ob der Benutzer bereits angemeldet ist.
* Die Sitzungs-ID wird geändert (Session Fixation), aber alle zuvor angemeldeten Bereiche werden in die neue Sitzung übernommen.
*/
internal fun login(
request: HttpServletRequest,
accessToken: String?,
password: String?,
userInfo: String?
): CheckAccessResult {
if (accessToken == null || password == null) {
return CheckAccessResult(failedAccessMessage = LoginResultStatus.FAILED.localizedMessage)
}
val loginProtection = LoginProtection.instance()
val clientIpAddress = RestUtils.getClientIp(request)
val offset = loginProtection.getFailedLoginTimeOffsetIfExists(accessToken, clientIpAddress)
if (offset > 0) {
// Zeitversatz besteht noch. Anmeldeversuch ignorieren.
val seconds = (offset / 1000).toString()
log.warn("Das Konto für '${accessToken}', ip=$clientIpAddress, userInfo='$userInfo' ist für $seconds Sekunden aufgrund fehlgeschlagener Anmeldeversuche gesperrt. Bitte versuchen Sie es später erneut.")
val numberOfFailedAttempts = loginProtection.getNumberOfFailedLoginAttempts(accessToken, clientIpAddress)
val loginResultStatus = LoginResultStatus.LOGIN_TIME_OFFSET
loginResultStatus.setMsgParams(
seconds,
numberOfFailedAttempts.toString()
)
return CheckAccessResult(failedAccessMessage = loginResultStatus.localizedMessage)
}
val dbo = dataTransferAreaDao.getAnonymousArea(accessToken)
if (dbo == null) {
log.warn { "Datentransferbereich mit externalAccessToken '$accessToken' nicht gefunden. Angefragt von ip=$clientIpAddress, userInfo='$userInfo'." }
loginProtection.incrementFailedLoginTimeOffset(accessToken, clientIpAddress)
return CheckAccessResult(failedAccessMessage = LoginResultStatus.FAILED.localizedMessage)
}
val errorMessage = checkDataBaseEntry(request, dbo, dbo.id!!, accessToken, password, userInfo)
if (errorMessage != null) {
loginProtection.incrementFailedLoginTimeOffset(accessToken, clientIpAddress)
return CheckAccessResult(failedAccessMessage = errorMessage)
}
// Erfolgreich angemeldet:
loginProtection.clearLoginTimeOffset(accessToken, null, clientIpAddress)
log.info { "Datentransferbereich mit externalAccessToken '$accessToken': Anmeldung erfolgreich von ip=$clientIpAddress, userInfo='$userInfo'." }
// Session Fixation: JSESSIONID nach der Anmeldung ändern (aus Sicherheitsgründen / XSS-Angriff auf die Anmeldeseite)
request.getSession(false)?.let { session ->
if (!session.isNew) {
val map = getSessionMap(request)
// ... (gekürzt, insgesamt 259 Zeilen)
868d6abb7 2025 -> 2026 63081666f Quellcode-Dateiköpfe: 2024 -> 2025. 4c04cfd65 MAJOR-CHANGE! Migration von Integer-IDs zu Long-IDs (einschließlich Fremdschlüsseln usw.) 77bade6df javax.* -> jakarta.* b6092df09 Copyright 2023 -> 2024