Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions auth/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ set(desktop_SRCS
src/desktop/sign_in_flow.cc
src/desktop/user_desktop.cc
src/desktop/user_view.cc
src/id_token_util.cc
src/id_token_util.h
src/desktop/rpcs/auth_request.cc
src/desktop/rpcs/auth_response.cc
src/desktop/rpcs/create_auth_uri_request.cc
Expand Down
43 changes: 43 additions & 0 deletions auth/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ class FirebaseAuthTest : public FirebaseTest {
return FirebaseTest::WaitForCompletion(future, fn, expected_error);
}

// Passthrough method to the base class's WaitForCompletion.
bool WaitForCompletion(firebase::Future<firebase::auth::IdTokenResult> future,
const char* fn,
int expected_error = firebase::auth::kAuthErrorNone) {
return FirebaseTest::WaitForCompletion(future, fn, expected_error);
}

// Passthrough method to the base class's WaitForCompletion.
bool WaitForCompletion(firebase::Future<void> future, const char* fn,
int expected_error = firebase::auth::kAuthErrorNone) {
Expand Down Expand Up @@ -428,6 +435,42 @@ TEST_F(FirebaseAuthTest, TestTokensAndAuthStateListeners) {
ElementsAre("", Not(""), Not(""), Not(""), "")));
}

TEST_F(FirebaseAuthTest, TestGetIdTokenResult) {
SignInAnonymously();
ASSERT_TRUE(auth_->current_user().is_valid());

firebase::Future<firebase::auth::IdTokenResult> result_future =
auth_->current_user().GetIdTokenResult(false);
WaitForCompletion(result_future, "GetIdTokenResult(false)");
ASSERT_NE(result_future.result(), nullptr);
const firebase::auth::IdTokenResult first_result = *result_future.result();
EXPECT_NE(first_result.token, "");
EXPECT_GT(first_result.auth_timestamp, 0ULL);
EXPECT_GT(first_result.issued_at_timestamp, 0ULL);
EXPECT_GT(first_result.expiration_timestamp,
first_result.issued_at_timestamp);
EXPECT_EQ(first_result.sign_in_provider, "anonymous");
EXPECT_FALSE(first_result.claims.empty());
auto sub_it = first_result.claims.find(firebase::Variant("sub"));
ASSERT_TRUE(sub_it != first_result.claims.end());
EXPECT_TRUE(sub_it->second.is_string());
EXPECT_EQ(std::string(sub_it->second.string_value()),
auth_->current_user().uid());

ProcessEvents(1000);
result_future = auth_->current_user().GetIdTokenResult(true);
WaitForCompletion(result_future, "GetIdTokenResult(true)");
ASSERT_NE(result_future.result(), nullptr);
const firebase::auth::IdTokenResult second_result = *result_future.result();
EXPECT_NE(second_result.token, "");
EXPECT_NE(first_result.token, second_result.token);
EXPECT_GE(second_result.issued_at_timestamp,
first_result.issued_at_timestamp);

DeleteUser();
SignOut();
}

#if FIREBASE_PLATFORM_ANDROID
#define INTEGRATION_TEST_PLATFORM_NAME "Android"
#elif FIREBASE_PLATFORM_IOS
Expand Down
28 changes: 28 additions & 0 deletions auth/samples/src/doc_samples.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

// [START auth_includes]
#include <cassert>
#include <cinttypes>
#include <cstdint>

#include "firebase/app.h"
Expand Down Expand Up @@ -644,6 +645,33 @@ void SendIdTokenToBackend(firebase::auth::Auth* auth) {
// [END send_id_token_to_backend]
}

void GetIdTokenClaims(firebase::auth::Auth* auth) {
// [START get_id_token_claims]
firebase::auth::User user = auth->current_user();
if (user.is_valid()) {
user.GetIdTokenResult(/*force_refresh=*/false)
.OnCompletion(
[](const firebase::Future<firebase::auth::IdTokenResult>& result) {
if (result.error() == firebase::auth::kAuthErrorNone) {
const firebase::auth::IdTokenResult* token_result =
result.result();
printf("Token: %s\n", token_result->token.c_str());
printf("Auth timestamp: %" PRIu64 "\n",
token_result->auth_timestamp);
printf("Expiration timestamp: %" PRIu64 "\n",
token_result->expiration_timestamp);
// Check custom claims
auto it = token_result->claims.find(firebase::Variant("admin"));
if (it != token_result->claims.end() && it->second.is_bool() &&
it->second.bool_value()) {
printf("User is an admin!\n");
}
}
});
}
// [END get_id_token_claims]
}

