Пример #1
0
def status_report(types, email, admin_access_token):
    """Retrieve a status report summary of the REANA system."""
    def _format_statuses(type_, statuses):
        """Format statuses dictionary object."""
        formatted_statuses = type_.upper() + "\n"
        for stat_name, stat_value in statuses.items():
            formatted_statuses += f"{stat_name}: {stat_value}\n"

        return formatted_statuses

    types = STATUS_OBJECT_TYPES.keys() if "all" in types else types
    status_report_output = ""
    for type_ in types:
        statuses_obj = STATUS_OBJECT_TYPES[type_]()
        statuses = statuses_obj.get_status()
        status_report_output += _format_statuses(type_, statuses) + "\n"

    click.echo(status_report_output)

    if email:
        status_report_body = (
            f'Status report for {REANA_URL or "REANA service"}\n'
            "---\n" + status_report_output)

        send_email(email, "REANA system status report", status_report_body)
        click.echo(f"Status report successfully sent by email to {email}.")
Пример #2
0
def token_grant(admin_access_token, id_, email):
    """Grant a token to the selected user."""
    try:
        admin = User.query.filter_by(id_=ADMIN_USER_ID).one_or_none()
        if admin_access_token != admin.access_token:
            raise ValueError("Admin access token invalid.")
        user = _get_user_by_criteria(id_, email)
        error_msg = None
        if not user:
            error_msg = f"User {id_ or email} does not exist."
        elif user.access_token:
            error_msg = (
                f"User {user.id_} ({user.email}) has already an active access token."
            )
        if error_msg:
            click.secho(f"ERROR: {error_msg}", fg="red")
            sys.exit(1)
        if user.access_token_status in [UserTokenStatus.revoked.name, None]:
            click.confirm(
                f"User {user.id_} ({user.email}) access token status"
                f" is {user.access_token_status}, do you want to"
                " proceed?",
                abort=True,
            )

        user_granted_token = secrets.token_urlsafe(16)
        user.access_token = user_granted_token
        Session.commit()
        log_msg = (f"Token for user {user.id_} ({user.email}) granted.\n"
                   f"\nToken: {user_granted_token}")
        click.secho(log_msg, fg="green")
        admin.log_action(AuditLogAction.grant_token, {"reana_admin": log_msg})
        # send notification to user by email
        email_subject = "REANA access token granted"
        email_body = JinjaEnv.render_template(
            "emails/token_granted.txt",
            user_full_name=user.full_name,
            reana_hostname=REANA_HOSTNAME,
            ui_config=REANAConfig.load("ui"),
            sender_email=ADMIN_EMAIL,
        )
        send_email(user.email, email_subject, email_body)

    except click.exceptions.Abort:
        click.echo("Grant token aborted.")
    except REANAEmailNotificationError as e:
        click.secho(
            "Something went wrong while sending email:\n{}".format(e),
            fg="red",
            err=True,
        )
    except Exception as e:
        click.secho(
            "Something went wrong while granting token:\n{}".format(e),
            fg="red",
            err=True,
        )
Пример #3
0
def test_send_email_missing_config():
    """Test send_email failure if mail connection config is missing."""
    with pytest.raises(REANAEmailNotificationError):
        send_email(
            "sender@localhost",
            "test subject",
            "test body",
            "login",
            "notification@localhost",
        )
Пример #4
0
def _send_confirmation_email(confirm_token, user):
    """Compose and send sign-up confirmation email."""
    email_body = JinjaEnv.render_template(
        "emails/email_confirmation.txt",
        user_full_name=user.full_name,
        reana_hostname=REANA_HOSTNAME,
        ui_config=REANAConfig.load("ui"),
        sender_email=ADMIN_EMAIL,
        confirm_token=confirm_token,
    )
    send_email(user.email, "Confirm your REANA email address", email_body)
Пример #5
0
def token_revoke(admin_access_token, id_, email):
    """Revoke selected user's token."""
    try:
        admin = User.query.filter_by(id_=ADMIN_USER_ID).one_or_none()
        if admin_access_token != admin.access_token:
            raise ValueError("Admin access token invalid.")
        user = _get_user_by_criteria(id_, email)

        error_msg = None
        if not user:
            error_msg = f"User {id_ or email} does not exist."
        elif not user.access_token:
            error_msg = (f"User {user.id_} ({user.email}) does not have an"
                         " active access token.")
        if error_msg:
            click.secho(f"ERROR: {error_msg}", fg="red")
            sys.exit(1)

        revoked_token = user.access_token
        user.active_token.status = UserTokenStatus.revoked
        Session.commit()
        log_msg = (f"User token {revoked_token} ({user.email}) was"
                   " successfully revoked.")
        click.secho(log_msg, fg="green")
        admin.log_action(AuditLogAction.revoke_token, {"reana_admin": log_msg})
        # send notification to user by email
        email_subject = "REANA access token revoked"
        email_body = JinjaEnv.render_template(
            "emails/token_revoked.txt",
            user_full_name=user.full_name,
            reana_hostname=REANA_HOSTNAME,
            ui_config=REANAConfig.load("ui"),
            sender_email=ADMIN_EMAIL,
        )
        send_email(user.email, email_subject, email_body)

    except REANAEmailNotificationError as e:
        click.secho(
            "Something went wrong while sending email:\n{}".format(e),
            fg="red",
            err=True,
        )
    except Exception as e:
        click.secho(
            "Something went wrong while revoking token:\n{}".format(e),
            fg="red",
            err=True,
        )
