Beispiel #1
0
    def create_project(self, data, **kwargs):
        """Create project row in db."""

        # Lock db, get unit row and update counter
        unit_row = (models.Unit.query.filter_by(
            id=auth.current_user().unit_id).with_for_update().one_or_none())
        if not unit_row:
            raise ddserr.AccessDeniedError(
                message="Error: Your user is not associated to a unit.")

        unit_row.counter = unit_row.counter + 1 if unit_row.counter else 1
        data["public_id"] = "{}{:05d}".format(unit_row.internal_ref,
                                              unit_row.counter)

        # Generate bucket name
        data["bucket"] = self.generate_bucketname(
            public_id=data["public_id"], created_time=data["date_created"])

        # Create project
        current_user = auth.current_user()
        new_project = models.Project(
            **{
                **data, "unit_id": current_user.unit.id,
                "created_by": current_user.username
            })
        new_project.project_statuses.append(
            models.ProjectStatuses(**{
                "status": "In Progress",
                "date_created": data["date_created"],
            }))
        generate_project_key_pair(current_user, new_project)

        return new_project
Beispiel #2
0
    def delete_invite(email):
        current_user_role = auth.current_user().role
        try:
            unanswered_invite = user_schemas.UnansweredInvite().load(
                {"email": email})
            if unanswered_invite:
                if current_user_role == "Super Admin" or (
                        current_user_role == "Unit Admin"
                        and unanswered_invite.role
                        in ["Unit Admin", "Unit Personnel", "Researcher"]):
                    db.session.delete(unanswered_invite)
                    db.session.commit()
                else:
                    raise ddserr.AccessDeniedError(
                        message=
                        "You do not have the correct permissions to delete this invite."
                    )
        except (sqlalchemy.exc.SQLAlchemyError,
                sqlalchemy.exc.OperationalError) as err:
            db.session.rollback()
            flask.current_app.logger.error(
                "The invite connected to the email "
                f"{email or '[no email provided]'} was not deleted.")
            raise ddserr.DatabaseError(
                message=str(err),
                alt_message=f"Failed to delete invite" +
                (": Database malfunction." if isinstance(
                    err, sqlalchemy.exc.OperationalError) else "."),
            ) from err

        return email
Beispiel #3
0
def verify_project_access(project):
    """Check users access to project."""

    if project not in auth.current_user().projects:
        raise ddserr.AccessDeniedError(
            message="Project access denied.",
            username=auth.current_user().username,
            project=project.public_id,
        )

    return project
Beispiel #4
0
    def get(self):
        current_user = auth.current_user()

        # Check that user is unit account
        if current_user.role not in ["Unit Admin", "Unit Personnel"]:
            raise ddserr.AccessDeniedError(
                "Access denied - only unit accounts can get invoicing information."
            )

        # Get unit info from table (incl safespring proj name)
        try:
            unit_info = models.Unit.query.filter(
                models.Unit.id == sqlalchemy.func.binary(
                    current_user.unit_id)).first()
        except (sqlalchemy.exc.SQLAlchemyError,
                sqlalchemy.exc.OperationalError) as err:
            flask.current_app.logger.exception(err)
            raise ddserr.DatabaseError(
                message=str(err),
                alt_message=f"Failed to get unit information." +
                (": Database malfunction." if isinstance(
                    err, sqlalchemy.exc.OperationalError) else "."),
            ) from err

        # Total number of GB hours and cost saved in the db for the specific unit
        total_gbhours_db = 0.0
        total_cost_db = 0.0

        # Project (bucket) specific info
        usage = {}
        for p in unit_info.projects:

            # Define fields in usage dict
            usage[p.public_id] = {"gbhours": 0.0, "cost": 0.0}

            for f in p.files:
                for v in f.versions:
                    # Calculate hours of the current file
                    time_uploaded = v.time_uploaded
                    time_deleted = (v.time_deleted if v.time_deleted else
                                    dds_web.utils.current_time())
                    file_hours = (time_deleted - time_uploaded).seconds / (60 *
                                                                           60)

                    # Calculate GBHours, if statement to avoid zerodivision exception
                    gb_hours = ((v.size_stored / 1e9) /
                                file_hours) if file_hours else 0.0

                    # Save file version gbhours to project info and increase total unit sum
                    usage[p.public_id]["gbhours"] += gb_hours
                    total_gbhours_db += gb_hours

                    # Calculate approximate cost per gbhour: kr per gb per month / (days * hours)
                    cost_gbhour = 0.09 / (30 * 24)
                    cost = gb_hours * cost_gbhour

                    # Save file cost to project info and increase total unit cost
                    usage[p.public_id]["cost"] += cost
                    total_cost_db += cost

            usage[p.public_id].update({
                "gbhours":
                round(usage[p.public_id]["gbhours"], 2),
                "cost":
                round(usage[p.public_id]["cost"], 2),
            })

        return {
            "total_usage": {
                "gbhours": round(total_gbhours_db, 2),
                "cost": round(total_cost_db, 2),
            },
            "project_usage": usage,
        }