firebase::auth::Auth* AuthOverview(firebase::App* app) {
/// [Auth overview]
// Get the Auth class for your App.
Expand Down
84 changes: 83 additions & 1 deletion auth/src/android/user_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ using util::JniUriToString;

// clang-format off
#define TOKEN_RESULT_METHODS(X) \
X(GetToken, "getToken", "()Ljava/lang/String;")
X(GetToken, "getToken", "()Ljava/lang/String;"), \
X(GetExpirationTimestamp, "getExpirationTimestamp", "()J"), \
X(GetAuthTimestamp, "getAuthTimestamp", "()J"), \
X(GetIssuedAtTimestamp, "getIssuedAtTimestamp", "()J"), \
X(GetSignInProvider, "getSignInProvider", "()Ljava/lang/String;"), \
X(GetSignInSecondFactor, "getSignInSecondFactor", "()Ljava/lang/String;"), \
X(GetClaims, "getClaims", "()Ljava/util/Map;")
// clang-format on
METHOD_LOOKUP_DECLARATION(tokenresult, TOKEN_RESULT_METHODS)
METHOD_LOOKUP_DEFINITION(tokenresult,
Expand Down Expand Up @@ -316,6 +322,82 @@ Future<std::string> User::GetToken(bool force_refresh) {
return MakeFuture(&futures, handle);
}

void ReadIdTokenResult(jobject result, FutureCallbackData<IdTokenResult>* d,
bool success, void* void_data) {
auto data = static_cast<IdTokenResult*>(void_data);
JNIEnv* env = Env(d->auth_data);

// `result` is of type GetTokenResult when `success` is true.
if (success) {
if (d->auth_data->ShouldTriggerIdTokenListenerCallback()) {
NotifyIdTokenListeners(d->auth_data);
}
// This comes from the successfully completed Task in Java, so it should
// always be valid.
FIREBASE_ASSERT(result != nullptr);
const jobject j_token = env->CallObjectMethod(
result, tokenresult::GetMethodId(tokenresult::kGetToken));
util::CheckAndClearJniExceptions(env);
const jlong expiration_timestamp = env->CallLongMethod(
result, tokenresult::GetMethodId(tokenresult::kGetExpirationTimestamp));
util::CheckAndClearJniExceptions(env);
const jlong auth_timestamp = env->CallLongMethod(
result, tokenresult::GetMethodId(tokenresult::kGetAuthTimestamp));
util::CheckAndClearJniExceptions(env);
const jlong issued_at_timestamp = env->CallLongMethod(
result, tokenresult::GetMethodId(tokenresult::kGetIssuedAtTimestamp));
util::CheckAndClearJniExceptions(env);
const jobject j_sign_in_provider = env->CallObjectMethod(
result, tokenresult::GetMethodId(tokenresult::kGetSignInProvider));
util::CheckAndClearJniExceptions(env);
const jobject j_sign_in_second_factor = env->CallObjectMethod(
result, tokenresult::GetMethodId(tokenresult::kGetSignInSecondFactor));
util::CheckAndClearJniExceptions(env);
const jobject j_claims = env->CallObjectMethod(
result, tokenresult::GetMethodId(tokenresult::kGetClaims));
util::CheckAndClearJniExceptions(env);

// Note that JniStringToString releases local references for us.
data->token = JniStringToString(env, j_token);
data->expiration_timestamp = static_cast<uint64_t>(expiration_timestamp);
data->auth_timestamp = static_cast<uint64_t>(auth_timestamp);
data->issued_at_timestamp = static_cast<uint64_t>(issued_at_timestamp);
data->sign_in_provider = JniStringToString(env, j_sign_in_provider);
data->sign_in_second_factor =
JniStringToString(env, j_sign_in_second_factor);
data->claims.clear();
if (j_claims != nullptr) {
util::JavaMapToVariantMap(env, &data->claims, j_claims);
env->DeleteLocalRef(j_claims);
}
} else {
*data = IdTokenResult();
}
}

Future<IdTokenResult> User::GetIdTokenResult(bool force_refresh) {
if (!ValidUser(auth_data_)) {
return Future<IdTokenResult>();
}
ReferenceCountedFutureImpl& futures = auth_data_->future_impl;
const auto handle =
futures.SafeAlloc<IdTokenResult>(kUserFn_GetIdTokenResult);
JNIEnv* env = Env(auth_data_);

auth_data_->SetExpectIdTokenListenerCallback(force_refresh);
jobject pending_result = env->CallObjectMethod(
UserImpl(auth_data_), user::GetMethodId(user::kToken), force_refresh);

if (!CheckAndCompleteFutureOnError(env, &futures, handle)) {
RegisterCallback(pending_result, handle, auth_data_, ReadIdTokenResult);
env->DeleteLocalRef(pending_result);
} else {
// If the method failed for some reason, clear the expected callback.
auth_data_->SetExpectIdTokenListenerCallback(false);
}
return MakeFuture(&futures, handle);
}

std::vector<UserInfoInterface> User::provider_data() const {
std::vector<UserInfoInterface> provider_data;
if (ValidUser(auth_data_)) {
Expand Down
1 change: 1 addition & 0 deletions auth/src/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ enum AuthApiFunction {

// External functions in the User API.
kUserFn_GetToken,
kUserFn_GetIdTokenResult,
kUserFn_UpdateEmail,
kUserFn_UpdatePassword,
kUserFn_Reauthenticate,
Expand Down
52 changes: 52 additions & 0 deletions auth/src/desktop/user_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "auth/src/desktop/sign_in_flow.h"
#include "auth/src/desktop/user_view.h"
#include "auth/src/desktop/validate_credential.h"
#include "auth/src/id_token_util.h"
#include "auth/src/include/firebase/auth/types.h"
#include "auth/user_data_generated.h"
#include "auth/user_data_resource.h"
Expand Down Expand Up @@ -698,6 +699,57 @@ Future<std::string> User::GetTokenInternal(const bool force_refresh,
callback);
}

Future<IdTokenResult> User::GetIdTokenResult(const bool force_refresh) {
if (auth_data_ == nullptr) {
return Future<IdTokenResult>();
}
Promise<IdTokenResult> promise(&auth_data_->future_impl,
kUserFn_GetIdTokenResult);
if (!ValidateCurrentUser(&promise, auth_data_)) {
return promise.LastResult();
}

GetTokenResult current_token(kAuthErrorFailure);
const bool is_user_logged_in =
UserView::TryRead(auth_data_, [&](const UserView::Reader& user) {
current_token = GetTokenIfFresh(user, force_refresh);
});

if (!is_user_logged_in) {
auto future = promise.future();
future.Release();
return future;
}
if (current_token.IsValid()) {
IdTokenResult id_token_result;
if (!ParseIdToken(current_token.token(), &id_token_result)) {
LogWarning("GetIdTokenResult: Failed to parse cached ID token.");
}
promise.CompleteWithResult(id_token_result);
return promise.future();
}
Comment thread
AustinBenoit marked this conversation as resolved.

const auto callback =
[](AuthDataHandle<IdTokenResult, rest::Request>* const handle) {
const GetTokenResult get_token_result =
EnsureFreshToken(handle->auth_data, true);
if (!get_token_result.IsValid()) {
FailPromise(&handle->promise, get_token_result.error());
return;
}

IdTokenResult id_token_result;
if (!ParseIdToken(get_token_result.token(), &id_token_result)) {
LogWarning("GetIdTokenResult: Failed to parse refreshed ID token.");
}
handle->promise.CompleteWithResult(id_token_result);
};
Comment thread
AustinBenoit marked this conversation as resolved.

// Note: request is deliberately null because EnsureFreshToken will create it.
return CallAsync(auth_data_, promise, std::unique_ptr<rest::Request>(),
callback);
}

Future<void> User::Delete() {
if (auth_data_ == nullptr) { // user is not valid
return Future<void>();
Expand Down
Loading
Loading