From 4d98b6c5e449be71aaa7355349f05015e6e2465e Mon Sep 17 00:00:00 2001 From: COVIDSafe Support <64945427+covidsafe-support@users.noreply.github.com> Date: Wed, 12 May 2021 17:39:38 -0700 Subject: [PATCH] COVIDSafe code from version 2.6 (#51) --- .circleci/README.md | 49 ++++ .circleci/config.yml | 69 +++++ CovidSafe.xcodeproj/project.pbxproj | 126 ++++---- .../xcschemes/covid-production.xcscheme | 2 +- CovidSafe/API/AuthenticationAPI.swift | 20 +- CovidSafe/API/ChangePostcodeAPI.swift | 8 +- CovidSafe/API/CovidNetworking.swift | 22 +- CovidSafe/API/CovidRequestRetrier.swift | 16 +- CovidSafe/API/GetTempIdAPI.swift | 4 +- CovidSafe/API/MessageAPI.swift | 13 +- CovidSafe/API/PhoneValidationAPI.swift | 20 +- CovidSafe/API/RestrictionsAPI.swift | 7 +- CovidSafe/API/StatisticsAPI.swift | 7 +- CovidSafe/AppDelegate.swift | 2 +- CovidSafe/HomeView.xib | 100 +++++-- CovidSafe/HomeViewController.swift | 45 ++- CovidSafe/InfoViewController.swift | 6 +- CovidSafe/InitialScreenViewController.swift | 2 +- CovidSafe/KeychainSwift+Singleton.swift | 13 + CovidSafe/OTPViewController.swift | 2 +- CovidSafe/PhoneNumberViewController.swift | 30 +- .../QuestionUploadDataViewController.swift | 2 +- .../RegistrationIntroViewController.swift | 2 +- .../RegistrationSuccessViewController.swift | 1 - CovidSafe/URLHelper.swift | 4 + CovidSafe/UploadHelper.swift | 2 +- CovidSafe/ar.lproj/Localizable.strings | 6 + CovidSafe/el.lproj/Localizable.strings | 6 + CovidSafe/en.lproj/Localizable.strings | 6 + CovidSafe/it.lproj/Localizable.strings | 6 + CovidSafe/ko.lproj/Localizable.strings | 6 + CovidSafe/pa-IN.lproj/Localizable.strings | 6 + CovidSafe/tr.lproj/Localizable.strings | 6 + CovidSafe/vi.lproj/Localizable.strings | 6 + CovidSafe/zh-Hans.lproj/Localizable.strings | 6 + CovidSafe/zh-Hant.lproj/Localizable.strings | 6 + Gemfile | 5 + Gemfile.lock | 269 ++++++++++++++++++ Podfile | 8 + Podfile.lock | 12 +- fastlane/Appfile | 6 + fastlane/Fastfile | 96 +++++++ fastlane/README.md | 24 ++ 43 files changed, 910 insertions(+), 144 deletions(-) create mode 100644 .circleci/README.md create mode 100644 .circleci/config.yml create mode 100644 CovidSafe/KeychainSwift+Singleton.swift create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 fastlane/Appfile create mode 100644 fastlane/Fastfile create mode 100644 fastlane/README.md diff --git a/.circleci/README.md b/.circleci/README.md new file mode 100644 index 0000000..380cc04 --- /dev/null +++ b/.circleci/README.md @@ -0,0 +1,49 @@ + +## Introduction + +CircleCI has a workflow configured to build and upload UAT builds once a merge is done to the dev branch. In the same way, there is a workflow for production and it runs when a merge is done to the master branch. + +Fastlane is used to build and distribute the app. + + +## Fastlane Configuration + +The approach taken for the app build and distribution was to provide the `provisioning profile` and the `certificate` manually rather than retrieving it automatically from appstore connect. For this, environment variables are set with the required values for both UAT and PROD. + +This aliviates the requirements for authentication and interaction with the appstore leaving upload as the only outstanding task in the lanes. With fastlane's `pilot` we upload the app to TestFlight using application specific password, this action **requires** both `apple_id` and `skip_waiting_for_build_processing`. For more information see https://docs.fastlane.tools/actions/upload_to_testflight/#use-an-application-specific-password-to-upload + +Note: Build numbers need to be set correctly on merge otherwise the upload will fail. In order to automate the build number update it is recomended to use an api_key. + +### Lanes + +There are 2 lanes defined per app build (UAT and PROD): + +- import_uat_distribution_certificate: This lane will decode and install the needed provisioning profile and signing certificate for the UAT build. +- import_distribution_certificate: This lane will decode and install the needed provisioning profile and signing certificate for the PROD build. +- beta: This lane will build and upload the UAT build to testflight +- release: This lane will build and upload the PROD build to testflight + +## CircleCI Project Configuration + +The following environment variables need to be set in the CircleCI web console project configuration. + + +- APP_STORE_UAT_PROFILE_B64: Base64 encoded provisioning profile to use with UAT builds +- DISTRIBUTION_UAT_P12_B64: Base64 encoded distribution certificate to use with UAT builds +- DISTRIBUTION_UAT_P12_PASSWORD: Password for the UAT certificate above. +- APPLE_ID_UAT: This is the apps apple id. Can be found in appstore connect under App information -> General information. + +- APP_STORE_PROFILE_B64: Base64 encoded provisioning profile to use with UAT builds +- DISTRIBUTION_P12_B64: Base64 encoded distribution certificate to use with UAT builds +- DISTRIBUTION_P12_PASSWORD: Password for the UAT certificate above. +- APPLE_ID: This is the apps apple id. Can be found in appstore connect under App information -> General information. + +- FASTLANE_USER: App store connect user +- FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: Application specific password generated for Fastlane. This is account specific rather than app specific, share the same for UAT and PROD. For more information on how to generate the password see https://docs.fastlane.tools/best-practices/continuous-integration/#method-3-application-specific-passwords + + +To get a base64 encoded string of the desired secret run + +``` +openssl base64 -A -in "filename.extension" +``` diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ae938a8 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,69 @@ +# Circle CI Build config for COVIDSafe + +version: 2.1 + + +commands: + + prepare-uat: + steps: + - checkout + - run: bundle install + - run: mkdir -pv ~/Library/MobileDevice/Provisioning\ Profiles/ + - run: echo ${APP_STORE_UAT_PROFILE_B64} | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/AppStore_UAT.mobileprovision + - run: echo ${DISTRIBUTION_UAT_P12_B64} | base64 --decode > Distribution_UAT.p12 + - run: bundle exec fastlane import_uat_distribution_certificate + + prepare-release: + steps: + - checkout + - run: bundle install + - run: mkdir -pv ~/Library/MobileDevice/Provisioning\ Profiles/ + - run: echo ${APP_STORE_PROFILE_B64} | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/AppStore.mobileprovision + - run: echo ${DISTRIBUTION_P12_B64} | base64 --decode > Distribution.p12 + - run: bundle exec fastlane import_distribution_certificate + +jobs: + + build-uat: + + macos: + xcode: 12.4.0 # Specify the Xcode version to use + environment: + FL_OUTPUT_DIR: output + steps: + - prepare-uat + - checkout + - run: bundle exec pod install + - run: bundle exec fastlane beta + - store_artifacts: + path: output + + build-release: + + macos: + xcode: 12.4.0 # Specify the Xcode version to use + environment: + FL_OUTPUT_DIR: output + steps: + - prepare-release + - checkout + - run: bundle exec pod install + - run: bundle exec fastlane release + - store_artifacts: + path: output + +workflows: + build-uat: + jobs: + - build-uat: + filters: + branches: + only: dev + + build-release: + jobs: + - build-release: + filters: + branches: + only: master diff --git a/CovidSafe.xcodeproj/project.pbxproj b/CovidSafe.xcodeproj/project.pbxproj index 2b21217..3d9aba3 100644 --- a/CovidSafe.xcodeproj/project.pbxproj +++ b/CovidSafe.xcodeproj/project.pbxproj @@ -316,6 +316,8 @@ 5BA3624225E463B6002CFF41 /* WebContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BA3624025E463B5002CFF41 /* WebContentView.xib */; }; 5BA3624625E4654F002CFF41 /* WebContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA3624525E4654E002CFF41 /* WebContentView.swift */; }; 5BA3624725E4654F002CFF41 /* WebContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA3624525E4654E002CFF41 /* WebContentView.swift */; }; + 5BB9B8A0262FE0870056C47E /* KeychainSwift+Singleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB9B89F262FE0870056C47E /* KeychainSwift+Singleton.swift */; }; + 5BB9B8A1262FE0870056C47E /* KeychainSwift+Singleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB9B89F262FE0870056C47E /* KeychainSwift+Singleton.swift */; }; 5BBC571D25526F99005E90AA /* staging-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BBC571C25526F99005E90AA /* staging-Info.plist */; }; 5BBE61B125633B6D00B8C983 /* CSGenericContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */; }; 5BBE61B225633B6D00B8C983 /* CSGenericContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */; }; @@ -564,6 +566,7 @@ 5BA3623325DE133D002CFF41 /* CSGenericErrorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSGenericErrorController.swift; sourceTree = ""; }; 5BA3624025E463B5002CFF41 /* WebContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WebContentView.xib; sourceTree = ""; }; 5BA3624525E4654E002CFF41 /* WebContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebContentView.swift; sourceTree = ""; }; + 5BB9B89F262FE0870056C47E /* KeychainSwift+Singleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeychainSwift+Singleton.swift"; sourceTree = ""; }; 5BBC571C25526F99005E90AA /* staging-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "staging-Info.plist"; sourceTree = ""; }; 5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CSGenericContentView.xib; sourceTree = ""; }; 5BBE61B625633E8D00B8C983 /* CSGenericViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSGenericViewController.swift; sourceTree = ""; }; @@ -800,6 +803,7 @@ isa = PBXGroup; children = ( B605A7B02427429D008BA819 /* PlistHelper.swift */, + 5BB9B89F262FE0870056C47E /* KeychainSwift+Singleton.swift */, 5B900FC02485C4EE00CAA419 /* String+Localization.swift */, 5B69C06525D382AF00DF536D /* String+HtmlAttributed.swift */, 0B1810112431EE610005D11F /* PhoneNumberParser.swift */, @@ -1240,7 +1244,7 @@ 5B92D66A243018040049877B /* Sources */, 5B92D6B7243018040049877B /* Frameworks */, 5B92D6B9243018040049877B /* Resources */, - 67401E6C50CE0E543E47346A /* [CP] Embed Pods Frameworks */, + 34C60B5DD40F4458AB9E6FD0 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1259,7 +1263,7 @@ 5DD41D3323DCB03B00FD4AB0 /* Sources */, 5DD41D3423DCB03B00FD4AB0 /* Frameworks */, 5DD41D3523DCB03B00FD4AB0 /* Resources */, - 2D8EF319C5D6408D45C0B585 /* [CP] Embed Pods Frameworks */, + DB7F6C492EEEEFCD9ACA09F9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1432,24 +1436,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2D8EF319C5D6408D45C0B585 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-CovidSafe/Pods-CovidSafe-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-CovidSafe/Pods-CovidSafe-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CovidSafe/Pods-CovidSafe-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 67401E6C50CE0E543E47346A /* [CP] Embed Pods Frameworks */ = { + 34C60B5DD40F4458AB9E6FD0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1488,6 +1475,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + DB7F6C492EEEEFCD9ACA09F9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CovidSafe/Pods-CovidSafe-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CovidSafe/Pods-CovidSafe-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CovidSafe/Pods-CovidSafe-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; DC4D19AF69C7C819FCC35F09 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1576,6 +1580,7 @@ 5B92D68A243018040049877B /* Outcome.swift in Sources */, 5B92D68B243018040049877B /* Encounter+EncounterRecord.swift in Sources */, 0B42D0DE2432B39E00E4F44C /* Question2ErrorViewController.swift in Sources */, + 5BB9B8A1262FE0870056C47E /* KeychainSwift+Singleton.swift in Sources */, 594E77C024736B77009B8B34 /* EncounterDB.swift in Sources */, 5B728B4724B5667000654ABC /* BLELogViewController.swift in Sources */, 590888B42431B9F5008C9B9F /* UploadDataNavigationController.swift in Sources */, @@ -1796,6 +1801,7 @@ A767D32F242DF1B100DC9E2A /* Errors.swift in Sources */, 5905462A2543E0F6009B82AD /* PayloadDataSupplier.swift in Sources */, 0B22A56B242F286900D1FE60 /* UINavigationBar+Style.swift in Sources */, + 5BB9B8A0262FE0870056C47E /* KeychainSwift+Singleton.swift in Sources */, 0B1810122431EE610005D11F /* PhoneNumberParser.swift in Sources */, 1B86119B24303FA200EA4B6B /* Question3ErrorViewController.swift in Sources */, 5BEDEF5425CBC4A900AEEC20 /* RestrictionsViewController.swift in Sources */, @@ -1971,9 +1977,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 121; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1981,11 +1987,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = COVIDSafe; SERVICE_UUID = "17E033D3-490E-4BC9-9FE8-2F567643F4D3"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -2055,9 +2061,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 121; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2065,10 +2071,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = COVIDSafe; SERVICE_UUID = "B82AB3FC-1595-4F6A-80F0-FE094CC218F9"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -2083,9 +2089,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 120; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 126; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2093,13 +2099,13 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe.uat; PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "CovidSafe UAT"; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -2113,9 +2119,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 120; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 126; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2123,13 +2129,13 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe.uat; PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "CovidSafe UAT"; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -2143,9 +2149,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 120; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 126; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2153,12 +2159,12 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe.uat; PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "CovidSafe UAT"; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -2173,9 +2179,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 120; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 126; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2183,12 +2189,12 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe.uat; PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "CovidSafe UAT"; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -2321,9 +2327,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 121; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2331,11 +2337,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = COVIDSafe; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -2349,9 +2355,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 121; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 45792XH5L8; INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -2359,10 +2365,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.6; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = COVIDSafe; SERVICE_UUID = "B82AB3FC-1595-4F6A-80F0-FE094CC218F9"; SWIFT_OBJC_BRIDGING_HEADER = "CovidSafe/covid-Bridging-Header.h"; SWIFT_REFLECTION_METADATA_LEVEL = none; diff --git a/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-production.xcscheme b/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-production.xcscheme index 1910c86..efa0167 100644 --- a/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-production.xcscheme +++ b/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-production.xcscheme @@ -31,7 +31,7 @@ Void) { - let keychain = KeychainSwift() + let keychain = KeychainSwift.shared // block api call only if refresh token exists, if it doesn't it means the app should get it for the first time if UserDefaults.standard.bool(forKey: "ReauthenticationNeededKey") && keychain.get("REFRESH_TOKEN") != nil { @@ -132,13 +137,20 @@ class AuthenticationAPI: CovidSafeAuthenticatedAPI { completion(response, nil) } } else { + let refreshTokenBeforeRefresh = keychain.get("REFRESH_TOKEN") AuthenticationAPI.issueJWTTokenAPI { (response, error) in guard let jwt = response?.token, let refresh = response?.refreshToken, error == nil else { - // set corrupted + // if the token in the system before the call is different than the one in keychain, this is a concurrency issue, the token was refresh already and that is why this failed. We should not start re-authentication + guard refreshTokenBeforeRefresh == keychain.get("REFRESH_TOKEN") else { + completion(response, .TokenAlreadyRefreshedError) + return + } + + // set corrupted only when it has not been changed UserDefaults.standard.set(true, forKey: "ReauthenticationNeededKey") completion(response, .TokenExpiredError) return diff --git a/CovidSafe/API/ChangePostcodeAPI.swift b/CovidSafe/API/ChangePostcodeAPI.swift index 72a79e6..18e7e23 100644 --- a/CovidSafe/API/ChangePostcodeAPI.swift +++ b/CovidSafe/API/ChangePostcodeAPI.swift @@ -20,11 +20,17 @@ class ChangePostcodeAPI: CovidSafeAuthenticatedAPI { let params = [ "postcode": newPostcode, ] + + guard let authHeaders = try? authenticatedHeaders() else { + completion(.RequestError) + return + } + CovidNetworking.shared.session.request("\(apiHost)/device", method: .post, parameters: params, encoding: JSONEncoding.default, - headers: authenticatedHeaders, + headers: authHeaders, interceptor: CovidRequestRetrier(retries:3)).validate().responseDecodable(of: DeviceResponse.self) { (response) in switch response.result { case .success: diff --git a/CovidSafe/API/CovidNetworking.swift b/CovidSafe/API/CovidNetworking.swift index e18cef6..15826d7 100644 --- a/CovidSafe/API/CovidNetworking.swift +++ b/CovidSafe/API/CovidNetworking.swift @@ -56,6 +56,8 @@ enum CovidSafeAPIError: Error { case ResponseError case ServerError case TokenExpiredError + case TokenAlreadyRefreshedError + case MaxRegistrationError case UnknownError } @@ -63,18 +65,16 @@ class CovidSafeAuthenticatedAPI { static var isBusy = false - static var authenticatedHeaders: HTTPHeaders { - get { - let keychain = KeychainSwift() - - guard let token = keychain.get("JWT_TOKEN") else { - return [] - } - let headers: HTTPHeaders = [ - "Authorization": "Bearer \(token)" - ] - return headers + static func authenticatedHeaders() throws -> HTTPHeaders { + let keychain = KeychainSwift.shared + + guard let token = keychain.get("JWT_TOKEN") else { + throw CovidSafeAPIError.RequestError } + let headers: HTTPHeaders = [ + "Authorization": "Bearer \(token)" + ] + return headers } static func processUnauthorizedError(_ data: Data) -> CovidSafeAPIError { diff --git a/CovidSafe/API/CovidRequestRetrier.swift b/CovidSafe/API/CovidRequestRetrier.swift index b038cbb..6b08182 100644 --- a/CovidSafe/API/CovidRequestRetrier.swift +++ b/CovidSafe/API/CovidRequestRetrier.swift @@ -20,8 +20,12 @@ final class CovidRequestRetrier: Alamofire.RequestInterceptor { func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { var urlRequest = urlRequest - let keychain = KeychainSwift() + let keychain = KeychainSwift.shared let refreshExists = keychain.get("REFRESH_TOKEN") != nil + + // turn off geolock error + UserDefaults.standard.setValue(false, forKey: showGeolockErrorKey) + // prevent authenticated api calls if the re-registration flow has been started if UserDefaults.standard.bool(forKey: "ReauthenticationNeededKey") && refreshExists { @@ -81,13 +85,19 @@ final class CovidRequestRetrier: Alamofire.RequestInterceptor { return completion(.retryWithDelay(1.0)) } + if let serverHeader = response.headers.first(where: { $0.name == "Server" }), + response.statusCode == 403 && serverHeader.value == "CloudFront" { + UserDefaults.standard.setValue(true, forKey: showGeolockErrorKey) + return completion(.doNotRetryWithError(error)) + } + if !triedRefresh && - (response.statusCode == 403 || response.statusCode == 401) { + (response.statusCode == 401 || response.statusCode == 403) { triedRefresh = true retriesExecuted += 1 AuthenticationAPI.issueTokensAPI { (response, authError) in // this will update the tokens automatically - guard let respError = authError, respError == .TokenExpiredError else { + if let respError = authError, respError == .TokenExpiredError { completion(.doNotRetryWithError(error)) return } diff --git a/CovidSafe/API/GetTempIdAPI.swift b/CovidSafe/API/GetTempIdAPI.swift index 3020484..58f6099 100644 --- a/CovidSafe/API/GetTempIdAPI.swift +++ b/CovidSafe/API/GetTempIdAPI.swift @@ -26,7 +26,7 @@ class GetTempIdAPI: CovidSafeAuthenticatedAPI { "version" : apiVersion ] - guard authenticatedHeaders.count > 0 else { + guard let authHeaders = try? authenticatedHeaders(), authHeaders.count > 0 else { completion(nil, nil, nil, .TokenExpiredError) return } @@ -34,7 +34,7 @@ class GetTempIdAPI: CovidSafeAuthenticatedAPI { CovidNetworking.shared.session.request("\(apiHost)/getTempId", method: .get, parameters: params, - headers: authenticatedHeaders, + headers: authHeaders, interceptor: CovidRequestRetrier(retries: 3)).validate().responseDecodable(of: TempIdResponse.self) { (response) in switch response.result { case .success: diff --git a/CovidSafe/API/MessageAPI.swift b/CovidSafe/API/MessageAPI.swift index 82867e3..0f000db 100644 --- a/CovidSafe/API/MessageAPI.swift +++ b/CovidSafe/API/MessageAPI.swift @@ -65,7 +65,7 @@ class MessageAPI: CovidSafeAuthenticatedAPI { var shouldGetMessages = true let calendar = NSCalendar.current - let currentDate = calendar.startOfDay(for: Date()) + let currentDate = Date() // if the current version is newer than the last version checked, allow messages call if let currVersionStr = Bundle.main.version, let currVersion = Int(currVersionStr), currVersion > versionChecked { @@ -77,7 +77,7 @@ class MessageAPI: CovidSafeAuthenticatedAPI { let components = calendar.dateComponents([.hour], from: lastCheckedDate, to: currentDate) if let numHours = components.hour { - shouldGetMessages = numHours > 4 + shouldGetMessages = numHours >= 4 } } @@ -109,10 +109,15 @@ class MessageAPI: CovidSafeAuthenticatedAPI { isBusy = true + guard let authHeaders = try? authenticatedHeaders() else { + completion(nil, .RequestError) + return + } + CovidNetworking.shared.session.request("\(apiHost)/messages", method: .get, parameters: params, - headers: authenticatedHeaders, + headers: authHeaders, interceptor: CovidRequestRetrier(retries: 3) ).validate().responseDecodable(of: MessageResponse.self) { (response) in switch response.result { @@ -120,7 +125,7 @@ class MessageAPI: CovidSafeAuthenticatedAPI { guard let messageResponse = response.value else { return } // save successful timestamp - let minutesToDefer = Int.random(in: 0..<10) + let minutesToDefer = Int.random(in: 0..<30) let calendar = NSCalendar.current let currentDate = Date() if let deferredDate = calendar.date(byAdding: .minute, value: minutesToDefer, to: currentDate) { diff --git a/CovidSafe/API/PhoneValidationAPI.swift b/CovidSafe/API/PhoneValidationAPI.swift index 4088050..d56ae82 100644 --- a/CovidSafe/API/PhoneValidationAPI.swift +++ b/CovidSafe/API/PhoneValidationAPI.swift @@ -11,7 +11,7 @@ import Alamofire class PhoneValidationAPI { static func verifyPhoneNumber(regInfo: RegistrationRequest, - completion: @escaping (String?, Swift.Error?) -> Void) { + completion: @escaping (String?, CovidSafeAPIError?) -> Void) { guard let apiHost = PlistHelper.getvalueFromInfoPlist(withKey: "API_Host", plistName: "CovidSafe-config") else { return @@ -33,8 +33,22 @@ class PhoneValidationAPI { case .success: guard let authResponse = response.value else { return } completion(authResponse.session, nil) - case let .failure(error): - completion(nil, error) + case .failure(_): + var apiError = CovidSafeAPIError.RequestError + + if let respData = response.data { + do { + let errorResponse = try JSONDecoder().decode(CovidSafeErrorResponse.self, from: respData) + if errorResponse.message == "MaxRegistrationsReached" { + apiError = .MaxRegistrationError + } + } catch { + // unable to parse response + apiError = .ResponseError + } + } + completion(nil, apiError) + } } } diff --git a/CovidSafe/API/RestrictionsAPI.swift b/CovidSafe/API/RestrictionsAPI.swift index 0c7000e..7b471e7 100644 --- a/CovidSafe/API/RestrictionsAPI.swift +++ b/CovidSafe/API/RestrictionsAPI.swift @@ -24,10 +24,15 @@ class RestrictionsAPI: CovidSafeAuthenticatedAPI { let params = ["state": "\(forState.rawValue.lowercased())"] + guard let authHeaders = try? authenticatedHeaders() else { + completion(nil, .RequestError) + return + } + CovidNetworking.shared.session.request("\(apiHost)/restrictions", method: .get, parameters: params, - headers: authenticatedHeaders, + headers: authHeaders, interceptor: CovidRequestRetrier(retries: 3) ).validate().responseDecodable(of: StateRestriction.self) { (response) in switch response.result { diff --git a/CovidSafe/API/StatisticsAPI.swift b/CovidSafe/API/StatisticsAPI.swift index 4f8a2d7..c5f79d3 100644 --- a/CovidSafe/API/StatisticsAPI.swift +++ b/CovidSafe/API/StatisticsAPI.swift @@ -20,10 +20,15 @@ class StatisticsAPI: CovidSafeAuthenticatedAPI { let parameters = ["state" : "\(forState.rawValue)"] + guard let authHeaders = try? authenticatedHeaders() else { + completion(nil, .RequestError) + return + } + CovidNetworking.shared.session.request("\(apiHost)/v2/statistics", method: .get, parameters: parameters, - headers: authenticatedHeaders, + headers: authHeaders, interceptor: CovidRequestRetrier(retries: 3) ).validate().responseDecodable(of: StatisticsResponse.self) { (response) in switch response.result { diff --git a/CovidSafe/AppDelegate.swift b/CovidSafe/AppDelegate.swift index 073de3f..bf97423 100644 --- a/CovidSafe/AppDelegate.swift +++ b/CovidSafe/AppDelegate.swift @@ -21,7 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { setupCoredataDir() let firstRun = UserDefaults.standard.bool(forKey: "HasBeenLaunched") if( !firstRun ) { - let keychain = KeychainSwift() + let keychain = KeychainSwift.shared keychain.clear() UserDefaults.standard.set(true, forKey: "HasBeenLaunched") } diff --git a/CovidSafe/HomeView.xib b/CovidSafe/HomeView.xib index 0cba1ef..12dc402 100644 --- a/CovidSafe/HomeView.xib +++ b/CovidSafe/HomeView.xib @@ -21,6 +21,7 @@ + @@ -127,19 +128,19 @@ - + - + - + - + - + @@ -246,7 +247,7 @@ - + @@ -255,8 +256,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -327,7 +389,7 @@ - + @@ -394,7 +456,7 @@ - + @@ -473,7 +535,7 @@ - + @@ -592,7 +654,7 @@ - + @@ -610,7 +672,7 @@ - + @@ -743,7 +805,7 @@ - + @@ -876,7 +938,7 @@ - + @@ -885,7 +947,7 @@