public DeviceInfo RegisterOrUpdateDevice(DeviceInfo device)
 {
     var login = GetCurrentLogin();
       ITrustedDevice deviceEnt = null;
       if(!string.IsNullOrWhiteSpace(device.Token))
     deviceEnt = login.GetDevice(device.Token);
       if(deviceEnt == null)
     deviceEnt = _loginManager.RegisterTrustedDevice(login, device.Type, device.TrustLevel);
       else {
     deviceEnt.TrustLevel = device.TrustLevel;
     var session = EntityHelper.GetSession(login);
     session.SaveChanges();
       }
       return new DeviceInfo() { Token = deviceEnt.Token, TrustLevel = deviceEnt.TrustLevel, Type = deviceEnt.Type };
 }
示例#2
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();
        }
示例#3
0
 public DeviceInfo UpdateDevice(DeviceInfo device)
 {
     return(RegisterOrUpdateDevice(device));
 }
 public DeviceInfo UpdateDevice([FromBody] DeviceInfo device)
 {
     return(RegisterOrUpdateDevice(device));
 }