Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user identities #2059

Merged
merged 1 commit into from
Oct 5, 2020
Merged
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
14 changes: 13 additions & 1 deletion Realm/Realm/Handles/SyncUserHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ public static extern void call_function(SyncUserHandle handle, AppHandle app,
IntPtr tcs_ptr, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_link_credentials", CallingConvention = CallingConvention.Cdecl)]
public static extern void link_credentials(SyncUserHandle hadnle, AppHandle app, Native.Credentials credentials, IntPtr tcs_ptr, out NativeException ex);
public static extern void link_credentials(SyncUserHandle handle, AppHandle app, Native.Credentials credentials, IntPtr tcs_ptr, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncuser_get_serialized_identities", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr get_identities(SyncUserHandle handle, IntPtr buffer, IntPtr bufsize, out NativeException ex);

#region Api Keys

Expand Down Expand Up @@ -253,6 +256,15 @@ public void LinkCredentials(AppHandle app, Native.Credentials credentials, TaskC
ex.ThrowIfNecessary(tcsHandle);
}

public string GetIdentities()
{
return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) =>
{
isNull = false;
return NativeMethods.get_identities(this, buffer, length, out ex);
});
}

#region Api Keys

public void CreateApiKey(AppHandle app, string name, TaskCompletionSource<UserApiKey[]> tcs)
Expand Down
14 changes: 14 additions & 0 deletions Realm/Realm/Sync/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using Realms.Helpers;
using Realms.Native;
using Realms.Sync.Exceptions;
Expand Down Expand Up @@ -115,6 +116,19 @@ public BsonDocument CustomData
}
}

/// <summary>
/// Gets a collection of all identities associated with this user.
/// </summary>
/// <value>The user's identities across different <see cref="Credentials.AuthProvider"/>s.</value>
public UserIdentity[] Identities
{
get
{
var serialized = Handle.GetIdentities();
return BsonSerializer.Deserialize<UserIdentity[]>(serialized);
}
}

/// <summary>
/// Gets a <see cref="ApiKeyApi"/> instance that exposes functionality about managing user API keys.
/// </summary>
Expand Down
62 changes: 62 additions & 0 deletions Realm/Realm/Sync/UserIdentity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

using MongoDB.Bson.Serialization.Attributes;

namespace Realms.Sync
{
/// <summary>
/// A class containing information about an identity associated with a user.
/// </summary>
[BsonNoId]
public class UserIdentity
{
/// <summary>
/// Gets the unique identifier for this identity.
/// </summary>
/// <value>The identity's Id.</value>
public string Id { get; private set; }

/// <summary>
/// Gets the auth provider defining this identity.
/// </summary>
/// <value>The identity's auth provider.</value>
public Credentials.AuthProvider Provider { get; private set; }

/// <inheritdoc/>
public override bool Equals(object obj) => (obj is UserIdentity id) && id.Id == Id && id.Provider == Provider;

/// <summary>
/// Gets the hash code.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
var hashCode = -1285871140;
hashCode = (hashCode * -1521134295) + (Id?.GetHashCode() ?? 0);
hashCode = (hashCode * -1521134295) + Provider.GetHashCode();
return hashCode;
}

/// <summary>
/// Returns a string representation of the value.
/// </summary>
/// <returns>A string representation of the value.</returns>
public override string ToString() => $"UserIdentity: {Id} ({Provider})";
}
}
28 changes: 28 additions & 0 deletions Tests/Realm.Tests/Sync/UserManagementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ public void AppSwitchUser_SwitchesCurrentUser()
public void AppSwitchUser_WhenUserIsCurrent_DoesNothing()
{
var first = GetFakeUser();
Assert.That(DefaultApp.CurrentUser, Is.EqualTo(first));

var second = GetFakeUser();

Assert.That(DefaultApp.CurrentUser, Is.EqualTo(second));
Expand Down Expand Up @@ -194,15 +196,27 @@ public void User_LinkCredentials_AllowsLoginWithNewCredentials()
SyncTestHelpers.RunBaasTestAsync(async () =>
{
var user = await DefaultApp.LogInAsync(Credentials.Anonymous());

Assert.That(user.Identities, Has.Length.EqualTo(1));
Assert.That(user.Identities[0].Provider, Is.EqualTo(Credentials.AuthProvider.Anonymous));
Assert.That(user.Identities[0].Id, Is.Not.Null);

var email = SyncTestHelpers.GetVerifiedUsername();
await DefaultApp.EmailPasswordAuth.RegisterUserAsync(email, SyncTestHelpers.DefaultPassword);
var linkedUser = await user.LinkCredentialsAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword));

Assert.That(user.Identities, Has.Length.EqualTo(2));
Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword));
Assert.That(user.Identities[1].Id, Is.Not.Null);

Assert.That(linkedUser.Identities, Has.Length.EqualTo(2));
Assert.That(linkedUser.Id, Is.EqualTo(user.Id));
Assert.That(linkedUser.Identities, Is.EquivalentTo(user.Identities));