Пример #6
0
def token_grant(admin_access_token, id_, email):
    """Grant a token to the selected user."""
    try:
        admin = User.query.filter_by(id_=ADMIN_USER_ID).one_or_none()
        if admin_access_token != admin.access_token:
            raise ValueError("Admin access token invalid.")
        user = _get_user_by_criteria(id_, email)
        error_msg = None
        if not user:
            error_msg = f"User {id_ or email} does not exist."
        elif user.access_token:
            error_msg = (f"User {user.id_} ({user.email}) has already an"
                         " active access token.")
        if error_msg:
            click.secho(f"ERROR: {error_msg}", fg="red")
            sys.exit(1)
        if user.access_token_status in [UserTokenStatus.revoked.name, None]:
            click.confirm(
                f"User {user.id_} ({user.email}) access token status"
                f" is {user.access_token_status}, do you want to"
                " proceed?",
                abort=True,
            )

        user_granted_token = secrets.token_urlsafe(16)
        user.access_token = user_granted_token
        Session.commit()
        log_msg = (f"Token for user {user.id_} ({user.email}) granted.\n"
                   f"\nToken: {user_granted_token}")
        click.secho(log_msg, fg="green")
        admin.log_action(AuditLogAction.grant_token, {"reana_admin": log_msg})
        # send notification to user by email
        email_subject = "REANA access token granted"
        email_body = (
            f"Dear {user.full_name},\n\nYour REANA access token has"
            f" been granted, please find it on https://{REANA_URL}/profile"
            "\n\nThe REANA support team")
        send_email(user.email, email_subject, email_body)

    except click.exceptions.Abort as e:
        click.echo("Grant token aborted.")
    except Exception as e:
        click.secho(
            "Something went wrong while granting token:\n{}".format(e),
            fg="red",
            err=True,
        )
Пример #7
0
def status_report(types, email, admin_access_token):
    """Retrieve a status report summary of the REANA system."""
    def _format_statuses(type_, statuses):
        """Format statuses dictionary object."""
        formatted_statuses = type_.upper() + "\n"
        for stat_name, stat_value in statuses.items():
            formatted_statuses += f"{stat_name}: {stat_value}\n"

        return formatted_statuses

    try:
        types = STATUS_OBJECT_TYPES.keys() if "all" in types else types
        status_report_output = ""
        for type_ in types:
            statuses_obj = STATUS_OBJECT_TYPES[type_]()
            statuses = statuses_obj.get_status()
            status_report_output += _format_statuses(type_, statuses) + "\n"

        if email:
            status_report_body = (
                f'Status report for {REANA_HOSTNAME or "REANA service"}\n'
                "---\n" + status_report_output)

            send_email(email, "REANA system status report", status_report_body)
            click.echo(f"Status report successfully sent by email to {email}.")
    except REANAEmailNotificationError as e:
        click.secho(
            "Something went wrong while sending email:\n{}".format(e),
            fg="red",
            err=True,
        )
    except Exception as e:
        click.secho(
            "Something went wrong while generating the status report:\n{}".
            format(e),
            fg="red",
            err=True,
        )
Пример #8
0
def token_revoke(admin_access_token, id_, email):
    """Revoke selected user's token."""
    try:
        admin = User.query.filter_by(id_=ADMIN_USER_ID).one_or_none()
        if admin_access_token != admin.access_token:
            raise ValueError("Admin access token invalid.")
        user = _get_user_by_criteria(id_, email)

        error_msg = None
        if not user:
            error_msg = f"User {id_ or email} does not exist."
        elif not user.access_token:
            error_msg = (f"User {user.id_} ({user.email}) does not have an"
                         " active access token.")
        if error_msg:
            click.secho(f"ERROR: {error_msg}", fg="red")
            sys.exit(1)

        revoked_token = user.access_token
        user.active_token.status = UserTokenStatus.revoked
        Session.commit()
        log_msg = (f"User token {revoked_token} ({user.email}) was"
                   " successfully revoked.")
        click.secho(log_msg, fg="green")
        admin.log_action(AuditLogAction.revoke_token, {"reana_admin": log_msg})
        # send notification to user by email
        email_subject = "REANA access token revoked"
        email_body = (f"Dear {user.full_name},\n\nYour REANA access token has"
                      " been revoked.\n\nThe REANA support team")
        send_email(user.email, email_subject, email_body)

    except Exception as e:
        click.secho(
            "Something went wrong while revoking token:\n{}".format(e),
            fg="red",
            err=True,
        )
