|
9 | 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10 | 10 | # See the License for the specific language governing permissions and
|
11 | 11 | # limitations under the License.
|
| 12 | +import time |
12 | 13 |
|
| 14 | +from http import HTTPStatus |
| 15 | + |
| 16 | +import faker |
13 | 17 | import pretend
|
14 | 18 | import pytest
|
15 | 19 |
|
|
20 | 24 | from warehouse.manage.views import organizations as org_views
|
21 | 25 | from warehouse.organizations.interfaces import IOrganizationService
|
22 | 26 | from warehouse.organizations.models import OrganizationType
|
| 27 | +from warehouse.utils.otp import _get_totp |
23 | 28 |
|
24 | 29 | from ...common.db.accounts import EmailFactory, UserFactory
|
25 | 30 |
|
@@ -48,6 +53,62 @@ def test_save_account(self, pyramid_services, user_service, db_request):
|
48 | 53 | assert user.name == "new name"
|
49 | 54 | assert user.public_email is None
|
50 | 55 |
|
| 56 | + def test_changing_password_succeeds(self, webtest, socket_enabled): |
| 57 | + """A user can log in, and change their password.""" |
| 58 | + # create a User |
| 59 | + user = UserFactory.create( |
| 60 | + with_verified_primary_email=True, clear_pwd="password" |
| 61 | + ) |
| 62 | + |
| 63 | + # visit login page |
| 64 | + login_page = webtest.get("/account/login/", status=HTTPStatus.OK) |
| 65 | + |
| 66 | + # Fill & submit the login form |
| 67 | + login_form = login_page.forms[2] # TODO: form should have an ID, doesn't yet |
| 68 | + anonymous_csrf_token = login_form["csrf_token"].value |
| 69 | + login_form["username"] = user.username |
| 70 | + login_form["password"] = "password" |
| 71 | + login_form["csrf_token"] = anonymous_csrf_token |
| 72 | + |
| 73 | + two_factor_page = login_form.submit().follow(status=HTTPStatus.OK) |
| 74 | + |
| 75 | + # TODO: form doesn't have an ID yet |
| 76 | + two_factor_form = two_factor_page.forms[2] |
| 77 | + two_factor_form["csrf_token"] = anonymous_csrf_token |
| 78 | + |
| 79 | + # Generate the correct TOTP value from the known secret |
| 80 | + two_factor_form["totp_value"] = ( |
| 81 | + _get_totp(user.totp_secret).generate(time.time()).decode() |
| 82 | + ) |
| 83 | + |
| 84 | + logged_in = two_factor_form.submit().follow(status=HTTPStatus.OK) |
| 85 | + assert logged_in.html.find("title", text="Warehouse · The Python Package Index") |
| 86 | + |
| 87 | + # Now visit the change password page |
| 88 | + change_password_page = logged_in.goto("/manage/account/", status=HTTPStatus.OK) |
| 89 | + |
| 90 | + # Ensure that the CSRF token changes once logged in and a session is established |
| 91 | + logged_in_csrf_token = change_password_page.html.find( |
| 92 | + "input", {"name": "csrf_token"} |
| 93 | + )["value"] |
| 94 | + assert anonymous_csrf_token != logged_in_csrf_token |
| 95 | + |
| 96 | + # Fill & submit the change password form |
| 97 | + # TODO: form doesn't have an ID yet |
| 98 | + new_password = faker.Faker().password() # a secure-enough password for testing |
| 99 | + change_password_form = change_password_page.forms[3] |
| 100 | + change_password_form["csrf_token"] = logged_in_csrf_token |
| 101 | + change_password_form["password"] = "password" |
| 102 | + change_password_form["new_password"] = new_password |
| 103 | + change_password_form["password_confirm"] = new_password |
| 104 | + |
| 105 | + change_password_form.submit().follow(status=HTTPStatus.OK) |
| 106 | + |
| 107 | + # Request the JavaScript-enabled flash messages directly to get the message |
| 108 | + resp = webtest.get("/_includes/flash-messages/", status=HTTPStatus.OK) |
| 109 | + success_message = resp.html.find("span", {"class": "notification-bar__message"}) |
| 110 | + assert success_message.text == "Password updated" |
| 111 | + |
51 | 112 |
|
52 | 113 | class TestManageOrganizations:
|
53 | 114 | @pytest.mark.usefixtures("_enable_organizations")
|
|
0 commit comments