Skip to content

Commit dcc9f86

Browse files
author
pavlo
committed
feature/appcheck/final
1 parent e2fabac commit dcc9f86

20 files changed

+1011
-666
lines changed

FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckApiClient.cs

Lines changed: 193 additions & 103 deletions
Large diffs are not rendered by default.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace FirebaseAdmin.AppCheck
5+
{
6+
/// <summary>
7+
/// Interface representing a decoded Firebase App Check token, returned from the {@link AppCheck.verifyToken} method..
8+
/// </summary>
9+
public class AppCheckDecodedToken
10+
{
11+
internal AppCheckDecodedToken(Args args)
12+
{
13+
this.AppId = args.AppId;
14+
this.Issuer = args.Issuer;
15+
this.Subject = args.Subject;
16+
this.Audience = args.Audience;
17+
this.ExpirationTimeSeconds = (int)args.ExpirationTimeSeconds;
18+
this.IssuedAtTimeSeconds = (int)args.IssuedAtTimeSeconds;
19+
}
20+
21+
/// <summary>
22+
/// Gets or sets the issuer identifier for the issuer of the response.
23+
/// </summary>
24+
public string Issuer { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the Firebase App ID corresponding to the app the token belonged to.
28+
/// As a convenience, this value is copied over to the {@link AppCheckDecodedToken.app_id | app_id} property.
29+
/// </summary>
30+
public string Subject { get; set; }
31+
32+
/// <summary>
33+
/// Gets or sets the audience for which this token is intended.
34+
/// This value is a JSON array of two strings, the first is the project number of your
35+
/// Firebase project, and the second is the project ID of the same project.
36+
/// </summary>
37+
public string[] Audience { get; set; }
38+
39+
/// <summary>
40+
/// Gets or sets the App Check token's c time, in seconds since the Unix epoch. That is, the
41+
/// time at which this App Check token expires and should no longer be considered valid.
42+
/// </summary>
43+
public int ExpirationTimeSeconds { get; set; }
44+
45+
/// <summary>
46+
/// Gets or sets the App Check token's issued-at time, in seconds since the Unix epoch. That is, the
47+
/// time at which this App Check token was issued and should start to be considered valid.
48+
/// </summary>
49+
public int IssuedAtTimeSeconds { get; set; }
50+
51+
/// <summary>
52+
/// Gets or sets the App ID corresponding to the App the App Check token belonged to.
53+
/// This value is not actually one of the JWT token claims. It is added as a
54+
/// convenience, and is set as the value of the {@link AppCheckDecodedToken.sub | sub} property.
55+
/// </summary>
56+
public string AppId { get; set; }
57+
58+
/// <summary>
59+
/// Gets or sets key .
60+
/// </summary>
61+
public Dictionary<string, string> Key { get; set; }
62+
////[key: string]: any;
63+
64+
internal sealed class Args
65+
{
66+
public string AppId { get; internal set; }
67+
68+
[JsonProperty("app_id")]
69+
internal string Issuer { get; set; }
70+
71+
[JsonProperty("sub")]
72+
internal string Subject { get; set; }
73+
74+
[JsonProperty("aud")]
75+
internal string[] Audience { get; set; }
76+
77+
[JsonProperty("exp")]
78+
internal long ExpirationTimeSeconds { get; set; }
79+
80+
[JsonProperty("iat")]
81+
internal long IssuedAtTimeSeconds { get; set; }
82+
83+
[JsonIgnore]
84+
internal IReadOnlyDictionary<string, object> Claims { get; set; }
85+
}
86+
}
87+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace FirebaseAdmin.AppCheck
2+
{
3+
/// <summary>
4+
/// Error codes that can be raised by the Firebase App Check APIs.
5+
/// </summary>
6+
public enum AppCheckErrorCode
7+
{
8+
/// <summary>
9+
/// Process is aborted
10+
/// </summary>
11+
Aborted,
12+
13+
/// <summary>
14+
/// Argument is not valid
15+
/// </summary>
16+
InvalidArgument,
17+
18+
/// <summary>
19+
/// Credential is not valid
20+
/// </summary>
21+
InvalidCredential,
22+
23+
/// <summary>
24+
/// The server internal error
25+
/// </summary>
26+
InternalError,
27+
28+
/// <summary>
29+
/// Permission is denied
30+
/// </summary>
31+
PermissionDenied,
32+
33+
/// <summary>
34+
/// Unauthenticated
35+
/// </summary>
36+
Unauthenticated,
37+
38+
/// <summary>
39+
/// Resource is not found
40+
/// </summary>
41+
NotFound,
42+
43+
/// <summary>
44+
/// App Check Token is expired
45+
/// </summary>
46+
AppCheckTokenExpired,
47+
48+
/// <summary>
49+
/// Unknown Error
50+
/// </summary>
51+
UnknownError,
52+
}
53+
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net.Http;
4+
using FirebaseAdmin.Util;
5+
using Google.Apis.Json;
6+
using Newtonsoft.Json;
7+
8+
namespace FirebaseAdmin.AppCheck
9+
{
10+
/// <summary>
11+
/// Parses error responses received from the Auth service, and creates instances of
12+
/// <see cref="FirebaseAppCheckException"/>.
13+
/// </summary>
14+
internal sealed class AppCheckErrorHandler
15+
: HttpErrorHandler<FirebaseAppCheckException>,
16+
IHttpRequestExceptionHandler<FirebaseAppCheckException>,
17+
IDeserializeExceptionHandler<FirebaseAppCheckException>
18+
{
19+
internal static readonly AppCheckErrorHandler Instance = new AppCheckErrorHandler();
20+
21+
private static readonly IReadOnlyDictionary<string, ErrorInfo> CodeToErrorInfo =
22+
new Dictionary<string, ErrorInfo>()
23+
{
24+
{
25+
"ABORTED",
26+
new ErrorInfo(
27+
ErrorCode.Aborted,
28+
AppCheckErrorCode.Aborted,
29+
"App check is aborted")
30+
},
31+
{
32+
"INVALID_ARGUMENT",
33+
new ErrorInfo(
34+
ErrorCode.InvalidArgument,
35+
AppCheckErrorCode.InvalidArgument,
36+
"An argument is not valid")
37+
},
38+
{
39+
"INVALID_CREDENTIAL",
40+
new ErrorInfo(
41+
ErrorCode.InvalidArgument,
42+
AppCheckErrorCode.InvalidCredential,
43+
"The credential is not valid")
44+
},
45+
{
46+
"PERMISSION_DENIED",
47+
new ErrorInfo(
48+
ErrorCode.PermissionDenied,
49+
AppCheckErrorCode.PermissionDenied,
50+
"The permission is denied")
51+
},
52+
{
53+
"UNAUTHENTICATED",
54+
new ErrorInfo(
55+
ErrorCode.Unauthenticated,
56+
AppCheckErrorCode.Unauthenticated,
57+
"Unauthenticated")
58+
},
59+
{
60+
"NOT_FOUND",
61+
new ErrorInfo(
62+
ErrorCode.NotFound,
63+
AppCheckErrorCode.NotFound,
64+
"The resource is not found")
65+
},
66+
{
67+
"UNKNOWN",
68+
new ErrorInfo(
69+
ErrorCode.Unknown,
70+
AppCheckErrorCode.UnknownError,
71+
"unknown-error")
72+
},
73+
};
74+
75+
private AppCheckErrorHandler() { }
76+
77+
public FirebaseAppCheckException HandleHttpRequestException(
78+
HttpRequestException exception)
79+
{
80+
var temp = exception.ToFirebaseException();
81+
return new FirebaseAppCheckException(
82+
temp.ErrorCode,
83+
temp.Message,
84+
inner: temp.InnerException,
85+
response: temp.HttpResponse);
86+
}
87+
88+
public FirebaseAppCheckException HandleDeserializeException(
89+
Exception exception, ResponseInfo responseInfo)
90+
{
91+
return new FirebaseAppCheckException(
92+
ErrorCode.Unknown,
93+
$"Error while parsing AppCheck service response. Deserialization error: {responseInfo.Body}",
94+
AppCheckErrorCode.UnknownError,
95+
inner: exception,
96+
response: responseInfo.HttpResponse);
97+
}
98+
99+
protected sealed override FirebaseExceptionArgs CreateExceptionArgs(
100+
HttpResponseMessage response, string body)
101+
{
102+
var appCheckError = this.ParseAppCheckError(body);
103+
104+
ErrorInfo info;
105+
CodeToErrorInfo.TryGetValue(appCheckError.Code, out info);
106+
107+
var defaults = base.CreateExceptionArgs(response, body);
108+
return new FirebaseAppCheckExceptionArgs()
109+
{
110+
Code = info?.ErrorCode ?? defaults.Code,
111+
Message = info?.GetMessage(appCheckError) ?? defaults.Message,
112+
HttpResponse = response,
113+
ResponseBody = body,
114+
AppCheckErrorCode = info?.AppCheckErrorCode,
115+
};
116+
}
117+
118+
protected override FirebaseAppCheckException CreateException(FirebaseExceptionArgs args)
119+
{
120+
return new FirebaseAppCheckException(
121+
args.Code,
122+
args.Message,
123+
(args as FirebaseAppCheckExceptionArgs).AppCheckErrorCode,
124+
response: args.HttpResponse);
125+
}
126+
127+
private AppCheckError ParseAppCheckError(string body)
128+
{
129+
try
130+
{
131+
var parsed = NewtonsoftJsonSerializer.Instance.Deserialize<AppCheckErrorResponse>(body);
132+
return parsed.Error ?? new AppCheckError();
133+
}
134+
catch
135+
{
136+
// Ignore any error that may occur while parsing the error response. The server
137+
// may have responded with a non-json body.
138+
return new AppCheckError();
139+
}
140+
}
141+
142+
/// <summary>
143+
/// Describes a class of errors that can be raised by the Firebase Auth backend API.
144+
/// </summary>
145+
private sealed class ErrorInfo
146+
{
147+
private readonly string message;
148+
149+
internal ErrorInfo(ErrorCode code, AppCheckErrorCode appCheckErrorCode, string message)
150+
{
151+
this.ErrorCode = code;
152+
this.AppCheckErrorCode = appCheckErrorCode;
153+
this.message = message;
154+
}
155+
156+
internal ErrorCode ErrorCode { get; private set; }
157+
158+
internal AppCheckErrorCode AppCheckErrorCode { get; private set; }
159+
160+
internal string GetMessage(AppCheckError appCheckError)
161+
{
162+
var message = $"{this.message} ({appCheckError.Code}).";
163+
if (!string.IsNullOrEmpty(appCheckError.Detail))
164+
{
165+
return $"{message}: {appCheckError.Detail}";
166+
}
167+
168+
return $"{message}";
169+
}
170+
}
171+
172+
private sealed class FirebaseAppCheckExceptionArgs : FirebaseExceptionArgs
173+
{
174+
internal AppCheckErrorCode? AppCheckErrorCode { get; set; }
175+
}
176+
177+
private sealed class AppCheckError
178+
{
179+
[JsonProperty("message")]
180+
internal string Message { get; set; }
181+
182+
/// <summary>
183+
/// Gets the Firebase Auth error code extracted from the response. Returns empty string
184+
/// if the error code cannot be determined.
185+
/// </summary>
186+
internal string Code
187+
{
188+
get
189+
{
190+
var separator = this.GetSeparator();
191+
if (separator != -1)
192+
{
193+
return this.Message.Substring(0, separator);
194+
}
195+
196+
return this.Message ?? string.Empty;
197+
}
198+
}
199+
200+
/// <summary>
201+
/// Gets the error detail sent by the Firebase Auth API. May be null.
202+
/// </summary>
203+
internal string Detail
204+
{
205+
get
206+
{
207+
var separator = this.GetSeparator();
208+
if (separator != -1)
209+
{
210+
return this.Message.Substring(separator + 1).Trim();
211+
}
212+
213+
return null;
214+
}
215+
}
216+
217+
private int GetSeparator()
218+
{
219+
return this.Message?.IndexOf(':') ?? -1;
220+
}
221+
}
222+
223+
private sealed class AppCheckErrorResponse
224+
{
225+
[JsonProperty("error")]
226+
internal AppCheckError Error { get; set; }
227+
}
228+
}
229+
}

FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckToken.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
4-
5-
namespace FirebaseAdmin.Check
1+
namespace FirebaseAdmin.AppCheck
62
{
73
/// <summary>
84
/// Interface representing an App Check token.
95
/// </summary>
10-
/// <remarks>
11-
/// Initializes a new instance of the <see cref="AppCheckToken"/> class.
12-
/// </remarks>
136
/// <param name="tokenValue">Generator from custom token.</param>
147
/// <param name="ttlValue">TTl value .</param>
158
public class AppCheckToken(string tokenValue, int ttlValue)
169
{
1710
/// <summary>
18-
/// Gets the Firebase App Check token.
11+
/// Gets or sets the Firebase App Check token.
1912
/// </summary>
20-
public string Token { get; } = tokenValue;
13+
public string Token { get; set; } = tokenValue;
2114

2215
/// <summary>
2316
/// Gets or sets the time-to-live duration of the token in milliseconds.

0 commit comments

Comments
 (0)