Skip to content

Commit 324493d

Browse files
authored
Merge pull request #479 from DevKor-github/codexd/451-configure-release-firebase
chore: validate release Firebase config
2 parents bd84cde + ada4e22 commit 324493d

3 files changed

Lines changed: 124 additions & 20 deletions

File tree

android/app/build.gradle

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,89 @@ plugins {
55
id "dev.flutter.flutter-gradle-plugin"
66
}
77

8+
def androidApplicationId = "club.devkor.ontime"
89
def isReleaseBuild = gradle.startParameter.taskNames.any {
910
it.toLowerCase().contains("release")
1011
}
1112
def googleServicesBuildType = isReleaseBuild ? "release" : "debug"
12-
def googleServicesConfigFiles = [
13-
file("src/${googleServicesBuildType}/google-services.json"),
14-
file("google-services.json"),
15-
file("src/google-services.json"),
16-
]
17-
def releaseGoogleServicesConfigFiles = [
18-
file("src/release/google-services.json"),
19-
file("google-services.json"),
20-
file("src/google-services.json"),
21-
]
22-
def hasReleaseGoogleServicesConfig = releaseGoogleServicesConfigFiles.any {
23-
it.exists()
13+
def googleServicesConfigCandidates = { buildType ->
14+
[
15+
file("src/${buildType}/google-services.json"),
16+
file("google-services.json"),
17+
file("src/google-services.json"),
18+
]
2419
}
20+
def selectGoogleServicesConfigFile = { buildType ->
21+
googleServicesConfigCandidates(buildType).find { it.exists() }
22+
}
23+
def releaseGoogleServicesConfigFile = selectGoogleServicesConfigFile("release")
24+
def validateGoogleServicesPackageName = { File configFile ->
25+
def parsedConfig
26+
27+
try {
28+
parsedConfig = new groovy.json.JsonSlurper().parse(configFile)
29+
} catch (Exception exception) {
30+
throw new GradleException(
31+
"Unable to parse Android Firebase config at ${configFile}. " +
32+
"Confirm ANDROID_GOOGLE_SERVICES_JSON_B64 decodes to valid google-services.json.",
33+
exception
34+
)
35+
}
36+
37+
def matchingClient = (parsedConfig.client ?: []).find { client ->
38+
client?.client_info?.android_client_info?.package_name == androidApplicationId
39+
}
40+
41+
if (!matchingClient) {
42+
throw new GradleException(
43+
"Android Firebase config at ${configFile} must include a client for package " +
44+
"${androidApplicationId}. Update the release google-services.json source or " +
45+
"ANDROID_GOOGLE_SERVICES_JSON_B64 secret before building a release."
46+
)
47+
}
48+
}
49+
50+
if (isReleaseBuild) {
51+
if (!releaseGoogleServicesConfigFile) {
52+
throw new GradleException(
53+
"Android release builds require Firebase config. " +
54+
"Provide android/app/src/release/google-services.json, usually by " +
55+
"decoding the ANDROID_GOOGLE_SERVICES_JSON_B64 CI secret before " +
56+
"running a release build."
57+
)
58+
}
2559

26-
if (isReleaseBuild && !hasReleaseGoogleServicesConfig) {
27-
throw new GradleException(
28-
"Android release builds require Firebase config. " +
29-
"Provide android/app/src/release/google-services.json, usually by " +
30-
"decoding the ANDROID_GOOGLE_SERVICES_JSON_B64 CI secret before " +
31-
"running a release build."
32-
)
60+
validateGoogleServicesPackageName(releaseGoogleServicesConfigFile)
3361
}
3462

63+
def googleServicesConfigFiles = googleServicesConfigCandidates(googleServicesBuildType)
64+
3565
if (googleServicesConfigFiles.any { it.exists() }) {
3666
apply plugin: "com.google.gms.google-services"
3767
} else {
3868
logger.lifecycle("google-services.json not found; skipping Google Services plugin for this local build.")
3969
}
4070

71+
tasks.register("validateAndroidGoogleServices") {
72+
group = "verification"
73+
description = "Validates Android release google-services.json exists and matches ${androidApplicationId}."
74+
75+
doLast {
76+
def configFile = selectGoogleServicesConfigFile("release")
77+
78+
if (!configFile) {
79+
throw new GradleException(
80+
"Android release Firebase config is missing. Provide " +
81+
"android/app/src/release/google-services.json or decode " +
82+
"ANDROID_GOOGLE_SERVICES_JSON_B64 before running this task."
83+
)
84+
}
85+
86+
validateGoogleServicesPackageName(configFile)
87+
logger.lifecycle("Validated Android release Firebase config: ${configFile}")
88+
}
89+
}
90+
4191
def isReleaseTask = gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") }
4292
def releaseSigningProperties = new Properties()
4393
def releaseSigningPropertiesFile = rootProject.file("key.properties")
@@ -107,7 +157,7 @@ android {
107157
}
108158

109159
defaultConfig {
110-
applicationId = "club.devkor.ontime"
160+
applicationId = androidApplicationId
111161
minSdk = 23
112162
targetSdk = flutter.targetSdkVersion
113163
versionCode = flutter.versionCode

docs/Android-Release-Configuration.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Android release builds must include the production Firebase client config so Fir
88

99
The release verification workflow decodes this secret to `android/app/src/release/google-services.json` before running the Android release build. Generated `google-services.json` files are ignored and must not be committed.
1010

11+
The decoded file must include an Android client whose package name is `club.devkor.ontime`. Gradle validates this package match for release builds and through the focused validation task below.
12+
1113
## Google Play Internal Testing Deploy
1214

1315
Use the `Android Play Internal Deploy` GitHub Actions workflow to build a signed
@@ -73,6 +75,19 @@ On macOS, use `base64 -D` instead of `base64 --decode`.
7375

7476
Debug/local builds may run without Android Firebase config. Release builds fail clearly if `android/app/src/release/google-services.json`, `android/app/google-services.json`, or `android/app/src/google-services.json` is missing.
7577

78+
To validate only the Firebase config without requiring release signing inputs, materialize the ignored release config file and run:
79+
80+
```sh
81+
cd android
82+
gradle :app:validateAndroidGoogleServices
83+
```
84+
85+
The task fails if the file is missing, invalid JSON, or missing a `client_info.android_client_info.package_name` entry for `club.devkor.ontime`.
86+
87+
## Auth Fingerprints
88+
89+
Release SHA-1 and SHA-256 fingerprints must be added to Firebase after the release signing owner, upload key, and Play App Signing setup are finalized. Track that work in #454; do not rotate or replace `ANDROID_GOOGLE_SERVICES_JSON_B64` for fingerprint changes until the release owner confirms the final signing fingerprints.
90+
7691
## Verification
7792

7893
Use the `Android Release Verification` GitHub Actions workflow to confirm CI can reproduce the release build with the configured secret. For production readiness, also install a real Android release build and verify Firebase initializes and FCM token registration reaches the backend.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Release Firebase Config Plan (#451)
2+
3+
## Scope
4+
5+
- Define the Android release Firebase config source for `google-services.json`.
6+
- Keep the real Firebase config out of git by documenting the CI secret decode path.
7+
- Validate that the release config contains an Android client for package `club.devkor.ontime`.
8+
- Document that release auth SHA-1/SHA-256 fingerprints are added after signing decisions are finalized.
9+
10+
## Files Likely Touched
11+
12+
- `android/app/build.gradle`
13+
- `docs/Android-Release-Configuration.md`
14+
- `plans/release-firebase-config-451.md`
15+
16+
## Implementation Approach
17+
18+
1. Reuse the existing release config locations, preferring `android/app/src/release/google-services.json`.
19+
2. Parse the selected release `google-services.json` with Gradle/Groovy JSON support when a release build is requested.
20+
3. Fail release builds clearly when the config is missing or when no Android client has package name `club.devkor.ontime`.
21+
4. Add a focused Gradle validation task so CI or developers can validate the release Firebase config before a full release build.
22+
5. Update release docs with the validation behavior, CI secret source, and the remaining fingerprint handoff to the Play signing issue.
23+
24+
## Verification
25+
26+
- `cd android && gradle :app:validateAndroidGoogleServices` with a temporary matching `android/app/src/release/google-services.json`.
27+
- `cd android && gradle :app:validateAndroidGoogleServices` with a temporary mismatched package to confirm failure.
28+
- `flutter analyze` if local Flutter/Android tooling is available and the change does not require secrets.
29+
30+
## Blockers
31+
32+
- The actual production `ANDROID_GOOGLE_SERVICES_JSON_B64` secret cannot be verified without repository/environment secret access.
33+
- Release auth SHA-1/SHA-256 fingerprints cannot be added until signing and Play App Signing decisions are finalized in #450/#454.
34+
35+
## Explicitly Left Out
36+
37+
- Creating or committing a real `google-services.json`.
38+
- Modifying release signing ownership, keystore setup, or versioning.
39+
- Building/uploading the signed AAB or performing Play Console verification.

0 commit comments

Comments
 (0)