public async Task <UserLoginResult> CreateAccountAndLoginAsync(IActivityMonitor monitor, IWebFrontAuthAutoCreateAccountContext context)
        {
            var userTable  = _stObjMap.StObjs.Obtain <CustomUserTable>();
            var actorEmail = _stObjMap.StObjs.Obtain <ActorEMailTable>();
            var oidcTable  = _stObjMap.StObjs.Obtain <UserOidcTable>();

            ICustomUserOidcInfos infos = (ICustomUserOidcInfos)context.Payload;
            ISqlCallContext      ctx   = context.HttpContext.RequestServices.GetService <ISqlCallContext>();

            SqlDefaultDatabase db = _stObjMap.StObjs.Obtain <SqlDefaultDatabase>();

            using (var sqlCtx = new SqlStandardCallContext())
            {
                UserQueries userQueries = new UserQueries(sqlCtx, db);
                int         exists      = await userQueries.CheckEmail(infos.Email);

                if (exists == 0)
                {
                    int userId = await userTable.CreateUserAsync(ctx, 1, Guid.NewGuid().ToString(),
                                                                 infos.FirstName, infos.LastName);

                    await actorEmail.AddEMailAsync(ctx, 1, userId, infos.Email, true, false);

                    UCLResult result = await oidcTable.CreateOrUpdateOidcUserAsync(
                        ctx, 1, userId, infos, UCLMode.CreateOrUpdate | UCLMode.WithActualLogin);

                    if (result.OperationResult != UCResult.Created)
                    {
                        return(null);
                    }

                    return(await _dbAuth.CreateUserLoginResultFromDatabase(ctx, _typeSystem, result.LoginResult));
                }
                else
                {
                    UCLResult result = await oidcTable.CreateOrUpdateOidcUserAsync(
                        ctx, 1, exists, infos, UCLMode.CreateOrUpdate | UCLMode.WithActualLogin);

                    return(await _dbAuth.CreateUserLoginResultFromDatabase(ctx, _typeSystem, result.LoginResult));
                }
            }
        }
Ejemplo n.º 2
0
        async Task <LoginResult> DoVerifyAsync(ISqlCallContext ctx, SqlCommand hashReader, string password, object objectKey, bool actualLogin, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(password))
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidCredentials));
            }
            // 1 - Get the PwdHash, UserId and FailedAttemptCount.
            //     hash is null if the user is not a UserPassword: we'll try to migrate it.
            //     hash can be empty iif a previous attempt to migrate it has failed.

            var read = await ctx[Database].ExecuteSingleRowAsync(hashReader,
                                                                 row => row == null
                                    ? (0, null, -1)
                                    : (UserId: row.GetInt32(1),
                                       PwdHash:
                                       row.IsDBNull(0) ? null : row.GetBytes(0),
                                       FailedAttemptCount:
                                       row.IsDBNull(2) ? -1 : row.GetByte(2)));

            if (read.UserId == 0)
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidUserKey));
            }

            PasswordVerificationResult result   = PasswordVerificationResult.Failed;
            PasswordHasher             p        = null;
            IUserPasswordMigrator      migrator = null;

            // 2 - Handle external password migration or check the hash.
            if (read.PwdHash == null || read.PwdHash.Length == 0)
            {
                migrator = UserPasswordPackage.PasswordMigrator;
                if (migrator != null && migrator.VerifyPassword(ctx, read.UserId, password))
                {
                    result = PasswordVerificationResult.SuccessRehashNeeded;
                    p      = new PasswordHasher(HashIterationCount);
                }
            }
            else
            {
                p      = new PasswordHasher(HashIterationCount);
                result = p.VerifyHashedPassword(read.PwdHash, password);
            }
            // 3 - Handle result.
            var mode = actualLogin ? UCLMode.WithActualLogin : UCLMode.WithCheckLogin;

            if (result == PasswordVerificationResult.SuccessRehashNeeded)
            {
                // 3.1 - If migration occurred, create the user with its hashed password.
                //       Otherwise, rehash the password and update the database.
                mode |= UCLMode.CreateOrUpdate;
                UCLResult r = await PasswordUserUCLAsync(ctx, 1, read.UserId, p.HashPassword(password), mode, null, cancellationToken).ConfigureAwait(false);

                if (r.OperationResult != UCResult.None && migrator != null)
                {
                    migrator.MigrationDone(ctx, read.UserId);
                }
                return(r.LoginResult);
            }
            if (result == PasswordVerificationResult.Failed && migrator != null)
            {
                // 3.2 - Migration failed, create the user with an empty hash.
                //       so that FailedAttemptCount (or other) can be handled.
                mode |= UCLMode.CreateOnly;
                mode &= ~UCLMode.UpdateOnly;
                // KnownLoginFailureCode is UnregisteredUser: the fact that we have a password migrator
                // (that failed to migrate) results in the user actually not registered in the UserPassword provider.
                UCLResult r = await PasswordUserUCLAsync(ctx, 1, read.UserId, Array.Empty <byte>(), mode, (int)KnownLoginFailureCode.UnregisteredUser, cancellationToken);

                return(r.LoginResult);
            }
            // 4 - Challenges the database login checks.
            int?failureCode = null;

            if (result == PasswordVerificationResult.Failed)
            {
                failureCode = (int)KnownLoginFailureCode.InvalidCredentials;
            }
            return((await PasswordUserUCLAsync(ctx, 1, read.UserId, null, mode, failureCode, cancellationToken)
                    .ConfigureAwait(false)).LoginResult);
        }
