Skip to content

Commit c4618f5

Browse files
authored
Add more use cases to the App Check testapps (#719)
* Add some basic App Check tests * Address feedback
1 parent 3948e20 commit c4618f5

File tree

4 files changed

+199
-16
lines changed

4 files changed

+199
-16
lines changed

app_check/testapp/Assets/Firebase/Sample/AppCheck/UIHandler.cs

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,103 @@ public class UIHandler : MonoBehaviour {
4040

4141
private DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;
4242

43+
// Your Firebase project's Debug token goes here.
44+
// You can get this from Firebase Console, in the App Check settings.
45+
private string appCheckDebugToken = "REPLACE_WITH_APP_CHECK_TOKEN";
46+
47+
// If the App Check factory has been set
48+
protected bool factoryConfigured = false;
49+
50+
// If the testapp is running automated tests
51+
protected bool runningAutomatedTests = false;
52+
53+
public class TestAppCheckProvider : IAppCheckProvider {
54+
public TestAppCheckProvider() {}
55+
56+
public System.Threading.Tasks.Task<AppCheckToken> GetTokenAsync() {
57+
// In a normal app, you would connect to the attestation service,
58+
// and get a valid token to return.
59+
AppCheckToken token = new AppCheckToken() {
60+
Token = "TEST_TOKEN",
61+
ExpireTime = DateTime.UtcNow.AddMinutes(60)
62+
};
63+
return Task<AppCheckToken>.FromResult(token);
64+
}
65+
}
66+
67+
public class TestAppCheckProviderFactory : IAppCheckProviderFactory {
68+
public TestAppCheckProvider provider;
69+
70+
public TestAppCheckProviderFactory() {
71+
provider = new TestAppCheckProvider();
72+
}
73+
74+
public IAppCheckProvider CreateProvider(FirebaseApp app) {
75+
return provider;
76+
}
77+
}
78+
79+
protected void PrintToken(string prefix, AppCheckToken token) {
80+
DebugLog(prefix + "\n" +
81+
" " + token.Token + "\n" +
82+
" " + token.ExpireTime);
83+
}
84+
85+
void OnTokenChanged(object sender, TokenChangedEventArgs tokenArgs) {
86+
PrintToken("OnTokenChanged called:", tokenArgs.Token);
87+
}
88+
4389
// When the app starts, check to make sure that we have
4490
// the required dependencies to use Firebase, and if not,
4591
// add them if possible.
4692
protected virtual void Start() {
47-
// TODO(amaurice): Add App Check initialization logic here
93+
UIEnabled = true;
94+
95+
// Configure the Debug Factory with the Token
96+
DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugToken);
97+
}
98+
99+
void UseTestFactory() {
100+
DebugLog("Using Test Factory");
101+
FirebaseAppCheck.SetAppCheckProviderFactory(new TestAppCheckProviderFactory());
102+
InitializeFirebase();
103+
factoryConfigured = true;
104+
}
48105

106+
void UseDebugFactory() {
107+
DebugLog("Using Debug Factory");
108+
FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance);
109+
InitializeFirebase();
110+
factoryConfigured = true;
111+
}
112+
113+
void GetTokenFromDebug() {
114+
DebugLog("Getting token from Debug factory");
115+
IAppCheckProvider provider = DebugAppCheckProviderFactory.Instance.CreateProvider(FirebaseApp.DefaultInstance);
116+
provider.GetTokenAsync().ContinueWithOnMainThread(task => {
117+
if (task.IsFaulted) {
118+
DebugLog("GetTokenFromDebug failed: " + task.Exception);
119+
} else {
120+
PrintToken("GetTokenFromDebug:", task.Result);
121+
}
122+
});
123+
}
124+
125+
void AddTokenChangedListener() {
126+
DebugLog("Adding token changed listener");
127+
FirebaseAppCheck.DefaultInstance.TokenChanged += OnTokenChanged;
128+
}
129+
130+
void RemoveTokenChangedListener() {
131+
DebugLog("Removing token changed listener");
132+
FirebaseAppCheck.DefaultInstance.TokenChanged -= OnTokenChanged;
133+
}
134+
135+
protected void InitializeFirebase() {
49136
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
50137
dependencyStatus = task.Result;
51138
if (dependencyStatus == DependencyStatus.Available) {
52-
UIEnabled = true;
139+
DebugLog("Firebase Ready: " + FirebaseApp.DefaultInstance);
53140
} else {
54141
Debug.LogError(
55142
"Could not resolve all Firebase dependencies: " + dependencyStatus);
@@ -84,17 +171,54 @@ void GUIDisplayLog() {
84171
GUILayout.EndScrollView();
85172
}
86173

174+
void HandleGetAppCheckToken(Task<AppCheckToken> task) {
175+
if (task.IsFaulted) {
176+
DebugLog("GetAppCheckToken failed: " + task.Exception);
177+
} else {
178+
PrintToken("GetAppCheckToken succeeded:", task.Result);
179+
}
180+
}
181+
87182
// Render the buttons and other controls.
88183
void GUIDisplayControls() {
89184
if (UIEnabled) {
90185

186+
if (runningAutomatedTests) {
187+
GUILayout.Label("Running automated tests");
188+
return;
189+
}
190+
91191
controlsScrollViewVector = GUILayout.BeginScrollView(controlsScrollViewVector);
92192

93193
GUILayout.BeginVertical();
94194

95-
if (GUILayout.Button("Get App Check Token")) {
96-
// TODO(amaurice): That
97-
DebugLog("GetAppCheckToken unimplemented!");
195+
if (!factoryConfigured) {
196+
if (GUILayout.Button("Use Test Provider")) {
197+
UseTestFactory();
198+
}
199+
if (GUILayout.Button("Use Debug Provider")) {
200+
UseDebugFactory();
201+
}
202+
} else {
203+
if (GUILayout.Button("Get App Check Token")) {
204+
DebugLog("GetAppCheckTokenAsync(false) triggered!");
205+
FirebaseAppCheck.DefaultInstance.GetAppCheckTokenAsync(false).ContinueWithOnMainThread(HandleGetAppCheckToken);
206+
}
207+
if (GUILayout.Button("Force New App Check Token")) {
208+
DebugLog("GetAppCheckTokenAsync(true) triggered!");
209+
FirebaseAppCheck.DefaultInstance.GetAppCheckTokenAsync(true).ContinueWithOnMainThread(HandleGetAppCheckToken);
210+
}
211+
if (GUILayout.Button("Add Token Changed Listener")) {
212+
AddTokenChangedListener();
213+
}
214+
if (GUILayout.Button("Remove Token Changed Listener")) {
215+
RemoveTokenChangedListener();
216+
}
217+
}
218+
219+
// Can be called regardless of Factory status
220+
if (GUILayout.Button("Get App Check Token from Debug Provider")) {
221+
GetTokenFromDebug();
98222
}
99223

100224
GUILayout.EndVertical();
@@ -105,11 +229,6 @@ void GUIDisplayControls() {
105229
// Render the GUI:
106230
void OnGUI() {
107231
GUI.skin = fb_GUISkin;
108-
if (dependencyStatus != Firebase.DependencyStatus.Available) {
109-
GUILayout.Label("One or more Firebase dependencies are not present.");
110-
GUILayout.Label("Current dependency status: " + dependencyStatus.ToString());
111-
return;
112-
}
113232

114233
GUI.skin.textArea.fontSize = GUI.skin.textField.fontSize;
115234
// Reduce the text size on the desktop.

app_check/testapp/Assets/Firebase/Sample/AppCheck/UIHandlerAutomated.cs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,20 @@ public class UIHandlerAutomated : UIHandler {
3131

3232
private Firebase.Sample.AutomatedTestRunner testRunner;
3333

34+
// Your Firebase project's Debug token goes here.
35+
// You can get this from Firebase Console, in the App Check settings.
36+
private string appCheckDebugTokenForAutomated = "REPLACE_WITH_APP_CHECK_TOKEN";
37+
3438
protected override void Start() {
39+
// Set up the debug token, and install it as the factory to use.
40+
DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugTokenForAutomated);
41+
FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance);
42+
3543
// Set the list of tests to run, note this is done at Start since they are
3644
// non-static.
3745
Func<Task>[] tests = {
38-
// Add tests here
39-
TestDummyTest
46+
TestGetDebugToken,
47+
TestGetAppCheckToken
4048
};
4149

4250
testRunner = AutomatedTestRunner.CreateTestRunner(
@@ -49,6 +57,9 @@ protected override void Start() {
4957
"enabled. `Error Pause` should be disabled to execute this test.");
5058

5159
UIEnabled = true;
60+
// Set this state to true, since App Check is a bit finicky around start up,
61+
// this will just disable the usual UIHandler UI, since it won't work as expected.
62+
runningAutomatedTests = true;
5263
// Do not call base.Start(), as we don't want to initialize Firebase (and instead do it in the tests).
5364
}
5465

@@ -76,11 +87,50 @@ private void AssertEq<T>(string message, T value1, T value2) {
7687
}
7788
}
7889

79-
// Placeholder dummy test
80-
Task TestDummyTest() {
81-
return Task.Run(() => {
82-
DebugLog("Ran the dummy test");
90+
Task TestGetDebugToken() {
91+
IAppCheckProvider provider = DebugAppCheckProviderFactory.Instance.CreateProvider(FirebaseApp.DefaultInstance);
92+
return provider.GetTokenAsync().ContinueWithOnMainThread(task => {
93+
if (task.IsFaulted) {
94+
throw task.Exception;
95+
} else {
96+
Assert("Debug token is empty", task.Result.Token != "");
97+
98+
// The expire time should be within roughly an hour, so check for that.
99+
DateTime time = DateTime.UtcNow.AddMinutes(120);
100+
Assert("Debug token time is too long", task.Result.ExpireTime < time);
101+
}
102+
});
103+
}
104+
105+
Task TestGetAppCheckToken() {
106+
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
107+
108+
FirebaseAppCheck.DefaultInstance.GetAppCheckTokenAsync(true).ContinueWithOnMainThread(t1 => {
109+
if (t1.IsFaulted) {
110+
tcs.TrySetException(t1.Exception);
111+
} else {
112+
FirebaseAppCheck.DefaultInstance.GetAppCheckTokenAsync(false).ContinueWithOnMainThread(t2 => {
113+
if (t2.IsFaulted) {
114+
tcs.TrySetException(t2.Exception);
115+
} else if (t1.Result.Token != t2.Result.Token) {
116+
tcs.TrySetException(new Exception("GetAppCheckTokenAsync(false) returned a different token"));
117+
} else {
118+
FirebaseAppCheck.DefaultInstance.GetAppCheckTokenAsync(true).ContinueWithOnMainThread(t3 => {
119+
if (t3.IsFaulted) {
120+
throw t3.Exception;
121+
} else if (t1.Result.Token == t3.Result.Token) {
122+
tcs.TrySetException(new Exception("GetAppCheckTokenAsync(true) returned the same token"));
123+
} else {
124+
// Done with the test
125+
tcs.TrySetResult(true);
126+
}
127+
});
128+
}
129+
});
130+
}
83131
});
132+
133+
return tcs.Task;
84134
}
85135
}
86136
}

app_check/testapp/readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ Once you have done this, you can run the Unity Editor and test the application.
5858
into the folder.
5959
- NOTE: `GoogleService-Info.plist` can be placed anywhere under the
6060
`Assets` folder.
61+
- Setup your project's debug token
62+
- Generate a debug token for your Firebase project by following the
63+
instructions laid out in the
64+
[App Check documentation](https://firebase.google.com/docs/app-check/unity/debug-provider)
65+
- Edit `Assets/Firebase/Sample/AppCheck/UIHandler.cs`, replacing
66+
REPLACE_WITH_APP_CHECK_TOKEN with the debug token you have generated.
6167
- Optional: Update the Project Bundle Identifier
6268
- If you did not use `com.google.firebase.unity.appcheck.testapp`
6369
as the iOS bundle ID when creating your app in the Firebase

scripts/gha/restore_secrets.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ def main(argv):
139139
file_path = os.path.join(repo_dir, "storage", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["storage"], "UIHandler.cs")
140140
_patch_file(file_path, "REPLACE_WITH_YOUR_STORAGE_BUCKET", bucket)
141141

142+
print("Attempting to patch App Check debug token.")
143+
debug_token_path = os.path.join(secrets_dir, "app_check", "app_check_token.txt.gpg")
144+
debug_token = _decrypt(debug_token_path, passphrase)
145+
file_path = os.path.join(repo_dir, "app_check", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["app_check"], "UIHandlerAutomated.cs")
146+
_patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token)
147+
file_path = os.path.join(repo_dir, "app_check", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["app_check"], "UIHandler.cs")
148+
_patch_file(file_path, "REPLACE_WITH_APP_CHECK_TOKEN", debug_token)
149+
142150
print("Attempting to decrypt GCS service account key file.")
143151
decrypted_key_file = os.path.join(secrets_dir, "gcs_key_file.json")
144152
encrypted_key_file = decrypted_key_file + ".gpg"

0 commit comments

Comments
 (0)