Skip to content

Commit

Permalink
1080: added test for administrtion card hash with startDate, added te…
Browse files Browse the repository at this point in the history
…st for card info utils isCardNotYetValid, add a check that expiry date is after start day
  • Loading branch information
f1sh1918 committed Aug 21, 2023
1 parent bd4da2a commit 2e276f0
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 19 deletions.
23 changes: 19 additions & 4 deletions administration/src/bp-modules/cards/AddCardForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { ChangeEvent } from 'react'
import styled from 'styled-components'

import { CardBlueprint } from '../../cards/CardBlueprint'
import StartDayExtension from '../../cards/extensions/StartDayExtension'
import { ExtensionInstance } from '../../cards/extensions/extensions'
import PlainDate from '../../util/PlainDate'

Expand All @@ -18,6 +19,7 @@ const CardHeader = styled.div`
interface ExtensionFormProps {
extension: ExtensionInstance
onUpdate: () => void
hasFormDependencyError?: boolean
}

interface CreateCardsFormProps {
Expand All @@ -26,19 +28,27 @@ interface CreateCardsFormProps {
onRemove: () => void
}

const ExtensionForm = ({ extension, onUpdate }: ExtensionFormProps) => {
const ExtensionForm = ({ extension, onUpdate, hasFormDependencyError }: ExtensionFormProps) => {
return extension.createForm(() => {
onUpdate()
})
}, hasFormDependencyError)
}

const maxCardValidity = { years: 99 }
const hasCardExpirationError = (expirationDate: PlainDate): boolean => {
const today = PlainDate.fromLocalDate(new Date())

return expirationDate.isBefore(today) || expirationDate.isAfter(today.add(maxCardValidity))
}

const hasStartAfterExpiryDateError = (expirationDate: PlainDate, extensions: ExtensionInstance[]): boolean => {
const startDayExtension = extensions.find(el => el.name === 'StartDayExtension') as StartDayExtension | undefined
if (startDayExtension?.state?.startDay) {
const startDay = PlainDate.fromDaysSinceEpoch(startDayExtension.state.startDay)
return startDay.isAfter(expirationDate)
}
return false
}

const CreateCardForm = ({ cardBlueprint, onRemove, onUpdate }: CreateCardsFormProps) => {
const today = PlainDate.fromLocalDate(new Date())
return (
Expand Down Expand Up @@ -87,7 +97,12 @@ const CreateCardForm = ({ cardBlueprint, onRemove, onUpdate }: CreateCardsFormPr
/>
</FormGroup>
{cardBlueprint.extensions.map((ext, i) => (
<ExtensionForm key={i} extension={ext} onUpdate={onUpdate} />
<ExtensionForm
key={i}
extension={ext}
onUpdate={onUpdate}
hasFormDependencyError={hasStartAfterExpiryDateError(cardBlueprint.expirationDate!, cardBlueprint.extensions)}
/>
))}
</Card>
)
Expand Down
19 changes: 6 additions & 13 deletions administration/src/cards/extensions/StartDayExtension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,11 @@ class StartDayExtension extends Extension<StartDayState, null> {
this.state = { startDay: today.toDaysSinceEpoch() }
}

hasValidStartDayDate(startDay?: number): boolean {
if (startDay === undefined) {
return false
}
const date = PlainDate.fromDaysSinceEpoch(startDay)
const today = PlainDate.fromLocalDate(new Date())
return !date.isBefore(today)
}

createForm(onUpdate: () => void) {
createForm(onUpdate: () => void, hasFormDependencyError?: boolean) {
const startDayDate =
this.state?.startDay !== undefined ? PlainDate.fromDaysSinceEpoch(this.state.startDay) : PlainDate.fromLocalDate(new Date())
this.state?.startDay !== undefined
? PlainDate.fromDaysSinceEpoch(this.state.startDay)
: PlainDate.fromLocalDate(new Date())

return (
<FormGroup label='Startdatum'>
Expand All @@ -36,7 +29,7 @@ class StartDayExtension extends Extension<StartDayState, null> {
type='date'
required
size='small'
error={!this.isValid()}
error={!this.isValid() || hasFormDependencyError}
value={startDayDate.toString()}
sx={{ '& input[value=""]:not(:focus)': { color: 'transparent' }, '& fieldset': { borderRadius: 0 } }}
inputProps={{
Expand Down Expand Up @@ -69,7 +62,7 @@ class StartDayExtension extends Extension<StartDayState, null> {
}

isValid() {
return this.state !== null && this.hasValidStartDayDate(this.state.startDay)
return this.state?.startDay !== undefined
}

/**
Expand Down
23 changes: 23 additions & 0 deletions administration/src/cards/hashCardInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,27 @@ describe('hashCardInfo', () => {

expect(uint8ArrayToBase64(hash)).toBe('zogEJOhnSSp//8qhym/DdorQYgL/763Kfq4slWduxMg=')
})

it('should be stable for a Nuernberg Pass with startDay', async () => {
const cardInfo = new CardInfo({
fullName: 'Max Mustermann',
expirationDay: 365 * 40, // Equals 14.600
extensions: new CardExtensions({
extensionRegion: new RegionExtension({
regionId: 93,
}),
extensionBirthday: new BirthdayExtension({
birthday: -365 * 10,
}),
extensionNuernbergPassNumber: new NuernbergPassNumberExtension({
passNumber: 99999999,
}),
extensionStartDay: new StartDayExtension({ startDay: 365 * 2 }),
}),
})
const pepper = base64ToUint8Array('MvMjEqa0ulFDAgACElMjWA==')
const hash = await hashCardInfo(cardInfo, pepper)

expect(uint8ArrayToBase64(hash)).toBe('1ChHiAvWygwu+bH2yOZOk1zdmwTDZ4mkvu079cyuLjE=')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ object Cards : IntIdTable() {
val cardInfoHash = binary("cardInfoHash", CARD_INFO_HASH_LENGTH).uniqueIndex()
val codeType = enumeration("codeType", CodeType::class)
val firstActivationDate = timestamp("firstActivationDate").nullable()

// startDay describes the first day on which the card is valid.
// If this field is null, the card is valid until `expirationDay` without explicitly stating when the validity period started.
// Days since 1970-01-01. For more information refer to the card.proto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ object CardVerifier {
public fun verifyStaticCard(project: String, cardHash: ByteArray, timezone: ZoneId): Boolean {
val card = transaction { CardRepository.findByHash(project, cardHash) } ?: return false
return !isExpired(card.expirationDay, timezone) && isYetValid(card.startDay, timezone) &&
!card.revoked
!card.revoked
}

public fun verifyDynamicCard(project: String, cardHash: ByteArray, totp: Int, timezone: ZoneId): Boolean {
val card = transaction { CardRepository.findByHash(project, cardHash) } ?: return false
return !isExpired(card.expirationDay, timezone) && isYetValid(card.startDay, timezone) &&
!card.revoked &&
!card.revoked &&
isTotpValid(totp, card.totpSecret)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class CardMutationService {
val activationSecretHash = card?.activationSecretHash

if (card == null || activationSecretHash == null) {
logger.info("${context.remoteIp} failed to activate card, card not found with cardHash:$cardHash")
return@t CardActivationResultModel(ActivationState.failed)
}

Expand All @@ -95,6 +96,7 @@ class CardMutationService {
}

if (CardVerifier.isExpired(card.expirationDay, projectConfig.timezone) || card.revoked) {
logger.info("${context.remoteIp} failed to activate card with id:${card.id} and overwrite: $overwrite because card isExpired or revoked")
return@t CardActivationResultModel(ActivationState.failed)
}

Expand Down
21 changes: 21 additions & 0 deletions frontend/test/canonical_json_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,26 @@ void main() {
},
});
});

test("should map a cardInfo for a Nuernberg Pass wit startDay correctly", () {
final cardInfo = CardInfo()
..fullName = 'Max Mustermann'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionRegion = (RegionExtension()..regionId = 93)
..extensionBirthday = (BirthdayExtension()..birthday = -365 * 10)
..extensionNuernbergPassNumber = (NuernbergPassNumberExtension()..passNumber = 99999999)
..extensionStartDay = (StartDayExtension()..startDay = 365 * 2));
expect(cardInfo.toCanonicalJsonObject(), {
'1': 'Max Mustermann',
'2': '14600',
'3': {
'1': {'1': '93'}, // extensionRegion
'2': {'1': '-3650'}, // extensionBirthday
'3': {'1': '99999999'}, // extensionNuernbergPassNumber
'5': {'1': '730'} // startDay extension
},
});
});
});
}
37 changes: 37 additions & 0 deletions frontend/test/card_info_util_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,41 @@ void main() {
expect(cardInfo.hash(pepper), '1ChHiAvWygwu+bH2yOZOk1zdmwTDZ4mkvu079cyuLjE=');
});
});

group("isCardNotYetValid", () {
test("should return true if startDay is in the future", () {
final cardInfo = CardInfo()
..fullName = 'Max Mustermann'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionRegion = (RegionExtension()..regionId = 93)
..extensionBirthday = (BirthdayExtension()..birthday = -365 * 10)
..extensionNuernbergPassNumber = (NuernbergPassNumberExtension()..passNumber = 99999999)
..extensionStartDay = (StartDayExtension()..startDay = 365 * 70));
expect(isCardNotYetValid(cardInfo), true);
});

test("should return false if startDay is in the past", () {
final cardInfo = CardInfo()
..fullName = 'Max Mustermann'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionRegion = (RegionExtension()..regionId = 93)
..extensionBirthday = (BirthdayExtension()..birthday = -365 * 10)
..extensionNuernbergPassNumber = (NuernbergPassNumberExtension()..passNumber = 99999999)
..extensionStartDay = (StartDayExtension()..startDay = 365 * 30));
expect(isCardNotYetValid(cardInfo), false);
});

test("should be return false if no startDay was set", () {
final cardInfo = CardInfo()
..fullName = 'Max Mustermann'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionRegion = (RegionExtension()..regionId = 93)
..extensionBirthday = (BirthdayExtension()..birthday = -365 * 10)
..extensionNuernbergPassNumber = (NuernbergPassNumberExtension()..passNumber = 99999999));
expect(isCardNotYetValid(cardInfo), false);
});
});
}

0 comments on commit 2e276f0

Please sign in to comment.