API & Client - user can delete his account - fix #17
This commit is contained in:
@ -117,6 +117,15 @@ def user_2():
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_2_admin():
|
||||
user = User(username='toto', email='toto@toto.com', password='87654321')
|
||||
user.admin = True
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_3():
|
||||
user = User(username='sam', email='sam@test.com', password='12345678')
|
||||
|
@ -1,4 +1,5 @@
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
from fittrackee_api.users.models import User
|
||||
|
||||
@ -409,3 +410,186 @@ def test_it_returns_error_if_user_can_not_change_admin_rights(
|
||||
assert response.status_code == 403
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
|
||||
def test_user_can_delete_its_own_account(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/test',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_user_with_activity_can_delete_its_own_account(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}',
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token'],
|
||||
),
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/test',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_user_with_picture_can_delete_its_own_account(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
client.post(
|
||||
'/api/auth/picture',
|
||||
data=dict(file=(BytesIO(b'avatar'), 'avatar.png')),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token'],
|
||||
),
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/test',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_user_can_not_delete_another_user_account(app, user_1, user_2):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/toto',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 403
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
|
||||
def test_it_returns_error_when_deleting_non_existing_user(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='test@test.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/not_existing',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert 'User does not exist.' in data['message']
|
||||
|
||||
|
||||
def test_admin_can_delete_another_user_account(app, user_1_admin, user_2):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/toto',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_admin_can_delete_its_own_account(app, user_1_admin, user_2_admin):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/admin',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_admin_can_not_delete_its_own_account_if_no_other_admin(
|
||||
app, user_1_admin, user_2
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(email='admin@example.com', password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/users/admin',
|
||||
headers=dict(
|
||||
Authorization='Bearer '
|
||||
+ json.loads(resp_login.data.decode())['auth_token']
|
||||
),
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 403
|
||||
assert 'error' in data['status']
|
||||
assert (
|
||||
'You can not delete your account, no other user has admin rights.'
|
||||
in data['message']
|
||||
)
|
||||
|
@ -1,9 +1,12 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from fittrackee_api import appLog, db
|
||||
from flask import Blueprint, jsonify, request, send_file
|
||||
from sqlalchemy import exc
|
||||
|
||||
from ..activities.utils_files import get_absolute_file_path
|
||||
from .models import User
|
||||
from .models import Activity, User
|
||||
from .utils import authenticate, authenticate_as_admin
|
||||
|
||||
users_blueprint = Blueprint('users', __name__)
|
||||
@ -270,7 +273,7 @@ def update_user(auth_user_id, user_name):
|
||||
}
|
||||
|
||||
:param integer auth_user_id: authenticate user id (from JSON Web Token)
|
||||
:param integer user_name: user name
|
||||
:param string user_name: user name
|
||||
|
||||
:<json boolean admin: does the user have administrator rights
|
||||
|
||||
@ -316,6 +319,111 @@ def update_user(auth_user_id, user_name):
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@users_blueprint.route('/users/<user_name>', methods=['DELETE'])
|
||||
@authenticate
|
||||
def delete_activity(auth_user_id, user_name):
|
||||
"""
|
||||
Delete a user account
|
||||
- a user can only delete his own account
|
||||
- an admin can delete all accounts except his account if he's the only
|
||||
one admin
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/users/john_doe HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 NO CONTENT
|
||||
Content-Type: application/json
|
||||
|
||||
:param integer auth_user_id: authenticate user id (from JSON Web Token)
|
||||
:param string user_name: user name
|
||||
|
||||
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||
|
||||
:statuscode 204: user account deleted
|
||||
:statuscode 401:
|
||||
- Provide a valid auth token.
|
||||
- Signature expired. Please log in again.
|
||||
- Invalid token. Please log in again.
|
||||
:statuscode 403:
|
||||
- You do not have permissions.
|
||||
- You can not delete your account, no other user has admin rights.
|
||||
:statuscode 404:
|
||||
- User does not exist.
|
||||
:statuscode 500: Error. Please try again or contact the administrator.
|
||||
|
||||
"""
|
||||
try:
|
||||
auth_user = User.query.filter_by(id=auth_user_id).first()
|
||||
user = User.query.filter_by(username=user_name).first()
|
||||
if user:
|
||||
if user.id != auth_user_id and not auth_user.admin:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'You do not have permissions.',
|
||||
}
|
||||
return response_object, 403
|
||||
if (
|
||||
user.admin is True
|
||||
and User.query.filter_by(admin=True).count() == 1
|
||||
):
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': (
|
||||
'You can not delete your account, '
|
||||
'no other user has admin rights.'
|
||||
),
|
||||
}
|
||||
return response_object, 403
|
||||
for activity in Activity.query.filter_by(user_id=user.id).all():
|
||||
db.session.delete(activity)
|
||||
db.session.flush()
|
||||
user_picture = user.picture
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
if user_picture:
|
||||
picture_path = get_absolute_file_path(user.picture)
|
||||
if os.path.isfile(picture_path):
|
||||
os.remove(picture_path)
|
||||
shutil.rmtree(
|
||||
get_absolute_file_path(f'activities/{user.id}'),
|
||||
ignore_errors=True,
|
||||
)
|
||||
shutil.rmtree(
|
||||
get_absolute_file_path(f'pictures/{user.id}'),
|
||||
ignore_errors=True,
|
||||
)
|
||||
response_object = {'status': 'no content'}
|
||||
code = 204
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'message': 'User does not exist.',
|
||||
}
|
||||
code = 404
|
||||
except (
|
||||
exc.IntegrityError,
|
||||
exc.OperationalError,
|
||||
ValueError,
|
||||
OSError,
|
||||
) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.',
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@users_blueprint.route('/ping', methods=['GET'])
|
||||
def ping_pong():
|
||||
""" health check endpoint
|
||||
|
Reference in New Issue
Block a user