This commit is contained in:
Artur Savitskiy 2024-06-03 12:05:19 +02:00
parent 4bad9a4455
commit 9d9e1cfb2b
11 changed files with 303 additions and 21 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -65,6 +65,7 @@
6E2A3E2B28A706FB002EB204 /* downArrow@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6E2A3E2228A706FB002EB204 /* downArrow@3x.png */; };
6E2A3E2D28A706FB002EB204 /* downArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = 6E2A3E2428A706FB002EB204 /* downArrow.png */; };
6E31A84C28CF1D0F0046CB23 /* UIImageWithAlpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E31A84B28CF1D0F0046CB23 /* UIImageWithAlpha.swift */; };
6E37C7182BFDFA9600433471 /* RequestAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E37C7172BFDFA9600433471 /* RequestAsync.swift */; };
6E3BBFB128A2657B0010B7F2 /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3BBFB028A2657B0010B7F2 /* UISlider.swift */; };
6E3BBFB328A265BE0010B7F2 /* UISlider.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6E3BBFB228A265BE0010B7F2 /* UISlider.xib */; };
6E3CAB64287C7E3300E3064D /* Labor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3CAB63287C7E3300E3064D /* Labor.swift */; };
@ -184,6 +185,7 @@
6E2A3E2428A706FB002EB204 /* downArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = downArrow.png; sourceTree = "<group>"; };
6E2A3E2F28A70C22002EB204 /* Befund-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Befund-Bridging-Header.h"; sourceTree = "<group>"; };
6E31A84B28CF1D0F0046CB23 /* UIImageWithAlpha.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageWithAlpha.swift; sourceTree = "<group>"; };
6E37C7172BFDFA9600433471 /* RequestAsync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestAsync.swift; sourceTree = "<group>"; };
6E3BBFB028A2657B0010B7F2 /* UISlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISlider.swift; sourceTree = "<group>"; };
6E3BBFB228A265BE0010B7F2 /* UISlider.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UISlider.xib; sourceTree = "<group>"; };
6E3CAB63287C7E3300E3064D /* Labor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Labor.swift; sourceTree = "<group>"; };
@ -259,6 +261,7 @@
children = (
694FF35F282F86D40070C3F2 /* Request.swift */,
6949A0F6286A079D00987D04 /* Servers.swift */,
6E37C7172BFDFA9600433471 /* RequestAsync.swift */,
);
path = Https;
sourceTree = "<group>";
@ -718,6 +721,7 @@
698D54F428301C6800766CE3 /* Subscribe.swift in Sources */,
69AB1FA028422AAC008045E1 /* Download.swift in Sources */,
698D54F2283015E000766CE3 /* EncryptedRequest.swift in Sources */,
6E37C7182BFDFA9600433471 /* RequestAsync.swift in Sources */,
699DCADE283F7DFF0072D121 /* Results.swift in Sources */,
6EA6D4EF289BC8C80009F0E7 /* Support.swift in Sources */,
6E3CAB662889AD6A00E3064D /* SettingsController.swift in Sources */,
@ -929,7 +933,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = W86CN3N7WD;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Befund/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Patienten Befundapp";
INFOPLIST_KEY_CFBundleDisplayName = "Limbach Befund2Go";
INFOPLIST_KEY_NSCameraUsageDescription = "Die Kamera wird verwendet, um den QR-Code des Labors zu scannen";
INFOPLIST_KEY_NSFaceIDUsageDescription = "Biometrische Daten sind notwendig um die PIN anzufordern";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -970,7 +974,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = W86CN3N7WD;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Befund/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Patienten Befundapp";
INFOPLIST_KEY_CFBundleDisplayName = "Limbach Befund2Go";
INFOPLIST_KEY_NSCameraUsageDescription = "Die Kamera wird verwendet, um den QR-Code des Labors zu scannen";
INFOPLIST_KEY_NSFaceIDUsageDescription = "Biometrische Daten sind notwendig um die PIN anzufordern";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;

View File

