Skip to content

Commit

Permalink
Merge pull request #1531 from ScilifelabDataCentre/dev
Browse files Browse the repository at this point in the history
New release
  • Loading branch information
rv0lt authored May 30, 2024
2 parents 781cacf + dd770b3 commit 24e01fa
Show file tree
Hide file tree
Showing 22 changed files with 1,891 additions and 49 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
Changelog
==========

.. _2.7.0:

2.7.0 - 2024-05-29
~~~~~~~~~~~~~~~~~~~~~~~

- New features:
- Fix the User endpoint according to OpenAPI standard.
- Added email contact to troubleshooting page
- Dependencies:
- `Werkzeug` from `2.2.3` to `3.0.3`
- `Flask-WTF` from `1.0.0` to `1.1.2`
- `Flask-Login` from `0.6.2` to `0.6.3`
- `Flask-HTTPAuth` from `4.5.0` to `4.8.0`
- Bugs fixed:
- Boolean inputs in requests are parsed with flask types.


.. _2.6.4:

2.6.4 - 2023-04-10
Expand Down
11 changes: 11 additions & 0 deletions SPRINTLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,14 @@ _Nothing merged in CLI during this sprint_
# 2024-04-8 - 2024-04-19

- New version: 2.6.4 ([#1526](https://github.com/ScilifelabDataCentre/dds_web/pull/1526))

# 2024-05-6 - 2024-05-17

- Fix the User endpoints according to OpenAPI standar ([#1524](https://github.com/ScilifelabDataCentre/dds_web/pull/1524))

# 2024-05-20 - 2024-05-31

- Update Werkzeug and related libraries to solve CVE([#1530](https://github.com/ScilifelabDataCentre/dds_web/pull/1530))
- Fix raising error when archiving project, bucket deleted but DB error ([#1524](https://github.com/ScilifelabDataCentre/dds_web/pull/1524))
- Increase the identified less covered files([#1521](https://github.com/ScilifelabDataCentre/dds_web/pull/1521))
- Parse boolean inputs correctly ([#1528](https://github.com/ScilifelabDataCentre/dds_web/pull/1528))
3 changes: 2 additions & 1 deletion dds_web/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import botocore
import flask
import flask_restful
from flask_restful import inputs
import sqlalchemy
import werkzeug

Expand Down Expand Up @@ -304,7 +305,7 @@ def get(self):
)

# Check if to return file size
show_size = flask.request.args.get("show_size")
show_size = flask.request.args.get("show_size", type=inputs.boolean, default=False)

# Check if to get from root or folder
subpath = "."
Expand Down
30 changes: 17 additions & 13 deletions dds_web/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,19 +767,23 @@ def delete_project_contents(project, delete_bucket=False):
sqlalchemy.exc.OperationalError,
AttributeError,
) as sqlerr:
raise DeletionError(
project=project.public_id,
message=str(sqlerr),
alt_message=(
"Project bucket contents were deleted, but they were not deleted from the "
"database. Please contact SciLifeLab Data Centre."
+ (
"Database malfunction."
if isinstance(err, sqlalchemy.exc.OperationalError)
else "."
)
),
) from sqlerr
error_msg = (
"Project bucket contents were deleted, but they were not deleted from the "
"database. Please contact SciLifeLab Data Centre."
+ (
"Database malfunction."
if isinstance(sqlerr, sqlalchemy.exc.OperationalError)
else "."
)
)
if flask.request:
raise DeletionError(
project=project.public_id,
message=str(sqlerr),
alt_message=error_msg,
) from sqlerr
else:
flask.current_app.logger.exception(error_msg)


class CreateProject(flask_restful.Resource):
Expand Down
146 changes: 146 additions & 0 deletions dds_web/api/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# Installed
import flask
import flask_restful
from flask_restful import inputs
import flask_mail
import itsdangerous
import structlog
Expand Down Expand Up @@ -750,6 +751,80 @@ def delete(self):
"""Delete user or invite in the DDS."""
current_user = auth.current_user()

if "api/v1" in flask.request.path:
# requests comming from api/v1 should be handled as before
return self.old_delete(current_user)

elif "api/v3" in flask.request.path:

is_invite = flask.request.args.get("is_invite", type=inputs.boolean, default=False)
email = flask.request.args.get("email")
if is_invite:
email = self.delete_invite(email=email)
return {
"message": ("The invite connected to email " f"'{email}' has been deleted.")
}

try:
user = user_schemas.UserSchema().load({"email": email})
except sqlalchemy.exc.OperationalError as err:
raise ddserr.DatabaseError(
message=str(err), alt_message="Unexpected database error."
)

if not user:
raise ddserr.UserDeletionError(
message=(
"This e-mail address is not associated with a user in the DDS, "
"make sure it is not misspelled."
)
)

user_email_str = user.primary_email
current_user = auth.current_user()

if current_user.role == "Unit Admin":
if user.role not in ["Unit Admin", "Unit Personnel"]:
raise ddserr.UserDeletionError(
message="You can only delete users with the role Unit Admin or Unit Personnel."
)
if current_user.unit != user.unit:
raise ddserr.UserDeletionError(
message=(
"As a Unit Admin, you're can only delete Unit Admins "
"and Unit Personnel within your specific unit."
)
)

if current_user == user:
raise ddserr.UserDeletionError(
message="To delete your own account, use the '--self' flag instead!"
)

self.delete_user(user)

msg = (
f"The user account {user.username} ({user_email_str}, {user.role}) has been "
f"terminated successfully been by {current_user.name} ({current_user.role})."
)
flask.current_app.logger.info(msg)

with structlog.threadlocal.bound_threadlocal(
who={"user": user.username, "role": user.role},
by_whom={"user": current_user.username, "role": current_user.role},
):
action_logger.info(self.__class__)

return {
"message": (
f"You successfully deleted the account {user.username} "
f"({user_email_str}, {user.role})!"
)
}

def old_delete(self, current_user):
"""Implementation of old get method. Should be removed when api/v1 is removed."""

json_info = flask.request.get_json(silent=True)
if json_info:
is_invite = json_info.pop("is_invite", False)
Expand Down Expand Up @@ -1248,6 +1323,77 @@ class Users(flask_restful.Resource):
def get(self):
"""List unit users within the unit the current user is connected to, or the one defined by a superadmin."""

if "api/v1" in flask.request.path:
# requests comming from api/v1 should be handled as before
return self.old_get()

elif "api/v3" in flask.request.path:

# Function only accessible here
def get_users(unit: models.Unit = None):
"""Get users, either all or from specific unit."""
users_to_iterate = None
if unit:
users_to_iterate = unit.users
else:
users_to_iterate = models.User.query.all()

users_to_return = [
{
"Name": user.name,
"Username": user.username,
"Email": user.primary_email,
"Role": user.role,
"Active": user.is_active,
}
for user in users_to_iterate
]
return users_to_return

# End of function

# Keys to return
keys = ["Name", "Username", "Email", "Role", "Active"]

# Super Admins can list users in units or all users,
if auth.current_user().role == "Super Admin":
unit = flask.request.args.get("unit")
if unit:
# Verify unit public id
unit_row = models.Unit.query.filter_by(public_id=unit).one_or_none()
if not unit_row:
raise ddserr.DDSArgumentError(
message=f"There is no unit with the public id '{unit}'."
)

# Get users in unit
users_to_return = get_users(unit=unit_row)
return {
"users": users_to_return,
"unit": unit_row.name,
"keys": keys,
"empty": not users_to_return,
}

# Get all users if no unit specified
users_to_return = get_users()
return {"users": users_to_return, "keys": keys, "empty": not users_to_return}

# Unit Personnel and Unit Admins can list all users within their units
unit_row = auth.current_user().unit

# Get users in unit
users_to_return = get_users(unit=unit_row)
return {
"users": users_to_return,
"unit": unit_row.name,
"keys": keys,
"empty": not users_to_return,
}

def old_get(self):
"""Implementation of old get method. Should be removed when api/v1 is removed."""

# Function only accessible here
def get_users(unit: models.Unit = None):
"""Get users, either all or from specific unit."""
Expand Down
13 changes: 7 additions & 6 deletions dds_web/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,14 +678,14 @@ def set_expired_to_archived():
project.current_status,
project.current_deadline,
)
new_status_row, delete_message = archive.archive_project(
project=project,
current_time=current_time(),
)
flask.current_app.logger.debug(delete_message.strip())
project.project_statuses.append(new_status_row)

try:
new_status_row, delete_message = archive.archive_project(
project=project,
current_time=current_time(),
)
project.project_statuses.append(new_status_row)
flask.current_app.logger.debug(delete_message.strip())
db.session.commit()
flask.current_app.logger.debug(
"Project: %s has status Archived now!", project.public_id
Expand All @@ -694,6 +694,7 @@ def set_expired_to_archived():
sqlalchemy.exc.OperationalError,
sqlalchemy.exc.SQLAlchemyError,
) as err:
# archive or commit operation failed, save error message, log it and continue to next project
flask.current_app.logger.exception(err)
db.session.rollback()
errors[unit.name][project.public_id] = str(err)
Expand Down
3 changes: 2 additions & 1 deletion dds_web/static/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,8 @@ paths:
- user
summary: List unit users within the unit the current user is connected to, or the one defined by a superadmin.
CHECK METHOD FOR SUPERADMINS
description: TODO - for superadmin, it can be passed a unit, currently in body, needs to be in query.
description: Unit Users will get all personnel in their units. <br>
Superadmins can pass a unit as a parameter, currently in body, needs to be in query.
deprecated: true
operationId: getUsers
parameters:
Expand Down
25 changes: 12 additions & 13 deletions dds_web/static/swaggerv3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -503,12 +503,7 @@ paths:
tags:
- user
summary: Delete user or invite in the DDS. Unit Admins can delete Unit Admins and Unit Personnel.
Super admins can delete any user CHECK METHOD
description: This method requires the data
to be passed in the request body instead of the query.
Since this does not comply with the openAPI standards, swagger cannot document it properly,
therefore we need to change/remove it in the future.
deprecated: true
Superadmins can delete any user
operationId: deleteUser
parameters:
- $ref: "#/components/parameters/defaultHeader"
Expand Down Expand Up @@ -690,12 +685,15 @@ paths:
tags:
- user
summary: List unit users within the unit the current user is connected to, or the one defined by a superadmin.
CHECK METHOD FOR SUPERADMINS
description: TODO - for superadmin, it can be passed a unit, currently in body, needs to be in query.
deprecated: true
description: Unit Users will get all personnel in their units. <br>
Superadmins can pass a unit as a parameter.
operationId: getUsers
parameters:
- $ref: "#/components/parameters/defaultHeader"
- in: query
name: unit
schema:
type: string
responses:
"401":
$ref: "#/components/responses/UnauthorizedToken"
Expand Down Expand Up @@ -1661,19 +1659,20 @@ components:
in: query
schema:
type: string
description: project id to query
description: Project id to query
email:
name: email
in: query
schema:
type: string
description: email of the user/invite to query
description: Email of the user/invite to query
is_invite:
name: is_invite
in: query
schema:
type: string
description: flag to mark if the user to query is an invite
type: boolean
example: false
description: Flag to query for an invite. If false or not present, it will query for a user.
filesToQuery:
name: files
in: query
Expand Down
2 changes: 1 addition & 1 deletion dds_web/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Do not do major version upgrade during 2024.
# If mid or minor version reaches 9, continue with 10, 11 etc etc.
__version__ = "2.6.4"
__version__ = "2.7.0"
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ factory-boy==3.2.1
Faker==12.0.0
Flask==2.2.5
Flask-APScheduler==1.12.3
Flask-HTTPAuth==4.5.0
Flask-HTTPAuth==4.8.0
Flask-Limiter==2.1.3
Flask-Login==0.6.2
Flask-Login==0.6.3
Flask-Mail==0.9.1
flask-marshmallow==0.14.0
Flask-Migrate==3.1.0
Flask-RESTful==0.3.9
Flask-SQLAlchemy==2.5.1
Flask-WTF==1.0.0
Flask-WTF==1.1.2
flask_swagger_ui==4.11.1
freezegun==1.2.2
idna==3.3
Expand Down Expand Up @@ -65,6 +65,6 @@ tzdata==2021.5
tzlocal==4.1
urllib3==1.26.18
visitor==0.1.3
Werkzeug==2.2.3
Werkzeug==3.0.3
wrapt==1.13.3
WTForms==3.0.1
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def fake_web_login(self, client):
def set_session_cookie(client):
app = flask.current_app
val = app.session_interface.get_signing_serializer(app).dumps(dict(session))
client.set_cookie("localhost", app.config["SESSION_COOKIE_NAME"], val)
client.set_cookie(key=app.config["SESSION_COOKIE_NAME"], value=val, domain="localhost")

flask_login.login_user(user)
set_session_cookie(client)
Expand Down
Loading

0 comments on commit 24e01fa

Please sign in to comment.