Skip to content

Commit

Permalink
feature/appcheck/final
Browse files Browse the repository at this point in the history
  • Loading branch information
taeul committed Jan 27, 2024
1 parent e2fabac commit dcc9f86
Show file tree
Hide file tree
Showing 20 changed files with 1,011 additions and 666 deletions.
296 changes: 193 additions & 103 deletions FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckApiClient.cs

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckDecodedToken.cs
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; }
}
}
}
53 changes: 53 additions & 0 deletions FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckErrorCode.cs
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 FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckErrorHandler.cs
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; }
}
}
}
13 changes: 3 additions & 10 deletions FirebaseAdmin/FirebaseAdmin/AppCheck/AppCheckToken.cs
Original file line number Diff line number Diff line change
@@ -1,23 1,16 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace FirebaseAdmin.Check
namespace FirebaseAdmin.AppCheck
{
/// <summary>
/// Interface representing an App Check token.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="AppCheckToken"/> class.
/// </remarks>
/// <param name="tokenValue">Generator from custom token.</param>
/// <param name="ttlValue">TTl value .</param>
public class AppCheckToken(string tokenValue, int ttlValue)
{
/// <summary>
/// Gets the Firebase App Check token.
/// Gets or sets the Firebase App Check token.
/// </summary>
public string Token { get; } = tokenValue;
public string Token { get; set; } = tokenValue;

/// <summary>
/// Gets or sets the time-to-live duration of the token in milliseconds.
Expand Down
Loading

0 comments on commit dcc9f86

Please sign in to comment.