Пример #9
0
def request_token(user):
    r"""Endpoint to request user access token.

    ---
    put:
      summary: Requests a new access token for the authenticated user.
      description: >-
        This resource allows the user to create an empty REANA access token
        and mark it as requested.
      operationId: request_token
      produces:
        - application/json
      parameters:
        - name: access_token
          in: query
          description: API access_token of user.
          required: false
          type: string
      responses:
        200:
          description: >-
            User information correspoding to the session cookie sent
            in the request.
          schema:
            type: object
            properties:
              reana_token:
                type: object
                properties:
                  status:
                    type: string
                  requested_at:
                    type: string
          examples:
            application/json:
              {
                "reana_token": {
                  "status": "requested",
                  "requested_at": "Mon, 25 May 2020 10:45:15 GMT"
                }
              }
        401:
          description: >-
            Error message indicating that the uses is not authenticated.
          schema:
            type: object
            properties:
              error:
                type: string
          examples:
            application/json:
              {
                "error": "User not logged in"
              }
        403:
          description: >-
            Request failed. User token not valid.
          examples:
            application/json:
              {
                "message": "Token is not valid."
              }
        500:
          description: >-
            Request failed. Internal server error.
          examples:
            application/json:
              {
                "message": "Internal server error."
              }
    """
    try:
        user.request_access_token()
        user.log_action(AuditLogAction.request_token)
        email_subject = f"[{REANA_HOSTNAME}] Token request ({user.email})"
        fields = [
            "id_",
            "email",
            "full_name",
            "username",
            "access_token",
            "access_token_status",
        ]
        user_data = "\n".join([f"{f}: {getattr(user, f, None)}" for f in fields])
        email_body = JinjaEnv.render_template(
            "emails/token_request.txt",
            user_data=user_data,
            user_email=user.email,
            reana_hostname=REANA_HOSTNAME,
        )
        try:
            send_email(ADMIN_EMAIL, email_subject, email_body)
        except REANAEmailNotificationError:
            logging.error(traceback.format_exc())

        return (
            jsonify(
                {
                    "reana_token": {
                        "status": user.access_token_status,
                        "requested_at": user.latest_access_token.created,
                    }
                }
            ),
            200,
        )

    except HTTPError as e:
        logging.error(traceback.format_exc())
        return jsonify(e.response.json()), e.response.status_code
    except ValueError as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 403
    except Exception as e:
        logging.error(traceback.format_exc())
        return jsonify({"message": str(e)}), 500
Пример #10
0
def status_report(types, email, admin_access_token):
    """Retrieve a status report summary of the REANA system."""
    def _print_row(data, column_widths):
        return (
            "  {email:<{email_width}} | {used:>{used_width}} | {limit:>{limit_width}} "
            "| {percentage:>{percentage_width}}\n".format(
                **data, **column_widths))

    def _format_quota_statuses(type_, statuses):
        formatted_statuses = type_.upper()
        for status_name, data in statuses.items():
            if not data:
                continue
            formatted_statuses += f"\n{status_name}:\n"
            columns = {
                "email": "EMAIL",
                "used": "USED",
                "limit": "LIMIT",
                "percentage": "PERCENTAGE",
            }
            column_widths = {
                "email_width": max([len(item["email"]) for item in data]),
                "used_width": max([len(item["used"]) for item in data]),
                "limit_width": max([len(item["limit"]) for item in data]),
                "percentage_width": len("percentage"),
            }
            formatted_statuses += _print_row(columns, column_widths)
            for row in data:
                formatted_statuses += _print_row(row, column_widths)

        return formatted_statuses

    def _format_statuses(type_, statuses):
        """Format statuses dictionary object."""
        if type_ == "quota-usage":
            return _format_quota_statuses(type_, statuses)
        formatted_statuses = type_.upper() + "\n"
        for stat_name, stat_value in statuses.items():
            formatted_statuses += f"{stat_name}: {stat_value}\n"

        return formatted_statuses

    try:
        types = STATUS_OBJECT_TYPES.keys() if "all" in types else types
        status_report_output = ""
        hostname = REANA_HOSTNAME or "REANA service"
        for type_ in types:
            statuses_obj = STATUS_OBJECT_TYPES[type_]()
            statuses = statuses_obj.get_status()
            status_report_output += _format_statuses(type_, statuses) + "\n"

        status_report_body = (
            f"Status report for {hostname}\n---\n{status_report_output}")

        if email:
            send_email(email, f"{hostname} system status report",
                       status_report_body)
            click.echo(f"Status report successfully sent by email to {email}.")
        else:
            click.echo(status_report_body)
    except REANAEmailNotificationError as e:
        click.secho(
            "Something went wrong while sending email:\n{}".format(e),
            fg="red",
            err=True,
        )
    except Exception as e:
        click.secho(
            "Something went wrong while generating the status report:\n{}".
            format(e),
            fg="red",
            err=True,
        )