Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1790: Verein360 endpoint protection #1811

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions administration/src/assets/verein360.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 45 additions & 27 deletions administration/src/bp-modules/applications/ApplicationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 {
Expand Down Expand Up @@ -58,6 +58,7 @@ export const CollapseIcon = styled(Icon)`
align-self: center;
padding: 2px;
${printAwareCss};

:hover {
cursor: pointer;
color: ${Colors.GRAY1};
Expand Down Expand Up @@ -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 = <PreVerifiedQuickIndicator type={PreVerifiedQuickIndicatorType.Juleica} />;
} else if (isPreverified()) {
quickIndicator = <PreVerifiedQuickIndicator type={PreVerifiedQuickIndicatorType.Verein360} />;
} else {
quickIndicator = <VerificationsQuickIndicator verifications={application.verifications} />;
}

return (
<RightElementContainer>
{!!application.note && application.note.trim() && <Icon icon='annotation' intent='none' />}
{isJuleicaEntitlementType() ? (
<JuleicaVerificationQuickIndicator />
) : (
<VerificationsQuickIndicator verifications={application.verifications} />
)}
{!!application.note && application.note.trim() && <Icon icon="annotation" intent="none" />}
{quickIndicator}
</RightElementContainer>
)
}
Expand All @@ -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)
Expand All @@ -160,7 +178,7 @@ const ApplicationCard = ({
console.error('Delete operation returned false.')
appToaster?.show({ intent: 'danger', message: 'Etwas ist schief gelaufen.' })
}
},
}
})

const createCardQuery = useMemo(
Expand All @@ -187,7 +205,7 @@ const ApplicationCard = ({
}
rightElement={<RightElement jsonField={jsonField} application={application} />}
elevation={1}
icon={withdrawalDate ? <Icon icon='warning-sign' intent='warning' /> : undefined}
icon={withdrawalDate ? <Icon icon="warning-sign" intent="warning" /> : undefined}
collapseProps={{ isOpen: isExpanded, onToggle: () => setIsExpanded(!isExpanded), keepChildrenMounted: true }}
collapsible={!isSelectedForPrint}
$hideInPrintMode={!isSelectedForPrint}>
Expand All @@ -201,7 +219,7 @@ const ApplicationCard = ({
/>

{!!withdrawalDate && (
<WithdrawAlert intent='warning'>
<WithdrawAlert intent="warning">
Der Antrag wurde vom Antragsteller am {formatDateWithTimezone(withdrawalDate, config.timezone)}{' '}
zurückgezogen. <br />
Bitte löschen Sie den Antrag zeitnah.
Expand All @@ -224,28 +242,28 @@ const ApplicationCard = ({
<ButtonContainer>
<Tooltip
disabled={!!createCardQuery}
content='Es existiert kein passendes Mapping, um aus diesem Antrag das Kartenformular vollständig auszufüllen.'>
content="Es existiert kein passendes Mapping, um aus diesem Antrag das Kartenformular vollständig auszufüllen.">
<PrintAwareAnchorButton
disabled={!createCardQuery}
href={createCardQuery ? `./cards/add${createCardQuery}` : undefined}
icon='id-number'
intent='primary'>
icon="id-number"
intent="primary">
{cardCreated ? 'Karte erneut erstellen' : 'Karte erstellen'}
</PrintAwareAnchorButton>
</Tooltip>
<PrintAwareButton onClick={() => setDeleteDialogOpen(true)} intent='danger' icon='trash'>
<PrintAwareButton onClick={() => setDeleteDialogOpen(true)} intent="danger" icon="trash">
Antrag löschen
</PrintAwareButton>
<PrintAwareButton onClick={() => printApplicationById(id)} intent='none' icon='print'>
<PrintAwareButton onClick={() => printApplicationById(id)} intent="none" icon="print">
PDF exportieren
</PrintAwareButton>
<CollapseIcon icon='chevron-up' onClick={() => setIsExpanded(!isExpanded)} style={{ marginLeft: 'auto' }} />
<CollapseIcon icon="chevron-up" onClick={() => setIsExpanded(!isExpanded)} style={{ marginLeft: 'auto' }} />
</ButtonContainer>
<Alert
cancelButtonText='Abbrechen'
confirmButtonText='Antrag löschen'
icon='trash'
intent='danger'
cancelButtonText="Abbrechen"
confirmButtonText="Antrag löschen"
icon="trash"
intent="danger"
isOpen={deleteDialogOpen}
loading={loading}
onCancel={() => setDeleteDialogOpen(false)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Tooltip
content={
<div>
Expand All @@ -16,9 +24,9 @@ const JuleicaVerificationQuickIndicator = memo(() => (
}>
<UnFocusedDiv>
<Indicator status={VerificationStatus.Verified} />
<img src={JuleicaLogo} alt='juleica' height='100%' />
<img src={logo} alt={type.toString()} height='100%' />
</UnFocusedDiv>
</Tooltip>
))
)})

export default memo(JuleicaVerificationQuickIndicator)
export default memo(PreVerifiedQuickIndicator)
1 change: 1 addition & 0 deletions administration/src/util/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
f1sh1918 marked this conversation as resolved.
Show resolved Hide resolved
"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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package app.ehrenamtskarte.backend.common.webservice
ztefanie marked this conversation as resolved.
Show resolved Hide resolved

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)
}
}
Loading