Exemplo n.º 1
0
        public async Task SendPin([FromBody] SendPinRequest request)
        {
            OpContext.WebContext.MarkConfidential();
            OpContext.ThrowIfNull(request, ClientFaultCodes.ContentMissing, "SendPinRequest", "Pin request object must be provided.");
            OpContext.ValidateNotEmpty(request.ProcessToken, "ProcessToken", "Process token should be provided.");
            OpContext.ValidateNotEmpty(request.Factor, "Factor", "Factor (email or phone) should be provided.");
            OpContext.ThrowValidation();
            var session = OpContext.OpenSession();
            var process = GetActiveProcess(session, request.ProcessToken, confirmedOnly: false);

            if (process == null)
            {
                return; //no indication process exist or not
            }
            OpContext.ThrowIf(process.CurrentFactor != null, ClientFaultCodes.InvalidAction, "token", "The previous process step is not completed.");
            var iFactor = ProcessService.FindLoginExtraFactor(process.Login, request.Factor);

            //now having completed at least one extra factor, we can openly indicate that we could not find next factor
            OpContext.ThrowIfNull(iFactor, ClientFaultCodes.InvalidValue, "factor", "Login factor (email or phone) is not found for a user.");
            //Check that factor type is one in the pending steps
            var factorOk = process.PendingFactors.IsSet(iFactor.FactorType);

            OpContext.ThrowIf(!factorOk, ClientFaultCodes.InvalidValue, "factor", "Login factor type attempted (email or phone) is not pending in the process.");
            await ProcessService.SendPinAsync(process, iFactor, request.Factor); //we use factor from request, to avoid unencrypting twice
        }
Exemplo n.º 2
0
        public void SendPinForMultiFactor(SendPinRequest pinRequest)
        {
            var process = GetMutiFactorProcess(pinRequest.ProcessToken);

            Context.ThrowIf(process.CurrentFactor != null, ClientFaultCodes.InvalidAction, "token", "Factor verification pending, the previous process step is not completed.");
            var pendingFactorTypes = process.PendingFactors;

            Context.ThrowIf(!pendingFactorTypes.IsSet(pinRequest.FactorType), ClientFaultCodes.InvalidValue, "factortype", "Factor type is not pending in login process");
            var factor = process.Login.ExtraFactors.FirstOrDefault(f => f.FactorType == pinRequest.FactorType);

            Context.ThrowIfNull(factor, ClientFaultCodes.ObjectNotFound, "factor",
                                "Login factor (email or phone) not setup in user account; factor type: {0}", pinRequest.FactorType);
            _processService.SendPin(process, factor);
        }
Exemplo n.º 3
0
        public async Task SendPinForMultiFactor([FromBody] SendPinRequest pinRequest)
        {
            var session = OpContext.OpenSession();
            var process = GetMutiFactorProcess(session, pinRequest.ProcessToken);

            OpContext.ThrowIf(process.CurrentFactor != null, ClientFaultCodes.InvalidAction, "token", "Factor verification pending, the previous process step is not completed.");
            var pendingFactorTypes = process.PendingFactors;

            OpContext.ThrowIf(!pendingFactorTypes.IsSet(pinRequest.FactorType), ClientFaultCodes.InvalidValue, "factortype", "Factor type is not pending in login process");
            var factor = process.Login.ExtraFactors.FirstOrDefault(f => f.FactorType == pinRequest.FactorType);

            OpContext.ThrowIfNull(factor, ClientFaultCodes.ObjectNotFound, "factor",
                                  "Login factor (email or phone) not setup in user account; factor type: {0}", pinRequest.FactorType);
            var processService = OpContext.App.GetService <ILoginProcessService>();
            await processService.SendPinAsync(process, factor);
        }
