From ded340fe4fbbce3f208cc6fc1a0740a63d19269e Mon Sep 17 00:00:00 2001 From: ztefanie Date: Tue, 3 Dec 2024 11:06:57 +0100 Subject: [PATCH 1/6] 1790: Protect alreadyVerified applications endpoint --- .../webservice/utils/ApplicationHandler.kt | 6 ++-- .../backend/common/webservice/Utils.kt | 35 +++++++++++++++++++ .../userdata/webservice/UserImportHandler.kt | 20 ++--------- 3 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt index 47c855954..a537519a9 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt @@ -4,8 +4,9 @@ import app.ehrenamtskarte.backend.application.database.ApplicationEntity import app.ehrenamtskarte.backend.application.database.ApplicationVerificationEntity import app.ehrenamtskarte.backend.application.database.repos.ApplicationRepository import app.ehrenamtskarte.backend.application.webservice.schema.create.Application +import app.ehrenamtskarte.backend.auth.database.ApiTokenType import app.ehrenamtskarte.backend.common.webservice.GraphQLContext -import app.ehrenamtskarte.backend.exception.service.UnauthorizedException +import app.ehrenamtskarte.backend.common.webservice.Utils import app.ehrenamtskarte.backend.exception.webservice.exceptions.InvalidFileSizeException import app.ehrenamtskarte.backend.exception.webservice.exceptions.InvalidFileTypeException import app.ehrenamtskarte.backend.exception.webservice.exceptions.MailNotSentException @@ -95,7 +96,8 @@ class ApplicationHandler( return when { isAlreadyVerifiedList.all { it == false || it == null } -> false isAlreadyVerifiedList.all { it == true } -> { - throw UnauthorizedException() + Utils.authenticate(context.request, ApiTokenType.VERIFIED_APPLICATION) + return true } else -> throw BadRequestResponse("isAlreadyVerified must be the same for all entries") } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt new file mode 100644 index 000000000..ab05353a2 --- /dev/null +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt @@ -0,0 +1,35 @@ +package app.ehrenamtskarte.backend.common.webservice + +import app.ehrenamtskarte.backend.auth.database.ApiTokenEntity +import app.ehrenamtskarte.backend.auth.database.ApiTokenType +import app.ehrenamtskarte.backend.auth.database.PasswordCrypto +import app.ehrenamtskarte.backend.auth.database.repos.ApiTokensRepository +import app.ehrenamtskarte.backend.exception.service.ForbiddenException +import app.ehrenamtskarte.backend.exception.service.UnauthorizedException +import io.javalin.http.Context +import jakarta.servlet.http.HttpServletRequest +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDate + +class Utils { + companion object utils { + private fun authenticateToken(header: String?, neededType: ApiTokenType): ApiTokenEntity { + val authHeader = header?.takeIf { it.startsWith("Bearer ") } + ?: throw UnauthorizedException() + + val tokenHash = PasswordCrypto.hashWithSHA256(authHeader.substring(7).toByteArray()) + + return transaction { + ApiTokensRepository.findByTokenHash(tokenHash) + ?.takeIf { it.expirationDate > LocalDate.now() && it.type == neededType } + ?: throw ForbiddenException() + } + } + + fun authenticate(context: Context, neededType: ApiTokenType): ApiTokenEntity = + authenticateToken(context.header("Authorization"), neededType) + + fun authenticate(request: HttpServletRequest, neededType: ApiTokenType): ApiTokenEntity = + authenticateToken(request.getHeader("Authorization"), neededType) + } +} diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt index fb4f6b76e..abfedeac9 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt @@ -1,11 +1,9 @@ package app.ehrenamtskarte.backend.userdata.webservice -import app.ehrenamtskarte.backend.auth.database.ApiTokenEntity import app.ehrenamtskarte.backend.auth.database.ApiTokenType -import app.ehrenamtskarte.backend.auth.database.PasswordCrypto -import app.ehrenamtskarte.backend.auth.database.repos.ApiTokensRepository import app.ehrenamtskarte.backend.cards.Argon2IdHasher import app.ehrenamtskarte.backend.cards.database.repos.CardRepository +import app.ehrenamtskarte.backend.common.webservice.Utils import app.ehrenamtskarte.backend.config.BackendConfiguration import app.ehrenamtskarte.backend.exception.service.ForbiddenException import app.ehrenamtskarte.backend.exception.service.ProjectNotFoundException @@ -34,10 +32,7 @@ class UserImportHandler( fun handle(context: Context) { try { - val apiToken = authenticate(context) - if (apiToken.type != ApiTokenType.USER_IMPORT) { - throw ForbiddenException() - } + val apiToken = Utils.authenticate(context, ApiTokenType.USER_IMPORT) val project = transaction { ProjectEntity.find { Projects.id eq apiToken.projectId }.single() } val projectConfig = backendConfiguration.getProjectConfig(project.project) @@ -74,17 +69,6 @@ class UserImportHandler( } } - private fun authenticate(context: Context): ApiTokenEntity { - val authHeader = context.header("Authorization")?.takeIf { it.startsWith("Bearer ") } - ?: throw UnauthorizedException() - val tokenHash = PasswordCrypto.hashWithSHA256(authHeader.substring(7).toByteArray()) - - return transaction { - ApiTokensRepository.findByTokenHash(tokenHash)?.takeIf { it.expirationDate > LocalDate.now() } - ?: throw ForbiddenException() - } - } - private fun getCSVParser(reader: BufferedReader): CSVParser { return CSVParser( reader, From 62e1ae582e3e4304019196778fcb08d3e2970c3b Mon Sep 17 00:00:00 2001 From: ztefanie Date: Tue, 3 Dec 2024 12:09:10 +0100 Subject: [PATCH 2/6] 1625: Add verein360 logo to applications view --- administration/src/assets/verein360.svg | 28 ++++++++ .../applications/ApplicationCard.tsx | 72 ++++++++++++------- ...ator.tsx => PreVerifiedQuickIndicator.tsx} | 16 +++-- administration/src/util/translations/de.json | 1 + .../backend/common/webservice/Utils.kt | 2 +- 5 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 administration/src/assets/verein360.svg rename administration/src/bp-modules/applications/{JuleicaVerificationQuickIndicator.tsx => PreVerifiedQuickIndicator.tsx} (55%) diff --git a/administration/src/assets/verein360.svg b/administration/src/assets/verein360.svg new file mode 100644 index 000000000..7627da659 --- /dev/null +++ b/administration/src/assets/verein360.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/administration/src/bp-modules/applications/ApplicationCard.tsx b/administration/src/bp-modules/applications/ApplicationCard.tsx index 0698f2bfb..14a958f00 100644 --- a/administration/src/bp-modules/applications/ApplicationCard.tsx +++ b/administration/src/bp-modules/applications/ApplicationCard.tsx @@ -8,7 +8,7 @@ import { Icon, Section, SectionCard, - Tooltip, + Tooltip } from '@blueprintjs/core' import React, { ReactElement, memo, useContext, useMemo, useState } from 'react' import styled, { css } from 'styled-components' @@ -21,10 +21,10 @@ import getApiBaseUrl from '../../util/getApiBaseUrl' import { useAppToaster } from '../AppToaster' import { Application } from './ApplicationsOverview' import JsonFieldView, { JsonField, findValue } from './JsonFieldView' -import JuleicaVerificationQuickIndicator from './JuleicaVerificationQuickIndicator' import NoteDialogController from './NoteDialogController' import VerificationsQuickIndicator from './VerificationsQuickIndicator' import VerificationsView from './VerificationsView' +import PreVerifiedQuickIndicator, { PreVerifiedQuickIndicatorType } from './PreVerifiedQuickIndicator' export const printAwareCss = css` @media print { @@ -58,6 +58,7 @@ export const CollapseIcon = styled(Icon)` align-self: center; padding: 2px; ${printAwareCss}; + :hover { cursor: pointer; color: ${Colors.GRAY1}; @@ -113,14 +114,31 @@ const RightElement = ({ jsonField, application }: RightElementProps): ReactEleme return !!blueCardJuleicaEntitlement } + const isPreverified = (): boolean => { + const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField; + const workAtOrganizationsEntitlement = findValue(applicationDetails, 'blueCardWorkAtOrganizationsEntitlement', 'Array')?.value ?? []; + + const isAlreadyVerified = workAtOrganizationsEntitlement.some((entitlement: any) => + entitlement.value.some((organization: any) => + organization.name === 'isAlreadyVerified' && organization.value === true + ) + ) + return isAlreadyVerified + } + + let quickIndicator; + if (isJuleicaEntitlementType()) { + quickIndicator = ; + } else if (isPreverified()) { + quickIndicator = ; + } else { + quickIndicator = ; + } + return ( - {!!application.note && application.note.trim() && } - {isJuleicaEntitlementType() ? ( - - ) : ( - - )} + {!!application.note && application.note.trim() && } + {quickIndicator} ) } @@ -134,12 +152,12 @@ export type ApplicationCardProps = { } const ApplicationCard = ({ - application, - onDelete, - printApplicationById, - isSelectedForPrint, - onChange, -}: ApplicationCardProps) => { + application, + onDelete, + printApplicationById, + isSelectedForPrint, + onChange + }: ApplicationCardProps) => { const [isExpanded, setIsExpanded] = useState(false) const { createdDate: createdDateString, jsonValue, id, withdrawalDate, cardCreated } = application const jsonField: JsonField<'Array'> = JSON.parse(jsonValue) @@ -160,7 +178,7 @@ const ApplicationCard = ({ console.error('Delete operation returned false.') appToaster?.show({ intent: 'danger', message: 'Etwas ist schief gelaufen.' }) } - }, + } }) const createCardQuery = useMemo( @@ -187,7 +205,7 @@ const ApplicationCard = ({ } rightElement={} elevation={1} - icon={withdrawalDate ? : undefined} + icon={withdrawalDate ? : undefined} collapseProps={{ isOpen: isExpanded, onToggle: () => setIsExpanded(!isExpanded), keepChildrenMounted: true }} collapsible={!isSelectedForPrint} $hideInPrintMode={!isSelectedForPrint}> @@ -201,7 +219,7 @@ const ApplicationCard = ({ /> {!!withdrawalDate && ( - + Der Antrag wurde vom Antragsteller am {formatDateWithTimezone(withdrawalDate, config.timezone)}{' '} zurückgezogen.
Bitte löschen Sie den Antrag zeitnah. @@ -224,28 +242,28 @@ const ApplicationCard = ({ + content="Es existiert kein passendes Mapping, um aus diesem Antrag das Kartenformular vollständig auszufüllen."> + icon="id-number" + intent="primary"> {cardCreated ? 'Karte erneut erstellen' : 'Karte erstellen'} - setDeleteDialogOpen(true)} intent='danger' icon='trash'> + setDeleteDialogOpen(true)} intent="danger" icon="trash"> Antrag löschen - printApplicationById(id)} intent='none' icon='print'> + printApplicationById(id)} intent="none" icon="print"> PDF exportieren - setIsExpanded(!isExpanded)} style={{ marginLeft: 'auto' }} /> + setIsExpanded(!isExpanded)} style={{ marginLeft: 'auto' }} /> setDeleteDialogOpen(false)} diff --git a/administration/src/bp-modules/applications/JuleicaVerificationQuickIndicator.tsx b/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx similarity index 55% rename from administration/src/bp-modules/applications/JuleicaVerificationQuickIndicator.tsx rename to administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx index 53ad488f5..b5d161f57 100644 --- a/administration/src/bp-modules/applications/JuleicaVerificationQuickIndicator.tsx +++ b/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx @@ -2,10 +2,18 @@ import { Tooltip } from '@blueprintjs/core' import React, { memo } from 'react' import JuleicaLogo from '../../assets/juleica.svg' +import Verein360Logo from '../../assets/verein360.svg' import { UnFocusedDiv } from './VerificationsQuickIndicator' import { Indicator, VerificationStatus } from './VerificationsView' -const JuleicaVerificationQuickIndicator = memo(() => ( +export enum PreVerifiedQuickIndicatorType { + Juleica, + Verein360, +} + +const PreVerifiedQuickIndicator = memo(({type}: {type: PreVerifiedQuickIndicatorType}) => { + const logo = type == PreVerifiedQuickIndicatorType.Juleica ? JuleicaLogo : Verein360Logo + return ( @@ -16,9 +24,9 @@ const JuleicaVerificationQuickIndicator = memo(() => ( }> - juleica + {type.toString()} -)) +)}) -export default memo(JuleicaVerificationQuickIndicator) +export default memo(PreVerifiedQuickIndicator) diff --git a/administration/src/util/translations/de.json b/administration/src/util/translations/de.json index 33750b22a..00d97408a 100644 --- a/administration/src/util/translations/de.json +++ b/administration/src/util/translations/de.json @@ -41,6 +41,7 @@ "responsibility": "Tätigkeit", "amountOfWork": "Arbeitsstunden pro Woche (Durchschnitt)", "workSinceDate": "Tätig seit", + "isAlreadyVerified": "Die Angaben wurden bereits durch den zuständigen Verein bei Beantragung verifiziert", "payment": "Für diese ehrenamtliche Tätigkeit wurde eine Aufwandsentschädigung gewährt, die über den jährlichen Freibetrag hinaus geht (840 Euro Ehrenamtspauschale bzw. 3000 Euro Übungsleiterpauschale):", "certificate": "Tätigkeitsnachweis", "hasAcceptedPrivacyPolicy": "Ich erkläre mich damit einverstanden, dass meine Daten zum Zwecke der Antragsverarbeitung gespeichert werden und akzeptiere die Datenschutzerklärung", diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt index ab05353a2..3dabfa2bb 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt @@ -12,7 +12,7 @@ import org.jetbrains.exposed.sql.transactions.transaction import java.time.LocalDate class Utils { - companion object utils { + companion object Utils { private fun authenticateToken(header: String?, neededType: ApiTokenType): ApiTokenEntity { val authHeader = header?.takeIf { it.startsWith("Bearer ") } ?: throw UnauthorizedException() From 11528d4d3449e6bd36bc66f5b800f116bc22c684 Mon Sep 17 00:00:00 2001 From: ztefanie Date: Wed, 4 Dec 2024 10:19:55 +0100 Subject: [PATCH 3/6] 1790: Add checks for verein360 applications --- .../EakApplicationMutationService.kt | 3 +- .../webservice/utils/ApplicationHandler.kt | 53 +++++++++++++++++-- .../webservice/TokenAuthenticator.kt} | 6 +-- .../userdata/webservice/UserImportHandler.kt | 4 +- 4 files changed, 56 insertions(+), 10 deletions(-) rename backend/src/main/kotlin/app/ehrenamtskarte/backend/{common/webservice/Utils.kt => auth/webservice/TokenAuthenticator.kt} (92%) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt index 4c556eac5..430105535 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt @@ -41,9 +41,10 @@ class EakApplicationMutationService { if (isPreVerified) { applicationHandler.setApplicationVerificationToVerifiedNow(verificationEntities) + } else { + applicationHandler.sendApplicationMails(applicationEntity, verificationEntities, dataFetcherResultBuilder) } - applicationHandler.sendApplicationMails(applicationEntity, verificationEntities, dataFetcherResultBuilder) return dataFetcherResultBuilder.data(true).build() } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt index a537519a9..dfd581a44 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/utils/ApplicationHandler.kt @@ -4,9 +4,12 @@ import app.ehrenamtskarte.backend.application.database.ApplicationEntity import app.ehrenamtskarte.backend.application.database.ApplicationVerificationEntity import app.ehrenamtskarte.backend.application.database.repos.ApplicationRepository import app.ehrenamtskarte.backend.application.webservice.schema.create.Application +import app.ehrenamtskarte.backend.application.webservice.schema.create.ApplicationType +import app.ehrenamtskarte.backend.application.webservice.schema.create.BavariaCardType +import app.ehrenamtskarte.backend.application.webservice.schema.create.BlueCardEntitlementType import app.ehrenamtskarte.backend.auth.database.ApiTokenType +import app.ehrenamtskarte.backend.auth.webservice.TokenAuthenticator import app.ehrenamtskarte.backend.common.webservice.GraphQLContext -import app.ehrenamtskarte.backend.common.webservice.Utils import app.ehrenamtskarte.backend.exception.webservice.exceptions.InvalidFileSizeException import app.ehrenamtskarte.backend.exception.webservice.exceptions.InvalidFileTypeException import app.ehrenamtskarte.backend.exception.webservice.exceptions.MailNotSentException @@ -93,14 +96,56 @@ class ApplicationHandler( val isAlreadyVerifiedList = application.applicationDetails.blueCardEntitlement?.workAtOrganizationsEntitlement?.list?.map { it.isAlreadyVerified } ?: emptyList() - return when { + val allAlreadyVerifiedWithToken = when { isAlreadyVerifiedList.all { it == false || it == null } -> false isAlreadyVerifiedList.all { it == true } -> { - Utils.authenticate(context.request, ApiTokenType.VERIFIED_APPLICATION) - return true + TokenAuthenticator.authenticate(context.request, ApiTokenType.VERIFIED_APPLICATION) + true } + else -> throw BadRequestResponse("isAlreadyVerified must be the same for all entries") } + if (!allAlreadyVerifiedWithToken) return false + validateAllAttributesForPreVerifiedApplication() + return true + } + + private fun validateAllAttributesForPreVerifiedApplication() { + try { + val applicationDetails = application.applicationDetails + + require(applicationDetails.applicationType == ApplicationType.FIRST_APPLICATION) { + "Application type must be FIRST_APPLICATION if application is already verified" + } + require(applicationDetails.cardType == BavariaCardType.BLUE) { + "Card type must be BLUE if application is already verified" + } + require(applicationDetails.wantsDigitalCard) { + "Digital card must be true if application is already verified" + } + require(!applicationDetails.wantsPhysicalCard) { + "Physical card must be false if application is already verified" + } + val blueCardEntitlement = applicationDetails.blueCardEntitlement + ?: throw IllegalArgumentException("Blue card entitlement must be set if application is already verified") + + val workAtOrganizationsEntitlement = blueCardEntitlement.workAtOrganizationsEntitlement + ?: throw IllegalArgumentException("Work at organizations entitlement must be set if application is already verified") + + require(blueCardEntitlement.entitlementType == BlueCardEntitlementType.WORK_AT_ORGANIZATIONS) { + "Entitlement type must be WORK_AT_ORGANIZATIONS if application is already verified" + } + + val organizations = workAtOrganizationsEntitlement.list + require(!organizations.isNullOrEmpty()) { + "Work at organizations list cannot be empty if application is already verified" + } + require(organizations.all { it.organization.category.shortText == "Sport" }) { + "All organizations must be of category Sport if application is already verified" + } + } catch (e: IllegalArgumentException) { + throw BadRequestResponse(e.message!!) + } } fun setApplicationVerificationToVerifiedNow(verificationEntities: List) { diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/auth/webservice/TokenAuthenticator.kt similarity index 92% rename from backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt rename to backend/src/main/kotlin/app/ehrenamtskarte/backend/auth/webservice/TokenAuthenticator.kt index 3dabfa2bb..30b385807 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Utils.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/auth/webservice/TokenAuthenticator.kt @@ -1,4 +1,4 @@ -package app.ehrenamtskarte.backend.common.webservice +package app.ehrenamtskarte.backend.auth.webservice import app.ehrenamtskarte.backend.auth.database.ApiTokenEntity import app.ehrenamtskarte.backend.auth.database.ApiTokenType @@ -11,8 +11,8 @@ import jakarta.servlet.http.HttpServletRequest import org.jetbrains.exposed.sql.transactions.transaction import java.time.LocalDate -class Utils { - companion object Utils { +class TokenAuthenticator { + companion object TokenAuthenticator { private fun authenticateToken(header: String?, neededType: ApiTokenType): ApiTokenEntity { val authHeader = header?.takeIf { it.startsWith("Bearer ") } ?: throw UnauthorizedException() diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt index abfedeac9..812e9209b 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/webservice/UserImportHandler.kt @@ -1,9 +1,9 @@ package app.ehrenamtskarte.backend.userdata.webservice import app.ehrenamtskarte.backend.auth.database.ApiTokenType +import app.ehrenamtskarte.backend.auth.webservice.TokenAuthenticator import app.ehrenamtskarte.backend.cards.Argon2IdHasher import app.ehrenamtskarte.backend.cards.database.repos.CardRepository -import app.ehrenamtskarte.backend.common.webservice.Utils import app.ehrenamtskarte.backend.config.BackendConfiguration import app.ehrenamtskarte.backend.exception.service.ForbiddenException import app.ehrenamtskarte.backend.exception.service.ProjectNotFoundException @@ -32,7 +32,7 @@ class UserImportHandler( fun handle(context: Context) { try { - val apiToken = Utils.authenticate(context, ApiTokenType.USER_IMPORT) + val apiToken = TokenAuthenticator.authenticate(context, ApiTokenType.USER_IMPORT) val project = transaction { ProjectEntity.find { Projects.id eq apiToken.projectId }.single() } val projectConfig = backendConfiguration.getProjectConfig(project.project) From 98ddfa86224daa5c633bb18fc4f61db21091228d Mon Sep 17 00:00:00 2001 From: ztefanie Date: Wed, 4 Dec 2024 10:52:07 +0100 Subject: [PATCH 4/6] 1790: Fix formatting --- .../applications/ApplicationCard.tsx | 74 ++++++++++--------- .../PreVerifiedQuickIndicator.tsx | 33 +++++---- 2 files changed, 56 insertions(+), 51 deletions(-) diff --git a/administration/src/bp-modules/applications/ApplicationCard.tsx b/administration/src/bp-modules/applications/ApplicationCard.tsx index 14a958f00..0527e10a3 100644 --- a/administration/src/bp-modules/applications/ApplicationCard.tsx +++ b/administration/src/bp-modules/applications/ApplicationCard.tsx @@ -8,7 +8,7 @@ import { Icon, Section, SectionCard, - Tooltip + Tooltip, } from '@blueprintjs/core' import React, { ReactElement, memo, useContext, useMemo, useState } from 'react' import styled, { css } from 'styled-components' @@ -20,11 +20,11 @@ import formatDateWithTimezone from '../../util/formatDate' import getApiBaseUrl from '../../util/getApiBaseUrl' import { useAppToaster } from '../AppToaster' import { Application } from './ApplicationsOverview' -import JsonFieldView, { JsonField, findValue } from './JsonFieldView' +import JsonFieldView, { GeneralJsonField, JsonField, findValue } from './JsonFieldView' import NoteDialogController from './NoteDialogController' +import PreVerifiedQuickIndicator, { PreVerifiedQuickIndicatorType } from './PreVerifiedQuickIndicator' import VerificationsQuickIndicator from './VerificationsQuickIndicator' import VerificationsView from './VerificationsView' -import PreVerifiedQuickIndicator, { PreVerifiedQuickIndicatorType } from './PreVerifiedQuickIndicator' export const printAwareCss = css` @media print { @@ -114,30 +114,34 @@ const RightElement = ({ jsonField, application }: RightElementProps): ReactEleme return !!blueCardJuleicaEntitlement } - const isPreverified = (): boolean => { - const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField; - const workAtOrganizationsEntitlement = findValue(applicationDetails, 'blueCardWorkAtOrganizationsEntitlement', 'Array')?.value ?? []; + const isPreVerified = (): boolean => { + const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField + const workAtOrganizationsEntitlement = + findValue(applicationDetails, 'blueCardWorkAtOrganizationsEntitlement', 'Array')?.value ?? [] - const isAlreadyVerified = workAtOrganizationsEntitlement.some((entitlement: any) => - entitlement.value.some((organization: any) => - organization.name === 'isAlreadyVerified' && organization.value === true - ) + const isAlreadyVerified = workAtOrganizationsEntitlement.some( + (entitlement: GeneralJsonField) => + Array.isArray(entitlement.value) && + entitlement.value.some( + (organizationField: GeneralJsonField) => + organizationField.name === 'isAlreadyVerified' && organizationField.value === true + ) ) return isAlreadyVerified } - let quickIndicator; + let quickIndicator if (isJuleicaEntitlementType()) { - quickIndicator = ; - } else if (isPreverified()) { - quickIndicator = ; + quickIndicator = + } else if (isPreVerified()) { + quickIndicator = } else { - quickIndicator = ; + quickIndicator = } return ( - {!!application.note && application.note.trim() && } + {!!application.note && application.note.trim() && } {quickIndicator} ) @@ -152,12 +156,12 @@ export type ApplicationCardProps = { } const ApplicationCard = ({ - application, - onDelete, - printApplicationById, - isSelectedForPrint, - onChange - }: ApplicationCardProps) => { + application, + onDelete, + printApplicationById, + isSelectedForPrint, + onChange, +}: ApplicationCardProps) => { const [isExpanded, setIsExpanded] = useState(false) const { createdDate: createdDateString, jsonValue, id, withdrawalDate, cardCreated } = application const jsonField: JsonField<'Array'> = JSON.parse(jsonValue) @@ -178,7 +182,7 @@ const ApplicationCard = ({ console.error('Delete operation returned false.') appToaster?.show({ intent: 'danger', message: 'Etwas ist schief gelaufen.' }) } - } + }, }) const createCardQuery = useMemo( @@ -205,7 +209,7 @@ const ApplicationCard = ({ } rightElement={} elevation={1} - icon={withdrawalDate ? : undefined} + icon={withdrawalDate ? : undefined} collapseProps={{ isOpen: isExpanded, onToggle: () => setIsExpanded(!isExpanded), keepChildrenMounted: true }} collapsible={!isSelectedForPrint} $hideInPrintMode={!isSelectedForPrint}> @@ -219,7 +223,7 @@ const ApplicationCard = ({ /> {!!withdrawalDate && ( - + Der Antrag wurde vom Antragsteller am {formatDateWithTimezone(withdrawalDate, config.timezone)}{' '} zurückgezogen.
Bitte löschen Sie den Antrag zeitnah. @@ -242,28 +246,28 @@ const ApplicationCard = ({ + content='Es existiert kein passendes Mapping, um aus diesem Antrag das Kartenformular vollständig auszufüllen.'> + icon='id-number' + intent='primary'> {cardCreated ? 'Karte erneut erstellen' : 'Karte erstellen'} - setDeleteDialogOpen(true)} intent="danger" icon="trash"> + setDeleteDialogOpen(true)} intent='danger' icon='trash'> Antrag löschen - printApplicationById(id)} intent="none" icon="print"> + printApplicationById(id)} intent='none' icon='print'> PDF exportieren - setIsExpanded(!isExpanded)} style={{ marginLeft: 'auto' }} /> + setIsExpanded(!isExpanded)} style={{ marginLeft: 'auto' }} /> setDeleteDialogOpen(false)} diff --git a/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx b/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx index b5d161f57..62a339f39 100644 --- a/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx +++ b/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx @@ -11,22 +11,23 @@ export enum PreVerifiedQuickIndicatorType { Verein360, } -const PreVerifiedQuickIndicator = memo(({type}: {type: PreVerifiedQuickIndicatorType}) => { - const logo = type == PreVerifiedQuickIndicatorType.Juleica ? JuleicaLogo : Verein360Logo +const PreVerifiedQuickIndicator = memo(({ type }: { type: PreVerifiedQuickIndicatorType }) => { + const logo = type === PreVerifiedQuickIndicatorType.Juleica ? JuleicaLogo : Verein360Logo return ( - - Bestätigung(en) durch Organisationen: -
- Bestätigung ist nicht erforderlich - - }> - - - {type.toString()} - -
-)}) + + Bestätigung(en) durch Organisationen: +
+ Bestätigung ist nicht erforderlich + + }> + + + {type.toString()} + +
+ ) +}) export default memo(PreVerifiedQuickIndicator) From 7870cf66d42a783fc069d0d9beb2b136448c4c8b Mon Sep 17 00:00:00 2001 From: ztefanie Date: Wed, 4 Dec 2024 11:41:46 +0100 Subject: [PATCH 5/6] 1790: Fix test --- .../webservice/EakApplicationMutationServiceTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationServiceTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationServiceTest.kt index d0c06ff1e..409eddb5b 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationServiceTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationServiceTest.kt @@ -13,6 +13,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkConstructor import io.mockk.verify +import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.Part import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.selectAll @@ -38,6 +39,7 @@ internal class EakApplicationMutationServiceTest : IntegrationTest() { private val mockDfe = mockk() private val mockContext = mockk() private val mockJwtPayload = mockk() + private val mockRequest = mockk() private val mockApplicationData = File("mockFile1") private val mockFiles = listOf() @@ -72,9 +74,11 @@ internal class EakApplicationMutationServiceTest : IntegrationTest() { fun addEakApplication_storesNoApplicationIfIsVerifiedButNoToken() { every { mockDfe.getContext() } returns mockContext every { mockContext.enforceSignedIn() } returns mockJwtPayload + every { mockContext.request } returns mockRequest every { mockContext.backendConfiguration } returns loadTestConfig() every { mockContext.applicationData } returns mockApplicationData every { mockContext.files } returns mockFiles + every { mockRequest.getHeader("Authorization") } returns null mockkConstructor(ApplicationHandler::class) every { anyConstructed().sendApplicationMails(any(), any(), any()) } returns Unit From 288e9a09e350ad588d8b13405fbbc696db4fdbd2 Mon Sep 17 00:00:00 2001 From: ztefanie Date: Wed, 4 Dec 2024 14:18:42 +0100 Subject: [PATCH 6/6] 1790: Improve readability of ApplicationCard --- .../applications/ApplicationCard.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/administration/src/bp-modules/applications/ApplicationCard.tsx b/administration/src/bp-modules/applications/ApplicationCard.tsx index 0527e10a3..eebadf233 100644 --- a/administration/src/bp-modules/applications/ApplicationCard.tsx +++ b/administration/src/bp-modules/applications/ApplicationCard.tsx @@ -114,7 +114,7 @@ const RightElement = ({ jsonField, application }: RightElementProps): ReactEleme return !!blueCardJuleicaEntitlement } - const isPreVerified = (): boolean => { + const isPreVerifiedByOrganization = (): boolean => { const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField const workAtOrganizationsEntitlement = findValue(applicationDetails, 'blueCardWorkAtOrganizationsEntitlement', 'Array')?.value ?? [] @@ -130,19 +130,20 @@ const RightElement = ({ jsonField, application }: RightElementProps): ReactEleme return isAlreadyVerified } - let quickIndicator - if (isJuleicaEntitlementType()) { - quickIndicator = - } else if (isPreVerified()) { - quickIndicator = - } else { - quickIndicator = - } + const isPreVerified = isJuleicaEntitlementType() || isPreVerifiedByOrganization() return ( {!!application.note && application.note.trim() && } - {quickIndicator} + {isPreVerified ? ( + + ) : ( + + )} ) }