Ejemplo n.º 1
0
 public static void InitTest(TestContext ctx)
 {
     Startup.Init();
 }
Ejemplo n.º 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       = Startup.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.Get <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.Get <List <LoginExtraFactor> >("api/mylogin/factors");

            Assert.AreEqual(0, factors.Count, "expected 0 factors");
            var newEmail = new LoginExtraFactor()
            {
                Type = ExtraFactorTypes.Email, Value = ferbEmail
            };

            client.Post <LoginExtraFactor, LoginExtraFactor>(newEmail, "api/mylogin/factors");

            //Get factors again - now we have email, unconfirmed
            factors = client.Get <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
            // The call returns processToken identifying email verificaiton process
            var processToken = client.Post <object, string>(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
            processToken = client.Post <object, string>(null, "api/mylogin/factors/{0}/pin", emailFactor.Id);
            var pinEmail = Startup.GetLastMessageTo(ferbEmail);

            Assert.IsNotNull(pinEmail, "Pin email not received.");
            var pin = pinEmail.Pin; //get pin from email

            Assert.IsFalse(string.IsNullOrEmpty(pin), "Expected non-null pin");
            //submit pin
            // method 1 - endpoint for logged in user, used when user copy/pastes pin on a page
            // var pinOk = client.Put<object, bool>(null, "api/mylogin/factors/{0}/pin/{1}", emailFactor.Id, pin);
            // method 2 - endpoint not requiring logged-in user; use it in a page activated from URL embedded in email:
            var pinOk = client.Put <object, bool>(null, "api/login/factors/verify-pin?processtoken={0}&&pin={1}",
                                                  processToken, pin);

            Assert.IsTrue(pinOk, "Pin submit failed.");
            //Now email should not be listed as incomplete factor
            loginInfo = client.Get <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.Get <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.Put <SecretQuestionAnswer[], HttpStatusCode>(answers, "api/mylogin/answers");
            // Read back LoginInfo - now it should have no incomplete factors
            loginInfo = client.Get <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.Put <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.Put <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.Put <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.Put <PasswordChangeInfo, PasswordStrength>(pwdChange, "api/login/passwordcheck");
            Assert.AreEqual(PasswordStrength.Strong, strength, "Expected Strong result.");
            // actually change
            var status = client.Put <PasswordChangeInfo, HttpStatusCode>(pwdChange, "api/mylogin/password");

            Assert.AreEqual(HttpStatusCode.OK, 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"
            };

            processToken = client.Post <PasswordResetStartRequest, string>(request, "api/passwordreset/start");
            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.Get <LoginProcess>("api/passwordreset/process?token={0}", processToken);

            Assert.IsNull(process, "Expected server hiding process object");
            // 2. Send pin using email
            var sendPinRequest = new SendPinRequest()
            {
                ProcessToken = processToken, FactorType = ExtraFactorTypes.Email, Factor = ferbEmail
            };
            var httpStatus = client.Post <SendPinRequest, HttpStatusCode>(sendPinRequest, "api/passwordreset/pin/send");

            Assert.AreEqual(HttpStatusCode.OK, httpStatus, "Failed to send pin.");
            // 3. Ferb receives email - we check our mock email service, retrieve the message and pin
            pinEmail = Startup.GetLastMessageTo(ferbEmail);
            Assert.IsNotNull(pinEmail, "Email with pin not received.");
            pin = pinEmail.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
            var checkPinReq = new VerifyPinRequest()
            {
                ProcessToken = processToken, Pin = pin
            };

            httpStatus = client.Put <VerifyPinRequest, HttpStatusCode>(checkPinReq, "api/passwordreset/pin/verify");
            Assert.AreEqual(HttpStatusCode.OK, 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.Get <LoginProcess>("api/passwordreset/process?token={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.Get <IList <SecretQuestion> >("api/passwordreset/userquestions?token={0}", 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.Put <List <SecretQuestionAnswer>, bool>(ferbAnswers, "api/passwordreset/userquestions/answers?token={0}", processToken);

            Assert.IsFalse(answersOk, "Expected bad answers to fail.");
            //Now correct answers
            ferbAnswers[0].Answer = "Phineas"; //this is correct
            answersOk             = client.Put <List <SecretQuestionAnswer>, bool>(ferbAnswers, "api/passwordreset/userquestions/answers?token={0}", processToken);
            Assert.IsTrue(answersOk, "Expected answers to succeed.");
            // 7. Get the process object - there should be no pending factors
            process = client.Get <LoginProcess>("api/passwordreset/process?token={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.Put <PasswordChangeInfo, bool>(passwordResetReq, "api/passwordreset/new?token={0}", processToken);

            Assert.IsTrue(success, "Failed to change password");
            // 9. Verify that email notification was sent about password change
            var notifEmail = Startup.GetLastMessageTo(ferbEmail);

            Assert.IsNotNull(notifEmail, "Password change notification was not sent.");
            Assert.AreEqual(LoginMessageType.PasswordResetCompleted, notifEmail.MessageType, "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.Get <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.Post <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.Get <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.Flags |= LoginFlags.RequireMultiFactor;
            loginInfo.MultiFactorLoginFactors = ExtraFactorTypes.Email | ExtraFactorTypes.GoogleAuthenticator;
            client.Put <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.Get <LoginProcess>("api/login/multifactor/process?token={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
            sendPinRequest = new SendPinRequest()
            {
                ProcessToken = processToken, FactorType = ExtraFactorTypes.Email
            };
            httpStatus = client.Post <SendPinRequest, HttpStatusCode>(sendPinRequest, "api/login/multifactor/pin/send");
            Assert.AreEqual(HttpStatusCode.OK, httpStatus, "Expected OK status");
            //Get message with pin from mock inbox and extract pin
            pinEmail = Startup.GetLastMessageTo(ferbEmail);
            Assert.IsNotNull(pinEmail, "Email with pin not sent.");
            pin = pinEmail.Pin;
            // Ferb copies pin from email and enters it in UI. UI submits the pin
            checkPinReq = new VerifyPinRequest()
            {
                ProcessToken = processToken, Pin = pin
            };
            success = client.Put <VerifyPinRequest, bool>(checkPinReq, "api/login/multifactor/pin/verify");
            Assert.IsTrue(success, "Email pin submit failed");
            // Google authenticator.
            //Tell server to 'send pin' - it won't send anything, but will set GoogleAuth as current factor in the process
            sendPinRequest = new SendPinRequest()
            {
                ProcessToken = processToken, FactorType = ExtraFactorTypes.GoogleAuthenticator
            };
            httpStatus = client.Post <SendPinRequest, HttpStatusCode>(sendPinRequest, "api/login/multifactor/pin/send");
            // 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
            checkPinReq = new VerifyPinRequest()
            {
                ProcessToken = processToken, Pin = gaPassCode
            };
            success = client.Put <VerifyPinRequest, bool>(checkPinReq, "api/login/multifactor/pin/verify");
            Assert.IsTrue(success, "Google authenticator pin failed.");

            //Get process again - now there should be no pending factors
            process = client.Get <LoginProcess>("api/login/multifactor/process?token={0}", processToken);
            Assert.AreEqual(ExtraFactorTypes.None, process.PendingFactors, "Expected no pending factors");
            //complete login using process token - returned LoginResponse object represents successful login
            var mfCompleteReq = new MultifactorLoginRequest()
            {
                ProcessToken = processToken
            };

            ferbLogin = client.Post <MultifactorLoginRequest, LoginResponse>(mfCompleteReq, "api/login/multifactor/complete");
            Assert.IsNotNull(ferbLogin, "Failed to complete login.");
            Assert.AreEqual(LoginAttemptStatus.Success, ferbLogin.Status, "Expected success status");
            client.AddAuthorizationHeader(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.Post <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();
        }
Ejemplo n.º 3
0
 public static void TestCleanup()
 {
     Startup.ShutDown();
 }