Ejemplo n.º 3
0
        static public async Task StandardTestForGenericAuthenticationProviderAsync(
            Package auth,
            string schemeOrProviderName,
            Func <int, string, object> payloadForCreateOrUpdate,
            Func <int, string, object> payloadForLogin,
            Func <int, string, object> payloadForLoginFail
            )
        {
            var user = TestHelper.StObjMap.Default.Obtain <Actor.UserTable>();
            IGenericAuthenticationProvider g = auth.FindProvider(schemeOrProviderName);

            using (var ctx = new SqlStandardCallContext())
            {
                string userName = Guid.NewGuid().ToString();
                int    userId   = await user.CreateUserAsync(ctx, 1, userName);

                using (TestHelper.Monitor.OpenInfo($"StandardTestAsync for generic {schemeOrProviderName} with userId:{userId} and userName:{userName}."))
                {
                    IUserAuthInfo info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                    info.UserId.Should().Be(userId);
                    info.UserName.Should().Be(userName);
                    info.Schemes.Should().BeEmpty();

                    using (TestHelper.Monitor.OpenInfo("CreateOrUpdateUser without login."))
                    {
                        (await g.CreateOrUpdateUserAsync(ctx, 1, userId, payloadForCreateOrUpdate(userId, userName))).OperationResult.Should().Be(UCResult.Created);
                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().BeEmpty("Still no scheme since we did not use WithLogin.");

                        (await g.LoginUserAsync(ctx, payloadForLogin(userId, userName), actualLogin: false)).UserId.Should().Be(userId);
                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().BeEmpty("Still no scheme since we challenge login but not use WithLogin.");

                        (await g.LoginUserAsync(ctx, payloadForLogin(userId, userName))).UserId.Should().Be(userId);
                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().HaveCount(1);
                        info.Schemes[0].Name.Should().StartWith(g.ProviderName);
                        info.Schemes[0].Name.Should().BeEquivalentTo(schemeOrProviderName);
                        info.Schemes[0].LastUsed.Should().BeCloseTo(DateTime.UtcNow, 1000);

                        await g.DestroyUserAsync(ctx, 1, userId);

                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().BeEmpty();
                    }
                    using (TestHelper.Monitor.OpenInfo("CreateOrUpdateUser WithActualLogin."))
                    {
                        info.UserId.Should().Be(userId);
                        info.UserName.Should().Be(userName);
                        info.Schemes.Should().BeEmpty();

                        var result = await g.CreateOrUpdateUserAsync(ctx, 1, userId, payloadForCreateOrUpdate( userId, userName ), UCLMode.CreateOnly | UCLMode.WithActualLogin);

                        result.OperationResult.Should().Be(UCResult.Created);
                        result.LoginResult.UserId.Should().Be(userId);
                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Count.Should().Be(1);
                        info.Schemes[0].Name.Should().StartWith(g.ProviderName);
                        info.Schemes[0].Name.Should().BeEquivalentTo(schemeOrProviderName);
                        info.Schemes[0].LastUsed.Should().BeCloseTo(DateTime.UtcNow, 1000);

                        (await g.LoginUserAsync(ctx, payloadForLoginFail(userId, userName))).UserId.Should().Be(0);

                        await g.DestroyUserAsync(ctx, 1, userId);

                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().BeEmpty();
                    }
                    using (TestHelper.Monitor.OpenInfo("Login for an unregistered user."))
                    {
                        info.UserId.Should().Be(userId);
                        info.UserName.Should().Be(userName);
                        info.Schemes.Should().BeEmpty();

                        var result = await g.LoginUserAsync(ctx, payloadForLogin( userId, userName ));

                        result.IsSuccess.Should().BeFalse();
                        result.UserId.Should().Be(0);
                        result.FailureCode.Should().Be((int)KnownLoginFailureCode.UnregisteredUser);
                        result.FailureReason.Should().Be("Unregistered user.");
                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().BeEmpty();

                        await g.DestroyUserAsync(ctx, 1, userId);

                        info = await auth.ReadUserAuthInfoAsync(ctx, 1, userId);

                        info.Schemes.Should().BeEmpty();
                    }
                    using (TestHelper.Monitor.OpenInfo("Invalid payload MUST throw an ArgumentException."))
                    {
                        g.Awaiting(sut => sut.CreateOrUpdateUserAsync(ctx, 1, userId, DBNull.Value)).Should().Throw <ArgumentException>();
                        g.Awaiting(sut => sut.LoginUserAsync(ctx, DBNull.Value)).Should().Throw <ArgumentException>();
                    }
                    using (TestHelper.Monitor.OpenInfo("Injecting disabled user in sAuthUserOnLogin."))
                        using (auth.Database.TemporaryTransform(@"
                            create transformer on CK.sAuthUserOnLogin
                            as
                            begin
                                inject ""set @FailureCode = 6; -- GloballyDisabledUser"" into ""CheckLoginFailure"";
                            end
                        "))
                        {
                            UCLResult result = await g.CreateOrUpdateUserAsync(ctx, 1, userId, payloadForCreateOrUpdate( userId, userName ), UCLMode.CreateOnly | UCLMode.WithActualLogin);

                            result.OperationResult.Should().Be(UCResult.Created);
                            result.LoginResult.UserId.Should().Be(0);
                            result.LoginResult.IsSuccess.Should().BeFalse();
                            result.LoginResult.FailureCode.Should().Be((int)KnownLoginFailureCode.GloballyDisabledUser);
                            LoginResult login = await g.LoginUserAsync(ctx, payloadForLogin( userId, userName ));

                            login.IsSuccess.Should().BeFalse();
                            login.UserId.Should().Be(0);
                            login.FailureCode.Should().Be((int)KnownLoginFailureCode.GloballyDisabledUser);
                            login = await g.LoginUserAsync(ctx, payloadForLogin( userId, userName ), actualLogin : false);

                            login.IsSuccess.Should().BeFalse();
                            login.UserId.Should().Be(0);
                            login.FailureCode.Should().Be((int)KnownLoginFailureCode.GloballyDisabledUser);
                        }
                }
                await user.DestroyUserAsync(ctx, 1, userId);
            }
        }
Ejemplo n.º 4
0
        async Task <LoginResult> DoVerifyAsync(ISqlCallContext ctx, SqlCommand hashReader, string password, object objectKey, bool actualLogin, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(password))
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidCredentials));
            }
            // 1 - Get the PwdHash and UserId.
            //     hash is null if the user is not a UserPassword: we'll try to migrate it.

            var read = await ctx[Database].ExecuteSingleRowAsync(hashReader,
                                                                 row => row == null
                        ? (0, null)
                        : (UserId: row.GetInt32(1),
                           PwdHash: row.IsDBNull(0)
                                        ? null
                                        : row.GetBytes(0)));

            if (read.UserId == 0)
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidUserKey));
            }
            PasswordVerificationResult result   = PasswordVerificationResult.Failed;
            PasswordHasher             p        = null;
            IUserPasswordMigrator      migrator = null;

            // 2 - Handle external password migration or check the hash.
            if (read.PwdHash == null)
            {
                migrator = _package.PasswordMigrator;
                if (migrator != null && migrator.VerifyPassword(ctx, read.UserId, password))
                {
                    result = PasswordVerificationResult.SuccessRehashNeeded;
                    p      = new PasswordHasher(HashIterationCount);
                }
            }
            else
            {
                p      = new PasswordHasher(HashIterationCount);
                result = p.VerifyHashedPassword(read.PwdHash, password);
            }
            // 3 - Handle result.
            var mode = actualLogin ? UCLMode.WithActualLogin : UCLMode.WithCheckLogin;

            if (result == PasswordVerificationResult.SuccessRehashNeeded)
            {
                // 3.1 - If migration occurred, create the user with its hashed password.
                //       Otherwise, rehash the password and update the database.
                mode |= UCLMode.CreateOrUpdate;
                UCLResult r = await PasswordUserUCLAsync(ctx, 1, read.UserId, p.HashPassword(password), mode, null, cancellationToken).ConfigureAwait(false);

                if (r.OperationResult != UCResult.None && migrator != null)
                {
                    migrator.MigrationDone(ctx, read.UserId);
                }
                return(r.LoginResult);
            }
            // 4 - Challenges the database login checks.
            int?failureCode = null;

            if (result == PasswordVerificationResult.Failed)
            {
                failureCode = (int)KnownLoginFailureCode.InvalidCredentials;
            }
            return((await PasswordUserUCLAsync(ctx, 1, read.UserId, null, mode, failureCode, cancellationToken)
                    .ConfigureAwait(false)).LoginResult);
        }