-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
pavlo
committed
Jan 27, 2024
1 parent
e2fabac
commit dcc9f86
Showing
20 changed files
with
1,011 additions
and
666 deletions.
There are no files selected for viewing
296 changes: 193 additions & 103 deletions
296
FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckApiClient.cs
Large diffs are not rendered by default.
Oops, something went wrong.
87 changes: 87 additions & 0 deletions
87
FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckDecodedToken.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using System.Collections.Generic; | ||
using Newtonsoft.Json; | ||
|
||
namespace FirebaseAdmin.AppCheck | ||
{ | ||
/// <summary> | ||
/// Interface representing a decoded Firebase App Check token, returned from the {@link AppCheck.verifyToken} method.. | ||
/// </summary> | ||
public class AppCheckDecodedToken | ||
{ | ||
internal AppCheckDecodedToken(Args args) | ||
{ | ||
this.AppId = args.AppId; | ||
this.Issuer = args.Issuer; | ||
this.Subject = args.Subject; | ||
this.Audience = args.Audience; | ||
this.ExpirationTimeSeconds = (int)args.ExpirationTimeSeconds; | ||
this.IssuedAtTimeSeconds = (int)args.IssuedAtTimeSeconds; | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets the issuer identifier for the issuer of the response. | ||
/// </summary> | ||
public string Issuer { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the Firebase App ID corresponding to the app the token belonged to. | ||
/// As a convenience, this value is copied over to the {@link AppCheckDecodedToken.app_id | app_id} property. | ||
/// </summary> | ||
public string Subject { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the audience for which this token is intended. | ||
/// This value is a JSON array of two strings, the first is the project number of your | ||
/// Firebase project, and the second is the project ID of the same project. | ||
/// </summary> | ||
public string[] Audience { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the App Check token's c time, in seconds since the Unix epoch. That is, the | ||
/// time at which this App Check token expires and should no longer be considered valid. | ||
/// </summary> | ||
public int ExpirationTimeSeconds { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the App Check token's issued-at time, in seconds since the Unix epoch. That is, the | ||
/// time at which this App Check token was issued and should start to be considered valid. | ||
/// </summary> | ||
public int IssuedAtTimeSeconds { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the App ID corresponding to the App the App Check token belonged to. | ||
/// This value is not actually one of the JWT token claims. It is added as a | ||
/// convenience, and is set as the value of the {@link AppCheckDecodedToken.sub | sub} property. | ||
/// </summary> | ||
public string AppId { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets key . | ||
/// </summary> | ||
public Dictionary<string, string> Key { get; set; } | ||
////[key: string]: any; | ||
|
||
internal sealed class Args | ||
{ | ||
public string AppId { get; internal set; } | ||
|
||
[JsonProperty("app_id")] | ||
internal string Issuer { get; set; } | ||
|
||
[JsonProperty("sub")] | ||
internal string Subject { get; set; } | ||
|
||
[JsonProperty("aud")] | ||
internal string[] Audience { get; set; } | ||
|
||
[JsonProperty("exp")] | ||
internal long ExpirationTimeSeconds { get; set; } | ||
|
||
[JsonProperty("iat")] | ||
internal long IssuedAtTimeSeconds { get; set; } | ||
|
||
[JsonIgnore] | ||
internal IReadOnlyDictionary<string, object> Claims { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
namespace FirebaseAdmin.AppCheck | ||
{ | ||
/// <summary> | ||
/// Error codes that can be raised by the Firebase App Check APIs. | ||
/// </summary> | ||
public enum AppCheckErrorCode | ||
{ | ||
/// <summary> | ||
/// Process is aborted | ||
/// </summary> | ||
Aborted, | ||
|
||
/// <summary> | ||
/// Argument is not valid | ||
/// </summary> | ||
InvalidArgument, | ||
|
||
/// <summary> | ||
/// Credential is not valid | ||
/// </summary> | ||
InvalidCredential, | ||
|
||
/// <summary> | ||
/// The server internal error | ||
/// </summary> | ||
InternalError, | ||
|
||
/// <summary> | ||
/// Permission is denied | ||
/// </summary> | ||
PermissionDenied, | ||
|
||
/// <summary> | ||
/// Unauthenticated | ||
/// </summary> | ||
Unauthenticated, | ||
|
||
/// <summary> | ||
/// Resource is not found | ||
/// </summary> | ||
NotFound, | ||
|
||
/// <summary> | ||
/// App Check Token is expired | ||
/// </summary> | ||
AppCheckTokenExpired, | ||
|
||
/// <summary> | ||
/// Unknown Error | ||
/// </summary> | ||
UnknownError, | ||
} | ||
} |
229 changes: 229 additions & 0 deletions
229
FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckErrorHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using FirebaseAdmin.Util; | ||
using Google.Apis.Json; | ||
using Newtonsoft.Json; | ||
|
||
namespace FirebaseAdmin.AppCheck | ||
{ | ||
/// <summary> | ||
/// Parses error responses received from the Auth service, and creates instances of | ||
/// <see cref="FirebaseAppCheckException"/>. | ||
/// </summary> | ||
internal sealed class AppCheckErrorHandler | ||
: HttpErrorHandler<FirebaseAppCheckException>, | ||
IHttpRequestExceptionHandler<FirebaseAppCheckException>, | ||
IDeserializeExceptionHandler<FirebaseAppCheckException> | ||
{ | ||
internal static readonly AppCheckErrorHandler Instance = new AppCheckErrorHandler(); | ||
|
||
private static readonly IReadOnlyDictionary<string, ErrorInfo> CodeToErrorInfo = | ||
new Dictionary<string, ErrorInfo>() | ||
{ | ||
{ | ||
"ABORTED", | ||
new ErrorInfo( | ||
ErrorCode.Aborted, | ||
AppCheckErrorCode.Aborted, | ||
"App check is aborted") | ||
}, | ||
{ | ||
"INVALID_ARGUMENT", | ||
new ErrorInfo( | ||
ErrorCode.InvalidArgument, | ||
AppCheckErrorCode.InvalidArgument, | ||
"An argument is not valid") | ||
}, | ||
{ | ||
"INVALID_CREDENTIAL", | ||
new ErrorInfo( | ||
ErrorCode.InvalidArgument, | ||
AppCheckErrorCode.InvalidCredential, | ||
"The credential is not valid") | ||
}, | ||
{ | ||
"PERMISSION_DENIED", | ||
new ErrorInfo( | ||
ErrorCode.PermissionDenied, | ||
AppCheckErrorCode.PermissionDenied, | ||
"The permission is denied") | ||
}, | ||
{ | ||
"UNAUTHENTICATED", | ||
new ErrorInfo( | ||
ErrorCode.Unauthenticated, | ||
AppCheckErrorCode.Unauthenticated, | ||
"Unauthenticated") | ||
}, | ||
{ | ||
"NOT_FOUND", | ||
new ErrorInfo( | ||
ErrorCode.NotFound, | ||
AppCheckErrorCode.NotFound, | ||
"The resource is not found") | ||
}, | ||
{ | ||
"UNKNOWN", | ||
new ErrorInfo( | ||
ErrorCode.Unknown, | ||
AppCheckErrorCode.UnknownError, | ||
"unknown-error") | ||
}, | ||
}; | ||
|
||
private AppCheckErrorHandler() { } | ||
|
||
public FirebaseAppCheckException HandleHttpRequestException( | ||
HttpRequestException exception) | ||
{ | ||
var temp = exception.ToFirebaseException(); | ||
return new FirebaseAppCheckException( | ||
temp.ErrorCode, | ||
temp.Message, | ||
inner: temp.InnerException, | ||
response: temp.HttpResponse); | ||
} | ||
|
||
public FirebaseAppCheckException HandleDeserializeException( | ||
Exception exception, ResponseInfo responseInfo) | ||
{ | ||
return new FirebaseAppCheckException( | ||
ErrorCode.Unknown, | ||
$"Error while parsing AppCheck service response. Deserialization error: {responseInfo.Body}", | ||
AppCheckErrorCode.UnknownError, | ||
inner: exception, | ||
response: responseInfo.HttpResponse); | ||
} | ||
|
||
protected sealed override FirebaseExceptionArgs CreateExceptionArgs( | ||
HttpResponseMessage response, string body) | ||
{ | ||
var appCheckError = this.ParseAppCheckError(body); | ||
|
||
ErrorInfo info; | ||
CodeToErrorInfo.TryGetValue(appCheckError.Code, out info); | ||
|
||
var defaults = base.CreateExceptionArgs(response, body); | ||
return new FirebaseAppCheckExceptionArgs() | ||
{ | ||
Code = info?.ErrorCode ?? defaults.Code, | ||
Message = info?.GetMessage(appCheckError) ?? defaults.Message, | ||
HttpResponse = response, | ||
ResponseBody = body, | ||
AppCheckErrorCode = info?.AppCheckErrorCode, | ||
}; | ||
} | ||
|
||
protected override FirebaseAppCheckException CreateException(FirebaseExceptionArgs args) | ||
{ | ||
return new FirebaseAppCheckException( | ||
args.Code, | ||
args.Message, | ||
(args as FirebaseAppCheckExceptionArgs).AppCheckErrorCode, | ||
response: args.HttpResponse); | ||
} | ||
|
||
private AppCheckError ParseAppCheckError(string body) | ||
{ | ||
try | ||
{ | ||
var parsed = NewtonsoftJsonSerializer.Instance.Deserialize<AppCheckErrorResponse>(body); | ||
return parsed.Error ?? new AppCheckError(); | ||
} | ||
catch | ||
{ | ||
// Ignore any error that may occur while parsing the error response. The server | ||
// may have responded with a non-json body. | ||
return new AppCheckError(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Describes a class of errors that can be raised by the Firebase Auth backend API. | ||
/// </summary> | ||
private sealed class ErrorInfo | ||
{ | ||
private readonly string message; | ||
|
||
internal ErrorInfo(ErrorCode code, AppCheckErrorCode appCheckErrorCode, string message) | ||
{ | ||
this.ErrorCode = code; | ||
this.AppCheckErrorCode = appCheckErrorCode; | ||
this.message = message; | ||
} | ||
|
||
internal ErrorCode ErrorCode { get; private set; } | ||
|
||
internal AppCheckErrorCode AppCheckErrorCode { get; private set; } | ||
|
||
internal string GetMessage(AppCheckError appCheckError) | ||
{ | ||
var message = $"{this.message} ({appCheckError.Code})."; | ||
if (!string.IsNullOrEmpty(appCheckError.Detail)) | ||
{ | ||
return $"{message}: {appCheckError.Detail}"; | ||
} | ||
|
||
return $"{message}"; | ||
} | ||
} | ||
|
||
private sealed class FirebaseAppCheckExceptionArgs : FirebaseExceptionArgs | ||
{ | ||
internal AppCheckErrorCode? AppCheckErrorCode { get; set; } | ||
} | ||
|
||
private sealed class AppCheckError | ||
{ | ||
[JsonProperty("message")] | ||
internal string Message { get; set; } | ||
|
||
/// <summary> | ||
/// Gets the Firebase Auth error code extracted from the response. Returns empty string | ||
/// if the error code cannot be determined. | ||
/// </summary> | ||
internal string Code | ||
{ | ||
get | ||
{ | ||
var separator = this.GetSeparator(); | ||
if (separator != -1) | ||
{ | ||
return this.Message.Substring(0, separator); | ||
} | ||
|
||
return this.Message ?? string.Empty; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the error detail sent by the Firebase Auth API. May be null. | ||
/// </summary> | ||
internal string Detail | ||
{ | ||
get | ||
{ | ||
var separator = this.GetSeparator(); | ||
if (separator != -1) | ||
{ | ||
return this.Message.Substring(separator + 1).Trim(); | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
|
||
private int GetSeparator() | ||
{ | ||
return this.Message?.IndexOf(':') ?? -1; | ||
} | ||
} | ||
|
||
private sealed class AppCheckErrorResponse | ||
{ | ||
[JsonProperty("error")] | ||
internal AppCheckError Error { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.