Ejemplo n.º 1
0
        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));
            }
        }
Ejemplo n.º 2
0
 protected abstract Task <RawResult> RawCreateOrUpdateGoogleUserAsync(
     ISqlCallContext ctx,
     int actorId,
     int userId,
     [ParameterSource] IUserGoogleInfo info,
     CreateOrUpdateMode mode,
     CancellationToken cancellationToken);
Ejemplo n.º 3
0
        /// <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);
        }
Ejemplo n.º 4
0
        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);
            }
        }
Ejemplo n.º 5
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="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);
        }
Ejemplo n.º 6
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);
        }
Ejemplo n.º 7
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="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);
        }
Ejemplo n.º 8
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));
        }
Ejemplo n.º 9
0
        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));
        }
Ejemplo n.º 10
0
        int?IGenericAuthenticationProvider.LoginUser(ISqlCallContext ctx, object payload, bool actualLogin)
        {
            IUserGoogleInfo info = payload as IUserGoogleInfo;

            if (info == null)
            {
                return(null);
            }
            return(LoginUser(ctx, info, actualLogin));
        }
Ejemplo n.º 11
0
        /// <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);
        }
Ejemplo n.º 12
0
 /// <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));
 }
Ejemplo n.º 13
0
        /// <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);
        }
Ejemplo n.º 14
0
 protected abstract UCLResult UserGoogleUCL(
     ISqlCallContext ctx,
     int actorId,
     int userId,
     [ParameterSource] IUserGoogleInfo info,
     UCLMode mode);
Ejemplo n.º 15
0
 /// <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));
 }
Ejemplo n.º 16
0
 protected abstract RawResult RawCreateOrUpdateGoogleUser(
     ISqlCallContext ctx,
     int actorId,
     int userId,
     [ParameterSource] IUserGoogleInfo info,
     CreateOrUpdateMode mode);
Ejemplo n.º 17
0
        /// <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);
        }