@ -183,7 +183,7 @@ class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDel
}
else
{
self.found(code: "befund://labor?id=LIS_SIMULATOR")
self.found(code: "befund://labor?id=DEVELOPMENT")
}
//self.found(code: "befund://labor?id=LABOR_MOENCHENGLADBACH")
#endif

BIN
Befund/Core/.DS_Store vendored

Binary file not shown.

View File

@ -350,7 +350,7 @@ extension Core
}
}
}
/*
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
#if DEBUG
@ -365,7 +365,7 @@ extension Core
completionHandler(.performDefaultHandling, nil)
}
#endif
}
}*/
}
/**

View File

@ -1,9 +1,287 @@
//
// File.swift
// Request.swift
// Befund
//
// Created by Artur Savitskiy on 22.05.24.
// Copyright © 2024 MVZ Dr. Stein und Kollegen. All rights reserved.
//
// Created by Irakli Abetschkhrischwili on 15.05.22.
// Copyright © 2022 MVZ Dr. Stein und Kollegen. All rights reserved.
import Foundation
import UIKit
import Combine
import CryptoKit
extension Core
{
enum RequestError: Error
{
case HANDLED_ERROR(msg: String)
case UNEXPECTED_ERROR(msg: String)
}
@MainActor
public class EncryptedResponse
{
var settings: Core.Models.Settings
var descriptor: String
var contentObject: Any
var controller: String
var action: String
var requestType: Core.Models.Request.EncryptedRequest.Types
private var _publicKey: Core.Models.Response.PublicKey? = nil
private var _keyPair: Core.Security.Curve25519.KeyPair? = nil
public init(settings: Core.Models.Settings, descriptor: String, contentObject: Any, controller: String, action: String, requestType: Core.Models.Request.EncryptedRequest.Types) {
self.settings = settings
self.descriptor = descriptor
self.contentObject = contentObject
self.controller = controller
self.action = action
self.requestType = requestType
}
private var host: Core.Https.Servers{
get {
return settings.labor?.host ?? .DEVELOPMENT
}
}
private var keyPair: Core.Security.Curve25519.KeyPair {
get {
if(_keyPair == nil){
_keyPair = Core.Security.Curve25519.GenerateKeyPair()
}
return _keyPair!
}
}
private var postRequest: HttpsAsync.Request.PostEncryptedRequestAsync{
get {
return HttpsAsync.Request.PostEncryptedRequestAsync()
}
}
private var publicKey: Core.Models.Response.PublicKey? {
get async throws {
if (_publicKey == nil)
{
_publicKey = try await postRequest.getPublicKey(host: host, controller: controller, action: action, _keyPair: keyPair)
}
return _publicKey
}
}
public var sharedKey: CryptoKit.SymmetricKey? {
get async throws{
return try await keyPair.GetSharedKey(peerPublicKeyBase64: publicKey?.key)
}
}
public var response: Core.Models.Response.EncryptedResponse? {
get async throws {
let encryptedRequest = Core.Models.Request.EncryptedRequest(descriptor: descriptor, contentObject: contentObject, requestType: requestType, key: try await sharedKey!)
let encryptedResponse = try await postRequest.getEncryptedResponse(host: host, controller: controller, action: action, _request: encryptedRequest, serverPublicKey: publicKey, _keyPair: keyPair)
return encryptedResponse
}
}
}
public struct HttpsAsync
{
public class Request
{
/**
* Low Level Header protector
*/
private static let HeaderSecureHash: String = "3HzKQw/&>*VH^?cAR{Qd5pP~6w(V5%aZ";
private static let HeaderValueSecureHash: String = "7*R3P!f:sC4Q8XS:@*J/z:4/sS;V3GnM";
/**
* Sends EncryptedRequest to the server in async mode and calls callback(onSucces or onError)
*/
@MainActor
public class PostEncryptedRequestAsync: NSObject, URLSessionDelegate
{
/**
* Runs encrypted request async and calls ether onSuccess or onError callback
*
* @param host - host of the server s. Servers enum
* @param controller - controller of request
* @param action - action of request
* @param _request - encrypted request that contains descriptor, request object and hmac
* @param serverPublicKey - Exchanged server public key from the first request
* @param _keyPair - Curve25519 KeyPair (Private & Public Keys)
*/
func getEncryptedResponse(host: Https.Servers, controller: String, action: String, _request: Core.Models.Request.EncryptedRequest, serverPublicKey: Core.Models.Response.PublicKey?, _keyPair: Core.Security.Curve25519.KeyPair? = nil) async throws -> Core.Models.Response.EncryptedResponse
{
do
{
let keyPair = PrepareKeyPair(_keyPair: _keyPair)
let request = try PrepareURLRequest(host: host, controller: controller, action: action, _request: _request, keyPair: keyPair)
let (data,response) = try await URLSession.shared.data(for: request)
let httpR = response as? HTTPURLResponse
if (httpR != nil)
{
if (httpR?.statusCode == 429)
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_TOO_MANY_REQUESTS"))
}
else if (httpR?.statusCode == 503)
{
AppDelegate.Session.Maintenance = true
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "MSG_MAINTENANCE"))
}
else
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
if(!Core.Https.Request.validServerHeaders(server: httpR!))
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
else
{
let encrR = try JSONDecoder().decode(Core.Models.Response.EncryptedResponse.self, from: data)
let symKey = keyPair.GetSharedKey(peerPublicKeyBase64: serverPublicKey?.key!)
if(Core.Https.Request.validServerSignature(server: httpR!, encryptedResponse: encrR, deriveKey: symKey!))
{
return encrR
}
else
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
}
}
}
else
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
}
catch
{
throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription)
}
}
@MainActor
func getPublicKey(host: Https.Servers, controller: String, action: String, _keyPair: Core.Security.Curve25519.KeyPair? = nil) async throws -> Core.Models.Response.PublicKey
{
do
{
let keyPair = PrepareKeyPair(_keyPair: _keyPair)
let request = try PrepareURLRequest(host: host, controller: controller, action: action, _request: nil, keyPair: keyPair)
let s = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
let (data,response) = try await s.data(for: request)
let httpR = response as? HTTPURLResponse
if(httpR != nil)
{
if (httpR!.statusCode == 429)
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_TOO_MANY_REQUESTS"))
}
else if (httpR!.statusCode == 503)
{
AppDelegate.Session.Maintenance = true
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "MSG_MAINTENANCE"))
}
else
{
AppDelegate.Session.Maintenance = false
AppDelegate.Session.MaintenanceNotified = false
if(!Core.Https.Request.validServerHeaders(server: httpR!))
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
else
{
let publicKey = try JSONDecoder().decode(Core.Models.Response.PublicKey.self, from: data)
return publicKey
}
}
}
else
{
throw RequestError.HANDLED_ERROR(msg: Core.Lang.Get(key: "ERROR_INVALID_RESPONSE"))
}
}
catch
{
throw RequestError.UNEXPECTED_ERROR(msg: error.localizedDescription)
}
}
nonisolated public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let trust = challenge.protectionSpace.serverTrust!
completionHandler(.useCredential, URLCredential(trust:trust))
return
}
completionHandler(.cancelAuthenticationChallenge, nil)
}
private func PrepareKeyPair(_keyPair: Core.Security.Curve25519.KeyPair? = nil) -> Core.Security.Curve25519.KeyPair
{
var keyPair = _keyPair;
if(keyPair == nil)
{
keyPair = Security.Curve25519.GenerateKeyPair()
}
return keyPair!
}
private func PrepareURLRequest(host: Https.Servers, controller: String, action: String, _request: Core.Models.Request.EncryptedRequest?, keyPair: Core.Security.Curve25519.KeyPair) throws -> URLRequest
{
let strURL = Core.Https.Request.wellFormedControllerAction(host: host, controller: controller, action: action)
let url = URL(string: strURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(host.rawValue, forHTTPHeaderField: "Host")
request.setValue("BefundApp", forHTTPHeaderField: "User-Agent")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(Security.AES.Encrypt(value: HeaderValueSecureHash, deriveKey: SymmetricKey(data: Security.AES.GetKey(password: HeaderSecureHash))), forHTTPHeaderField: "Client-Hash")
request.setValue(keyPair.PublicKey, forHTTPHeaderField: "Client-Key")
if(_request == nil)
{
request.httpBody = try JSONEncoder().encode(Core.Models.Request.KeyExchange())
}
else
{
request.setValue(keyPair.SigningPublicKey, forHTTPHeaderField: "Client-Signature-Key")
request.setValue(keyPair.GetSignature(signingValue: (_request!.encrypted_content != nil ? _request!.encrypted_content : _request!.descriptor)!), forHTTPHeaderField: "Client-Signature")
request.httpBody = try JSONEncoder().encode(_request)
}
return request
}
}
}
}
}