var emailPasswordUser = await DefaultApp.LogInAsync(Credentials.EmailPassword(email, SyncTestHelpers.DefaultPassword));

Assert.That(emailPasswordUser.Id, Is.EqualTo(user.Id));
Assert.That(emailPasswordUser.Identities, Is.EquivalentTo(user.Identities));
});
}

Expand All @@ -227,6 +241,16 @@ public void User_LinkCredentials_MultipleTimes_AllowsLoginWithAllCredentials()

var functionUser = await DefaultApp.LogInAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId }));
Assert.That(functionUser.Id, Is.EqualTo(user.Id));

Assert.That(user.Identities, Has.Length.EqualTo(3));
Assert.That(user.Identities[0].Provider, Is.EqualTo(Credentials.AuthProvider.Anonymous));
Assert.That(user.Identities[0].Id, Is.Not.Null);

Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.EmailPassword));
Assert.That(user.Identities[1].Id, Is.Not.Null);

Assert.That(user.Identities[2].Provider, Is.EqualTo(Credentials.AuthProvider.Function));
Assert.That(user.Identities[2].Id, Is.EqualTo(functionId));
});
}

Expand All @@ -246,6 +270,10 @@ public void User_LinkCredentials_MultipleTimesSameCredentials_IsNoOp()

var functionUser = await DefaultApp.LogInAsync(Credentials.Function(new { realmCustomAuthFuncUserId = functionId }));
Assert.That(functionUser.Id, Is.EqualTo(user.Id));

Assert.That(user.Identities, Has.Length.EqualTo(2));
Assert.That(user.Identities[1].Id, Is.EqualTo(functionId));
Assert.That(user.Identities[1].Provider, Is.EqualTo(Credentials.AuthProvider.Function));
});
}

Expand Down
95 changes: 57 additions & 38 deletions wrappers/src/sync_user_cs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,54 @@ namespace realm {

invoke_api_key_callback(tcs_ptr, api_keys, err);
}

inline AuthProvider to_auth_provider(const std::string& provider) {
if (provider == IdentityProviderAnonymous) {
return AuthProvider::ANONYMOUS;
}

if (provider == IdentityProviderFacebook) {
return AuthProvider::FACEBOOK;
}

if (provider == IdentityProviderGoogle) {
return AuthProvider::GOOGLE;
}

if (provider == IdentityProviderApple) {
return AuthProvider::APPLE;
}

if (provider == IdentityProviderCustom) {
return AuthProvider::CUSTOM;
}

if (provider == IdentityProviderUsernamePassword) {
return AuthProvider::USERNAME_PASSWORD;
}

if (provider == IdentityProviderFunction) {
return AuthProvider::FUNCTION;
}

if (provider == IdentityProviderUserAPIKey) {
return AuthProvider::USER_API_KEY;
}

if (provider == IdentityProviderServerAPIKey) {
return AuthProvider::SERVER_API_KEY;
}

return (AuthProvider)999;
}
}

void to_json(nlohmann::json& j, const SyncUserIdentity& i)
{
j = nlohmann::json{
{ "Id", i.id },
{ "Provider", to_auth_provider(i.provider_type)}
};
}
}

Expand Down Expand Up @@ -161,44 +209,7 @@ extern "C" {
REALM_EXPORT AuthProvider realm_syncuser_get_auth_provider(SharedSyncUser& user, NativeException::Marshallable& ex)
{
return handle_errors(ex, [&] {
auto provider = user->provider_type();
if (provider == IdentityProviderAnonymous) {
return AuthProvider::ANONYMOUS;
}

if (provider == IdentityProviderFacebook) {
return AuthProvider::FACEBOOK;
}

if (provider == IdentityProviderGoogle) {
return AuthProvider::GOOGLE;
}

if (provider == IdentityProviderApple) {
return AuthProvider::APPLE;
}

if (provider == IdentityProviderCustom) {
return AuthProvider::CUSTOM;
}

if (provider == IdentityProviderUsernamePassword) {
return AuthProvider::USERNAME_PASSWORD;
}

if (provider == IdentityProviderFunction) {
return AuthProvider::FUNCTION;
}

if (provider == IdentityProviderUserAPIKey) {
return AuthProvider::USER_API_KEY;
}

if (provider == IdentityProviderServerAPIKey) {
return AuthProvider::SERVER_API_KEY;
}

return (AuthProvider)999;
return to_auth_provider(user->provider_type());
});
}

Expand Down Expand Up @@ -280,6 +291,14 @@ extern "C" {
});
}

REALM_EXPORT size_t realm_syncuser_get_serialized_identities(SharedSyncUser& user, uint16_t* string_buffer, size_t buffer_size, NativeException::Marshallable& ex)
{
return handle_errors(ex, [&]() -> size_t {
nlohmann::json j = user->identities();
return stringdata_to_csharpstringbuffer(j.dump(), string_buffer, buffer_size);
});
}

REALM_EXPORT SharedApp* realm_syncuser_get_app(SharedSyncUser& user, NativeException::Marshallable& ex)
{
return handle_errors(ex, [&] {
Expand Down