class TestUserCreateOwner(unittest.TestCase): """Test cases for the /api/user/create_owner endpoint.""" def setUp(self): self.name = "Test Web API" self.dbman = CLIDbManager(DbState()) _, _name = self.dbman.create_new_db_cli(self.name, dbid="sqlite") with patch.dict("os.environ", {ENV_CONFIG_FILE: TEST_AUTH_CONFIG}): self.app = create_app() self.app.config["TESTING"] = True self.client = self.app.test_client() sqlauth = self.app.config["AUTH_PROVIDER"] sqlauth.create_table() self.ctx = self.app.test_request_context() self.ctx.push() def tearDown(self): self.ctx.pop() self.dbman.remove_database(self.name) def test_create_owner(self): rv = self.client.get(f"{BASE_URL}/token/create_owner/") assert rv.status_code == 200 token = rv.json["access_token"] assert self.app.config["AUTH_PROVIDER"].get_number_users() == 0 # data missing rv = self.client.post( f"{BASE_URL}/users/tree_owner/create_owner/", headers={"Authorization": "Bearer {}".format(token)}, json={"full_name": "My Name"}, ) assert rv.status_code == 422 rv = self.client.post( f"{BASE_URL}/users/tree_owner/create_owner/", headers={"Authorization": "Bearer {}".format(token)}, json={ "password": "******", "email": "*****@*****.**", "full_name": "My Name", }, ) assert rv.status_code == 201 assert self.app.config["AUTH_PROVIDER"].get_number_users() == 1 # try posting again rv = self.client.post( f"{BASE_URL}/users/tree_owner_2/create_owner/", headers={"Authorization": "Bearer {}".format(token)}, json={ "password": "******", "email": "*****@*****.**", "full_name": "My Name", }, ) assert rv.status_code == 405 assert self.app.config["AUTH_PROVIDER"].get_number_users() == 1 rv = self.client.get(f"{BASE_URL}/token/create_owner/") assert rv.status_code == 405
class GrampsDbBaseTest(unittest.TestCase): """Base class for unittest that need to be able to create test databases.""" def setUp(self): def dummy_callback(dummy): pass self.dbstate = DbState() self.dbman = CLIDbManager(self.dbstate) dirpath, name = self.dbman.create_new_db_cli("Test: bsddb", dbid="bsddb") self._db = make_database("bsddb") self._db.load(dirpath, None) def tearDown(self): self._db.close() self.dbman.remove_database("Test: bsddb") def _populate_database( self, num_sources=1, num_persons=0, num_families=0, num_events=0, num_places=0, num_media=0, num_links=1 ): # start with sources sources = [] for i in range(num_sources): sources.append(self._add_source()) # now for each of the other tables. Give each entry a link # to num_link sources, sources are chosen on a round robin # basis for num, add_func in ( (num_persons, self._add_person_with_sources), (num_families, self._add_family_with_sources), (num_events, self._add_event_with_sources), (num_places, self._add_place_with_sources), (num_media, self._add_media_with_sources), ): source_idx = 1 for person_idx in range(num): # Get the list of sources to link lnk_sources = set() for i in range(num_links): lnk_sources.add(sources[source_idx - 1]) source_idx = (source_idx + 1) % len(sources) try: add_func(lnk_sources) except: print("person_idx = ", person_idx) print("lnk_sources = ", repr(lnk_sources)) raise return def _add_source(self, repos=None): # Add a Source with DbTxn("Add Source and Citation", self._db) as tran: source = Source() if repos is not None: repo_ref = RepoRef() repo_ref.set_reference_handle(repos.get_handle()) source.add_repo_reference(repo_ref) self._db.add_source(source, tran) self._db.commit_source(source, tran) citation = Citation() citation.set_reference_handle(source.get_handle()) self._db.add_citation(citation, tran) self._db.commit_citation(citation, tran) return citation def _add_repository(self): # Add a Repository with DbTxn("Add Repository", self._db) as tran: repos = Repository() self._db.add_repository(repos, tran) self._db.commit_repository(repos, tran) return repos def _add_object_with_source(self, citations, object_class, add_method, commit_method): object = object_class() with DbTxn("Add Object", self._db) as tran: for citation in citations: object.add_citation(citation.get_handle()) add_method(object, tran) commit_method(object, tran) return object def _add_person_with_sources(self, citations): return self._add_object_with_source(citations, Person, self._db.add_person, self._db.commit_person) def _add_family_with_sources(self, citations): return self._add_object_with_source(citations, Family, self._db.add_family, self._db.commit_family) def _add_event_with_sources(self, citations): return self._add_object_with_source(citations, Event, self._db.add_event, self._db.commit_event) def _add_place_with_sources(self, citations): return self._add_object_with_source(citations, Place, self._db.add_place, self._db.commit_place) def _add_media_with_sources(self, citations): return self._add_object_with_source(citations, Media, self._db.add_media, self._db.commit_media)
class GrampsDbBaseTest(unittest.TestCase): """Base class for unittest that need to be able to create test databases.""" def setUp(self): def dummy_callback(dummy): pass self.dbstate = DbState() self.dbman = CLIDbManager(self.dbstate) dirpath, name = self.dbman.create_new_db_cli("Test: bsddb", dbid="bsddb") self._db = self.dbstate.make_database("bsddb") self._db.load(dirpath, None) def tearDown(self): self._db.close() self.dbman.remove_database("Test: bsddb") def _populate_database(self, num_sources=1, num_persons=0, num_families=0, num_events=0, num_places=0, num_media=0, num_links=1): # start with sources sources = [] for i in range(num_sources): sources.append(self._add_source()) # now for each of the other tables. Give each entry a link # to num_link sources, sources are chosen on a round robin # basis for num, add_func in ((num_persons, self._add_person_with_sources), (num_families, self._add_family_with_sources), (num_events, self._add_event_with_sources), (num_places, self._add_place_with_sources), (num_media, self._add_media_with_sources)): source_idx = 1 for person_idx in range(num): # Get the list of sources to link lnk_sources = set() for i in range(num_links): lnk_sources.add(sources[source_idx - 1]) source_idx = (source_idx + 1) % len(sources) try: add_func(lnk_sources) except: print("person_idx = ", person_idx) print("lnk_sources = ", repr(lnk_sources)) raise return def _add_source(self, repos=None): # Add a Source with DbTxn("Add Source and Citation", self._db) as tran: source = Source() if repos is not None: repo_ref = RepoRef() repo_ref.set_reference_handle(repos.get_handle()) source.add_repo_reference(repo_ref) self._db.add_source(source, tran) self._db.commit_source(source, tran) citation = Citation() citation.set_reference_handle(source.get_handle()) self._db.add_citation(citation, tran) self._db.commit_citation(citation, tran) return citation def _add_repository(self): # Add a Repository with DbTxn("Add Repository", self._db) as tran: repos = Repository() self._db.add_repository(repos, tran) self._db.commit_repository(repos, tran) return repos def _add_object_with_source(self, citations, object_class, add_method, commit_method): object = object_class() with DbTxn("Add Object", self._db) as tran: for citation in citations: object.add_citation(citation.get_handle()) add_method(object, tran) commit_method(object, tran) return object def _add_person_with_sources(self, citations): return self._add_object_with_source(citations, Person, self._db.add_person, self._db.commit_person) def _add_family_with_sources(self, citations): return self._add_object_with_source(citations, Family, self._db.add_family, self._db.commit_family) def _add_event_with_sources(self, citations): return self._add_object_with_source(citations, Event, self._db.add_event, self._db.commit_event) def _add_place_with_sources(self, citations): return self._add_object_with_source(citations, Place, self._db.add_place, self._db.commit_place) def _add_media_with_sources(self, citations): return self._add_object_with_source(citations, Media, self._db.add_media, self._db.commit_media)
class TestUser(unittest.TestCase): """Test cases for the /api/user endpoints.""" def setUp(self): self.name = "Test Web API" self.dbman = CLIDbManager(DbState()) _, _name = self.dbman.create_new_db_cli(self.name, dbid="sqlite") with patch.dict("os.environ", {ENV_CONFIG_FILE: TEST_AUTH_CONFIG}): self.app = create_app() self.app.config["TESTING"] = True self.client = self.app.test_client() sqlauth = self.app.config["AUTH_PROVIDER"] sqlauth.create_table() sqlauth.add_user(name="user", password="******", email="*****@*****.**", role=ROLE_MEMBER) sqlauth.add_user(name="owner", password="******", email="*****@*****.**", role=ROLE_OWNER) self.assertTrue(self.app.testing) self.ctx = self.app.test_request_context() self.ctx.push() def tearDown(self): self.ctx.pop() self.dbman.remove_database(self.name) def test_change_password_wrong_method(self): rv = self.client.get(BASE_URL + "/users/-/password/change") assert rv.status_code == 404 def test_change_password_no_token(self): rv = self.client.post( BASE_URL + "/users/-/password/change", json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 401 def test_change_password_wrong_old_pw(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token = rv.json["access_token"] rv = self.client.post( BASE_URL + "/users/-/password/change", headers={"Authorization": "Bearer {}".format(token)}, json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 403 def test_change_password(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token = rv.json["access_token"] rv = self.client.post( BASE_URL + "/users/-/password/change", headers={"Authorization": "Bearer {}".format(token)}, json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 201 rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 403 rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 def test_change_other_user_password(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_user = rv.json["access_token"] rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_owner = rv.json["access_token"] # user can't change owner's PW rv = self.client.post( BASE_URL + "/users/owner/password/change", headers={"Authorization": "Bearer {}".format(token_user)}, json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 403 # owner can change user's PW rv = self.client.post( BASE_URL + "/users/user/password/change", headers={"Authorization": "Bearer {}".format(token_owner)}, json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 201 rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 403 rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 def test_change_password_twice(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token = rv.json["access_token"] rv = self.client.post( BASE_URL + "/users/-/password/change", headers={"Authorization": "Bearer {}".format(token)}, json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 201 rv = self.client.post( BASE_URL + "/users/-/password/change", headers={"Authorization": "Bearer {}".format(token)}, json={ "old_password": "******", "new_password": "******" }, ) assert rv.status_code == 403 def test_reset_password_trigger_invalid_user(self): rv = self.client.post(BASE_URL + "/users/doesn_exist/password/reset/trigger/") assert rv.status_code == 404 def test_reset_password_trigger_status(self): with patch("smtplib.SMTP") as mock_smtp: rv = self.client.post(BASE_URL + "/users/user/password/reset/trigger/") assert rv.status_code == 201 def test_reset_password(self): with patch("smtplib.SMTP") as mock_smtp: rv = self.client.post(BASE_URL + "/users/user/password/reset/trigger/") context = mock_smtp.return_value context.send_message.assert_called() name, args, kwargs = context.method_calls.pop(0) msg = args[0] # extract the token from the message body body = msg.get_body().get_payload().replace("=\n", "") matches = re.findall(r".*jwt=([^\s]+).*", body) self.assertEqual(len(matches), 1, msg=body) token = matches[0] # try without token! rv = self.client.post( BASE_URL + "/users/-/password/reset/", json={"new_password": "******"}, ) self.assertEqual(rv.status_code, 401) # try empty PW! rv = self.client.post( BASE_URL + "/users/-/password/reset/", headers={"Authorization": "Bearer {}".format(token)}, json={"new_password": ""}, ) self.assertEqual(rv.status_code, 400) # now that should work rv = self.client.post( BASE_URL + "/users/-/password/reset/", headers={"Authorization": "Bearer {}".format(token)}, json={"new_password": "******"}, ) self.assertEqual(rv.status_code, 201) # try again with the same token! rv = self.client.post( BASE_URL + "/users/-/password/reset/", headers={"Authorization": "Bearer {}".format(token)}, json={"new_password": "******"}, ) self.assertEqual(rv.status_code, 409) # old password doesn't work anymore rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 403 # new password works! rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 def test_show_user(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_user = rv.json["access_token"] rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_owner = rv.json["access_token"] # user can view themselves rv = self.client.get( BASE_URL + "/users/-/", headers={"Authorization": "Bearer {}".format(token_user)}, ) assert rv.status_code == 200 self.assertEqual( rv.json, { "name": "user", "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": None, }, ) # user cannot view others rv = self.client.get( BASE_URL + "/users/owner/", headers={"Authorization": "Bearer {}".format(token_user)}, ) assert rv.status_code == 403 # owner can view others rv = self.client.get( BASE_URL + "/users/user/", headers={"Authorization": "Bearer {}".format(token_owner)}, ) assert rv.status_code == 200 self.assertEqual( rv.json, { "name": "user", "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": None, }, ) def test_show_users(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_user = rv.json["access_token"] rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_owner = rv.json["access_token"] # user cannot view users rv = self.client.get( BASE_URL + "/users/", headers={"Authorization": "Bearer {}".format(token_user)}, ) assert rv.status_code == 403 # owner can view users rv = self.client.get( BASE_URL + "/users/", headers={"Authorization": "Bearer {}".format(token_owner)}, ) assert rv.status_code == 200 self.assertEqual( set([user["name"] for user in rv.json]), {"user", "owner"}, ) def test_edit_user(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_user = rv.json["access_token"] rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_owner = rv.json["access_token"] # user can edit themselves rv = self.client.put( BASE_URL + "/users/-/", headers={"Authorization": "Bearer {}".format(token_user)}, json={"full_name": "My Name"}, ) assert rv.status_code == 201 rv = self.client.get( BASE_URL + "/users/-/", headers={"Authorization": "Bearer {}".format(token_user)}, ) assert rv.status_code == 200 # email is unchanged! self.assertEqual( rv.json, { "name": "user", "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "My Name", }, ) # user cannot change others rv = self.client.put( BASE_URL + "/users/owner/", headers={"Authorization": "Bearer {}".format(token_user)}, json={"full_name": "My Name"}, ) assert rv.status_code == 403 # owner can edit others rv = self.client.put( BASE_URL + "/users/user/", headers={"Authorization": "Bearer {}".format(token_owner)}, json={"full_name": "His Name"}, ) assert rv.status_code == 201 rv = self.client.get( BASE_URL + "/users/user/", headers={"Authorization": "Bearer {}".format(token_owner)}, ) assert rv.status_code == 200 self.assertEqual( rv.json, { "name": "user", "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "His Name", }, ) def test_add_user(self): rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200 token_user = rv.json["access_token"] rv = self.client.post( BASE_URL + "/token/", json={ "username": "******", "password": "******" }, ) assert rv.status_code == 200 token_owner = rv.json["access_token"] # user cannot add user rv = self.client.post( BASE_URL + "/users/new_user/", headers={"Authorization": "Bearer {}".format(token_user)}, json={ "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "My Name", "password": "******", }, ) assert rv.status_code == 403 # missing password rv = self.client.post( BASE_URL + "/users/new_user/", headers={"Authorization": "Bearer {}".format(token_owner)}, json={ "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "My Name", }, ) assert rv.status_code == 422 # existing user rv = self.client.post( BASE_URL + "/users/user/", headers={"Authorization": "Bearer {}".format(token_owner)}, json={ "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "New Name", "password": "******", }, ) assert rv.status_code == 409 # OK rv = self.client.post( BASE_URL + "/users/new_user/", headers={"Authorization": "Bearer {}".format(token_owner)}, json={ "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "New Name", "password": "******", }, ) assert rv.status_code == 201 rv = self.client.get( BASE_URL + "/users/new_user/", headers={"Authorization": "Bearer {}".format(token_owner)}, ) assert rv.status_code == 200 # email is unchanged! self.assertEqual( rv.json, { "email": "*****@*****.**", "role": ROLE_MEMBER, "full_name": "New Name", "name": "new_user", }, ) # check token for new user rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) assert rv.status_code == 200
class TestConfig(unittest.TestCase): """Test cases for the /api/config/ endpoints.""" def setUp(self): self.name = "Test Web API" self.dbman = CLIDbManager(DbState()) _, _name = self.dbman.create_new_db_cli(self.name, dbid="sqlite") with patch.dict("os.environ", {ENV_CONFIG_FILE: TEST_AUTH_CONFIG}): self.app = create_app() self.app.config["TESTING"] = True self.client = self.app.test_client() sqlauth = self.app.config["AUTH_PROVIDER"] sqlauth.create_table() sqlauth.add_user(name="user", password="******", email="*****@*****.**", role=ROLE_MEMBER) sqlauth.add_user(name="admin", password="******", email="*****@*****.**", role=ROLE_OWNER) self.ctx = self.app.test_request_context() self.ctx.push() rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) self.header_member = { "Authorization": f"Bearer {rv.json['access_token']}" } rv = self.client.post(BASE_URL + "/token/", json={ "username": "******", "password": "******" }) self.header_owner = { "Authorization": f"Bearer {rv.json['access_token']}" } def tearDown(self): self.ctx.pop() self.dbman.remove_database(self.name) def test_get_config(self): rv = self.client.get( f"{BASE_URL}/config/", headers=self.header_member, ) assert rv.status_code == 403 rv = self.client.get( f"{BASE_URL}/config/", headers=self.header_owner, ) assert rv.status_code == 200 assert rv.json == {} def test_set_config_unauth(self): rv = self.client.put( f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_member, json={"value": "myhost"}, ) assert rv.status_code == 403 def test_set_config_put(self): rv = self.client.put( f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_owner, json={"value": "host1"}, ) assert rv.status_code == 200 rv = self.client.get(f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_owner) assert rv.status_code == 200 assert rv.json == "host1" def test_config_delete(self): rv = self.client.put( f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_owner, json={"value": "host2"}, ) rv = self.client.get(f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_owner) assert rv.status_code == 200 assert rv.json == "host2" rv = self.client.delete( f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_owner, ) rv = self.client.get(f"{BASE_URL}/config/EMAIL_HOST/", headers=self.header_owner) assert rv.status_code == 404 def test_config_reset_password(self): """Check that the config options are picked up in the reset email.""" def get_from_host(): with patch("smtplib.SMTP_SSL") as mock_smtp: self.client.post( f"{BASE_URL}/users/user/password/reset/trigger/") context = mock_smtp.return_value context.send_message.assert_called() name, args, kwargs = context.method_calls.pop(0) msg = args[0] body = msg.get_body().get_payload().replace("=\n", "") matches = re.findall(r".*(https?://[^/]+)/api", body) host = matches[0] return msg["From"], host from_email, host = get_from_host() assert from_email == "" assert host == "http://localhost" self.client.put( f"{BASE_URL}/config/BASE_URL/", headers=self.header_owner, json={"value": "https://www.example.com"}, ) self.client.put( f"{BASE_URL}/config/DEFAULT_FROM_EMAIL/", headers=self.header_owner, json={"value": "*****@*****.**"}, ) from_email, host = get_from_host() assert from_email == "*****@*****.**" assert host == "https://www.example.com"