Skip to content

Commit

Permalink
Merge branch 'master' of github.com:realm/realm-core into mwb/fix-mul…
Browse files Browse the repository at this point in the history
…tiplex-sessions-crash
  • Loading branch information
Michael Wilkerson-Barker committed Jun 12, 2023
2 parents 011a17d + 2cfb24b commit 3f6cca0
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 79 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@

-----------

### Internals
* None.

----------------------------------------------

# 13.15.0 Release notes

### Enhancements
* It is now allowed to open old frozen versions with a schema that contains additional classes, but not additional properties. ([PR 6693](https://github.com/realm/realm-core/issues/6693))

### Fixed
* Properties in the frozen _before_ Realm instance in the client reset callbacks may have had properties reordered which could lead to exceptions if accessed. ([PR 6693](https://github.com/realm/realm-core/issues/6693), since v13.11.0)

### Breaking changes
* A new provider called `IdentityProviderAPIKey` replaces both `IdentityProviderUserAPIKey` and `IdentityProviderServerAPIKey` since those two did the same thing. If SDKs wish to keep the old behaviour without requiring users to make code changes, they can adapt both their existing server and user API key providers to use the new core type. ([#5914](https://github.com/realm/realm-core/issues/5914))

### Compatibility
* Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5.

-----------

### Internals
* Prebuilt libraries for Apple platforms are now built with Xcode 14.

Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// swift-tools-version:5.6
// swift-tools-version:5.5

import PackageDescription
import Foundation

let versionStr = "13.14.0"
let versionStr = "13.15.0"
let versionPieces = versionStr.split(separator: "-")
let versionCompontents = versionPieces[0].split(separator: ".")
let versionExtra = versionPieces.count > 1 ? versionPieces[1] : ""
Expand Down
2 changes: 1 addition & 1 deletion dependencies.list
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PACKAGE_NAME=realm-core
VERSION=13.14.0
VERSION=13.15.0
OPENSSL_VERSION=3.0.8
ZLIB_VERSION=1.2.13
MDBREALM_TEST_SERVER_TAG=2023-05-05
6 changes: 2 additions & 4 deletions src/realm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2751,8 +2751,7 @@ typedef enum realm_auth_provider {
RLM_AUTH_PROVIDER_CUSTOM,
RLM_AUTH_PROVIDER_EMAIL_PASSWORD,
RLM_AUTH_PROVIDER_FUNCTION,
RLM_AUTH_PROVIDER_USER_API_KEY,
RLM_AUTH_PROVIDER_SERVER_API_KEY,
RLM_AUTH_PROVIDER_API_KEY,
} realm_auth_provider_e;

typedef struct realm_app_user_apikey {
Expand Down Expand Up @@ -2828,8 +2827,7 @@ RLM_API realm_app_credentials_t* realm_app_credentials_new_apple(const char* id_
RLM_API realm_app_credentials_t* realm_app_credentials_new_jwt(const char* jwt_token) RLM_API_NOEXCEPT;
RLM_API realm_app_credentials_t* realm_app_credentials_new_email_password(const char* email,
realm_string_t password) RLM_API_NOEXCEPT;
RLM_API realm_app_credentials_t* realm_app_credentials_new_user_api_key(const char* api_key) RLM_API_NOEXCEPT;
RLM_API realm_app_credentials_t* realm_app_credentials_new_server_api_key(const char* api_key) RLM_API_NOEXCEPT;
RLM_API realm_app_credentials_t* realm_app_credentials_new_api_key(const char* api_key) RLM_API_NOEXCEPT;

/**
* Create Custom Function authentication app credentials.
Expand Down
12 changes: 3 additions & 9 deletions src/realm/object-store/c_api/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ static_assert(realm_auth_provider_e(AuthProvider::APPLE) == RLM_AUTH_PROVIDER_AP
static_assert(realm_auth_provider_e(AuthProvider::CUSTOM) == RLM_AUTH_PROVIDER_CUSTOM);
static_assert(realm_auth_provider_e(AuthProvider::USERNAME_PASSWORD) == RLM_AUTH_PROVIDER_EMAIL_PASSWORD);
static_assert(realm_auth_provider_e(AuthProvider::FUNCTION) == RLM_AUTH_PROVIDER_FUNCTION);
static_assert(realm_auth_provider_e(AuthProvider::USER_API_KEY) == RLM_AUTH_PROVIDER_USER_API_KEY);
static_assert(realm_auth_provider_e(AuthProvider::SERVER_API_KEY) == RLM_AUTH_PROVIDER_SERVER_API_KEY);
static_assert(realm_auth_provider_e(AuthProvider::API_KEY) == RLM_AUTH_PROVIDER_API_KEY);


static realm_app_error_t to_capi(const AppError& error)
Expand Down Expand Up @@ -169,14 +168,9 @@ RLM_API realm_app_credentials_t* realm_app_credentials_new_function(const char*
});
}

RLM_API realm_app_credentials_t* realm_app_credentials_new_user_api_key(const char* api_key) noexcept
RLM_API realm_app_credentials_t* realm_app_credentials_new_api_key(const char* api_key) noexcept
{
return new realm_app_credentials_t(AppCredentials::user_api_key(api_key));
}

RLM_API realm_app_credentials_t* realm_app_credentials_new_server_api_key(const char* api_key) noexcept
{
return new realm_app_credentials_t(AppCredentials::server_api_key(api_key));
return new realm_app_credentials_t(AppCredentials::api_key(api_key));
}

RLM_API realm_auth_provider_e realm_auth_credentials_get_provider(realm_app_credentials_t* credentials) noexcept
Expand Down
12 changes: 12 additions & 0 deletions src/realm/object-store/schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ bool operator==(Schema const& a, Schema const& b) noexcept
{
return static_cast<Schema::base const&>(a) == static_cast<Schema::base const&>(b);
}

std::ostream& operator<<(std::ostream& os, const Schema& schema)
{
for (auto& o : schema) {
os << o.name << ":\n";
for (auto& p : o.persisted_properties) {
os << util::format("\t%1<%2>\n", p.name, string_for_property_type(p.type));
}
}
return os;
}

} // namespace realm

Schema::Schema() noexcept = default;
Expand Down
2 changes: 2 additions & 0 deletions src/realm/object-store/schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#ifndef REALM_SCHEMA_HPP
#define REALM_SCHEMA_HPP

#include <ostream>
#include <string>
#include <vector>

Expand Down Expand Up @@ -200,6 +201,7 @@ class Schema : private std::vector<ObjectSchema> {
{
return !(a == b);
}
friend std::ostream& operator<<(std::ostream&, const Schema&);

using base::begin;
using base::const_iterator;
Expand Down
8 changes: 6 additions & 2 deletions src/realm/object-store/shared_realm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,13 @@ void Realm::update_schema(Schema schema, uint64_t version, MigrationFunction mig
Schema actual_schema = get_full_schema();

// Frozen Realms never modify the schema on disk and we just need to verify
// that the requested schema is a subset of what actually exists
// that the requested schema is compatible with what actually exists on disk
// at that frozen version. Tables are allowed to be missing as those can be
// represented by empty Results, but tables which exist must have all of the
// requested properties with the correct type.
if (m_frozen_version) {
ObjectStore::verify_valid_external_changes(schema.compare(actual_schema, m_config.schema_mode, true));
ObjectStore::verify_compatible_for_immutable_and_readonly(
actual_schema.compare(schema, m_config.schema_mode, true));
set_schema(actual_schema, std::move(schema));
return;
}
Expand Down
25 changes: 7 additions & 18 deletions src/realm/object-store/sync/app_credentials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ IdentityProvider const IdentityProviderApple = "oauth2-apple";
IdentityProvider const IdentityProviderUsernamePassword = "local-userpass";
IdentityProvider const IdentityProviderCustom = "custom-token";
IdentityProvider const IdentityProviderFunction = "custom-function";
IdentityProvider const IdentityProviderUserAPIKey = "api-key";
IdentityProvider const IdentityProviderServerAPIKey = "api-key";
IdentityProvider const IdentityProviderAPIKey = "api-key";

IdentityProvider provider_type_from_enum(AuthProvider provider)
{
Expand All @@ -51,10 +50,8 @@ IdentityProvider provider_type_from_enum(AuthProvider provider)
return IdentityProviderUsernamePassword;
case AuthProvider::FUNCTION:
return IdentityProviderFunction;
case AuthProvider::USER_API_KEY:
return IdentityProviderUserAPIKey;
case AuthProvider::SERVER_API_KEY:
return IdentityProviderServerAPIKey;
case AuthProvider::API_KEY:
return IdentityProviderAPIKey;
}
throw InvalidArgument("unknown provider type in provider_type_from_enum");
}
Expand Down Expand Up @@ -82,11 +79,8 @@ AuthProvider enum_from_provider_type(const IdentityProvider& provider)
else if (provider == IdentityProviderFunction) {
return AuthProvider::FUNCTION;
}
else if (provider == IdentityProviderUserAPIKey) {
return AuthProvider::USER_API_KEY;
}
else if (provider == IdentityProviderServerAPIKey) {
return AuthProvider::SERVER_API_KEY;
else if (provider == IdentityProviderAPIKey) {
return AuthProvider::API_KEY;
}
else {
REALM_UNREACHABLE();
Expand Down Expand Up @@ -179,14 +173,9 @@ AppCredentials AppCredentials::function(const std::string& serialized_payload)
}


AppCredentials AppCredentials::user_api_key(std::string api_key)
{
return AppCredentials(AuthProvider::USER_API_KEY, {{"key", api_key}});
}

AppCredentials AppCredentials::server_api_key(std::string api_key)
AppCredentials AppCredentials::api_key(std::string api_key)
{
return AppCredentials(AuthProvider::SERVER_API_KEY, {{"key", api_key}});
return AppCredentials(AuthProvider::API_KEY, {{"key", api_key}});
}

AppCredentials::AppCredentials(const AppCredentials& credentials)
Expand Down
19 changes: 5 additions & 14 deletions src/realm/object-store/sync/app_credentials.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,8 @@ extern IdentityProvider const IdentityProviderApple;
extern IdentityProvider const IdentityProviderFunction;

// A credential which can be used to log in as a Stitch user
// using the User API Key authentication provider.
extern IdentityProvider const IdentityProviderUserAPIKey;

// A credential which can be used to log in as a Stitch user
// using the Server API Key authentication provider.
extern IdentityProvider const IdentityProviderServerAPIKey;
// using the Server's API Key authentication provider.
extern IdentityProvider const IdentityProviderAPIKey;

enum class AuthProvider {
ANONYMOUS,
Expand All @@ -79,8 +75,7 @@ enum class AuthProvider {
CUSTOM,
USERNAME_PASSWORD,
FUNCTION,
USER_API_KEY,
SERVER_API_KEY
API_KEY,
};

IdentityProvider provider_type_from_enum(AuthProvider provider);
Expand Down Expand Up @@ -118,12 +113,8 @@ struct AppCredentials {
// The payload is a MongoDB document as json
static AppCredentials function(const std::string& serialized_payload);

// Construct and return credentials with the user api key.
static AppCredentials user_api_key(std::string api_key);

// Construct and return credentials with the server api key.
static AppCredentials server_api_key(std::string api_key);

// Construct and return credentials with the api key.
static AppCredentials api_key(std::string api_key);

// The provider of the credential
AuthProvider provider() const;
Expand Down
17 changes: 4 additions & 13 deletions src/realm/object-store/sync/sync_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -876,30 +876,21 @@ static sync::Session::Config::ClientReset make_client_reset_config(RealmConfig s

session_config.sync_config = std::make_shared<SyncConfig>(*session_config.sync_config); // deep copy
session_config.scheduler = nullptr;
auto make_frozen_config = [](RealmConfig config) {
// Opening without using any explicit schema implies that we will use whatever is on disk without making any
// schema changes. Otherwise we may hit a schema error if we are in a client reset when opening a Realm that
// has additional tables in the schema config than the one on disk. This may happen in an async open.
config.schema.reset();
return config;
};
if (session_config.sync_config->notify_after_client_reset) {
config.notify_after_client_reset = [config = session_config, &make_frozen_config](VersionID previous_version,
bool did_recover) {
config.notify_after_client_reset = [config = session_config](VersionID previous_version, bool did_recover) {
auto local_coordinator = RealmCoordinator::get_coordinator(config);
REALM_ASSERT(local_coordinator);
ThreadSafeReference active_after = local_coordinator->get_unbound_realm();
SharedRealm frozen_before = local_coordinator->get_realm(make_frozen_config(config), previous_version);
SharedRealm frozen_before = local_coordinator->get_realm(config, previous_version);
REALM_ASSERT(frozen_before);
REALM_ASSERT(frozen_before->is_frozen());
config.sync_config->notify_after_client_reset(std::move(frozen_before), std::move(active_after),
did_recover);
};
}
if (session_config.sync_config->notify_before_client_reset) {
config.notify_before_client_reset = [config = session_config, &make_frozen_config](VersionID version) {
config.sync_config->notify_before_client_reset(
Realm::get_frozen_realm(make_frozen_config(config), version));
config.notify_before_client_reset = [config = session_config](VersionID version) {
config.sync_config->notify_before_client_reset(Realm::get_frozen_realm(config, version));
};
}

Expand Down
52 changes: 48 additions & 4 deletions test/object-store/realm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,11 +844,19 @@ TEST_CASE("SharedRealm: schema_subset_mode") {
SECTION("obtaining a frozen realm with an incompatible schema throws") {
config.schema = Schema{{"object", {{"value", PropertyType::Int}}}};
auto old_realm = Realm::get_shared_realm(config);
{
auto tr = db->start_write();
auto table = tr->get_table("class_object");
table->create_object();
tr->commit();
}
old_realm->read_group();

{
auto tr = db->start_write();
tr->add_table("class_object 2")->add_column(type_Int, "value");
auto table = tr->add_table("class_object 2");
ColKey val_col = table->add_column(type_Int, "value");
table->create_object().set(val_col, 1);
tr->commit();
}

Expand All @@ -862,10 +870,46 @@ TEST_CASE("SharedRealm: schema_subset_mode") {
REQUIRE(old_realm->freeze()->schema().size() == 1);
REQUIRE(new_realm->freeze()->schema().size() == 2);
REQUIRE(Realm::get_frozen_realm(config, new_realm->read_transaction_version())->schema().size() == 2);
// Fails because the requested version doesn't have the "object 2" table
// required by the config
// An additive change is allowed, the unknown table is empty
REQUIRE(Realm::get_frozen_realm(config, old_realm->read_transaction_version())->schema().size() == 2);

config.schema = Schema{{"object", {{"value", PropertyType::String}}}}; // int -> string
// Fails because the schema has an invalid breaking change
REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, new_realm->read_transaction_version()),
InvalidReadOnlySchemaChangeException);
REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, old_realm->read_transaction_version()),
InvalidReadOnlySchemaChangeException);
config.schema = Schema{
{"object", {{"value", PropertyType::Int}}},
{"object 2", {{"value", PropertyType::String}}}, // int -> string
};
// fails due to invalid change on object 2 type
REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, new_realm->read_transaction_version()),
InvalidReadOnlySchemaChangeException);
// opening the old state does not fail because the schema is an additive change
auto frozen_old = Realm::get_frozen_realm(config, old_realm->read_transaction_version());
REQUIRE(frozen_old->schema().size() == 2);
{
TableRef table = frozen_old->read_group().get_table("class_object");
Results results(frozen_old, table);
REQUIRE(results.is_frozen());
REQUIRE(results.size() == 1);
}
{
TableRef table = frozen_old->read_group().get_table("class_object 2");
REQUIRE(!table);
Results results(frozen_old, table);
REQUIRE(results.is_frozen());
REQUIRE(results.size() == 0);
}
config.schema = Schema{
{"object", {{"value", PropertyType::Int}, {"value 2", PropertyType::String}}}, // add property
};
// fails due to additional property on object
REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, old_realm->read_transaction_version()),
InvalidExternalSchemaChangeException);
InvalidReadOnlySchemaChangeException);
REQUIRE_THROWS_AS(Realm::get_frozen_realm(config, new_realm->read_transaction_version()),
InvalidReadOnlySchemaChangeException);
}
}

Expand Down
16 changes: 5 additions & 11 deletions test/object-store/sync/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4484,18 +4484,12 @@ TEST_CASE("app: auth providers", "[sync][app]") {
CHECK(credentials.serialize_as_bson() == bson::BsonDocument{{"name", "mongo"}});
}

SECTION("auth providers user api key") {
auto credentials = AppCredentials::user_api_key("a key");
CHECK(credentials.provider() == AuthProvider::USER_API_KEY);
CHECK(credentials.provider_as_string() == IdentityProviderUserAPIKey);
CHECK(credentials.serialize_as_bson() == bson::BsonDocument{{"provider", "api-key"}, {"key", "a key"}});
}

SECTION("auth providers server api key") {
auto credentials = AppCredentials::server_api_key("a key");
CHECK(credentials.provider() == AuthProvider::SERVER_API_KEY);
CHECK(credentials.provider_as_string() == IdentityProviderServerAPIKey);
SECTION("auth providers api key") {
auto credentials = AppCredentials::api_key("a key");
CHECK(credentials.provider() == AuthProvider::API_KEY);
CHECK(credentials.provider_as_string() == IdentityProviderAPIKey);
CHECK(credentials.serialize_as_bson() == bson::BsonDocument{{"provider", "api-key"}, {"key", "a key"}});
CHECK(enum_from_provider_type(provider_type_from_enum(AuthProvider::API_KEY)) == AuthProvider::API_KEY);
}
}

Expand Down
Loading

0 comments on commit 3f6cca0

Please sign in to comment.