Beispiel #5
0
    def post(self):
        # Verify that user specified
        json_input = flask.request.json

        if "email" not in json_input:
            raise ddserr.DDSArgumentError(message="User email missing.")

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

        if not user:
            raise ddserr.NoSuchUserError()

        # Verify that the action is specified -- reactivate or deactivate
        action = json_input.get("action")
        if not action:
            raise ddserr.DDSArgumentError(
                message=
                "Please provide an action 'deactivate' or 'reactivate' for this request."
            )

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

        if current_user.role == "Unit Admin":
            # Unit Admin can only activate/deactivate Unit Admins and personnel
            if user.role not in ["Unit Admin", "Unit Personnel"]:
                raise ddserr.AccessDeniedError(
                    message=("You can only activate/deactivate users with "
                             "the role Unit Admin or Unit Personnel."))

            if current_user.unit != user.unit:
                raise ddserr.AccessDeniedError(message=(
                    "As a Unit Admin, you can only activate/deactivate other Unit Admins or "
                    "Unit Personnel within your specific unit."))

        if current_user == user:
            raise ddserr.AccessDeniedError(
                message=f"You cannot {action} your own account!")

        if (action == "reactivate"
                and user.is_active) or (action == "deactivate"
                                        and not user.is_active):
            raise ddserr.DDSArgumentError(
                message=f"User is already {action}d!")

        # TODO: Check if user has lost access to any projects and if so, grant access again.
        if action == "reactivate":
            user.active = True

            # TODO: Super admins (current_user) don't have access to projects currently, how handle this?
            list_of_projects = None
            if user.role in ["Project Owner", "Researcher"]:
                list_of_projects = [
                    x.project for x in user.project_associations
                ]
            elif user.role in ["Unit Personnel", "Unit Admin"]:
                list_of_projects = user.unit.projects

            from dds_web.api.project import ProjectAccess  # Needs to be here because of circ.import

            ProjectAccess.give_project_access(project_list=list_of_projects,
                                              current_user=current_user,
                                              user=user)

        else:
            user.active = False

        try:
            db.session.commit()
        except (sqlalchemy.exc.SQLAlchemyError,
                sqlalchemy.exc.OperationalError) as err:
            db.session.rollback()
            raise ddserr.DatabaseError(
                message=str(err),
                alt_message=f"Unexpected database error" +
                (": Database malfunction." if isinstance(
                    err, sqlalchemy.exc.OperationalError) else "."),
            ) from err
        msg = (
            f"The user account {user.username} ({user_email_str}, {user.role}) "
            f" has been {action}d 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 {action}d the account {user.username} "
             f"({user_email_str}, {user.role})!")
        }
Beispiel #6
0
    def delete(self):
        """Request deletion of own account."""
        current_user = auth.current_user()

        email_str = current_user.primary_email

        username = current_user.username

        proj_ids = None
        if current_user.role != "Super Admin":
            proj_ids = [proj.public_id for proj in current_user.projects]

        if current_user.role == "Unit Admin":
            num_admins = models.UnitUser.query.filter_by(
                unit_id=current_user.unit.id, is_admin=True).count()
            if num_admins <= 3:
                raise ddserr.AccessDeniedError(message=(
                    f"Your unit only has {num_admins} Unit Admins. "
                    "You cannot delete your account. "
                    "Invite a new Unit Admin first if you wish to proceed."))

        # Create URL safe token for invitation link
        s = itsdangerous.URLSafeTimedSerializer(
            flask.current_app.config["SECRET_KEY"])
        token = s.dumps(email_str, salt="email-delete")

        # Create deletion request in database unless it already exists
        try:
            if not dds_web.utils.delrequest_exists(email_str):
                new_delrequest = models.DeletionRequest(
                    **{
                        "requester": current_user,
                        "email": email_str,
                        "issued": dds_web.utils.current_time(),
                    })
                db.session.add(new_delrequest)
                db.session.commit()
            else:
                return {
                    "message": ("The confirmation link has already "
                                f"been sent to your address {email_str}!"),
                    "status":
                    http.HTTPStatus.OK,
                }

        except (sqlalchemy.exc.SQLAlchemyError,
                sqlalchemy.exc.OperationalError) as sqlerr:
            db.session.rollback()
            raise ddserr.DatabaseError(
                message=str(sqlerr),
                alt_message=f"Creation of self-deletion request failed" +
                (": Database malfunction." if isinstance(
                    sqlerr, sqlalchemy.exc.OperationalError) else "."),
            ) from sqlerr

        # Create link for deletion request email
        link = flask.url_for("auth_blueprint.confirm_self_deletion",
                             token=token,
                             _external=True)
        subject = f"Confirm deletion of your user account {username} in the SciLifeLab Data Delivery System"

        msg = flask_mail.Message(
            subject,
            recipients=[email_str],
        )

        # Need to attach the image to be able to use it
        msg.attach(
            "scilifelab_logo.png",
            "image/png",
            open(
                os.path.join(flask.current_app.static_folder,
                             "img/scilifelab_logo.png"), "rb").read(),
            "inline",
            headers=[
                ["Content-ID", "<Logo>"],
            ],
        )

        msg.body = flask.render_template(
            "mail/deletion_request.txt",
            link=link,
            sender_name=current_user.name,
            projects=proj_ids,
        )
        msg.html = flask.render_template(
            "mail/deletion_request.html",
            link=link,
            sender_name=current_user.name,
            projects=proj_ids,
        )

        mail.send(msg)

        flask.current_app.logger.info(
            f"The user account {username} / {email_str} ({current_user.role}) "
            "has requested self-deletion.")

        return {
            "message":
            ("Requested account deletion initiated. An e-mail with a "
             f"confirmation link has been sent to your address {email_str}!"),
        }