diff --git a/doc/design.md b/doc/design.md index 078763378..9132a452d 100644 --- a/doc/design.md +++ b/doc/design.md @@ -157,10 +157,59 @@ Returns: auth token - `400 BAD FORMAT`: missing email or password +**`GET`** `/api/users/` + +Fetches the list of existing users + +Required headers: + +- `Authorization: ` + +Returns: List of users: + +- format: `JSON` +- fields: + * users :: list + * email + * admin + * timestamp + +Status codes: + +- `200 OK` +- `401 UNAUTHORIZED`: authorization rejected; missing or invalid auth token +- `403 FORBIDDEN`: authorized user is not admin + + +**`GET`** `/api/users/EMAIL` + +Required headers: + +- `Authorization: ` + +Returns: Requested user + +- format: `JSON` +- fields: + * email + * admin + * timestamp + +Status codes: + +- `200 OK` +- `401 UNAUTHORIZED`: authorization rejected; missing or invalid auth token +- `403 FORBIDDEN`: authorized user is not admin + + **`POST`** `/api/users/` Creates a user +Required headers: + +- `Authorization: ` + `POST` body: - format: `JSON` @@ -189,25 +238,15 @@ Status codes: - `422 UPROCESSABLE ENTITY`: duplicate user or password too short -**`GET`** `/api/users/EMAIL` - -Returns: Requested user - -- format: `JSON` -- fields: - * email - * admin permissions - * timestamp - -Status codes: - -- `200 OK` - **`POST`** `/api/search` Performs search of remote system +Required headers: + +- `Authorization: ` + `POST` body: - format: `JSON` diff --git a/src/backend/expungeservice/database/user.py b/src/backend/expungeservice/database/user.py index 79e3950b0..b0822c25a 100644 --- a/src/backend/expungeservice/database/user.py +++ b/src/backend/expungeservice/database/user.py @@ -31,7 +31,8 @@ def get_user(database, lookup_field, key): database.cursor.execute( sql.SQL(""" - SELECT USERS.user_id::text user_id, email, admin, hashed_password, auth_id::text + SELECT USERS.user_id::text user_id, email, admin, + hashed_password, auth_id::text, date_created, date_modified FROM USERS JOIN AUTH ON USERS.user_id = AUTH.user_id WHERE users.{} = %(key)s ; @@ -43,3 +44,20 @@ def get_user(database, lookup_field, key): return res._asdict() else: return res + + +def get_all_users(database): + + database.cursor.execute( + sql.SQL(""" + SELECT USERS.user_id::text user_id, email, admin, hashed_password, auth_id::text, date_created, date_modified + FROM USERS JOIN AUTH ON USERS.user_id = AUTH.user_id + ; + """), {}) + + res = database.cursor.fetchall() + if res: + return [r._asdict() for r in res] + else: + return res + diff --git a/src/backend/expungeservice/endpoints/users.py b/src/backend/expungeservice/endpoints/users.py index ce8658ce6..e3df4d818 100644 --- a/src/backend/expungeservice/endpoints/users.py +++ b/src/backend/expungeservice/endpoints/users.py @@ -3,7 +3,7 @@ from werkzeug.security import generate_password_hash from flask import g -from expungeservice.database.user import create_user, get_user_by_email +from expungeservice.database.user import create_user, get_user_by_email, get_all_users from expungeservice.endpoints.auth import admin_auth_required from expungeservice.request import check_data_fields from psycopg2.errors import UniqueViolation @@ -51,5 +51,24 @@ def post(self): return jsonify(response_data), 201 + @admin_auth_required + def get(self): + """ + Fetch the list of users, including their email, admin clear + """ + + user_db_data = get_all_users(g.database) + + response_data={'users':[]} + for user_entry in user_db_data: + response_data['users'].append({ + 'email':user_entry['email'], + 'admin':user_entry['admin'], + 'timestamp': user_entry['date_created'] + }) + + return jsonify(response_data), 201 + + def register(app): app.add_url_rule('/api/users', view_func=Users.as_view('users')) diff --git a/src/backend/tests/database/test_database.py b/src/backend/tests/database/test_database.py index 8ea6bdf0a..d96d8a07d 100644 --- a/src/backend/tests/database/test_database.py +++ b/src/backend/tests/database/test_database.py @@ -66,6 +66,37 @@ def test_get_user(self): self.verify_user_data(email, hashed_password, admin) + + def test_get_all_users(self): + """ + test inserts two new users, then fetches from the table with the db get function and + raw sql to compare the results. Checks the number of returned rows is the same, + and that all of the columns match from each returned row. + """ + + email1 = "pytest_get_user@example.com" + hashed_password = 'examplepasswordhash2' + admin = True + self.create_example_user(email1, hashed_password, admin) + + email2 = "pytest_get_user_2@example.com" + hashed_password = 'examplepasswordhash3' + admin = True + self.create_example_user(email2, hashed_password, admin) + + users_get_endpoint_result = user.get_all_users(self.database) + + verify_query = """ + SELECT * FROM USERS;""" + self.database.cursor.execute(verify_query) + + verify_rows = [r._asdict() for r in self.database.cursor.fetchall()] + + assert len(verify_rows) == len(users_get_endpoint_result) + + for (email, hashed_password, admin) in [ + (r['email'], r['hashed_password'], r['admin']) for r in users_get_endpoint_result]: + self.verify_user_data(email, hashed_password, admin) def test_get_missing_user(self): email = "pytest_get_user_does_not_exist@example.com" @@ -92,6 +123,8 @@ def create_example_user(self, email, hashed_password, admin): #Helper function def verify_user_data(self, email, hashed_password, admin): + # is passed the data obtained from the app feature e.g. a database function, + # and checks the fields match a second, raw SQL query. verify_query = """ SELECT USERS.user_id::text, email, admin, hashed_password, auth_id::text, date_created, date_modified diff --git a/src/backend/tests/endpoints/test_users.py b/src/backend/tests/endpoints/test_users.py index 9d96583ce..a84d8af8b 100644 --- a/src/backend/tests/endpoints/test_users.py +++ b/src/backend/tests/endpoints/test_users.py @@ -159,3 +159,36 @@ def test_create_user_not_admin(self): 'admin': False}) assert(response.status_code == 403) + + + def test_get_users_success(self): + + generate_auth_response = self.generate_auth_token(self.admin_email, self.admin_password) + + response = self.client.get('/api/users', headers={ + 'Authorization': 'Bearer {}'.format(generate_auth_response.get_json()['auth_token'])}, + json = {}) + + + assert(response.status_code == 201) + + data = response.get_json() + + assert data['users'][0]['email'] + assert data['users'][0]['admin'] + assert data['users'][0]['timestamp'] + + + def test_get_users_not_admin(self): + + new_email = "pytest_create_user@endpoint_test.com" + new_password = "new_password" + + generate_auth_response = self.generate_auth_token(self.email, self.password) + + response = self.client.get('/api/users', headers={ + 'Authorization': 'Bearer {}'.format(generate_auth_response.get_json()['auth_token'])}, + json = {}) + + assert(response.status_code == 403) +