Skip to content

Commit 2906310

Browse files
authored
Merge pull request #332 from RADAR-base/google-health-api
Add Google Health API authorization support
2 parents e27a682 + 4765f2b commit 2906310

5 files changed

Lines changed: 329 additions & 14 deletions

File tree

authorizer-app-backend/authorizer.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,21 @@ restSourceClients:
2626
clientId: <CLIENT_ID>
2727
clientSecret: <CLIENT_SECRET>
2828
scope: activity heartrate sleep profile
29-
usesPkce: false
3029
# Garmin OAuth2 PKCE configuration.
3130
# oauthVersion: "oauth2" selects the GarminOAuth2AuthorizationService;
3231
# "oauth1" selects the legacy GarminOAuth1AuthorizationService.
33-
# usesPkce: must be true for Garmin OAuth2 (Garmin requires PKCE).
3432
- sourceType: Garmin
3533
authorizationEndpoint: https://connect.garmin.com/oauth2Confirm
3634
tokenEndpoint: https://diauth.garmin.com/di-oauth2-service/oauth/token
3735
deregistrationEndpoint: https://apis.garmin.com/wellness-api/rest/user/registration
3836
clientId: <GARMIN_CLIENT_ID>
3937
clientSecret: <GARMIN_CLIENT_SECRET>
4038
oauthVersion: oauth2
41-
usesPkce: true
39+
# Google Health API
40+
- sourceType: Google
41+
authorizationEndpoint: https://accounts.google.com/o/oauth2/v2/auth
42+
tokenEndpoint: https://oauth2.googleapis.com/token
43+
clientId: <GOOGLE_CLIENT_ID>
44+
clientSecret: <GOOGLE_CLIENT_SECRET>
45+
scope: https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly https://www.googleapis.com/auth/googlehealth.sleep.readonly https://www.googleapis.com/auth/googlehealth.profile.readonly https://www.googleapis.com/auth/googlehealth.nutrition.readonly https://www.googleapis.com/auth/googlehealth.irn.readonly https://www.googleapis.com/auth/googlehealth.ecg.readonly
46+
oauthVersion: oauth2

authorizer-app-backend/src/main/java/org/radarbase/authorizer/api/ApiDeclarations.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ data class RestOauth2UserId(
5656
val userId: String,
5757
)
5858

59+
@Serializable
60+
data class RestGoogleHealthIdentity(
61+
val healthUserId: String,
62+
val legacyUserId: String? = null,
63+
)
64+
5965
@Serializable
6066
data class OuraAuthUserId(
6167
val age: Int? = null,

authorizer-app-backend/src/main/java/org/radarbase/authorizer/enhancer/AuthorizerResourceEnhancer.kt

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ import org.radarbase.authorizer.doa.RestSourceUserRepositoryImpl
2828
import org.radarbase.authorizer.service.DelegatedRestSourceAuthorizationService
2929
import org.radarbase.authorizer.service.DelegatedRestSourceAuthorizationService.Companion.FITBIT_AUTH
3030
import org.radarbase.authorizer.service.DelegatedRestSourceAuthorizationService.Companion.GARMIN_AUTH
31+
import org.radarbase.authorizer.service.DelegatedRestSourceAuthorizationService.Companion.GOOGLE_AUTH
3132
import org.radarbase.authorizer.service.DelegatedRestSourceAuthorizationService.Companion.OURA_AUTH
3233
import org.radarbase.authorizer.service.GarminOAuth2AuthorizationService
3334
import org.radarbase.authorizer.service.GarminOauth1AuthorizationService
35+
import org.radarbase.authorizer.service.GoogleHealthAuthorizationService
3436
import org.radarbase.authorizer.service.OAuth2RestSourceAuthorizationService
3537
import org.radarbase.authorizer.service.OuraAuthorizationService
3638
import org.radarbase.authorizer.service.RegistrationService
@@ -46,21 +48,21 @@ class AuthorizerResourceEnhancer(
4648
private val restSourceClients = RestSourceClients(
4749
config.restSourceClients
4850
.map { it.withEnv() }
51+
.map {
52+
when {
53+
it.sourceType == GARMIN_AUTH && it.oauthVersion.equals("oauth2", ignoreCase = true) ->
54+
it.copy(usesPkce = true)
55+
it.sourceType == GOOGLE_AUTH ->
56+
it.copy(usesPkce = true)
57+
else -> it
58+
}
59+
}
4960
.onEach {
5061
requireNotNull(it.clientId) { "Client ID of ${it.sourceType} is missing" }
5162
requireNotNull(it.clientSecret) { "Client secret of ${it.sourceType} is missing" }
5263
},
5364
)
5465

55-
/**
56-
* Maps a source type to its configured OAuth version (e.g., "oauth1" or "oauth2").
57-
* This is used to conditionally bind the correct authorization service implementation.
58-
* Configure via the `oauthVersion` field in `authorizer.yml` under each `restSourceClients` entry.
59-
*/
60-
private val sourceTypeOauthMap: Map<String, String> = config.restSourceClients.associate {
61-
it.sourceType to it.oauthVersion.lowercase()
62-
}
63-
6466
override val classes: Array<Class<*>>
6567
get() = listOfNotNull(
6668
Filters.cache,
@@ -112,8 +114,9 @@ class AuthorizerResourceEnhancer(
112114
bind(DelegatedRestSourceAuthorizationService::class.java)
113115
.to(RestSourceAuthorizationService::class.java)
114116

115-
// Bind Garmin service based on a configured oauthVersion: "oauth2" → PKCE flow, "oauth1" → legacy flow.
116-
if (sourceTypeOauthMap[GARMIN_AUTH].equals("oauth2", ignoreCase = true)) {
117+
// Bind Garmin service based on configured oauthVersion: "oauth2" → PKCE flow, "oauth1" → legacy flow.
118+
val garminUsesPkce = restSourceClients.clients.firstOrNull { it.sourceType == GARMIN_AUTH }?.usesPkce == true
119+
if (garminUsesPkce) {
117120
bind(GarminOAuth2AuthorizationService::class.java)
118121
.to(RestSourceAuthorizationService::class.java)
119122
.named(GARMIN_AUTH)
@@ -134,5 +137,10 @@ class AuthorizerResourceEnhancer(
134137
.to(RestSourceAuthorizationService::class.java)
135138
.named(OURA_AUTH)
136139
.`in`(Singleton::class.java)
140+
141+
bind(GoogleHealthAuthorizationService::class.java)
142+
.to(RestSourceAuthorizationService::class.java)
143+
.named(GOOGLE_AUTH)
144+
.`in`(Singleton::class.java)
137145
}
138146
}

authorizer-app-backend/src/main/java/org/radarbase/authorizer/service/DelegatedRestSourceAuthorizationService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,6 @@ class DelegatedRestSourceAuthorizationService(
6363
const val GARMIN_AUTH = "Garmin"
6464
const val FITBIT_AUTH = "FitBit"
6565
const val OURA_AUTH = "Oura"
66+
const val GOOGLE_AUTH = "GoogleHealth"
6667
}
6768
}

0 commit comments

Comments
 (0)