Exemplo n.º 4
0
 public void SendPin(string token, SendPinRequest request)
 {
     Context.WebContext.MarkConfidential();
       Context.ThrowIfNull(request, ClientFaultCodes.ContentMissing, "SendPinRequest", "Pin request object must be provided.");
       Context.ValidateNotEmpty(token, "ProcessToken", "Process token should be provided.");
       Context.ValidateNotEmpty(request.Factor, "Factor", "Factor (email or phone) should be provided.");
       Context.ThrowValidation();
       var process = GetActiveProcess(token, confirmedOnly: false);
       if(process == null)
     return; //no indication process exist or not
       Context.ThrowIf(process.CurrentFactor != null, ClientFaultCodes.InvalidAction, "token", "The previous process step is not completed.");
       var iFactor = _processService.FindLoginExtraFactor(process.Login, request.Factor);
       //now having completed at least one extra factor, we can openly indicate that we could not find next factor
       Context.ThrowIfNull(iFactor, ClientFaultCodes.InvalidValue, "factor", "Login factor (email or phone) is not found for a user.");
       //Check that factor type is one in the pending steps
       var factorOk = process.PendingFactors.IsSet(iFactor.FactorType);
       Context.ThrowIf(!factorOk, ClientFaultCodes.InvalidValue, "factor", "Login factor type attempted (email or phone) is not pending in the process.");
       _processService.SendPin(process, iFactor, request.Factor); //we use factor from request, to avoid unencrypting twice
 }
