diff --git a/geoapi/models/project.py b/geoapi/models/project.py index 7795f75d..6732aac5 100644 --- a/geoapi/models/project.py +++ b/geoapi/models/project.py @@ -29,6 +29,8 @@ class Project(Base): id = Column(Integer, primary_key=True) uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, nullable=False) tenant_id = Column(String, nullable=False) + # Project system_id/system_path really not used except for analytics. + # This could be improved; see https://jira.tacc.utexas.edu/browse/WG-185 system_id = Column(String, nullable=True) system_path = Column(String, nullable=True) system_file = Column(String, nullable=True) diff --git a/geoapi/routes/projects.py b/geoapi/routes/projects.py index 09b68486..7f5fcd2d 100644 --- a/geoapi/routes/projects.py +++ b/geoapi/routes/projects.py @@ -169,14 +169,15 @@ def get(self): uuid_subset = query.get("uuid") if uuid_subset: - logger.info("Get a subset of projects for user:{} projects:{}".format(u.username, uuid_subset)) + logger.info(f"Getting a subset of projects for user:{u.username} project_uuid:{uuid_subset}") + # Check each project and abort if user (authenticated or anonymous) can't access the project subset = [check_access_and_get_project(request.current_user, uuid=uuid, allow_public_use=True) for uuid in uuid_subset] return subset else: if is_anonymous(u): abort(403, "Access denied") - logger.info("Get all projects for user:{}".format(u.username)) + logger.info(f"Get all projects for user:{u.username}") return ProjectsService.list(db_session, u) @api.doc(id="createProject", @@ -290,8 +291,23 @@ class ProjectFeaturesResource(Resource): @api.marshal_with(feature_collection_model, as_list=True) @project_permissions_allow_public def get(self, projectId: int): - logger.info("Get features of project:{} for user:{}".format( - projectId, request.current_user.username)) + # Following log is for analytics, see https://confluence.tacc.utexas.edu/display/DES/Hazmapper+Logging + application = request.headers.get('X-Geoapi-Application') + if application is None: + # Check if in query parameters due to https://tacc-main.atlassian.net/browse/WG-192 and WG-191 */ + application = request.args.get('application') + + if application is None: + application = "Unknown" + + from geoapi.routes.public_projects import PublicProjectFeaturesResource + is_public_view = issubclass(self.__class__, PublicProjectFeaturesResource) + + prj = ProjectsService.get(db_session, project_id=projectId, user=request.current_user) + logger.info(f"Get features of project for user:{request.current_user.username} application:{application}" + f" public_view:{is_public_view} project_uuid:{prj.uuid} project:{prj.id} tapis_system_id:{prj.system_id} " + f"tapis_system_path:{prj.system_path}") + query = self.parser.parse_args() return ProjectsService.getFeatures(db_session, projectId, query) diff --git a/geoapi/tests/api_tests/test_projects_routes.py b/geoapi/tests/api_tests/test_projects_routes.py index deb7b5f8..24314095 100644 --- a/geoapi/tests/api_tests/test_projects_routes.py +++ b/geoapi/tests/api_tests/test_projects_routes.py @@ -63,6 +63,16 @@ def test_get_projects_using_single_uuid(test_client, projects_fixture, projects_ assert data[0]["deletable"] is True +def test_get_projects_using_single_uuid_observable_project(test_client, observable_projects_fixture, user1): + resp = test_client.get('/projects/', + query_string='uuid={}'.format(observable_projects_fixture.project.uuid), + headers={'x-jwt-assertion-test': user1.jwt}) + data = resp.get_json() + assert resp.status_code == 200 + assert len(data) == 1 + assert data[0]["uuid"] == str(observable_projects_fixture.project.uuid) + + def test_get_projects_using_single_uuid_that_is_wrong(test_client, user1): resp = test_client.get('/projects/', query_string='uuid={}'.format(uuid.uuid4()), @@ -262,20 +272,39 @@ def test_get_point_cloud(test_client, projects_fixture, point_cloud_fixture, use assert resp.status_code == 200 -def test_get_project_features_empty(test_client, projects_fixture, user1): +def test_get_project_features_empty(test_client, projects_fixture, user1, caplog): resp = test_client.get(f'/projects/{projects_fixture.id}/features/', headers={'x-jwt-assertion-test': user1.jwt}) assert resp.status_code == 200 data = resp.get_json() assert len(data['features']) == 0 + log_statement_for_analytics = (f"Get features of project for user:{user1.username} application:Unknown public_view:False " + f"project_uuid:{projects_fixture.uuid} project:{projects_fixture.id} " + f"tapis_system_id:None tapis_system_path:None") + assert log_statement_for_analytics in caplog.text -def test_get_project_features_empty_public_access(test_client, public_projects_fixture): - resp = test_client.get('/projects/{}/features/'.format(public_projects_fixture.id)) +def test_get_project_features_empty_public_access(test_client, public_projects_fixture, caplog): + resp = test_client.get('/public-projects/{}/features/'.format(public_projects_fixture.id)) assert resp.status_code == 200 data = resp.get_json() assert len(data['features']) == 0 + log_statement_for_analytics = (f"Get features of project for user:Guest_Unknown application:Unknown public_view:True " + f"project_uuid:{public_projects_fixture.uuid} project:{public_projects_fixture.id} " + f"tapis_system_id:None tapis_system_path:None") + assert log_statement_for_analytics in caplog.text + + +def test_get_project_features_analytics_with_query_params(test_client, public_projects_fixture, caplog): + # send analytics-related params to projects endpoint only (until we use headers again + # in https://tacc-main.atlassian.net/browse/WG-192) + query = {'application': 'hazmapper', 'guest_uuid': "1234"} + test_client.get('/public-projects/{}/features/'.format(public_projects_fixture.id), query_string=query) + log_statement_for_analytics = (f"Get features of project for user:Guest_1234 application:hazmapper public_view:True " + f"project_uuid:{public_projects_fixture.uuid} project:{public_projects_fixture.id} " + f"tapis_system_id:None tapis_system_path:None") + assert log_statement_for_analytics in caplog.text def test_get_project_features_single_feature(test_client, projects_fixture, feature_fixture, user1): diff --git a/geoapi/tests/conftest.py b/geoapi/tests/conftest.py index 5acc390a..b711708b 100644 --- a/geoapi/tests/conftest.py +++ b/geoapi/tests/conftest.py @@ -123,6 +123,12 @@ def observable_projects_fixture(): path="/testPath", watch_content=True ) + + # Project system_id/system_path really not used except for analytics. + # This could be improved; see https://jira.tacc.utexas.edu/browse/WG-185 + proj.system_id = obs.system_id + proj.system_path = obs.path + obs.project = proj proj.users.append(u1) db_session.add(obs) diff --git a/geoapi/utils/decorators.py b/geoapi/utils/decorators.py index 65e2d126..320fca83 100644 --- a/geoapi/utils/decorators.py +++ b/geoapi/utils/decorators.py @@ -35,6 +35,9 @@ def wrapper(*args, **kwargs): except ValueError: # if not JWT information is provided in header, then this is a guest user guest_uuid = request.headers.get('X-Guest-UUID') + if guest_uuid is None: + # Check if in query parameters due to https://tacc-main.atlassian.net/browse/WG-192 and WG-191 */ + guest_uuid = request.args.get('guest_uuid') user = AnonymousUser(guest_unique_id=guest_uuid) if user is None: try: