public async Task explicit_refresh_token() { var p = TestHelper.StObjMap.Default.Obtain <Package>(); var userG = TestHelper.StObjMap.Default.Obtain <UserGoogleTable>(); // This is the PrimarySchool Google application. p.ClientId = "368841447214-b0hhtth684efi54lfjhs03uk4an28dd9.apps.googleusercontent.com"; p.ClientSecret = "GiApMZBp3RTxdNzsHbhAQKSG"; string googleAccountId = "112981383157638924429"; var user = TestHelper.StObjMap.Default.Obtain <UserTable>(); using (var ctx = new SqlStandardCallContext(TestHelper.Monitor)) { KnownUserGoogleInfo exists = await userG.FindUserInfoAsync(ctx, googleAccountId); IUserGoogleInfo info = exists?.Info; if (info == null) { var userName = Guid.NewGuid().ToString(); int userId = await user.CreateUserAsync(ctx, 1, userName); info = p.UserGoogleTable.CreateUserInfo <IUserGoogleInfo>(); info.GoogleAccountId = googleAccountId; info.RefreshToken = "1/t63rMARi7a9qQWIYEcKPVIrfnNJU51K2TpNB3hjrEjI"; await p.UserGoogleTable.CreateOrUpdateGoogleUserAsync(ctx, 1, userId, info); } info.AccessToken = null; Assert.That(await p.RefreshAccessTokenAsync(ctx, info)); Assert.That(info.AccessToken, Is.Not.Null); Assert.That(info.AccessTokenExpirationTime, Is.GreaterThan(DateTime.UtcNow)); Assert.That(info.AccessTokenExpirationTime, Is.LessThan(DateTime.UtcNow.AddDays(1))); Assert.That(await p.RefreshAccessTokenAsync(ctx, info)); } }
protected abstract Task <RawResult> RawCreateOrUpdateGoogleUserAsync( ISqlCallContext ctx, int actorId, int userId, [ParameterSource] IUserGoogleInfo info, CreateOrUpdateMode mode, CancellationToken cancellationToken);
/// <summary> /// Attempts to refreshes the user access token. /// On success, the database is updated. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="user"> /// The user must not be null and <see cref="UserGoogleInfoExtensions.IsValidForRefreshAccessToken"/> must be true. /// </param> /// <param name="cancellationToken">Optional cancellation token.</param> /// <returns>True on success, false on error.</returns> public async Task <bool> RefreshAccessTokenAsync(ISqlCallContext ctx, IUserGoogleInfo user, CancellationToken cancellationToken = default(CancellationToken)) { if (ctx == null) { throw new ArgumentNullException(nameof(ctx)); } if (user == null) { throw new ArgumentNullException(nameof(user)); } if (!user.IsValidForRefreshAccessToken()) { throw new ArgumentException("User info is not valid."); } try { var c = _client ?? (_client = CreateHttpClient(ApiUrl)); var parameters = new Dictionary <string, string> { { "grant_type", "refresh_token" }, { "refresh_token", user.RefreshToken }, { "client_id", ClientId }, { "client_secret", ClientSecret }, }; var response = await c.PostAsync(TokenEndpoint, new FormUrlEncodedContent( parameters ), cancellationToken).ConfigureAwait(false); var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); List <KeyValuePair <string, object> > token = null; if (response.IsSuccessStatusCode) { var m = new StringMatcher(content); object tok; if (m.MatchJSONObject(out tok)) { token = tok as List <KeyValuePair <string, object> >; } } if (token == null) { using (ctx.Monitor.OpenError().Send($"Unable to refresh token for GoogleAccountd = {user.GoogleAccountId}.")) { ctx.Monitor.Trace().Send($"Status: {response.StatusCode}, Reason: {response.ReasonPhrase}"); ctx.Monitor.Trace().Send(content); } return(false); } user.AccessToken = (string)token.Single(kv => kv.Key == "access_token").Value; double exp = (double)token.FirstOrDefault(kv => kv.Key == "expires_in").Value; user.AccessTokenExpirationTime = exp != 0 ? (DateTime?)DateTime.UtcNow.AddSeconds(exp) : null; // Creates or updates the user (ignoring the created/updated returned value). await UserGoogleTable.CreateOrUpdateGoogleUserAsync(ctx, 1, 0, user, CreateOrUpdateMode.UpdateOnly, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { ctx.Monitor.Error().Send(ex, $"Unable to refresh token for GoogleAccountd = {user.GoogleAccountId}."); return(false); } return(true); }
public async Task setting_default_scopes_impact_new_users() { var user = TestHelper.StObjMap.Default.Obtain <UserTable>(); var p = TestHelper.StObjMap.Default.Obtain <Package>(); var factory = TestHelper.StObjMap.Default.Obtain <IPocoFactory <IUserGoogleInfo> >(); using (var ctx = new SqlStandardCallContext()) { AuthScopeSet original = await p.ReadDefaultScopeSetAsync(ctx); original.Contains("nimp").Should().BeFalse(); original.Contains("thing").Should().BeFalse(); original.Contains("other").Should().BeFalse(); { int id = await user.CreateUserAsync(ctx, 1, Guid.NewGuid().ToString()); IUserGoogleInfo userInfo = factory.Create(); userInfo.GoogleAccountId = Guid.NewGuid().ToString(); await p.UserGoogleTable.CreateOrUpdateGoogleUserAsync(ctx, 1, id, userInfo); var info = await p.UserGoogleTable.FindKnownUserInfoAsync(ctx, userInfo.GoogleAccountId); AuthScopeSet userSet = await p.ReadScopeSetAsync(ctx, info.UserId); userSet.ToString().Should().Be(original.ToString()); } AuthScopeSet replaced = original.Clone(); replaced.Add(new AuthScopeItem("nimp")); replaced.Add(new AuthScopeItem("thing", ScopeWARStatus.Rejected)); replaced.Add(new AuthScopeItem("other", ScopeWARStatus.Accepted)); await p.AuthScopeSetTable.SetScopesAsync(ctx, 1, replaced); var readback = await p.ReadDefaultScopeSetAsync(ctx); readback.ToString().Should().Be(replaced.ToString()); // Default scopes have non W status! // This must not impact new users: their satus must always be be W. readback.ToString().Should().Contain("[R]thing") .And.Contain("[A]other"); { int id = await user.CreateUserAsync(ctx, 1, Guid.NewGuid().ToString()); IUserGoogleInfo userInfo = p.UserGoogleTable.CreateUserInfo <IUserGoogleInfo>(); userInfo.GoogleAccountId = Guid.NewGuid().ToString(); await p.UserGoogleTable.CreateOrUpdateGoogleUserAsync(ctx, 1, id, userInfo, UCLMode.CreateOnly | UCLMode.UpdateOnly); userInfo = (IUserGoogleInfo)(await p.UserGoogleTable.FindKnownUserInfoAsync(ctx, userInfo.GoogleAccountId)).Info; AuthScopeSet userSet = await p.ReadScopeSetAsync(ctx, id); userSet.ToString().Should().Contain("[W]thing") .And.Contain("[W]other") .And.Contain("[W]nimp"); } await p.AuthScopeSetTable.SetScopesAsync(ctx, 1, original); } }
/// <summary> /// Challenges <see cref="IUserGoogleInfo"/> data to identify a user. /// Note that a successful challenge may have side effects such as updating claims, access tokens or other data /// related to the user and this provider. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="payload">The payload to challenge.</param> /// <param name="actualLogin">Set it to false to avoid login side-effect (such as updating the LastLoginTime) on success.</param> /// <returns>The positive identifier of the user on success or 0 if the Google user does not exist.</returns> public async Task <int> LoginUserAsync(ISqlCallContext ctx, IUserGoogleInfo info, bool actualLogin = true, CancellationToken cancellationToken = default(CancellationToken)) { var mode = actualLogin ? CreateOrUpdateMode.UpdateOnly | CreateOrUpdateMode.WithLogin : CreateOrUpdateMode.UpdateOnly; var r = await RawCreateOrUpdateGoogleUserAsync(ctx, 1, 0, info, mode, cancellationToken).ConfigureAwait(false); return(r.Result == CreateOrUpdateResult.Updated ? r.UserId : 0); }
/// <summary> /// Challenges <see cref="IUserGoogleInfo"/> data to identify a user. /// Note that a successful challenge may have side effects such as updating claims, access tokens or other data /// related to the user and this provider. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="info">The payload to challenge.</param> /// <param name="actualLogin">Set it to false to avoid login side-effect (such as updating the LastLoginTime) on success.</param> /// <returns>The login result.</returns> public LoginResult LoginUser(ISqlCallContext ctx, IUserGoogleInfo info, bool actualLogin = true) { var mode = actualLogin ? UCLMode.UpdateOnly | UCLMode.WithActualLogin : UCLMode.UpdateOnly | UCLMode.WithCheckLogin; var r = UserGoogleUCL(ctx, 1, 0, info, mode); return(r.LoginResult); }
/// <summary> /// Challenges <see cref="IUserGoogleInfo"/> data to identify a user. /// Note that a successful challenge may have side effects such as updating claims, access tokens or other data /// related to the user and this provider. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="payload">The payload to challenge.</param> /// <param name="actualLogin">Set it to false to avoid login side-effect (such as updating the LastLoginTime) on success.</param> /// <returns>The positive identifier of the user on success or 0 if the Google user does not exist.</returns> public int LoginUser(ISqlCallContext ctx, IUserGoogleInfo info, bool actualLogin = true) { var mode = actualLogin ? CreateOrUpdateMode.UpdateOnly | CreateOrUpdateMode.WithLogin : CreateOrUpdateMode.UpdateOnly; var r = RawCreateOrUpdateGoogleUser(ctx, 1, 0, info, mode); return(r.Result == CreateOrUpdateResult.Updated ? r.UserId : 0); }
async Task <int?> IGenericAuthenticationProvider.LoginUserAsync(ISqlCallContext ctx, object payload, bool actualLogin, CancellationToken cancellationToken) { IUserGoogleInfo info = payload as IUserGoogleInfo; if (info == null) { return(null); } return(await LoginUserAsync(ctx, info, actualLogin, cancellationToken)); }
Task <CreateOrUpdateResult> IGenericAuthenticationProvider.CreateOrUpdateUserAsync(ISqlCallContext ctx, int actorId, int userId, object payload, CreateOrUpdateMode mode, CancellationToken cancellationToken) { IUserGoogleInfo info = payload as IUserGoogleInfo; if (info == null) { throw new ArgumentException(nameof(payload)); } return(CreateOrUpdateGoogleUserAsync(ctx, actorId, userId, info, mode, cancellationToken)); }
int?IGenericAuthenticationProvider.LoginUser(ISqlCallContext ctx, object payload, bool actualLogin) { IUserGoogleInfo info = payload as IUserGoogleInfo; if (info == null) { return(null); } return(LoginUser(ctx, info, actualLogin)); }
/// <summary> /// Fill UserInfo properties from reader. /// </summary> /// <param name="info">The info to fill.</param> /// <param name="r">The data reader.</param> /// <param name="idx">The index of the first column.</param> /// <returns>The updated index.</returns> protected virtual int FillUserGoogleInfo(IUserGoogleInfo info, SqlDataReader r, int idx) { var props = _infoFactory.PocoClassType.GetProperties().Where(p => p.Name != nameof(IUserGoogleInfo.GoogleAccountId)); foreach (var p in props) { p.SetValue(info, r.GetValue(idx++)); } return(idx); }
/// <summary> /// Creates or updates a user entry for this provider. /// This is the "binding account" feature since it binds an external identity to /// an already existing user that may already be registered into other authencation providers. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="actorId">The acting actor identifier.</param> /// <param name="userId">The user identifier that must be registered.</param> /// <param name="info">Provider specific data: the <see cref="IUserGoogleInfo"/> poco.</param> /// <param name="mode">Optionnaly configures Create, Update only or WithLogin behavior.</param> /// <returns>The result.</returns> public UCLResult CreateOrUpdateGoogleUser(ISqlCallContext ctx, int actorId, int userId, IUserGoogleInfo info, UCLMode mode = UCLMode.CreateOrUpdate) { return(UserGoogleUCL(ctx, actorId, userId, info, mode)); }
/// <summary> /// Creates or updates a user entry for this provider. /// This is the "binding account" feature since it binds an external identity to /// an already existing user that may already be registered into other authencation providers. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="actorId">The acting actor identifier.</param> /// <param name="userId">The user identifier that must be registered.</param> /// <param name="payload">Provider specific data.</param> /// <param name="mode">Optionnaly configures Create, Update only or WithLogin behavior.</param> /// <returns>The operation result.</returns> public async Task <CreateOrUpdateResult> CreateOrUpdateGoogleUserAsync(ISqlCallContext ctx, int actorId, int userId, IUserGoogleInfo info, CreateOrUpdateMode mode = CreateOrUpdateMode.CreateOrUpdate, CancellationToken cancellationToken = default(CancellationToken)) { var r = await RawCreateOrUpdateGoogleUserAsync(ctx, actorId, userId, info, mode, cancellationToken).ConfigureAwait(false); return(r.Result); }
protected abstract UCLResult UserGoogleUCL( ISqlCallContext ctx, int actorId, int userId, [ParameterSource] IUserGoogleInfo info, UCLMode mode);
/// <summary> /// Gets whether this user info is valid for refresh: the access token may not be valid (it can even be null), /// but the <see cref="IUserGoogleInfo.RefreshToken"/> and <see cref="IUserGoogleInfo.GoogleAccountId"/> must be not null nor empty. /// </summary> public static bool IsValidForRefreshAccessToken(this IUserGoogleInfo @this) { return(!string.IsNullOrWhiteSpace(@this.GoogleAccountId) && !string.IsNullOrWhiteSpace(@this.RefreshToken)); }
protected abstract RawResult RawCreateOrUpdateGoogleUser( ISqlCallContext ctx, int actorId, int userId, [ParameterSource] IUserGoogleInfo info, CreateOrUpdateMode mode);
/// <summary> /// Creates or updates a user entry for this provider. /// This is the "binding account" feature since it binds an external identity to /// an already existing user that may already be registered into other authencation providers. /// </summary> /// <param name="ctx">The call context to use.</param> /// <param name="actorId">The acting actor identifier.</param> /// <param name="userId">The user identifier that must be registered.</param> /// <param name="payload">Provider specific data.</param> /// <param name="mode">Optionnaly configures Create, Update only or WithLogin behavior.</param> /// <returns>The operation result.</returns> public CreateOrUpdateResult CreateOrUpdateGoogleUser(ISqlCallContext ctx, int actorId, int userId, IUserGoogleInfo info, CreateOrUpdateMode mode = CreateOrUpdateMode.CreateOrUpdate) { var r = RawCreateOrUpdateGoogleUser(ctx, actorId, userId, info, mode); return(r.Result); }