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}.")
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, )
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", )
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)
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, )
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, )
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, )
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, )
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
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, )