Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

Bump fideslib to handle base64 encoded password #820

Merged
merged 6 commits into from
Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The types of changes are:
* Fixed `check-migrations` Make command [#806](https://github.com/ethyca/fidesops/pull/806)
* Fix issue requiring separate install of snowflake-connector-python [#807](https://github.com/ethyca/fidesops/pull/807)
* Fix publish_docs CI action [#818](https://github.com/ethyca/fidesops/pull/818)
* Bump fideslib to handle base64 encoded password [#820](https://github.com/ethyca/fidesops/pull/820)

## [1.6.1](https://github.com/ethyca/fidesops/compare/1.6.0...1.6.1)

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fastapi-caching[redis]
fastapi-pagination[sqlalchemy]~= 0.8.3
fastapi[all]==0.78.0
fideslang==1.0.0
fideslib==2.1.1
fideslib==2.2.1
fideslog==1.2.1
multidimensional_urlencode==0.0.4
pandas==1.3.3
Expand Down
11 changes: 7 additions & 4 deletions src/fidesops/api/v1/endpoints/user_endpoints.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import logging

from fastapi import APIRouter, Depends, HTTPException, Security
from fideslib.cryptography.cryptographic_util import b64_str_to_str
from fideslib.models.client import ClientDetail
from fideslib.models.fides_user import FidesUser
from fideslib.oauth.schemas.user import UserResponse, UserUpdate
from fideslib.oauth.schemas.user import UserPasswordReset, UserResponse, UserUpdate
from sqlalchemy.orm import Session
from starlette.status import (
HTTP_200_OK,
Expand All @@ -16,7 +17,7 @@
from fidesops.api.v1 import urn_registry as urls
from fidesops.api.v1.scope_registry import USER_PASSWORD_RESET, USER_UPDATE
from fidesops.api.v1.urn_registry import V1_URL_PREFIX
from fidesops.schemas.user import UserPasswordReset
from fidesops.core.config import config
from fidesops.util.oauth_util import get_current_user, verify_oauth_client

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,12 +84,14 @@ def update_user_password(
"""
_validate_current_user(user_id, current_user)

if not current_user.credentials_valid(data.old_password):
if not current_user.credentials_valid(
b64_str_to_str(data.old_password), config.security.ENCODING
):
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED, detail="Incorrect password."
)

current_user.update_password(db=db, new_password=data.new_password)
current_user.update_password(db=db, new_password=b64_str_to_str(data.new_password))

logger.info(f"Updated user with id: '{current_user.id}'.")
return current_user
Expand Down
8 changes: 0 additions & 8 deletions src/fidesops/schemas/user.py

This file was deleted.

52 changes: 27 additions & 25 deletions tests/api/v1/endpoints/test_user_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
from fastapi_pagination import Params
from fideslib.cryptography.cryptographic_util import str_to_b64_str
from fideslib.cryptography.schemas.jwt import (
JWE_ISSUED_AT,
JWE_PAYLOAD_CLIENT_ID,
Expand Down Expand Up @@ -70,7 +71,10 @@ def test_create_user_bad_username(
url,
) -> None:
auth_header = generate_auth_header([USER_CREATE])
body = {"username": "spaces in name", "password": "TestP@ssword9"}
body = {
"username": "spaces in name",
"password": str_to_b64_str("TestP@ssword9"),
}

response = api_client.post(url, headers=auth_header, json=body)
assert HTTP_422_UNPROCESSABLE_ENTITY == response.status_code
Expand All @@ -84,7 +88,7 @@ def test_username_exists(
) -> None:
auth_header = generate_auth_header([USER_CREATE])

body = {"username": "test_user", "password": "TestP@ssword9"}
body = {"username": "test_user", "password": str_to_b64_str("TestP@ssword9")}
user = FidesUser.create(db=db, data=body)

response = api_client.post(url, headers=auth_header, json=body)
Expand All @@ -103,31 +107,31 @@ def test_create_user_bad_password(
) -> None:
auth_header = generate_auth_header([USER_CREATE])

body = {"username": "test_user", "password": "short"}
body = {"username": "test_user", "password": str_to_b64_str("short")}
response = api_client.post(url, headers=auth_header, json=body)
assert HTTP_422_UNPROCESSABLE_ENTITY == response.status_code
assert (
json.loads(response.text)["detail"][0]["msg"]
== "Password must have at least eight characters."
)

body = {"username": "test_user", "password": "longerpassword"}
body = {"username": "test_user", "password": str_to_b64_str("longerpassword")}
response = api_client.post(url, headers=auth_header, json=body)
assert HTTP_422_UNPROCESSABLE_ENTITY == response.status_code
assert (
json.loads(response.text)["detail"][0]["msg"]
== "Password must have at least one number."
)

body = {"username": "test_user", "password": "longer55password"}
body = {"username": "test_user", "password": str_to_b64_str("longer55password")}
response = api_client.post(url, headers=auth_header, json=body)
assert HTTP_422_UNPROCESSABLE_ENTITY == response.status_code
assert (
json.loads(response.text)["detail"][0]["msg"]
== "Password must have at least one capital letter."
)

body = {"username": "test_user", "password": "LoNgEr55paSSworD"}
body = {"username": "test_user", "password": str_to_b64_str("LoNgEr55paSSworD")}
response = api_client.post(url, headers=auth_header, json=body)
assert HTTP_422_UNPROCESSABLE_ENTITY == response.status_code
assert (
Expand All @@ -143,7 +147,7 @@ def test_create_user(
url,
) -> None:
auth_header = generate_auth_header([USER_CREATE])
body = {"username": "test_user", "password": "TestP@ssword9"}
body = {"username": "test_user", "password": str_to_b64_str("TestP@ssword9")}

response = api_client.post(url, headers=auth_header, json=body)

Expand All @@ -164,7 +168,7 @@ def test_create_user_with_name(
auth_header = generate_auth_header([USER_CREATE])
body = {
"username": "test_user",
"password": "TestP@ssword9",
"password": str_to_b64_str("TestP@ssword9"),
"first_name": "Test",
"last_name": "User",
}
Expand Down Expand Up @@ -211,7 +215,7 @@ def test_delete_self(self, api_client, db, generate_auth_header):
db=db,
data={
"username": "test_delete_user",
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
"password": str_to_b64_str("TESTdcnG@wzJeu0&%3Qe2fGo7"),
},
)
saved_user_id = user.id
Expand Down Expand Up @@ -264,7 +268,7 @@ def test_delete_user_as_root(self, api_client, db, generate_auth_header, user):
db=db,
data={
"username": "test_delete_user",
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
"password": str_to_b64_str("TESTdcnG@wzJeu0&%3Qe2fGo7"),
},
)

Expand Down Expand Up @@ -362,7 +366,7 @@ def test_get_users(self, api_client: TestClient, generate_auth_header, url, db):
for i in range(total_users):
body = {
"username": f"user{i}@example.com",
"password": "Password123!",
"password": str_to_b64_str("Password123!"),
"first_name": "Test",
"last_name": "User",
}
Expand Down Expand Up @@ -399,7 +403,7 @@ def test_get_filtered_users(
for i in range(total_users):
body = {
"username": f"user{i}@example.com",
"password": "Password123!",
"password": str_to_b64_str("Password123!"),
}
resp = api_client.post(url, headers=create_auth_header, json=body)
assert resp.status_code == HTTP_201_CREATED
Expand Down Expand Up @@ -577,8 +581,8 @@ def test_update_different_user_password(
f"{url_no_id}/{user.id}/reset-password",
headers=auth_header,
json={
"old_password": OLD_PASSWORD,
"new_password": NEW_PASSWORD,
"old_password": str_to_b64_str(OLD_PASSWORD),
"new_password": str_to_b64_str(NEW_PASSWORD),
},
)
assert resp.status_code == HTTP_401_UNAUTHORIZED
Expand Down Expand Up @@ -610,8 +614,8 @@ def test_update_user_password_invalid(
f"{url_no_id}/{application_user.id}/reset-password",
headers=auth_header,
json={
"old_password": "mismatching password",
"new_password": NEW_PASSWORD,
"old_password": str_to_b64_str("mismatching password"),
"new_password": str_to_b64_str(NEW_PASSWORD),
},
)
assert resp.status_code == HTTP_401_UNAUTHORIZED
Expand All @@ -631,7 +635,6 @@ def test_update_user_password(
OLD_PASSWORD = "oldpassword"
NEW_PASSWORD = "newpassword"
application_user.update_password(db=db, new_password=OLD_PASSWORD)

auth_header = generate_auth_header_for_user(
user=application_user,
scopes=[USER_PASSWORD_RESET],
Expand All @@ -640,12 +643,11 @@ def test_update_user_password(
f"{url_no_id}/{application_user.id}/reset-password",
headers=auth_header,
json={
"old_password": OLD_PASSWORD,
"new_password": NEW_PASSWORD,
"old_password": str_to_b64_str(OLD_PASSWORD),
"new_password": str_to_b64_str(NEW_PASSWORD),
},
)
assert resp.status_code == HTTP_200_OK

db.expunge(application_user)
application_user = application_user.refresh_from_db(db=db)
assert application_user.credentials_valid(password=NEW_PASSWORD)
Expand All @@ -659,15 +661,15 @@ def url(self, oauth_client: ClientDetail) -> str:
def test_user_does_not_exist(self, db, url, api_client):
body = {
"username": "does not exist",
"password": "idonotknowmypassword",
"password": str_to_b64_str("idonotknowmypassword"),
}
response = api_client.post(url, headers={}, json=body)
assert response.status_code == HTTP_404_NOT_FOUND

def test_bad_login(self, db, url, user, api_client):
body = {
"username": user.username,
"password": "idonotknowmypassword",
"password": str_to_b64_str("idonotknowmypassword"),
}
response = api_client.post(url, headers={}, json=body)
assert response.status_code == HTTP_403_FORBIDDEN
Expand All @@ -677,7 +679,7 @@ def test_login_creates_client(self, db, url, user, api_client):
user.client.delete(db)
body = {
"username": user.username,
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
"password": str_to_b64_str("TESTdcnG@wzJeu0&%3Qe2fGo7"),
}

assert user.client is None # client does not exist
Expand Down Expand Up @@ -705,7 +707,7 @@ def test_login_creates_client(self, db, url, user, api_client):
def test_login_updates_last_login_date(self, db, url, user, api_client):
body = {
"username": user.username,
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
"password": str_to_b64_str("TESTdcnG@wzJeu0&%3Qe2fGo7"),
}

response = api_client.post(url, headers={}, json=body)
Expand All @@ -717,7 +719,7 @@ def test_login_updates_last_login_date(self, db, url, user, api_client):
def test_login_uses_existing_client(self, db, url, user, api_client):
body = {
"username": user.username,
"password": "TESTdcnG@wzJeu0&%3Qe2fGo7",
"password": str_to_b64_str("TESTdcnG@wzJeu0&%3Qe2fGo7"),
}

existing_client_id = user.client.id
Expand Down
2 changes: 1 addition & 1 deletion tests/models/test_client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from fideslib.cryptography.cryptographic_util import hash_with_salt
from fideslib.models.client import ClientDetail
from sqlalchemy.orm import Session

from fidesops.api.v1.scope_registry import SCOPE_REGISTRY
from fidesops.core.config import config
from fidesops.util.cryptographic_util import hash_with_salt


class TestClientModel:
Expand Down
9 changes: 5 additions & 4 deletions tests/scripts/test_create_superuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest
from create_superuser import collect_username_and_password, create_user_and_client
from fideslib.cryptography.cryptographic_util import str_to_b64_str
from fideslib.exceptions import KeyOrNameAlreadyExists
from fideslib.models.client import ADMIN_UI_ROOT, ClientDetail
from fideslib.models.fides_user import FidesUser
Expand All @@ -23,7 +24,7 @@ def test_collect_username_and_password(
db,
):
GENERIC_INPUT = "some_input"
mock_pass.return_value = "TESTP@ssword9"
mock_pass.return_value = str_to_b64_str("TESTP@ssword9")
mock_user.return_value = "test_user"
mock_input.return_value = GENERIC_INPUT
user: UserCreate = collect_username_and_password(db)
Expand All @@ -47,7 +48,7 @@ def test_collect_username_and_password_user_exists(
db=db,
data={"username": "test_user", "password": "test_password"},
)
mock_pass.return_value = "TESTP@ssword9"
mock_pass.return_value = str_to_b64_str("TESTP@ssword9")
mock_user.return_value = "test_user"
mock_input.return_value = "some_input"

Expand All @@ -66,7 +67,7 @@ def test_collect_username_and_password_bad_data(
mock_user,
db,
):
mock_pass.return_value = "bad_password"
mock_pass.return_value = str_to_b64_str("bad_password")
mock_user.return_value = "test_user"
mock_input.return_value = "some_input"

Expand All @@ -83,7 +84,7 @@ def test_create_user_and_client(
mock_user,
db,
):
mock_pass.return_value = "TESTP@ssword9"
mock_pass.return_value = str_to_b64_str("TESTP@ssword9")
mock_user.return_value = "test_user"
mock_input.return_value = "some_input"

Expand Down