Skip to content

Commit

Permalink
Merge pull request #1397 from freedomofpress/users-endpoint
Browse files Browse the repository at this point in the history
Prepare for a smooth transition for new Deleted User
  • Loading branch information
sssoleileraaa authored Jan 25, 2022
2 parents a4ae5f5 + 7f3299f commit 8b2f0d4
Show file tree
Hide file tree
Showing 22 changed files with 13,169 additions and 12,279 deletions.
46 changes: 38 additions & 8 deletions securedrop_client/api_jobs/sync.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import logging
from typing import Any
from typing import Any, List

from sdclientapi import API
from sqlalchemy.orm.session import Session

from securedrop_client.api_jobs.base import ApiJob
from securedrop_client.storage import create_or_update_user, get_remote_data, update_local_storage
from securedrop_client.db import User
from securedrop_client.storage import get_remote_data, update_local_storage

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -37,11 +38,40 @@ def call_api(self, api_client: API, session: Session) -> Any:
# This timeout is used for 3 different requests: `get_sources`, `get_all_submissions`, and
# `get_all_replies`
api_client.default_request_timeout = 60
users = api_client.get_users()
MetadataSyncJob._update_users(session, users)
sources, submissions, replies = get_remote_data(api_client)

update_local_storage(session, sources, submissions, replies, self.data_dir)
user = api_client.get_current_user()
if "uuid" in user and "username" in user and "first_name" in user and "last_name" in user:
create_or_update_user(
user["uuid"], user["username"], user["first_name"], user["last_name"], session
)

def _update_users(session: Session, remote_users: List[User]) -> None:
"""
1. Create local user accounts for each remote user that doesn't already exist
2. Update existing local users
3. Delete all remaining local user accounts that no longer exist on the server
"""
local_users = {user.uuid: user for user in session.query(User).all()}
for remote_user in remote_users:
local_user = local_users.get(remote_user.uuid)
if not local_user:
new_user = User(
uuid=remote_user.uuid,
username=remote_user.username,
firstname=remote_user.first_name,
lastname=remote_user.last_name,
)
session.add(new_user)
logger.debug(f"Adding account for user {new_user.uuid}")
else:
if local_user.username != remote_user.username:
local_user.username = remote_user.username
if local_user.firstname != remote_user.first_name:
local_user.firstname = remote_user.first_name
if local_user.lastname != remote_user.last_name:
local_user.lastname = remote_user.last_name
del local_users[remote_user.uuid]

for uuid, account in local_users.items():
session.delete(account)
logger.debug(f"Deleting account for user {uuid}")

session.commit()
2 changes: 1 addition & 1 deletion securedrop_client/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def __repr__(self) -> str:

@property
def deleted(self) -> bool:
return True if self.uuid == "deleted" else False
return self.username == "deleted"

@property
def fullname(self) -> str:
Expand Down
60 changes: 60 additions & 0 deletions tests/api_jobs/test_sync.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,72 @@
import os

from securedrop_client.api_jobs.sync import MetadataSyncJob
from securedrop_client.db import User
from tests import factory

with open(os.path.join(os.path.dirname(__file__), "..", "files", "test-key.gpg.pub.asc")) as f:
PUB_KEY = f.read()


def test_MetadataSyncJob_creates_new_user(mocker, homedir, session, session_maker):
api_client = mocker.patch("securedrop_client.logic.sdclientapi.API")
remote_user = factory.RemoteUser()
api_client.get_users = mocker.MagicMock(return_value=[remote_user])

job = MetadataSyncJob(homedir)
job.call_api(api_client, session)

local_user = session.query(User).filter_by(uuid=remote_user.uuid).one_or_none()
assert local_user


def test_MetadataSyncJob_creates_new_special_deleted_user(mocker, homedir, session, session_maker):
api_client = mocker.patch("securedrop_client.logic.sdclientapi.API")
remote_user = factory.RemoteUser(username="deleted")
api_client.get_users = mocker.MagicMock(return_value=[remote_user])

job = MetadataSyncJob(homedir)
job.call_api(api_client, session)

local_user = session.query(User).filter_by(uuid=remote_user.uuid).one_or_none()
assert local_user.deleted


def test_MetadataSyncJob_updates_existing_user(mocker, homedir, session, session_maker):
api_client = mocker.patch("securedrop_client.logic.sdclientapi.API")
remote_user = factory.RemoteUser(
uuid="abc123-ima-uuid",
username="new-username",
first_name="NewFirstName",
last_name="NewLastName",
)
api_client.get_users = mocker.MagicMock(return_value=[remote_user])

exising_user = factory.User(uuid="abc123-ima-uuid")
session.add(exising_user)

job = MetadataSyncJob(homedir)
job.call_api(api_client, session)

assert exising_user.username == "new-username"
assert exising_user.firstname == "NewFirstName"
assert exising_user.lastname == "NewLastName"


def test_MetadataSyncJob_deletes_user(mocker, homedir, session, session_maker):
api_client = mocker.patch("securedrop_client.logic.sdclientapi.API")
api_client.get_users = mocker.MagicMock(return_value=[])

user = factory.User()
session.add(user)

job = MetadataSyncJob(homedir)
job.call_api(api_client, session)

local_user = session.query(User).filter_by(uuid=user.uuid).one_or_none()
assert not local_user


def test_MetadataSyncJob_success(mocker, homedir, session, session_maker):
job = MetadataSyncJob(homedir)

Expand Down
12 changes: 12 additions & 0 deletions tests/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from sdclientapi import Reply as SDKReply
from sdclientapi import Source as SDKSource
from sdclientapi import Submission as SDKSubmission
from sdclientapi import User as SDKUser

from securedrop_client import db
from securedrop_client.api_jobs.base import ApiJob
Expand Down Expand Up @@ -168,6 +169,17 @@ def call_api(self, api_client, session):
return DummyApiJob


def RemoteUser(**attrs):
defaults = dict(
uuid=str(uuid.uuid4()),
username="dellsberg",
first_name="Daniel",
last_name="Ellsberg",
)
defaults.update(attrs)
return SDKUser(**defaults)


def RemoteSource(**attrs):
with open(os.path.join(os.path.dirname(__file__), "files", "test-key.gpg.pub.asc")) as f:
pub_key = f.read()
Expand Down
Loading

0 comments on commit 8b2f0d4

Please sign in to comment.