diff --git a/Storage/Storage.Test/UserTest.cs b/Storage/Storage.Test/UserTest.cs index f839c47..024b93b 100644 --- a/Storage/Storage.Test/UserTest.cs +++ b/Storage/Storage.Test/UserTest.cs @@ -35,7 +35,7 @@ namespace LeanCloudTests { [Test] public async Task LoginWithEmail() { - AVUser user = await AVUser.LogInByEmailAsync("111111@qq.com", "111111"); + AVUser user = await AVUser.LogInWithEmailAsync("111111@qq.com", "111111"); Assert.AreEqual(user, AVUser.CurrentUser); TestContext.Out.WriteLine($"{AVUser.CurrentUser.SessionToken} login"); } @@ -49,7 +49,7 @@ namespace LeanCloudTests { [Test] public async Task IsAuthenticated() { - AVUser user = await AVUser.LogInByEmailAsync("111111@qq.com", "111111"); + AVUser user = await AVUser.LogInWithEmailAsync("111111@qq.com", "111111"); Assert.IsTrue(user.IsCurrent); Assert.AreEqual(user, AVUser.CurrentUser); bool authenticated = await user.IsAuthenticatedAsync(); @@ -58,7 +58,7 @@ namespace LeanCloudTests { [Test] public async Task RefreshSessionToken() { - AVUser user = await AVUser.LogInByEmailAsync("111111@qq.com", "111111"); + AVUser user = await AVUser.LogInWithEmailAsync("111111@qq.com", "111111"); Assert.IsTrue(user.IsCurrent); await user.RefreshSessionTokenAsync(); TestContext.Out.WriteLine(user.SessionToken); diff --git a/Storage/Storage/Internal/Authentication/IAVAuthenticationProvider.cs b/Storage/Storage/Internal/Authentication/IAVAuthenticationProvider.cs deleted file mode 100644 index 406e37c..0000000 --- a/Storage/Storage/Internal/Authentication/IAVAuthenticationProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace LeanCloud.Storage.Internal { - public interface IAVAuthenticationProvider { - /// - /// Authenticates with the service. - /// - /// The cancellation token. - Task> AuthenticateAsync(CancellationToken cancellationToken); - - /// - /// Deauthenticates (logs out) the user associated with this provider. This - /// call may block. - /// - void Deauthenticate(); - - /// - /// Restores authentication that has been serialized, such as session keys, - /// etc. - /// - /// The auth data for the provider. This value may be null - /// when unlinking an account. - /// true iff the authData was successfully synchronized. A false return - /// value indicates that the user should no longer be associated because of bad auth - /// data. - bool RestoreAuthentication(IDictionary authData); - - /// - /// Provides a unique name for the type of authentication the provider does. - /// For example, the FacebookAuthenticationProvider would return "facebook". - /// - string AuthType { get; } - } -} diff --git a/Storage/Storage/Public/AVUser.cs b/Storage/Storage/Public/AVUser.cs index 7a7f910..465397f 100644 --- a/Storage/Storage/Public/AVUser.cs +++ b/Storage/Storage/Public/AVUser.cs @@ -11,9 +11,6 @@ namespace LeanCloud { /// [AVClassName("_User")] public class AVUser : AVObject { - private static readonly IDictionary authProviders = - new Dictionary(); - internal static AVUserController UserController { get { return AVPlugins.Instance.UserController; @@ -21,52 +18,22 @@ namespace LeanCloud { } /// - /// 判断是否是当前用户 + /// 获取当前用户 /// - public bool IsCurrent { + public static AVUser CurrentUser { + get; + internal set; + } + + /// + /// 创建一个 AVUser 查询对象 + /// + public static AVQuery Query { get { - return CurrentUser == this; + return new AVQuery(); } } - /// - /// 判断当前用户的 Session Token 是否有效 - /// - /// - public async Task IsAuthenticatedAsync() { - lock (mutex) { - if (SessionToken == null || CurrentUser == null || CurrentUser.ObjectId != ObjectId) { - return false; - } - } - var command = new AVCommand { - Path = $"users/me?session_token={SessionToken}", - Method = HttpMethod.Get - }; - await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - return true; - } - - /// - /// 刷新用户的 Session Token,刷新后 Session Token 将会改变 - /// - /// - public async Task RefreshSessionTokenAsync() { - var serverState = await UserController.RefreshSessionTokenAsync(ObjectId); - HandleSave(serverState); - } - - /// - /// 获取 Session Token - /// - public string SessionToken { - get { - if (State.ContainsKey("sessionToken")) { - return State["sessionToken"] as string; - } - return null; - } - } /// /// 用户名 @@ -134,6 +101,33 @@ namespace LeanCloud { } } + /// + /// 获取 Session Token + /// + public string SessionToken { + get { + if (State.ContainsKey("sessionToken")) { + return State["sessionToken"] as string; + } + return null; + } + } + + /// + /// 用户数据 + /// + internal IDictionary> AuthData { + get { + if (TryGetValue("authData", out IDictionary> authData)) { + return authData; + } + return null; + } + private set { + this["authData"] = value; + } + } + /// /// 判断用户是否为匿名用户 /// @@ -143,6 +137,44 @@ namespace LeanCloud { } } + /// + /// 判断是否是当前用户 + /// + public bool IsCurrent { + get { + return CurrentUser == this; + } + } + + /// + /// 判断当前用户的 Session Token 是否有效 + /// + /// + public async Task IsAuthenticatedAsync() { + lock (mutex) { + if (SessionToken == null || CurrentUser == null || CurrentUser.ObjectId != ObjectId) { + return false; + } + } + var command = new AVCommand { + Path = $"users/me?session_token={SessionToken}", + Method = HttpMethod.Get + }; + await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); + return true; + } + + /// + /// 刷新用户的 Session Token,刷新后 Session Token 将会改变 + /// + /// + public async Task RefreshSessionTokenAsync() { + var serverState = await UserController.RefreshSessionTokenAsync(ObjectId); + HandleSave(serverState); + } + + #region 账号密码登陆 + /// /// 注册新用户,注册成功后将保存为当前用户。必须设置用户名和密码。 /// @@ -170,99 +202,6 @@ namespace LeanCloud { } } - #region 事件流系统相关 API - - /// - /// 关注某个用户 - /// - /// 被关注的用户 - /// - public Task FollowAsync(string userObjectId) { - return FollowAsync(userObjectId, null); - } - - /// - /// 关注某个用户 - /// - /// 被关注的用户Id - /// 关注的时候附加属性 - /// - public Task FollowAsync(string userObjectId, IDictionary data) { - if (data != null) { - data = EncodeForSaving(data); - } - var command = new AVCommand { - Path = $"users/{ObjectId}/friendship/{userObjectId}", - Method = HttpMethod.Post, - Content = data - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 取关某一个用户 - /// - /// - /// - public Task UnfollowAsync(string userObjectId) { - var command = new AVCommand { - Path = $"users/{ObjectId}/friendship/{userObjectId}", - Method = HttpMethod.Delete - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - /// - /// 获取当前用户的关注者的查询 - /// - /// - public AVQuery GetFollowerQuery() { - AVQuery query = new AVQuery { - Path = $"users/{ObjectId}/followers" - }; - return query; - } - - /// - /// 获取当前用户所关注的用户的查询 - /// - /// - public AVQuery GetFolloweeQuery() { - AVQuery query = new AVQuery { - Path = $"users/{ObjectId}/followees" - }; - return query; - } - - /// - /// 同时查询关注了当前用户的关注者和当前用户所关注的用户 - /// - /// - public AVQuery GetFollowersAndFolloweesQuery() { - AVQuery query = new AVQuery { - Path = $"users/{ObjectId}/followersAndFollowees" - }; - return query; - } - - /// - /// 获取当前用户的关注者 - /// - /// - public Task> GetFollowersAsync() { - return GetFollowerQuery().FindAsync(CancellationToken.None); - } - - /// - /// 获取当前用户所关注的用户 - /// - /// - public Task> GetFolloweesAsync() { - return GetFolloweeQuery().FindAsync(CancellationToken.None); - } - - #endregion - /// /// 使用用户名和密码登陆。登陆成功后,将用户设置为当前用户 /// @@ -277,45 +216,16 @@ namespace LeanCloud { } /// - /// 使用 Session Token 登录。 + /// 用邮箱作和密码匹配登录 /// - /// Session Token + /// 邮箱 + /// 密码 /// - public static async Task BecomeAsync(string sessionToken) { - var ret = await UserController.GetUserAsync(sessionToken); + public static async Task LogInWithEmailAsync(string email, string password) { + var ret = await UserController.LogInAsync(null, email, password); AVUser user = FromState(ret, "_User"); CurrentUser = user; - return user; - } - - /// - /// 用户登出 - /// - public static void LogOut() { - CurrentUser = null; - } - - private static void LogOutWithProviders() { - foreach (var provider in authProviders.Values) { - provider.Deauthenticate(); - } - } - - /// - /// 获取当前用户 - /// - public static AVUser CurrentUser { - get; - internal set; - } - - /// - /// 创建一个 AVUser 查询对象 - /// - public static AVQuery Query { - get { - return new AVQuery(); - } + return CurrentUser; } /// @@ -339,83 +249,99 @@ namespace LeanCloud { } /// - /// 用户数据 + /// 申请发送验证邮箱的邮件,一周之内有效 + /// 如果该邮箱已经验证通过,会直接返回 True,并不会真正发送邮件 + /// 注意,不能频繁的调用此接口,一天之内只允许向同一个邮箱发送验证邮件 3 次,超过调用次数,会直接返回错误 /// - internal IDictionary> AuthData { - get { - if (TryGetValue("authData", out IDictionary> authData)) { - return authData; - } - return null; - } - private set { - this["authData"] = value; - } + /// 邮箱地址 + /// + public static Task RequestEmailVerifyAsync(string email) { + Dictionary strs = new Dictionary { + { "email", email } + }; + var command = new AVCommand { + Path = "requestEmailVerify", + Method = HttpMethod.Post, + Content = strs + }; + return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); } - private static IAVAuthenticationProvider GetProvider(string providerName) { - if (authProviders.TryGetValue(providerName, out IAVAuthenticationProvider provider)) { - return provider; + #endregion + + + #region 第三方登录 + + /// + /// 使用第三方数据注册;如果已经存在相同的 Auth Data,则执行登录 + /// + /// Auth Data + /// 平台 + /// + /// + public static Task LogInWithAuthDataAsync(IDictionary authData, string platform, AVUserAuthDataLogInOption options = null) { + if (options == null) { + options = new AVUserAuthDataLogInOption(); } - return null; + return LogInWithAsync(platform, authData, options.FailOnNotExist); } /// - /// Removes null values from authData (which exist temporarily for unlinking) + /// 使用第三方数据注册; /// - private void CleanupAuthData() { - lock (mutex) { - if (!IsCurrent) { - return; - } - var authData = AuthData; - - if (authData == null) { - return; - } - - foreach (var pair in new Dictionary>(authData)) { - if (pair.Value == null) { - authData.Remove(pair.Key); - } - } + /// Auth data + /// 平台标识 + /// + /// + /// + public static Task LogInWithAuthDataAndUnionIdAsync(IDictionary authData, string platform, string unionId, + AVUserAuthDataLogInOption options = null) { + if (options == null) { + options = new AVUserAuthDataLogInOption(); } + MergeAuthData(authData, unionId, options); + return LogInWithAsync(platform, authData, options.FailOnNotExist); } /// - /// Synchronizes authData for all providers. + /// 绑定第三方登录 /// - private void SynchronizeAllAuthData() { - lock (mutex) { - var authData = AuthData; - - if (authData == null) { - return; - } - - foreach (var pair in authData) { - SynchronizeAuthData(GetProvider(pair.Key)); - } - } + /// Auth data + /// 平台标识 + /// + public Task AssociateAuthDataAsync(IDictionary authData, string platform) { + return LinkWithAsync(platform, authData); } - private void SynchronizeAuthData(IAVAuthenticationProvider provider) { - bool restorationSuccess = false; - lock (mutex) { - var authData = AuthData; - if (authData == null || provider == null) { - return; - } - if (authData.TryGetValue(provider.AuthType, out IDictionary data)) { - restorationSuccess = provider.RestoreAuthentication(data); - } - } - - if (!restorationSuccess) { - UnlinkFromAsync(provider.AuthType, CancellationToken.None); + /// + /// 绑定第三方登录 + /// + /// OAuth data + /// 平台标识 + /// + /// + /// + public Task AssociateAuthDataAndUnionIdAsync(IDictionary authData, string platform, string unionId, + AVUserAuthDataLogInOption options = null) { + if (options == null) { + options = new AVUserAuthDataLogInOption(); } + MergeAuthData(authData, unionId, options); + return LinkWithAsync(platform, authData); } + /// + /// 解绑第三方登录 + /// + /// 平台标识 + /// + public Task DisassociateWithAuthDataAsync(string platform) { + return LinkWithAsync(platform, null); + } + + #endregion + + public Task LinkWithAsync(string authType, IDictionary data) { AuthData = new Dictionary> { [authType] = data @@ -423,85 +349,25 @@ namespace LeanCloud { return SaveAsync(); } - public Task LinkWithAsync(string authType, CancellationToken cancellationToken) { - var provider = GetProvider(authType); - return provider.AuthenticateAsync(cancellationToken) - .OnSuccess(t => LinkWithAsync(authType, t.Result)) - .Unwrap(); - } - - /// - /// Unlinks a user from a service. - /// - public Task UnlinkFromAsync(string authType, CancellationToken cancellationToken) { - return LinkWithAsync(authType, null); - } - - /// - /// Checks whether a user is linked to a service. - /// - internal bool IsLinked(string authType) { - lock (mutex) { - return AuthData != null && AuthData.ContainsKey(authType) && AuthData[authType] != null; - } - } - internal static async Task LogInWithAsync(string authType, IDictionary data, bool failOnNotExist) { var ret = await UserController.LogInAsync(authType, data, failOnNotExist); AVUser user = FromState(ret, "_User"); - user.AuthData = new Dictionary>(); - user.AuthData[authType] = data; - user.SynchronizeAllAuthData(); + user.AuthData = new Dictionary> { + [authType] = data + }; CurrentUser = user; return CurrentUser; } - internal static Task LogInWithAsync(string authType) { - var provider = GetProvider(authType); - return provider.AuthenticateAsync(CancellationToken.None) - .OnSuccess(authData => LogInWithAsync(authType, authData.Result, false)) - .Unwrap(); - } - - internal static void RegisterProvider(IAVAuthenticationProvider provider) { - authProviders[provider.AuthType] = provider; - var curUser = CurrentUser; - if (curUser != null) { - curUser.SynchronizeAuthData(provider); - } - } - #region 手机号登录 - internal static async Task LogInWithParametersAsync(Dictionary strs) { - IObjectState ret = await UserController.LogInWithParametersAsync("login", strs); - AVUser user = CreateWithoutData(null); - user.HandleFetchResult(ret); - CurrentUser = user; - return CurrentUser; - } - - /// - /// 用邮箱作和密码匹配登录 - /// - /// 邮箱 - /// 密码 - /// - public static async Task LogInByEmailAsync(string email, string password) { - var ret = await UserController.LogInAsync(null, email, password); - AVUser user = FromState(ret, "_User"); - CurrentUser = user; - return CurrentUser; - } - - /// /// 用手机号和密码匹配登陆 /// /// 手机号 /// 密码 /// - public static Task LogInByMobilePhoneNumberAsync(string mobilePhoneNumber, string password) { + public static Task LogInWithMobilePhoneNumberAsync(string mobilePhoneNumber, string password) { Dictionary strs = new Dictionary { { "mobilePhoneNumber", mobilePhoneNumber }, { "password", password } @@ -515,7 +381,7 @@ namespace LeanCloud { /// 手机号 /// 短信验证码 /// - public static Task LogInByMobilePhoneNumberSmsCodeAsync(string mobilePhoneNumber, string smsCode) { + public static Task LogInWithMobilePhoneNumberSmsCodeAsync(string mobilePhoneNumber, string smsCode) { Dictionary strs = new Dictionary { { "mobilePhoneNumber", mobilePhoneNumber }, { "smsCode", smsCode } @@ -641,60 +507,10 @@ namespace LeanCloud { #endregion - #region 邮箱验证 - /// - /// 申请发送验证邮箱的邮件,一周之内有效 - /// 如果该邮箱已经验证通过,会直接返回 True,并不会真正发送邮件 - /// 注意,不能频繁的调用此接口,一天之内只允许向同一个邮箱发送验证邮件 3 次,超过调用次数,会直接返回错误 + /// 匿名登录 /// - /// 邮箱地址 /// - public static Task RequestEmailVerifyAsync(string email) { - Dictionary strs = new Dictionary { - { "email", email } - }; - var command = new AVCommand { - Path = "requestEmailVerify", - Method = HttpMethod.Post, - Content = strs - }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); - } - - #endregion - - #region AVUser Extension - - public IDictionary> GetAuthData() { - return AuthData; - } - - /// - /// use 3rd auth data to sign up or log in.if user with the same auth data exits,it will transfer as log in. - /// - /// OAuth data, like {"accessToken":"xxxxxx"} - /// auth platform,maybe "facebook"/"twiiter"/"weibo"/"weixin" .etc - /// - public static Task LogInWithAuthDataAsync(IDictionary data, string platform, AVUserAuthDataLogInOption options = null) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - return LogInWithAsync(platform, data, options.FailOnNotExist); - } - - public static Task LogInWithAuthDataAndUnionIdAsync( - IDictionary authData, - string platform, - string unionId, - AVUserAuthDataLogInOption options = null) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - MergeAuthData(authData, unionId, options); - return LogInWithAsync(platform, authData, options.FailOnNotExist); - } - public static Task LogInAnonymouslyAsync() { var data = new Dictionary { { "id", Guid.NewGuid().ToString() } @@ -704,35 +520,124 @@ namespace LeanCloud { } /// - /// link a 3rd auth account to the user. + /// 使用 Session Token 登录。 /// - /// OAuth data, like {"accessToken":"xxxxxx"} - /// auth platform,maybe "facebook"/"twiiter"/"weibo"/"weixin" .etc + /// Session Token /// - public Task AssociateAuthDataAsync(IDictionary data, string platform) { - return LinkWithAsync(platform, data); - } - - public Task AssociateAuthDataAndUnionIdAsync( - IDictionary authData, - string platform, - string unionId, - AVUserAuthDataLogInOption options = null) { - if (options == null) { - options = new AVUserAuthDataLogInOption(); - } - MergeAuthData(authData, unionId, options); - return LinkWithAsync(platform, authData); + public static async Task BecomeAsync(string sessionToken) { + var ret = await UserController.GetUserAsync(sessionToken); + AVUser user = FromState(ret, "_User"); + CurrentUser = user; + return user; } /// - /// unlink a 3rd auth account from the user. + /// 用户登出 /// - /// auth platform,maybe "facebook"/"twiiter"/"weibo"/"weixin" .etc - /// + public static void LogOut() { + CurrentUser = null; + } + + #region 事件流系统相关 API + + /// + /// 关注某个用户 + /// + /// 被关注的用户 /// - public Task DisassociateWithAuthDataAsync(string platform, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { - return UnlinkFromAsync(platform, cancellationToken); + public Task FollowAsync(string userObjectId) { + return FollowAsync(userObjectId, null); + } + + /// + /// 关注某个用户 + /// + /// 被关注的用户Id + /// 关注的时候附加属性 + /// + public Task FollowAsync(string userObjectId, IDictionary data) { + if (data != null) { + data = EncodeForSaving(data); + } + var command = new AVCommand { + Path = $"users/{ObjectId}/friendship/{userObjectId}", + Method = HttpMethod.Post, + Content = data + }; + return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); + } + + /// + /// 取关某一个用户 + /// + /// + /// + public Task UnfollowAsync(string userObjectId) { + var command = new AVCommand { + Path = $"users/{ObjectId}/friendship/{userObjectId}", + Method = HttpMethod.Delete + }; + return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command); + } + + /// + /// 获取当前用户的关注者的查询 + /// + /// + public AVQuery GetFollowerQuery() { + AVQuery query = new AVQuery { + Path = $"users/{ObjectId}/followers" + }; + return query; + } + + /// + /// 获取当前用户所关注的用户的查询 + /// + /// + public AVQuery GetFolloweeQuery() { + AVQuery query = new AVQuery { + Path = $"users/{ObjectId}/followees" + }; + return query; + } + + /// + /// 同时查询关注了当前用户的关注者和当前用户所关注的用户 + /// + /// + public AVQuery GetFollowersAndFolloweesQuery() { + AVQuery query = new AVQuery { + Path = $"users/{ObjectId}/followersAndFollowees" + }; + return query; + } + + /// + /// 获取当前用户的关注者 + /// + /// + public Task> GetFollowersAsync() { + return GetFollowerQuery().FindAsync(CancellationToken.None); + } + + /// + /// 获取当前用户所关注的用户 + /// + /// + public Task> GetFolloweesAsync() { + return GetFolloweeQuery().FindAsync(CancellationToken.None); + } + + #endregion + + + internal static async Task LogInWithParametersAsync(Dictionary strs) { + IObjectState ret = await UserController.LogInWithParametersAsync("login", strs); + AVUser user = CreateWithoutData(null); + user.HandleFetchResult(ret); + CurrentUser = user; + return CurrentUser; } /// 合并为支持 AuthData 的格式 @@ -741,7 +646,5 @@ namespace LeanCloud { authData["main_account"] = options.AsMainAccount; authData["unionid"] = unionId; } - - #endregion } }