Skip to content

Commit

Permalink
Feature/vaccine exemption (#133)
Browse files Browse the repository at this point in the history
* Added new Hcert type for vaccine exemption and relative Hcert extension

* New way to get Hcer uvci property

Added getUVCI() method in HCert+UVCI extension to return uvci information based on the new added HCert type (VaccineExemption)

* Added business rules for vaccine exemption validation

* feature: vaccine exemption

- Fix condition in isExemptionValid function

* feature: vaccine exemption

- Fix condition in isExemptionValid function

* code refactoring

* feat: vaccine exemption

remove comment

Co-authored-by: Ludovico Girolimini <[email protected]>
Co-authored-by: eapuzzo <[email protected]>
  • Loading branch information
3 people authored Jan 14, 2022
1 parent 8d8a301 commit 3d9a2ba
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ struct RecoveryValidityCheck {

guard let currentDate = Date.startOfDay else { return .notValid }

return Validator.validate(currentDate, from: validityStart, to: validityEnd, extendedTo: validityExtension)
let recoveryStatus = Validator.validate(currentDate, from: validityStart, to: validityEnd, extendedTo: validityExtension)

let scanMode: String = Store.get(key: .scanMode) ?? ""
guard scanMode != Constants.scanModeBooster else { return recoveryStatus == .valid ? .verificationIsNeeded : recoveryStatus }

return recoveryStatus
}

private func getStartDays(from hcert: HCert) -> Int? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct StatementValidityCheck {

func isStatementBlacklisted(_ hCert: HCert) -> Bool {
guard let blacklist = getBlacklist() else { return false }
return blacklist.split(separator: ";").contains("\(hCert.uvci)")
return blacklist.split(separator: ";").contains("\(hCert.getUVCI())")

}

Expand Down
2 changes: 2 additions & 0 deletions DGCAVerifier/BusinessRules/Internal/TestValidityCheck.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ struct TestValidityCheck {
}

func isTestValid(_ hcert: HCert) -> Status {
let scanMode: String = Store.get(key: .scanMode) ?? ""
guard scanMode != Constants.scanMode2G, scanMode != Constants.scanModeBooster else { return .notValid }
let testValidityResults = [isTestNegative(hcert), isTestDateValid(hcert)]
return testValidityResults.first(where: {$0 != .valid}) ?? .valid
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* license-start
*
* Copyright (C) 2021 Ministero della Salute and all other contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

//
// VaccineExemptionValidityCheck.swift
// Verifier
//
// Created by Ludovico Girolimini on 12/01/22.
//

import Foundation
import SwiftDGC

struct VaccineExemptionValidityCheck {

typealias Validator = MedicalRulesValidator

func isExemptionValid(_ hcert: HCert) -> Status {
guard let exemption = hcert.vaccineExemptionStatements.last else { return .notValid }
guard let dateFrom = exemption.dateFrom else { return .notValid }
guard let currentDate = Date.startOfDay else { return .notValid }
guard let dateUntil = exemption.dateUntil else {
let validity = Validator.validate(currentDate, from: dateFrom)
return isBoosterScanModeActive ? checkValidityOnBoosterScanMode(validity) : validity
}
let validity = Validator.validate(currentDate, from: dateFrom, to: dateUntil)
return isBoosterScanModeActive ? checkValidityOnBoosterScanMode(validity) : validity
}

private var isBoosterScanModeActive: Bool {
return Store.get(key: .scanMode) == Constants.scanModeBooster
}

private func checkValidityOnBoosterScanMode(_ certStatus: Status) -> Status {
guard isBoosterScanModeActive, certStatus == .valid else { return certStatus }
return .verificationIsNeeded
}

}
20 changes: 14 additions & 6 deletions DGCAVerifier/BusinessRules/MedicalRulesValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,33 @@ struct MedicalRulesValidator: Validator {
static func getStatus(from hCert: HCert) -> Status {
let statementValidityCheck = StatementValidityCheck()
guard !statementValidityCheck.isStatementBlacklisted(hCert) else { return .notValid }
let scanMode: String = Store.get(key: .scanMode) ?? ""
switch hCert.type {
switch hCert.extendedType {
case .test:
guard scanMode != Constants.scanMode2G, scanMode != Constants.scanModeBooster else { return .notValid }
let testValidityCheck = TestValidityCheck()
return testValidityCheck.isTestValid(hCert)
case .vaccine:
let vaccineValidityCheck = VaccineValidityCheck()
return vaccineValidityCheck.isVaccineDateValid(hCert)
case .recovery:
let recoveryValidityCheck = RecoveryValidityCheck()
let recoveryStatus = recoveryValidityCheck.isRecoveryValid(hCert)
guard scanMode != Constants.scanModeBooster else { return recoveryStatus == .valid ? .verificationIsNeeded : recoveryStatus }
return recoveryStatus
return recoveryValidityCheck.isRecoveryValid(hCert)
case .vaccineExemption:
let exemptionValidityCheck = VaccineExemptionValidityCheck()
return exemptionValidityCheck.isExemptionValid(hCert)
case .unknown:
return .notValid
}
}

static func validate(_ current: Date, from validityStart: Date) -> Status {
switch current {
case ..<validityStart:
return .notValidYet
default:
return .valid
}
}

static func validate(_ current: Date, from validityStart: Date, to validityEnd: Date) -> Status {
switch current {
case ..<validityStart:
Expand Down
2 changes: 1 addition & 1 deletion DGCAVerifier/BusinessRules/RulesValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ struct RulesValidator: Validator {

public static func isRevoked(_ hCert: HCert) -> Bool {
guard CRLSynchronizationManager.shared.isSyncEnabled else { return false }
let hash = hCert.uvci.sha256()
let hash = hCert.getUVCI().sha256()
guard !hash.isEmpty else { return false }
return CRLDataStorage.contains(hash: hash)
}
Expand Down
2 changes: 1 addition & 1 deletion DGCAVerifier/Protocols/Validator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension Validator {
guard hCert.cryptographicallyValid else { return false }
guard hCert.exp >= HCert.clock else { return false }
guard hCert.iat <= HCert.clock else { return false }
guard hCert.statement != nil else { return false }
guard hCert.lastStatement != nil else { return false }
return true
}

Expand Down
38 changes: 38 additions & 0 deletions DGCAVerifier/Utils/HCert/HCert+Type.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// HCert+Type.swift
// Verifier
//
// Created by Ludovico Girolimini on 10/01/22.
//

import Foundation
import SwiftDGC


public enum HCertExtensionTypes: String {
case test
case vaccine
case recovery
case vaccineExemption
case unknown
}

extension HCert {

var extendedType: HCertExtensionTypes {
switch self.type {
case .recovery:
return .recovery
case .vaccine:
return .vaccine
case .test:
return .test
case .unknown:
if self.vaccineExemptionStatements.isEmpty {
return .unknown
}
return .vaccineExemption
}
}

}
18 changes: 18 additions & 0 deletions DGCAVerifier/Utils/HCert/HCert+UVCI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// HCert+UVCI.swift
// Verifier
//
// Created by Ludovico Girolimini on 11/01/22.
//

import Foundation
import SwiftDGC


extension HCert {

func getUVCI() -> String {
lastStatement?.uvci ?? "empty"
}

}
28 changes: 28 additions & 0 deletions DGCAVerifier/Utils/HCert/HCert+VaccineExemptionStatement.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// HCert+Statement.swift
// Verifier
//
// Created by Ludovico Girolimini on 10/01/22.
//

import Foundation
import SwiftDGC


extension HCert {

var vaccineExemptionStatements: [VaccineExemptionEntry] {
return self.body["e"]
.array?
.compactMap {
VaccineExemptionEntry(body: $0)
} ?? []
}

var lastStatement: HCertEntry? {
guard self.statement == nil else { return self.statement }
guard !vaccineExemptionStatements.isEmpty else { return nil }
return vaccineExemptionStatements.last
}

}
63 changes: 63 additions & 0 deletions DGCAVerifier/Utils/HCert/VaccineExemptionEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// VaccineExemptionEntry.swift
// Verifier
//
// Created by Ludovico Girolimini on 10/01/22.
//

import Foundation
import SwiftDGC
import SwiftyJSON


struct VaccineExemptionEntry : HCertEntry {

var info: [InfoSection] {
return []
}

var typeAddon: String {
return ""
}

var validityFailures: [String] {
return []
}

let diseaseTargeted: String
let issuer: String
let countryCode: String
let uvci: String
let dateFrom: Date?
let dateUntil: Date?


enum Fields: String {
case diseaseTargeted = "tg"
case countryCode = "co"
case issuer = "is"
case uvci = "ci"
case dateFrom = "df"
case dateUntil = "du"
}

init?(body: JSON) {
guard
let diseaseTargeted = body[Fields.diseaseTargeted.rawValue].string,
let country = body[Fields.countryCode.rawValue].string,
let issuer = body[Fields.issuer.rawValue].string,
let uvci = body[Fields.uvci.rawValue].string,
let df = body[Fields.dateFrom.rawValue].string
else {
return nil
}
self.diseaseTargeted = diseaseTargeted
self.countryCode = country
self.issuer = issuer
self.uvci = uvci
self.dateFrom = Date(dateString: df)
let du = body[Fields.dateUntil.rawValue].string
self.dateUntil = du != nil ? Date(dateString: du!) : nil
}

}
Loading

0 comments on commit 3d9a2ba

Please sign in to comment.