Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Felix Foertsch
Luca Android
Commits
93bc59b9
Commit
93bc59b9
authored
May 07, 2021
by
Ulrich Scheller
Browse files
Release 1.7.0
parent
0525447b
Changes
111
Expand all
Hide whitespace changes
Inline
Side-by-side
CHANGELOG.md
View file @
93bc59b9
# Release 1.7.0
-Include "my luca" tab
-Bug fixes and improvements
With this update we introduce another tab: The "my luca" tab. In this tab you are able to import documents that may be helpful for the locations you check into.
# Release 1.6.7
-
Added support for CWA compatible QR codes
...
...
Luca/Jenkinsfile
View file @
93bc59b9
...
...
@@ -2,6 +2,9 @@ pipeline {
agent
{
label
(
'docker'
)
}
options
{
timeout
(
time:
30
,
unit:
'MINUTES'
)
}
environment
{
CONTAINER
=
'harbor.seamlessme.local/seamlessme/android:build'
...
...
@@ -26,19 +29,22 @@ pipeline {
steps
{
sh
'''
cd Luca
docker run --rm -u $(id -u):$(id -g) -v `pwd`:/Luca ${CONTAINER} bash -c "cd /Luca/app; ./../gradlew :app:test"
docker run --rm -u $(id -u):$(id -g) -v `pwd`:/Luca ${CONTAINER} bash -c "cd /Luca/app; ./../gradlew :app:test
Debug
"
'''
}
}
stage
(
'Build Debug'
)
{
stage
(
'Build Debug
& QA
'
)
{
steps
{
withCredentials
([
usernamePassword
(
credentialsId:
"luca-staging-api-basic-auth-user-pw"
,
usernameVariable:
'STAGING_API_USERNAME'
,
passwordVariable:
'STAGING_API_PASSWORD'
),
])
{
sh
'''
cd Luca
BUILD_NAME=$(python -c "print('$BUILD_TAG'.split('%2F')[-1].replace(' ', ''))")
STAGING_PARAMS="-PSTAGING_API_USERNAME=$QUOTE$STAGING_API_USERNAME$QUOTE -PSTAGING_API_PASSWORD=$QUOTE$STAGING_API_PASSWORD$QUOTE"
CMD="cd /Luca/app; ./../gradlew :app:assembleDebug $STAGING_PARAMS && mv build/outputs/apk/debug/app-debug.apk build/outputs/apk/debug/app-debug_${BUILD_NUMBER}.apk"
CMD="cd /Luca/app; ./../gradlew :app:assembleDebug :app:assembleQa $STAGING_PARAMS"
CMD="$CMD && cd build/outputs/apk/debug && mv app-debug.apk app-debug_${BUILD_NAME}.apk"
CMD="$CMD && cd ../qa/ && mv app-qa.apk app-qa_${BUILD_NAME}.apk"
docker run --rm -u $(id -u):$(id -g) -v `pwd`:/Luca ${CONTAINER} bash -c "$CMD"
'''
}
...
...
@@ -54,10 +60,11 @@ pipeline {
])
{
sh
'''
cd Luca
BUILD_NAME=$(python -c "print('$BUILD_TAG'.split('%2F')[-1].replace(' ', ''))")
LOCAL_KEYSTORE_FILE=$(basename $KEYSTORE_FILE)
KEYSTORE_PARAMS="-PC4L_SIGNING_STORE_FILE=$LOCAL_KEYSTORE_FILE -PC4L_SIGNING_KEY_PASSWORD=$C4L_SIGNING_KEY_PASSWORD -PC4L_SIGNING_KEY_ALIAS=$C4L_SIGNING_KEY_ALIAS -PC4L_SIGNING_STORE_PASSWORD=$C4L_SIGNING_STORE_PASSWORD"
CMD="cd /Luca/app; ./../gradlew :app:assembleRelease $KEYSTORE_PARAMS"
CMD="$CMD &&
mv
build/outputs/apk/release
/
app-release.apk
build/outputs/apk/release/
app-release_${BUILD_N
UMBER
}.apk"
CMD="$CMD &&
cd
build/outputs/apk/release
&& mv
app-release.apk app-release_${BUILD_N
AME
}.apk"
docker run --rm -u $(id -u):$(id -g) -v `pwd`:/Luca -v $KEYSTORE_FILE:/Luca/app/$LOCAL_KEYSTORE_FILE ${CONTAINER} bash -c "$CMD"
'''
}
...
...
@@ -96,6 +103,7 @@ pipeline {
post
{
always
{
junit
'Luca/app/build/test-results/**/*.xml'
cleanWs
()
}
}
...
...
Luca/app/build.gradle
View file @
93bc59b9
...
...
@@ -8,8 +8,8 @@ android {
applicationId
"de.culture4life.luca"
minSdkVersion
21
targetSdkVersion
30
versionCode
5
8
versionName
"1.
6.7
"
versionCode
5
9
versionName
"1.
7.0
"
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs
{
...
...
@@ -33,6 +33,7 @@ android {
shrinkResources
true
proguardFiles
getDefaultProguardFile
(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
signingConfig
signingConfigs
.
culture4life
buildConfigField
"String"
,
"API_BASE_URL"
,
'"https://app.luca-app.de"'
buildConfigField
"String"
,
"STAGING_API_USERNAME"
,
'""'
buildConfigField
"String"
,
"STAGING_API_PASSWORD"
,
'""'
}
...
...
@@ -42,23 +43,34 @@ android {
versionNameSuffix
" Debug"
applicationIdSuffix
".debug"
signingConfig
signingConfigs
.
debug
buildConfigField
"String"
,
"API_BASE_URL"
,
'"https://app-dev.luca-app.de"'
buildConfigField
"String"
,
"STAGING_API_USERNAME"
,
project
.
getProperties
().
getOrDefault
(
"STAGING_API_USERNAME"
,
'"<staging username>"'
)
buildConfigField
"String"
,
"STAGING_API_PASSWORD"
,
project
.
getProperties
().
getOrDefault
(
"STAGING_API_PASSWORD"
,
'"<staging password>"'
)
}
qa
{
initWith
debug
versionNameSuffix
" QA"
applicationIdSuffix
".qa"
buildConfigField
"String"
,
"API_BASE_URL"
,
'"https://app-qs.luca-app.de"'
}
}
compileOptions
{
sourceCompatibility
JavaVersion
.
VERSION_1_8
targetCompatibility
JavaVersion
.
VERSION_1_8
}
testOptions
{
unitTests
.
includeAndroidResources
true
animationsDisabled
true
unitTests
{
includeAndroidResources
=
true
returnDefaultValues
=
false
}
}
}
play
{
serviceAccountCredentials
.
set
(
file
(
"service-account.json"
))
defaultToAppBundles
.
set
(
true
)
track
.
set
(
"internal
-testing
"
)
track
.
set
(
"internal"
)
}
dependencies
{
...
...
@@ -77,6 +89,8 @@ dependencies {
implementation
'androidx.work:work-runtime:2.5.0'
implementation
'androidx.work:work-rxjava3:2.5.0'
implementation
'com.auth0:java-jwt:3.15.0'
implementation
'com.github.akarnokd:rxjava3-extensions:3.0.1'
implementation
'com.github.akarnokd:rxjava3-bridge:3.0.0'
implementation
'com.github.kenglxn.QRGen:android:2.6.0'
...
...
Luca/app/src/androidTest/java/de/culture4life/luca/network/NetworkManagerTest.java
0 → 100644
View file @
93bc59b9
package
de.culture4life.luca.network
;
import
com.google.gson.JsonObject
;
import
de.culture4life.luca.BuildConfig
;
import
de.culture4life.luca.LucaApplication
;
import
de.culture4life.luca.network.endpoints.LucaEndpointsV3
;
import
de.culture4life.luca.network.pojo.UserDeletionRequestData
;
import
org.junit.Assert
;
import
org.junit.Before
;
import
org.junit.Test
;
import
androidx.test.platform.app.InstrumentationRegistry
;
import
retrofit2.HttpException
;
public
class
NetworkManagerTest
{
private
LucaApplication
application
;
private
NetworkManager
networkManager
;
private
LucaEndpointsV3
lucaEndpoints
;
@Before
public
void
setup
()
{
application
=
(
LucaApplication
)
InstrumentationRegistry
.
getInstrumentation
().
getTargetContext
().
getApplicationContext
();
application
.
getCryptoManager
().
initialize
(
application
).
blockingAwait
();
networkManager
=
application
.
getNetworkManager
();
networkManager
.
doInitialize
(
application
).
blockingAwait
();
lucaEndpoints
=
networkManager
.
getLucaEndpointsV3
().
blockingGet
();
}
@Test
public
void
getDailyKeyPairPublicKey_call_isSuccessful
()
{
JsonObject
response
=
lucaEndpoints
.
getDailyKeyPairPublicKey
().
blockingGet
();
Assert
.
assertTrue
(
response
.
has
(
"publicKey"
));
}
@Test
(
expected
=
HttpException
.
class
)
public
void
deleteUser_invalidRequestBody_fails
()
{
if
(
BuildConfig
.
DEBUG
)
{
UserDeletionRequestData
data
=
new
UserDeletionRequestData
(
"..."
);
lucaEndpoints
.
deleteUser
(
"9a5c8715-2810-4e17-a3c9-0c8190507dd5"
,
data
)
.
blockingAwait
();
}
}
}
\ No newline at end of file
Luca/app/src/androidTest/java/de/culture4life/luca/registration/RegistrationManagerTest.java
0 → 100644
View file @
93bc59b9
package
de.culture4life.luca.registration
;
import
de.culture4life.luca.BuildConfig
;
import
de.culture4life.luca.LucaApplication
;
import
org.junit.Assert
;
import
org.junit.Assume
;
import
org.junit.Before
;
import
org.junit.Test
;
import
androidx.test.platform.app.InstrumentationRegistry
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
public
class
RegistrationManagerTest
{
private
LucaApplication
application
;
private
RegistrationManager
registrationManager
;
@Before
public
void
setup
()
{
Assume
.
assumeTrue
(
BuildConfig
.
DEBUG
);
application
=
(
LucaApplication
)
InstrumentationRegistry
.
getInstrumentation
().
getTargetContext
().
getApplicationContext
();
registrationManager
=
application
.
getRegistrationManager
();
registrationManager
.
doInitialize
(
application
).
blockingAwait
();
}
@Test
public
void
registerUser_hasCompletedRegistration_isTrue
()
{
registrationManager
.
registerUser
().
blockingAwait
();
Boolean
isRegistered
=
registrationManager
.
hasCompletedRegistration
().
blockingGet
();
Assert
.
assertTrue
(
isRegistered
);
}
@Test
public
void
hasCompletedRegistration_afterDeleteAll_isFalse
()
{
registrationManager
.
registerUser
().
blockingAwait
();
registrationManager
.
deleteRegistrationData
().
blockingAwait
();
Boolean
isRegistered
=
registrationManager
.
hasCompletedRegistration
().
blockingGet
();
assertFalse
(
isRegistered
);
}
}
\ No newline at end of file
Luca/app/src/main/AndroidManifest.xml
View file @
93bc59b9
...
...
@@ -21,12 +21,12 @@
android:label=
"@string/app_name"
android:roundIcon=
"@mipmap/ic_launcher_round"
android:supportsRtl=
"true"
android:theme=
"@style/Theme.
App
.DayNight"
>
android:theme=
"@style/Theme.
Luca
.DayNight"
>
<activity
android:name=
".ui.splash.SplashActivity"
android:screenOrientation=
"portrait"
android:theme=
"@style/Theme.
App
.DayNight.Splash"
>
android:theme=
"@style/Theme.
Luca
.DayNight.Splash"
>
<intent-filter>
<action
android:name=
"android.intent.action.MAIN"
/>
<category
android:name=
"android.intent.category.LAUNCHER"
/>
...
...
Luca/app/src/main/java/de/culture4life/luca/LucaApplication.java
View file @
93bc59b9
...
...
@@ -25,8 +25,10 @@ import de.culture4life.luca.notification.LucaNotificationManager;
import
de.culture4life.luca.preference.PreferencesManager
;
import
de.culture4life.luca.registration.RegistrationManager
;
import
de.culture4life.luca.service.LucaService
;
import
de.culture4life.luca.testing.TestingManager
;
import
de.culture4life.luca.ui.ViewError
;
import
de.culture4life.luca.ui.dialog.BaseDialogFragment
;
import
de.culture4life.luca.util.TimeUtil
;
import
java.util.ArrayList
;
import
java.util.HashSet
;
...
...
@@ -55,6 +57,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
public
class
LucaApplication
extends
MultiDexApplication
{
private
static
final
long
MAXIMUM_TIMESTAMP_OFFSET
=
TimeUnit
.
MINUTES
.
toMillis
(
1
);
private
final
PreferencesManager
preferencesManager
;
private
final
CryptoManager
cryptoManager
;
private
final
NetworkManager
networkManager
;
...
...
@@ -65,6 +69,7 @@ public class LucaApplication extends MultiDexApplication {
private
final
MeetingManager
meetingManager
;
private
final
HistoryManager
historyManager
;
private
final
DataAccessManager
dataAccessManager
;
private
final
TestingManager
testingManager
;
private
final
GeofenceManager
geofenceManager
;
private
final
CompositeDisposable
applicationDisposable
;
...
...
@@ -91,6 +96,7 @@ public class LucaApplication extends MultiDexApplication {
meetingManager
=
new
MeetingManager
(
preferencesManager
,
networkManager
,
locationManager
,
historyManager
,
cryptoManager
);
checkInManager
=
new
CheckInManager
(
preferencesManager
,
networkManager
,
geofenceManager
,
locationManager
,
historyManager
,
cryptoManager
,
notificationManager
);
dataAccessManager
=
new
DataAccessManager
(
preferencesManager
,
networkManager
,
notificationManager
,
checkInManager
,
historyManager
,
cryptoManager
);
testingManager
=
new
TestingManager
(
preferencesManager
,
historyManager
,
registrationManager
);
applicationDisposable
=
new
CompositeDisposable
();
...
...
@@ -152,14 +158,44 @@ public class LucaApplication extends MultiDexApplication {
checkInManager
.
initialize
(
this
).
subscribeOn
(
Schedulers
.
io
()),
historyManager
.
initialize
(
this
).
subscribeOn
(
Schedulers
.
io
()),
dataAccessManager
.
initialize
(
this
).
subscribeOn
(
Schedulers
.
io
()),
testingManager
.
initialize
(
this
).
subscribeOn
(
Schedulers
.
io
()),
geofenceManager
.
initialize
(
this
).
subscribeOn
(
Schedulers
.
io
())
).
andThen
(
Completable
.
mergeArray
(
invokeServerTimeCheck
(),
invokeRotatingBackendPublicKeyUpdate
(),
invokeAccessedDataUpdate
(),
startKeepingDataUpdated
()
));
}
private
Completable
invokeServerTimeCheck
()
{
return
Completable
.
fromAction
(()
->
applicationDisposable
.
add
(
networkManager
.
getLucaEndpointsV3
()
.
flatMap
(
LucaEndpointsV3:
:
getServerTime
)
.
map
(
jsonObject
->
jsonObject
.
get
(
"unix"
).
getAsInt
())
.
flatMap
(
TimeUtil:
:
convertFromUnixTimestamp
)
.
map
(
serverTimestamp
->
Math
.
abs
(
System
.
currentTimeMillis
()
-
serverTimestamp
))
.
subscribeOn
(
Schedulers
.
io
())
.
subscribe
(
timestampOffset
->
{
Timber
.
d
(
"Timestamp offset: %d"
,
timestampOffset
);
if
(
timestampOffset
>
MAXIMUM_TIMESTAMP_OFFSET
)
{
showErrorAsDialog
(
new
ViewError
.
Builder
(
this
)
.
withTitle
(
R
.
string
.
error_timestamp_offset_title
)
.
withDescription
(
R
.
string
.
error_timestamp_offset_description
)
.
withResolveLabel
(
R
.
string
.
action_resolve
)
.
withResolveAction
(
Completable
.
fromAction
(()
->
{
Intent
intent
=
new
Intent
(
android
.
provider
.
Settings
.
ACTION_DATE_SETTINGS
);
intent
.
setFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
);
startActivity
(
intent
);
}))
.
removeWhenShown
()
.
build
());
}
},
throwable
->
Timber
.
w
(
"Unable to get server time offset: %s"
,
throwable
.
toString
())
)));
}
private
Completable
invokeRotatingBackendPublicKeyUpdate
()
{
return
Completable
.
fromAction
(()
->
applicationDisposable
.
add
(
cryptoManager
.
updateDailyKeyPairPublicKey
()
.
doOnError
(
throwable
->
{
...
...
@@ -262,6 +298,8 @@ public class LucaApplication extends MultiDexApplication {
notificationManager
.
dispose
();
preferencesManager
.
dispose
();
geofenceManager
.
dispose
();
dataAccessManager
.
dispose
();
testingManager
.
dispose
();
stopService
();
Timber
.
i
(
"Stopping application"
);
System
.
exit
(
0
);
...
...
@@ -408,6 +446,10 @@ public class LucaApplication extends MultiDexApplication {
return
dataAccessManager
;
}
public
TestingManager
getTestingManager
()
{
return
testingManager
;
}
public
GeofenceManager
getGeofenceManager
()
{
return
geofenceManager
;
}
...
...
Luca/app/src/main/java/de/culture4life/luca/checkin/ArchivedCheckInData.java
View file @
93bc59b9
...
...
@@ -6,6 +6,10 @@ import com.google.gson.annotations.SerializedName;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* Model of previous check-ins, stored locally and removed after two weeks {@link
* CheckInManager#deleteOldArchivedCheckInData()}.
*/
public
class
ArchivedCheckInData
{
@Expose
...
...
Luca/app/src/main/java/de/culture4life/luca/checkin/CheckInData.java
View file @
93bc59b9
...
...
@@ -7,6 +7,13 @@ import java.util.UUID;
import
androidx.annotation.Nullable
;
/**
* Check-In data containing trace id enabling health departments to contact guests in case of an
* infection.
*
* @see <a href="https://luca-app.de/securityoverview/properties/assets.html#term-Check-In">Security
* Overview: Assets</a>
*/
public
class
CheckInData
{
@SerializedName
(
"traceId"
)
...
...
Luca/app/src/main/java/de/culture4life/luca/checkin/CheckInManager.java
View file @
93bc59b9
...
...
@@ -61,12 +61,23 @@ import timber.log.Timber;
import
static
android
.
Manifest
.
permission
.
ACCESS_COARSE_LOCATION
;
import
static
android
.
Manifest
.
permission
.
ACCESS_FINE_LOCATION
;
import
static
de
.
culture4life
.
luca
.
crypto
.
HashProvider
.
TRIMMED_HASH_LENGTH
;
import
static
de
.
culture4life
.
luca
.
history
.
HistoryManager
.
KEEP_DATA_DURATION
;
import
static
de
.
culture4life
.
luca
.
location
.
GeofenceManager
.
MAXIMUM_GEOFENCE_RADIUS
;
import
static
de
.
culture4life
.
luca
.
location
.
GeofenceManager
.
MINIMUM_GEOFENCE_RADIUS
;
import
static
de
.
culture4life
.
luca
.
location
.
GeofenceManager
.
UPDATE_INTERVAL_DEFAULT
;
import
static
de
.
culture4life
.
luca
.
notification
.
LucaNotificationManager
.
NOTIFICATION_ID_EVENT
;
import
static
de
.
culture4life
.
luca
.
util
.
SerializationUtil
.
serializeToBase64
;
/**
* Facilitates check-in to a venues either by having a shown barcode scanned or scanning a printed
* QR-code.
*
* @see <a href="https://www.luca-app.de/securityoverview/processes/guest_app_checkin.html">Security
* Overview: Check-In via Mobile Phone App</a>
* @see <a href="https://www.luca-app.de/securityoverview/processes/guest_self_checkin.html">Security
* Overview: Check-In via a Printed QR Code</a>
*/
public
class
CheckInManager
extends
Manager
{
public
static
final
String
KEY_CHECKED_IN_TRACE_ID
=
"checked_in_trace_id"
;
...
...
@@ -142,6 +153,13 @@ public class CheckInManager extends Manager {
Check-in
*/
/**
* Perform self check-in, generating the check-in data locally and uploading it to the luca
* server.
*
* @see <a href="https://www.luca-app.de/securityoverview/processes/guest_self_checkin.html">Security
* Overview: Check-In via a Printed QR Code</a>
*/
public
Completable
checkIn
(
@NonNull
UUID
scannerId
,
@NonNull
QrCodeData
qrCodeData
)
{
return
assertNotCheckedIn
()
.
andThen
(
generateCheckInData
(
qrCodeData
,
scannerId
)
...
...
@@ -152,7 +170,7 @@ public class CheckInManager extends Manager {
}
/**
* Should be called after a check-in occurred (either triggered by the user or in the backend)
* Should be called after a check-in occurred (either triggered by the user or in the backend)
.
*/
private
Completable
processCheckIn
(
@NonNull
CheckInData
checkInData
)
{
return
Completable
.
fromAction
(()
->
this
.
checkInData
=
checkInData
)
...
...
@@ -189,6 +207,13 @@ public class CheckInManager extends Manager {
.
doOnSuccess
(
checkInRequestData
->
checkInRequestData
.
setScannerId
(
scannerId
.
toString
()));
}
/**
* Generate data required for checking in. The encrypted contact data is encrypted a second time
* using the venue's key.
*
* @see <a href="https://www.luca-app.de/securityoverview/processes/guest_self_checkin.html">Security
* Overview: Check-In via a Printed QR Code</a>
*/
private
Single
<
CheckInRequestData
>
generateCheckInData
(
@NonNull
QrCodeData
qrCodeData
,
@NonNull
PublicKey
locationPublicKey
)
{
return
Single
.
fromCallable
(()
->
{
CheckInRequestData
checkInRequestData
=
new
CheckInRequestData
();
...
...
@@ -209,7 +234,7 @@ public class CheckInManager extends Manager {
.
flatMap
(
SerializationUtil:
:
serializeToBase64
).
blockingGet
();
checkInRequestData
.
setScannerEphemeralPublicKey
(
serializedScannerPublicKey
);
byte
[]
iv
=
cryptoManager
.
generateSecureRandomData
(
16
).
blockingGet
();
byte
[]
iv
=
cryptoManager
.
generateSecureRandomData
(
TRIMMED_HASH_LENGTH
).
blockingGet
();
String
encodedIv
=
serializeToBase64
(
iv
).
blockingGet
();
checkInRequestData
.
setIv
(
encodedIv
);
...
...
@@ -311,7 +336,7 @@ public class CheckInManager extends Manager {
String
serializedTraceId
=
serializeToBase64
(
traceId
).
blockingGet
();
additionalCheckInProperties
.
setTraceId
(
serializedTraceId
);
byte
[]
iv
=
cryptoManager
.
generateSecureRandomData
(
16
).
blockingGet
();
byte
[]
iv
=
cryptoManager
.
generateSecureRandomData
(
TRIMMED_HASH_LENGTH
).
blockingGet
();
String
encodedIv
=
serializeToBase64
(
iv
).
blockingGet
();
additionalCheckInProperties
.
setIv
(
encodedIv
);
...
...
@@ -352,6 +377,12 @@ public class CheckInManager extends Manager {
Check-out
*/
/**
* Perform check-out, uploading trace ID and checkout time to the luca server.
*
* @see <a href="https://www.luca-app.de/securityoverview/processes/guest_checkout.html#checkout-process">Security
* Overview: Checkout Process</a>
*/
@SuppressLint
(
"MissingPermission"
)
public
Completable
checkOut
()
{
return
assertCheckedIn
()
...
...
@@ -377,7 +408,8 @@ public class CheckInManager extends Manager {
}
/**
* Should be called after a check-out occurred (either triggered by the user or in the backend)
* Should be called after a check-out occurred (either triggered by the user or in the
* backend).
*/
private
Completable
processCheckOut
()
{
return
getCheckInDataIfAvailable
()
...
...
@@ -386,6 +418,12 @@ public class CheckInManager extends Manager {
.
andThen
(
disableAutomaticCheckOut
());
}
/**
* Create check-out data of current trace ID and the current timestamp.
*
* @see <a href="https://www.luca-app.de/securityoverview/processes/guest_checkout.html#checkout-process">Security
* Overview: Checkout Process</a>
*/
private
Single
<
CheckOutRequestData
>
generateCheckOutData
()
{
return
Single
.
just
(
new
CheckOutRequestData
())
.
flatMap
(
checkOutRequestData
->
getCheckedInTraceId
()
...
...
@@ -601,6 +639,15 @@ public class CheckInManager extends Manager {
.
doOnNext
(
checkInData
->
Timber
.
v
(
"Check-in data updated from preferences: %s"
,
checkInData
));
}
/**
* Checking in using a scanner doesn't require the device to be online, nevertheless the backend
* is polled regularly in an attempt to provide visual feedback of a successful check-in.
*
* @param interval to poll backend at (millis)
* @return Completable to finalize check-in {@link #processCheckIn(CheckInData)}
* @see <a href="https://luca-app.de/securityoverview/processes/guest_app_checkin.html#qr-code-scanning-feedback">Security
* Overview: QR Code Scanning Feedback</a>
*/
public
Completable
requestCheckInDataUpdates
(
long
interval
)
{
return
pollCheckInData
(
interval
)
.
distinctUntilChanged
((
previous
,
current
)
->
{
...
...
@@ -773,7 +820,7 @@ public class CheckInManager extends Manager {
public
Completable
deleteOldArchivedCheckInData
()
{
return
getArchivedCheckInData
()
.
filter
(
checkInData
->
checkInData
.
getTimestamp
()
>
System
.
currentTimeMillis
()
-
TimeUnit
.
DAYS
.
toMillis
(
14
)
)
.
filter
(
checkInData
->
checkInData
.
getTimestamp
()
>
System
.
currentTimeMillis
()
-
KEEP_DATA_DURATION
)
.
toList
()
.
map
(
ArchivedCheckInData:
:
new
)
.
flatMapCompletable
(
archivedCheckInData
->
preferencesManager
.
persist
(
KEY_ARCHIVED_CHECK_IN_DATA
,
archivedCheckInData
))
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/AsymmetricCipherProvider.java
View file @
93bc59b9
...
...
@@ -29,6 +29,11 @@ import io.reactivex.rxjava3.core.Single;
import
static
com
.
nexenio
.
rxkeystore
.
RxKeyStore
.
PROVIDER_BOUNCY_CASTLE
;
/**
* Provides EC cryptography using the secp256r1 curve.
*
* Uses Bouncy Castle due to Android limitations.
*/
public
class
AsymmetricCipherProvider
extends
EcCipherProvider
{
private
static
final
String
CURVE_NAME
=
"secp256r1"
;
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/CryptoManager.java
View file @
93bc59b9
This diff is collapsed.
Click to expand it.
Luca/app/src/main/java/de/culture4life/luca/crypto/DailyKeyPairPublicKeyWrapper.java
View file @
93bc59b9
...
...
@@ -3,8 +3,13 @@ package de.culture4life.luca.crypto;
import
com.google.gson.annotations.Expose
;
import
com.google.gson.annotations.SerializedName
;
import
de.culture4life.luca.checkin.CheckInData
;
import
java.security.interfaces.ECPublicKey
;
/**
* Public part of the daily key pair, used to encrypt {@link CheckInData}.
*/
public
class
DailyKeyPairPublicKeyWrapper
{
@SerializedName
(
"id"
)
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/HashProvider.java
View file @
93bc59b9
...
...
@@ -5,8 +5,13 @@ import com.nexenio.rxkeystore.provider.hash.Sha256HashProvider;
import
androidx.annotation.NonNull
;
/**
* Provides hashes using SHA256.
*/
public
class
HashProvider
extends
Sha256HashProvider
{
public
static
final
int
TRIMMED_HASH_LENGTH
=
16
;
public
HashProvider
(
@NonNull
RxKeyStore
rxKeyStore
)
{
super
(
rxKeyStore
);
}
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/MacProvider.java
View file @
93bc59b9
...
...
@@ -5,6 +5,9 @@ import com.nexenio.rxkeystore.provider.mac.HmacProvider;
import
androidx.annotation.NonNull
;
/**
* Provides message authentication codes using HMAC-SHA256.
*/
public
class
MacProvider
extends
HmacProvider
{
public
MacProvider
(
@NonNull
RxKeyStore
rxKeyStore
)
{
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/SignatureProvider.java
View file @
93bc59b9
...
...
@@ -5,6 +5,9 @@ import com.nexenio.rxkeystore.provider.signature.BaseSignatureProvider;
import
androidx.annotation.NonNull
;
/**
* Signature provider used for signing contact data and verifying daily key.
*/
public
class
SignatureProvider
extends
BaseSignatureProvider
{
public
SignatureProvider
(
@NonNull
RxKeyStore
rxKeyStore
)
{
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/SymmetricCipherProvider.java
View file @
93bc59b9
...
...
@@ -6,12 +6,22 @@ import android.os.Build;
import
com.nexenio.rxkeystore.RxKeyStore
;
import
com.nexenio.rxkeystore.provider.cipher.symmetric.aes.AesCipherProvider
;
import
de.culture4life.luca.network.pojo.ContactData
;
import
de.culture4life.luca.network.pojo.TransferData
;
import
de.culture4life.luca.ui.qrcode.QrCodeData
;
import
javax.crypto.SecretKey
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.RequiresApi
;
import
io.reactivex.rxjava3.core.Single
;
/**
* Symmetric cipher provider, used to encrypt {@link ContactData}, {@link TransferData} and {@link
* QrCodeData} using AES-CTR with 128 bit keys.
*
* Uses Bouncy Castle due to Android limitations
*/
@RequiresApi
(
api
=
Build
.
VERSION_CODES
.
M
)
public
class
SymmetricCipherProvider
extends
AesCipherProvider
{
...
...
Luca/app/src/main/java/de/culture4life/luca/crypto/TraceIdWrapper.java