Skip to content

Commit a0d8507

Browse files
authored
Add implementation for FirebaseAppCheck class (#674)
* Add support for GetAppCheckToken * Rename set callback function * Add logic for listening to token changes * Use the callback queue for C# calls * Update based on feedback * Add more comments around the callbacks
1 parent 8224b90 commit a0d8507

File tree

3 files changed

+347
-13
lines changed

3 files changed

+347
-13
lines changed

app_check/src/AppCheckToken.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
using System;
18+
1719
namespace Firebase.AppCheck {
1820

1921
/// @brief Token used by the Firebase App Check service.
@@ -27,7 +29,25 @@ public struct AppCheckToken {
2729
public string Token { get; set; }
2830

2931
/// The time at which the token will expire.
30-
public System.DateTime ExpireTime { get; set; }
32+
public DateTime ExpireTime { get; set; }
33+
34+
internal static readonly DateTime s_unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
35+
36+
// Construct an AppCheckToken given the swig generated class for one.
37+
internal static AppCheckToken FromAppCheckTokenInternal(AppCheckTokenInternal tokenInternal) {
38+
return new AppCheckToken {
39+
Token = tokenInternal.token,
40+
ExpireTime = s_unixEpoch.AddMilliseconds((double)tokenInternal.expire_time_millis)
41+
};
42+
}
43+
44+
// Get the expire time in milliseconds since Epoch, which is used by C++.
45+
internal long ExpireTimeMs {
46+
get {
47+
TimeSpan ts = ExpireTime - s_unixEpoch;
48+
return (long)ts.TotalMilliseconds;
49+
}
50+
}
3151
}
3252

3353
}

app_check/src/FirebaseAppCheck.cs

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,21 @@ public sealed class FirebaseAppCheck {
2424
// The C++ object that this wraps.
2525
private AppCheckInternal appCheckInternal;
2626

27-
private static Dictionary<FirebaseApp, FirebaseAppCheck> appCheckMap =
28-
new Dictionary<FirebaseApp, FirebaseAppCheck>();
27+
// Use the FirebaseApp's name instead of the App itself, to not
28+
// keep it alive unnecessarily.
29+
private static Dictionary<string, FirebaseAppCheck> appCheckMap =
30+
new Dictionary<string, FirebaseAppCheck>();
2931
// The user provided Factory.
3032
private static IAppCheckProviderFactory appCheckFactory;
31-
private static Dictionary<FirebaseApp, IAppCheckProvider> providerMap =
32-
new Dictionary<FirebaseApp, IAppCheckProvider>();
33+
private static Dictionary<string, IAppCheckProvider> providerMap =
34+
new Dictionary<string, IAppCheckProvider>();
35+
36+
// Function for C++ to call when it needs to fetch a Token.
37+
private static AppCheckUtil.GetTokenFromCSharpDelegate getTokenDelegate =
38+
new AppCheckUtil.GetTokenFromCSharpDelegate(GetTokenFromCSharpMethod);
39+
// Function for C++ to call when the Token changes.
40+
private static AppCheckUtil.TokenChangedDelegate tokenChangedDelegate =
41+
new AppCheckUtil.TokenChangedDelegate(TokenChangedMethod);
3342

3443
// Make the constructor private, since users aren't meant to make it.
3544
private FirebaseAppCheck(AppCheckInternal internalObject) {
@@ -55,11 +64,10 @@ public static FirebaseAppCheck DefaultInstance {
5564
/// {@link FirebaseApp} instance.
5665
public static FirebaseAppCheck GetInstance(FirebaseApp app) {
5766
FirebaseAppCheck result;
58-
if (!appCheckMap.TryGetValue(app, out result)) {
67+
if (!appCheckMap.TryGetValue(app.Name, out result)) {
5968
AppCheckInternal internalObject = AppCheckInternal.GetInstance(app);
6069
result = new FirebaseAppCheck(internalObject);
61-
appCheckMap[app] = result;
62-
// TODO(amaurice): Logic to remove from map when App is destroyed?
70+
appCheckMap[app.Name] = result;
6371
}
6472
return result;
6573
}
@@ -79,8 +87,20 @@ public static FirebaseAppCheck GetInstance(FirebaseApp app) {
7987
///
8088
/// This method should be called before initializing the Firebase App.
8189
public static void SetAppCheckProviderFactory(IAppCheckProviderFactory factory) {
90+
if (appCheckFactory == factory) return;
91+
8292
appCheckFactory = factory;
83-
// TODO(amaurice): Clear the provider map when the factory changes?
93+
// Clear out the Providers that were previously made. When future calls to
94+
// GetToken fails to find a provider in the map, it will use the new factory
95+
// to create a new provider.
96+
providerMap.Clear();
97+
98+
// Register the callback for C++ SDK to use that will reach this factory.
99+
if (factory == null) {
100+
AppCheckUtil.SetGetTokenCallback(null);
101+
} else {
102+
AppCheckUtil.SetGetTokenCallback(getTokenDelegate);
103+
}
84104
}
85105

86106
/// Sets the {@code isTokenAutoRefreshEnabled} flag.
@@ -95,11 +115,93 @@ public void SetTokenAutoRefreshEnabled(bool isTokenAutoRefreshEnabled) {
95115
public System.Threading.Tasks.Task<AppCheckToken>
96116
GetAppCheckTokenAsync(bool forceRefresh) {
97117
ThrowIfNull();
98-
throw new NotImplementedException();
118+
return appCheckInternal.GetAppCheckTokenAsync(forceRefresh).ContinueWith(task => {
119+
if (task.IsFaulted) {
120+
throw task.Exception;
121+
}
122+
AppCheckTokenInternal tokenInternal = task.Result;
123+
return AppCheckToken.FromAppCheckTokenInternal(tokenInternal);
124+
});
99125
}
100126

101127
/// Called on the client when an AppCheckToken is created or changed.
102-
public System.EventHandler<TokenChangedEventArgs> TokenChanged;
128+
private event EventHandler<TokenChangedEventArgs> TokenChangedImpl;
129+
public event EventHandler<TokenChangedEventArgs> TokenChanged {
130+
add {
131+
ThrowIfNull();
132+
// If this is the first listener, hook into C++.
133+
if (TokenChangedImpl == null ||
134+
TokenChangedImpl.GetInvocationList().Length == 0) {
135+
AppCheckUtil.SetTokenChangedCallback(appCheckInternal, tokenChangedDelegate);
136+
}
137+
138+
TokenChangedImpl += value;
139+
}
140+
remove {
141+
ThrowIfNull();
142+
TokenChangedImpl -= value;
143+
144+
// If that was the last listener, remove the C++ hooks.
145+
if (TokenChangedImpl == null ||
146+
TokenChangedImpl.GetInvocationList().Length == 0) {
147+
AppCheckUtil.SetTokenChangedCallback(appCheckInternal, null);
148+
}
149+
}
150+
}
151+
152+
internal void OnTokenChanged(AppCheckToken token) {
153+
EventHandler<TokenChangedEventArgs> handler = TokenChangedImpl;
154+
if (handler != null) {
155+
handler(this, new TokenChangedEventArgs() { Token = token });
156+
}
157+
}
158+
159+
[MonoPInvokeCallback(typeof(AppCheckUtil.GetTokenFromCSharpDelegate))]
160+
private static void GetTokenFromCSharpMethod(string appName, int key) {
161+
if (appCheckFactory == null) {
162+
AppCheckUtil.FinishGetTokenCallback(key, "", 0,
163+
(int)AppCheckError.InvalidConfiguration,
164+
"Missing IAppCheckProviderFactory.");
165+
}
166+
FirebaseApp app = FirebaseApp.GetInstance(appName);
167+
if (app == null) {
168+
AppCheckUtil.FinishGetTokenCallback(key, "", 0,
169+
(int)AppCheckError.Unknown,
170+
"Unable to find App with name: " + appName);
171+
}
172+
IAppCheckProvider provider;
173+
if (!providerMap.TryGetValue(app.Name, out provider)) {
174+
provider = appCheckFactory.CreateProvider(app);
175+
if (provider == null) {
176+
AppCheckUtil.FinishGetTokenCallback(key, "", 0,
177+
(int)AppCheckError.InvalidConfiguration,
178+
"Failed to create IAppCheckProvider for App: " + appName);
179+
}
180+
providerMap[app.Name] = provider;
181+
}
182+
provider.GetTokenAsync().ContinueWith(task => {
183+
if (task.IsFaulted) {
184+
AppCheckUtil.FinishGetTokenCallback(key, "", 0,
185+
(int)AppCheckError.Unknown,
186+
"Provider returned an Exception: " + task.Exception);
187+
} else {
188+
AppCheckToken token = task.Result;
189+
AppCheckUtil.FinishGetTokenCallback(key, token.Token,
190+
token.ExpireTimeMs, 0, "");
191+
}
192+
});
193+
}
194+
195+
[MonoPInvokeCallback(typeof(AppCheckUtil.TokenChangedDelegate))]
196+
private static void TokenChangedMethod(string appName, System.IntPtr tokenCPtr) {
197+
AppCheckTokenInternal tokenInternal = new AppCheckTokenInternal(tokenCPtr, false);
198+
AppCheckToken token = AppCheckToken.FromAppCheckTokenInternal(tokenInternal);
199+
200+
FirebaseAppCheck appCheck;
201+
if (appCheckMap.TryGetValue(appName, out appCheck)) {
202+
appCheck.OnTokenChanged(token);
203+
}
204+
}
103205
}
104206

105207
}

0 commit comments

Comments
 (0)