Skip to content

Commit

Permalink
Merge branch 'master' into feature/questionnaire
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanfranklin authored Dec 18, 2023
2 parents 12f6563 + 553106f commit 10e9ca5
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Related Jira tickets: ##

* [WG-999](https://jira.tacc.utexas.edu/browse/WG-999)
* [WG-XYZ](https://tacc-main.atlassian.net/browse/WG-XYZ)

## Summary of Changes: ##

Expand Down
19 changes: 7 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,15 @@ under gunicorn on port 8000

`docker exec -it geoapi python initdb.py`

###### Create a JWT
###### Obtain a JWT

Copy this string to here: https://jwt.io/

```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ3c28yLm9yZy9wcm9kdWN0cy9hbSIsImV4cCI6MjM4NDQ4MTcxMzg0MiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy9zdWJzY3JpYmVyIjoiWU9VUl9VU0VSTkFNRSIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvYXBwbGljYXRpb25pZCI6IjQ0IiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy9hcHBsaWNhdGlvbm5hbWUiOiJEZWZhdWx0QXBwbGljYXRpb24iLCJodHRwOi8vd3NvMi5vcmcvY2xhaW1zL2FwcGxpY2F0aW9udGllciI6IlVubGltaXRlZCIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvYXBpY29udGV4dCI6Ii9hcHBzIiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy92ZXJzaW9uIjoiMi4wIiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy90aWVyIjoiVW5saW1pdGVkIiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy9rZXl0eXBlIjoiUFJPRFVDVElPTiIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvdXNlcnR5cGUiOiJBUFBMSUNBVElPTl9VU0VSIiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy9lbmR1c2VyIjoiWU9VUl9VU0VSTkFNRSIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvZW5kdXNlclRlbmFudElkIjoiMTAiLCJodHRwOi8vd3NvMi5vcmcvY2xhaW1zL2VtYWlsYWRkcmVzcyI6InRlc3R1c2VyM0B0ZXN0LmNvbSIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvZnVsbG5hbWUiOiJEZXYgVXNlciIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvZ2l2ZW5uYW1lIjoiRGV2IiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy9sYXN0bmFtZSI6IlVzZXIiLCJodHRwOi8vd3NvMi5vcmcvY2xhaW1zL3ByaW1hcnlDaGFsbGVuZ2VRdWVzdGlvbiI6Ik4vQSIsImh0dHA6Ly93c28yLm9yZy9jbGFpbXMvcm9sZSI6IkludGVybmFsL2V2ZXJ5b25lIiwiaHR0cDovL3dzbzIub3JnL2NsYWltcy90aXRsZSI6Ik4vQSJ9.0dIfuYvmXJwES1m1NJKKAPclynbGnaxzX3ygSz-3dqA
```

Edit the `YOUR_USERNAME`s in there for your TACC username and copy the modified string
Refer to the confluence page or ask a colleague for assistance in obtaining a JWT.

###### Make some requests

You need to add the following header for authentication:

`X-JWT-Assertion` to equal the JWT created above
`X-JWT-Assertion-designsafe` to equal the JWT obtained above

###### Create a new map project

Expand All @@ -65,16 +59,17 @@ send a GET request to `localhost:8000/projects` and you should get that back.

See https://github.com/TACC-Cloud/hazmapper for details.

### Migrations
### Creating migrations when updating database models

These are useful steps to follow when there are changes to the database model.

Applying migrations
First, apply migrations:

```
docker exec -it geoapi alembic upgrade head
```

Creating migrations
Then, create migrations:

```
docker exec -it geoapi /bin/bash
Expand Down
2 changes: 2 additions & 0 deletions geoapi/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 20 additions & 4 deletions geoapi/routes/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)

Expand Down
35 changes: 32 additions & 3 deletions geoapi/tests/api_tests/test_projects_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down Expand Up @@ -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):
Expand Down
6 changes: 6 additions & 0 deletions geoapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions geoapi/utils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,22 @@ 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:
# TODO: validate token
decoded = jwt.decode(token, pub_key, verify=False)
decoded = jwt.decode(token, pub_key, algorithms=["RS256"], verify=not settings.TESTING)
username = decoded["http://wso2.org/claims/enduser"]
# remove ant @carbon.super or other nonsense, the tenant
# we get from the header anyway
username = username.split("@")[0]

# Exceptions
except Exception as e:
logger.exception(e)
abort(400, 'could not decode JWT')
logger.error(f'There is an issue decoding the JWT: {e}')
abort(400, f'There is an issue decoding the JWT: {e}')

user = UserService.getUser(db_session, username, tenant)
if not user:
Expand Down

0 comments on commit 10e9ca5

Please sign in to comment.