View File

@ -13,7 +13,7 @@ extension Core.Https
public enum Servers : String
{
// Development server
case DEVELOPMENT = "192.168.178.86"
case DEVELOPMENT = "192.168.0.169"
/**
* HOST´s of productive outside services

View File

@ -13,7 +13,7 @@ extension Core.Models
{
public class Labor
{
public var id: String = ""
public var id: String = "DEVELOPMENT"
public var name: String = ""
public var street: String = ""
public var city: String = ""

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -26,8 +26,8 @@
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PATIENTEN BEFUNDAPP" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kwd-Q8-S1y">
<rect key="frame" x="30" y="300" width="354" height="100"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LIMBACH BEFUND2GO" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kwd-Q8-S1y">
<rect key="frame" x="30" y="304" width="354" height="100"/>
<constraints>
<constraint firstAttribute="height" constant="100" id="mS9-Bd-GXA"/>
</constraints>
@ -36,7 +36,7 @@
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="limbach-gruppe-logo" translatesAutoresizingMaskIntoConstraints="NO" id="3Vu-Oi-Rx3">
<rect key="frame" x="96" y="400" width="222" height="100"/>
<rect key="frame" x="96" y="404" width="222" height="100"/>
<constraints>
<constraint firstAttribute="width" constant="222" id="O5z-iy-gwL"/>
<constraint firstAttribute="height" constant="100" id="OzS-o3-CIO"/>
@ -63,6 +63,6 @@
</scene>
</scenes>
<resources>
<image name="limbach-gruppe-logo" width="222" height="34"/>
<image name="limbach-gruppe-logo" width="222" height="70"/>
</resources>
</document>

View File

@ -201,7 +201,7 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PATIENTEN BEFUNDAPP" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2Gp-7O-KxR">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LIMBACH BEFUND2GO" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2Gp-7O-KxR">
<rect key="frame" x="30" y="315" width="354" height="100"/>
<constraints>
<constraint firstAttribute="height" constant="100" id="LRO-4m-57T"/>
@ -1313,7 +1313,7 @@
</constraints>
</view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="limbach-gruppe-logo" translatesAutoresizingMaskIntoConstraints="NO" id="LqP-hE-OIa">
<rect key="frame" x="104" y="277" width="222" height="34"/>
<rect key="frame" x="74.666666666666657" y="222.33333333333334" width="280.66666666666674" height="88.666666666666657"/>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -2963,7 +2963,7 @@
<rect key="frame" x="0.0" y="0.0" width="430" height="120"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="limbach-gruppe-logo" translatesAutoresizingMaskIntoConstraints="NO" id="RwA-fz-eeK">
<rect key="frame" x="104" y="70" width="222" height="50"/>
<rect key="frame" x="74.666666666666657" y="70" width="280.66666666666674" height="50"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="Zgo-fy-Izf"/>
</constraints>
@ -3162,7 +3162,7 @@
<image name="ic_pin" width="58.666667938232422" height="58.666667938232422"/>
<image name="ic_question_answer" width="58.666667938232422" height="58.666667938232422"/>
<image name="ic_settings" width="58.666667938232422" height="58.666667938232422"/>
<image name="limbach-gruppe-logo" width="222" height="34"/>
<image name="limbach-gruppe-logo" width="280.66665649414062" height="88.666664123535156"/>
<image name="square.and.arrow.up" catalog="system" width="108" height="128"/>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>