Exemplo n.º 5
0
        public void TestLoginAdvancedFeatures()
        {
            // ================================ Completing login setup - verifying email, setup secret questions =====================
              // User Ferb has email (non-verified) and no secret questions setup. Let's setup his account thru API calls.
              var client = SetupHelper.Client;
              var ferbUserName = "******";
              var ferbEmail = "*****@*****.**";

              var ferbLogin = LoginAs(ferbUserName);
              Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Ferb login failed.");
              Assert.IsTrue(ferbLogin.Actions.IsSet(PostLoginActions.SetupExtraFactors), "Expected request to setup extra factors.");

              // Get login info, check which factors we need to setup
              var loginInfo = client.ExecuteGet<LoginInfo>("api/mylogin");
              Assert.IsNotNull(loginInfo, "Failed to get LoginInfo");
              Assert.AreEqual(ExtraFactorTypes.Email | ExtraFactorTypes.SecretQuestions, loginInfo.IncompleteFactors, "Wrong set of incomplete factors.");
              //We need to setup email; email might exist or not, and if exists, it might be not verified.
              var factors = client.ExecuteGet<List<LoginExtraFactor>>("api/mylogin/factors");
              Assert.AreEqual(0, factors.Count, "expected 0 factors");
              var newEmail = new LoginExtraFactor() { Type = ExtraFactorTypes.Email, Value = ferbEmail };
              client.ExecutePost<LoginExtraFactor, LoginExtraFactor>(newEmail, "api/mylogin/factors");

              //Get factors again - now we have email, unconfirmed
              factors = client.ExecuteGet<List<LoginExtraFactor>>("api/mylogin/factors");
              var emailFactor = factors[0];
              Assert.AreEqual(ExtraFactorTypes.Email, emailFactor.Type, "Expected email");
              Assert.IsFalse(emailFactor.Confirmed, "Email should not be confirmed.");
              //Let's confirm it - send pin, read it from email and confirm it
              client.ExecutePost<object, HttpStatusCode>(null, "api/mylogin/factors/{0}/pin", emailFactor.Id);
              //let's do it twice - to make sure it works even if we have multiple pins sent
              client.ExecutePost<object, HttpStatusCode>(null, "api/mylogin/factors/{0}/pin", emailFactor.Id);
              var pinEmail = SetupHelper.GetLastMessageTo(ferbEmail);
              Assert.IsNotNull(pinEmail, "Pin email not received.");
              var pin = pinEmail.GetString("Pin"); //get pin from email
              Assert.IsFalse(string.IsNullOrEmpty(pin), "Expected non-null pin");
              //submit pin
              var pinOk = client.ExecutePut<object, bool>(null, "api/mylogin/factors/{0}/pin/{1}", emailFactor.Id, pin);
              Assert.IsTrue(pinOk, "Pin submit failed.");
              //Now email should not be listed as incomplete factor
              loginInfo = client.ExecuteGet<LoginInfo>("api/mylogin");
              Assert.AreEqual(ExtraFactorTypes.SecretQuestions, loginInfo.IncompleteFactors, "Expected only questions as incomplete factors.");
              //Let's setup secret questions/answers. Let's get all secret questions, choose three and submit answers
              var allQuestions = client.ExecuteGet<List<SecretQuestion>>("api/mylogin/allquestions");
              Assert.IsTrue(allQuestions.Count > 20, "Failed to retrieve all questions.");
              // let's choose 3
              var qFriend = allQuestions.First(q => q.Question.Contains("friend")); // childhood friend
              var qFood = allQuestions.First(q => q.Question.Contains("favorite food"));
              var qColor = allQuestions.First(q => q.Question.Contains("favorite color"));
              var answers = new SecretQuestionAnswer[] {
            new SecretQuestionAnswer() {QuestionId = qFriend.Id, Answer = "Phineas"},
            new SecretQuestionAnswer() {QuestionId = qFood.Id, Answer = "Potato"},
            new SecretQuestionAnswer() {QuestionId = qColor.Id, Answer = "Blue"}
              };
              //submit answers
              client.ExecutePut<SecretQuestionAnswer[], HttpStatusCode>(answers, "api/mylogin/answers");
              // Read back LoginInfo - now it should have no incomplete factors
              loginInfo = client.ExecuteGet<LoginInfo>("api/mylogin");
              Assert.AreEqual(ExtraFactorTypes.None, loginInfo.IncompleteFactors, "Expected no incomplete factors.");
              //Now if Ferb logs in again, no post-login actions should be required
              Logout();
              ferbLogin = LoginAs(ferbUserName);
              Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Ferb login failed.");
              Assert.AreEqual(PostLoginActions.None, ferbLogin.Actions, "Expected no post-login actions.");

              //============================== Password change =============================================
              // Ferb changes his password; let's first try invalid old password
              var oldPassword = Samples.BookStore.SampleData.SampleDataGenerator.DefaultPassword;
              var newPass = oldPassword + "New";
              var pwdChange = new PasswordChangeInfo() { OldPassword = "******", NewPassword = newPass };
              var cfExc = TestUtil.ExpectClientFault(() => client.ExecutePut<PasswordChangeInfo, HttpStatusCode>(pwdChange, "api/mylogin/password"));
              Assert.AreEqual(ClientFaultCodes.InvalidValue, cfExc.Faults[0].Code, "Expected 'InvalidValue' for OldPassword");
              Assert.AreEqual("OldPassword", cfExc.Faults[0].Tag, "Expected OldPassword as invalid value");
              // Let's try weak password and check it
              pwdChange = new PasswordChangeInfo() { OldPassword = oldPassword, NewPassword = "******" };
              var strength = client.ExecutePut<PasswordChangeInfo, PasswordStrength>(pwdChange, "api/login/passwordcheck");
              Assert.AreEqual(PasswordStrength.Weak, strength, "Expected Weak or unacceptable result.");
              // We set min strength to Medium in login settings
              // let's try weak password - should fail with 'WeakPassword' fault;
              cfExc = TestUtil.ExpectClientFault(() => client.ExecutePut<PasswordChangeInfo, HttpStatusCode>(pwdChange, "api/mylogin/password"));
              Assert.AreEqual(LoginFaultCodes.WeakPassword, cfExc.Faults[0].Code, "Expected WeakPassword fault.");
              // good password
              pwdChange.NewPassword = newPass;
              // check strength
              strength = client.ExecutePut<PasswordChangeInfo, PasswordStrength>(pwdChange, "api/login/passwordcheck");
              Assert.AreEqual(PasswordStrength.Strong, strength, "Expected Strong result.");
              // actually change
              var status = client.ExecutePut<PasswordChangeInfo, HttpStatusCode>(pwdChange, "api/mylogin/password");
              Assert.AreEqual(HttpStatusCode.NoContent, status, "Password change failed");
              //verify it
              Logout();
              ferbLogin = LoginAs(ferbUserName, pwdChange.NewPassword);
              Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Failed to login with new password.");
              Logout();

              //========================================== Password reset =============================================================
              // Ferb has everything setup for proper password reset (email is comfirmed and secret questions are entered).
              // Lets do password reset. Ferb forgets his password and comes back to our site
              // 1. Start password reset process.  Ferb clicks Forgot Password link and is redirected to initial reset page.
              //  He enters email in a text box, solves captcha and clicks Submit. The client code executes a request to start reset process
              //  "Magic" is a magic captcha value (it is set in login module settings) to bypass captcha check in unit tests.
              var request = new PasswordResetStartRequest() { Factor = ferbEmail, Captcha = "Magic" };
              var processToken = client.ExecutePost<PasswordResetStartRequest, string>(request, "api/passwordreset");
              Assert.IsFalse(string.IsNullOrWhiteSpace(processToken), "Expected process token.");
              // We do not disclose any details, even the fact that actual process started or not;
              // even if ferb's email is not found, the server returns a process token as if everything is ok.
              // This is done to avoid disclosing if the user is signed up at our site or not (if we are a p**n site we should not disclose membership)
              // Client can use process token to retrieve LoginProcess object - except right after process is started, it returns null - to avoid disclosing membership.
              // Only after at least one factor (email or phone) is confirmed (pin submitted back), the process information becomes visible.
              // So at this point trying to get process returns null
              // NOTE: in some cases hiding membership is not needed - when we have a business system with employees as users.
              // For this case, you can set a flag DoNotConcealMembership in ILogin record(s), and system would behave accordingly
              var process = client.ExecuteGet<LoginProcess>("api/passwordreset/{0}", processToken);
              Assert.IsNull(process, "Expected server hiding process object");
              // 2. Send pin using email
              var sendPinRequest = new SendPinRequest() { Factor = ferbEmail };
              var httpStatus = client.ExecutePost<SendPinRequest, HttpStatusCode>(sendPinRequest, "api/passwordreset/{0}/pin", processToken);
              Assert.AreEqual(HttpStatusCode.NoContent, httpStatus, "Failed to send pin.");
              // 3. Ferb receives email - we check our mock email service, retrieve the message and pin
              pinEmail = SetupHelper.GetLastMessageTo(ferbEmail);
              Assert.IsNotNull(pinEmail, "Email with pin not received.");
              pin = (string)pinEmail.GetString("Pin");
              Assert.IsTrue(!string.IsNullOrWhiteSpace(pin), "Failed to receive/extract pin.");
              // 4. Ferb copies pin from email and enters it in a page. The UI submits the pin
              httpStatus = client.ExecutePut<object, HttpStatusCode>(null, "api/passwordreset/{0}/pin/{1}", processToken, pin);
              Assert.AreEqual(HttpStatusCode.NoContent, httpStatus, "Failed to submit pin.");
              // 5. UI retrieves the process to see if pin was correct and to see further steps.
              //    If the pin was correct, the email is confirmed, and now we can retrieve the process object; otherwise the call would return null.
              process = client.ExecuteGet<LoginProcess>("api/passwordreset/{0}", processToken);
              Assert.IsNotNull(process, "Failed to retrieve process object.");
              Assert.AreEqual(LoginProcessType.PasswordReset, process.ProcessType, "Process type does not match.");
              Assert.AreEqual(ExtraFactorTypes.Email, process.CompletedFactors, "Expected email as completed factor.");
              Assert.AreEqual(ExtraFactorTypes.SecretQuestions, process.PendingFactors, "Expected SecretQuestions as pending factor.");
              // 6. Next step is in process.PendingFactors - it is secret questions; get Ferb's questions and submit answers.
              var questions = client.ExecuteGet<IList<SecretQuestion>>("api/passwordreset/{0}/userquestions", processToken);
              Assert.AreEqual(3, questions.Count, "Expected 3 questions");
              //Let's first try incorrect answers
              var ferbAnswers = new List<SecretQuestionAnswer>();
              ferbAnswers.Add(new SecretQuestionAnswer() { QuestionId = questions[0].Id, Answer = "Candice" }); //best childhood friend - incorrect
              ferbAnswers.Add(new SecretQuestionAnswer() { QuestionId = questions[1].Id, Answer = "Potato" });        //favorite food
              ferbAnswers.Add(new SecretQuestionAnswer() { QuestionId = questions[2].Id, Answer = "blue" });       //favorite color
              var answersOk = client.ExecutePut<List<SecretQuestionAnswer>, bool>(ferbAnswers, "api/passwordreset/{0}/questionanswers", processToken);
              Assert.IsFalse(answersOk, "Expected bad answers to fail.");
              //Now correct answers
              ferbAnswers[0].Answer = "Phineas"; //this is correct
              answersOk = client.ExecutePut<List<SecretQuestionAnswer>, bool>(ferbAnswers, "api/passwordreset/{0}/questionanswers", processToken);
              Assert.IsTrue(answersOk, "Expected answers to succeed.");
              // 7. Get the process object - there should be no pending factors
              process = client.ExecuteGet<LoginProcess>("api/passwordreset/{0}", processToken);
              Assert.AreEqual(ExtraFactorTypes.None, process.PendingFactors, "Expected no pending factors");
              // 8. Once all steps are completed, and server cleared all pending factors, the server will allow us to change password
              //   in the context of the process. So let's actually change the password
              var passwordResetReq = new PasswordChangeInfo() { NewPassword = oldPassword }; //same as the original one
              var success = client.ExecutePut<PasswordChangeInfo, bool>(passwordResetReq, "api/passwordreset/{0}", processToken);
              Assert.IsTrue(success, "Failed to change password");
              // 9. Verify that email notification was sent about password change
              var notifEmail = SetupHelper.GetLastMessageTo(ferbEmail);
              Assert.IsNotNull(notifEmail, "Password change notification was not sent.");
              Assert.AreEqual(LoginNotificationTypes.PasswordReset, notifEmail.Type, "Expected password change message.");
              // 10. Try to login with changed password
              ferbLogin = LoginAs(ferbUserName, oldPassword);
              Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Login failed after reset.");

              // ============================ Multi-factor login ==============================================
              // Ferb decides to enable multi-factor login - with email and google authenticator; we need to add GA first as a factor
              loginInfo = client.ExecuteGet<LoginInfo>("api/mylogin");
              Assert.IsNotNull(loginInfo, "Failed to get LoginInfo");
              //Add GoogleAuthenticator factor
              var googleAuth = new LoginExtraFactor() { Type = ExtraFactorTypes.GoogleAuthenticator, Value = null }; // value is ignored, but returned object contains secret
              var gAuth = client.ExecutePost<LoginExtraFactor, LoginExtraFactor>(googleAuth, "api/mylogin/factors");
              var gSecret = gAuth.Value; // Ferb can use it to entry secret manually on his phone
              // Ferb can also use QR reader;
              var qrUrl = client.ExecuteGet<string>("api/mylogin/factors/{0}/qr", gAuth.Id);
              Assert.IsTrue(!string.IsNullOrWhiteSpace(qrUrl), "Expected QR url.");
              // Find the URL in debug output, paste it in browser address line, see the picture and use Google Authenticator app on your phone
              // to add an account by scanning the QR pic. It should add "BooksEntityApp:ferb" account, and start showing 6 digit code
              Debug.WriteLine("Ferb's QR URL: " + qrUrl);
              //Enable multi-factor login
              loginInfo.RequireMultiFactorLogin = true;
              loginInfo.MultiFactorLoginFactors = ExtraFactorTypes.Email | ExtraFactorTypes.GoogleAuthenticator;
              client.ExecutePut<LoginInfo, HttpStatusCode>(loginInfo, "api/mylogin");
              Logout();
              // now if Ferb tries to login, he gets multi-factor pending status; the server process (represented by process token) is started automatically
              ferbLogin = LoginAs(ferbUserName, assertSuccess: false);
              Assert.AreEqual(LoginAttemptStatus.PendingMultifactor, ferbLogin.Status, "Expected multi-factor status.");
              processToken = ferbLogin.MultiFactorProcessToken; //the process already started
              Assert.IsFalse(string.IsNullOrEmpty(processToken), "Expected process token");
              // We do not need to conceal the existense of the process like we do in password reset, so request for process returns non-null object
              process = client.ExecuteGet<LoginProcess>("api/login/{0}", processToken);
              Assert.IsNotNull(process, "Expected process object.");
              Assert.AreEqual(ExtraFactorTypes.Email | ExtraFactorTypes.GoogleAuthenticator, process.PendingFactors, "Expected email and Google Auth pending factors.");
              // Email: Ask server to send pin by email
              httpStatus = client.ExecutePost<object, HttpStatusCode>(null, "api/login/{0}/pin?factortype={1}", processToken, ExtraFactorTypes.Email);
              Assert.AreEqual(HttpStatusCode.NoContent, httpStatus, "Expected NoContent status");
              //Get message with pin from mock inbox and extract pin
              pinEmail = SetupHelper.GetLastMessageTo(ferbEmail);
              Assert.IsNotNull(pinEmail, "Email with pin not sent.");
              pin = pinEmail.GetString("Pin");
              // Ferb copies pin from email and enters it in UI. UI submits the pin
              success = client.ExecutePut<object, bool>(null, "api/login/{0}/pin/{1}", processToken, pin);
              Assert.IsTrue(success, "Email pin submit failed");
              // Google authenticator.
              //Tell server to 'send pin' - it won't send anything, but will set GA as current factor in the process
              httpStatus = client.ExecutePost<object, HttpStatusCode>(null, "api/login/{0}/pin?factortype={1}", processToken, ExtraFactorTypes.GoogleAuthenticator);
              // Pretend Ferb has GA installed on his phone, he opens the app and reads the current value.
              // In this test we use back door and compute it - we know the secret from the call when we added the factor (Google Authenticator as extra factor)
              var gaPassCode = Vita.Modules.Login.GoogleAuthenticator.GoogleAuthenticatorUtil.GeneratePasscode(gSecret);
              //Submit passcode as pin
              success = client.ExecutePut<object, bool>(null, "api/login/{0}/pin/{1}", processToken, gaPassCode);
              Assert.IsTrue(success, "Google authenticator pin failed.");

              //Get process again - now there should be no pending factors
              process = client.ExecuteGet<LoginProcess>("api/login/{0}", processToken);
              Assert.AreEqual(ExtraFactorTypes.None, process.PendingFactors, "Expected no pending factors");
              //complete login using process token - returned LoginResponse object represents successful login
              ferbLogin = client.ExecutePost<object, LoginResponse>(null, "api/login/{0}", processToken);
              Assert.IsNotNull(ferbLogin, "Failed to complete login.");
              Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Expected success status");
              client.AddRequestHeader("Authorization", ferbLogin.AuthenticationToken); //we have to add it explicitly here
              // Ferb identifies the computer as personal (safe) device, and sets to skip multi-factor on this device
              var deviceInfo = new DeviceInfo() { Type = DeviceType.Computer, TrustLevel = DeviceTrustLevel.AllowSingleFactor };
              //register the computer
              deviceInfo = client.ExecutePost<DeviceInfo, DeviceInfo>(deviceInfo, "api/mylogin/device");
              // The returned token should be saved in local storage and used in future logins
              var deviceToken = deviceInfo.Token;
              //Now let's try to logout and login again, using deviceToken
              Logout();
              ferbLogin = LoginAs(ferbUserName, deviceToken: deviceToken, assertSuccess: false);
              Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Expected no multi-factor on trusted device");
              Logout();
        }