def initialize_testing_environment(auth: BaseAuthentication) -> None: assert TESTING faker = get_faker() email = faker.ascii_email() password = faker.password(strong=True) default_group = auth.get_group(name=DEFAULT_GROUP_NAME) user = auth.create_user( { "email": email, "name": "Default", "surname": "User", "password": password, "last_password_change": datetime.now(pytz.utc), }, # It will be expanded with the default role roles=[], ) auth.add_user_to_group(user, default_group) # This is required to execute the commit on sqlalchemy... auth.save_user(user) for _ in range(0, 20): payload, full_payload = auth.fill_payload(user) token = auth.create_token(payload) auth.save_token(user, token, full_payload)
def is_session_user_admin(request: FlaskRequest, auth: BaseAuthentication) -> bool: if not request: return False _, token = HTTPTokenAuth.get_authorization_token( allow_access_token_parameter=False) _, _, _, user = auth.verify_token(token) return auth.is_admin(user)
def verify_token_is_not_valid(auth: BaseAuthentication, token: str, ttype: Optional[str] = None) -> None: unpacked_token = auth.verify_token(token, token_type=ttype) assert not unpacked_token[0] assert unpacked_token[1] is None assert unpacked_token[2] is None assert unpacked_token[3] is None with pytest.raises(Exception): auth.verify_token(token, token_type=ttype, raiseErrors=True)
def do_login(self, client, USER, PWD, status_code=hcodes.HTTP_OK_BASIC, error=None, **kwargs): """ Make login and return both token and authorization header """ if USER is None or PWD is None: BaseAuthentication.myinit() if USER is None: USER = BaseAuthentication.default_user if PWD is None: PWD = BaseAuthentication.default_password # AUTH_MAX_LOGIN_ATTEMPTS=0 # AUTH_REGISTER_FAILED_LOGIN=False # AUTH_SECOND_FACTOR_AUTHENTICATION=None # AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER=0 # AUTH_MAX_PASSWORD_VALIDITY=0 data = {'username': USER, 'password': PWD} for v in kwargs: data[v] = kwargs[v] r = client.post(AUTH_URI + '/login', data=json.dumps(data)) if r.status_code != hcodes.HTTP_OK_BASIC: # VERY IMPORTANT FOR DEBUGGING WHEN ADVANCED AUTH OPTIONS ARE ON c = json.loads(r.data.decode('utf-8')) log.error(c['Response']['errors']) assert r.status_code == status_code content = json.loads(r.data.decode('utf-8')) if error is not None: errors = content['Response']['errors'] if errors is not None: assert errors[0] == error token = '' if content is not None: data = content.get('Response', {}).get('data', {}) if data is not None: token = data.get('token', '') return {'Authorization': 'Bearer ' + token}, token
def verify_token_is_not_valid(auth: BaseAuthentication, token: str, ttype: Optional[str] = None) -> None: unpacked_token = auth.verify_token(token, token_type=ttype) assert not unpacked_token[0] assert unpacked_token[1] is None assert unpacked_token[2] is None assert unpacked_token[3] is None try: auth.verify_token(token, token_type=ttype, raiseErrors=True) pytest.fail("No exception raised") # pragma: no cover except BaseException: pass
def setUp(self): """ Note: in this base tests, I also want to check if i can run multiple Flask applications. Thi is why i prefer setUp on setUpClass """ log.debug('### Setting up the Flask server ###') app = create_app(testing_mode=True) self.app = app.test_client() # Auth init from base/custom config ba.myinit() self._username = ba.default_user self._password = ba.default_password
def change_password(self, user, password, new_password, password_confirm): if new_password != password_confirm: msg = "Your password doesn't match the confirmation" raise RestApiException(msg, status_code=hcodes.HTTP_BAD_CONFLICT) if self.auth.VERIFY_PASSWORD_STRENGTH: check = True if password is not None: check, msg = self.verify_password_strength( new_password, old_pwd=password ) else: check, msg = self.verify_password_strength( new_password, old_hash=user.password ) if not check: raise RestApiException(msg, status_code=hcodes.HTTP_BAD_CONFLICT) if new_password is not None and password_confirm is not None: now = datetime.now(pytz.utc) user.password = BaseAuthentication.get_password_hash(new_password) user.last_password_change = now self.auth.save_user(user) tokens = self.auth.get_tokens(user=user) for token in tokens: self.auth.invalidate_token(token=token["token"]) # changes the user uuid invalidating all tokens self.auth.invalidate_all_tokens() return True
def verify_password_strength(self, pwd, old_pwd=None, old_hash=None): if old_pwd is not None and pwd == old_pwd: return False, "The new password cannot match the previous password" if old_hash is not None: new_hash = BaseAuthentication.get_password_hash(pwd) if old_hash == new_hash: return False, "The new password cannot match the previous password" # FIXME: min length should configurable? if len(pwd) < 8: return False, "Password is too short, use at least 8 characters" if not re.search("[a-z]", pwd): return False, "Password is too weak, missing lower case letters" if not re.search("[A-Z]", pwd): return False, "Password is too weak, missing upper case letters" if not re.search("[0-9]", pwd): return False, "Password is too weak, missing numbers" # special_characters = "['\s!#$%&\"(),*+,-./:;<=>?@[\\]^_`{|}~']" special_characters = "[^a-zA-Z0-9]" if not re.search(special_characters, pwd): return False, "Password is too weak, missing special characters" return True, None
def verify_token_is_valid(auth: BaseAuthentication, token: str, ttype: Optional[str] = None) -> None: unpacked_token = auth.verify_token(token, token_type=ttype) assert unpacked_token[0] assert unpacked_token[1] is not None assert unpacked_token[2] is not None assert unpacked_token[3] is not None
def handle_response(response): response.headers["_RV"] = str(version) PROJECT_VERSION = get_project_configuration("project.version", default=None) if PROJECT_VERSION is not None: response.headers["Version"] = str(PROJECT_VERSION) # If it is an upload, DO NOT consume request.data or request.json, # otherwise the content gets lost try: if request.mimetype in ["application/octet-stream", "multipart/form-data"]: data = "STREAM_UPLOAD" elif request.data: data = handle_log_output(request.data) elif request.form: data = obfuscate_dict(request.form) else: data = "" if data: data = f" {data}" except Exception as e: # pragma: no cover log.debug(e) data = "" url = obfuscate_query_parameters(request.url) if GZIP_ENABLE and "gzip" in request.headers.get("Accept-Encoding", "").lower(): response.direct_passthrough = False content, headers = ResponseMaker.gzip_response( response.data, response.status_code, response.headers.get("Content-Encoding"), response.headers.get("Content-Type"), ) if content: response.data = content try: response.headers.update(headers) # Back-compatibility for Werkzeug 0.16.1 as used in B2STAGE except AttributeError: # pragma: no cover for k, v in headers.items(): response.headers.set(k, v) resp = str(response).replace("<Response ", "").replace(">", "") log.info( "{} {} {}{} -> {}", BaseAuthentication.get_remote_ip(), request.method, url, data, resp, ) return response
def prepare_message(instance, user=None, isjson=False, **params): """ { # start "request_id": # build a hash for the current request "edmo_code": # which eudat centre is "json": "the json input file" # parameters maris "datetime":"20180328T10:08:30", # timestamp "ip_number":"544.544.544.544", # request.remote_addr "program":"program/function name", # what's mine? "url" ? "user":"******", # from maris? marine id? "log_string":"start" } { # end "datetime":"20180328T10:08:30", "request_id":"from the json input file", "ip_number":"544.544.544.544", "program":"program of function = name", "user":"******", "edmo_code":"sample 353", "log_string":"end" } """ logmsg = dict(params) instance_id = str(id(instance)) logmsg['request_id'] = instance_id # logmsg['request_id'] = instance_id[len(instance_id) - 6:] from seadata.apis.commons.seadatacloud import seadata_vars logmsg['edmo_code'] = seadata_vars.get('edmo_code') from datetime import datetime logmsg['datetime'] = datetime.now().strftime("%Y%m%dT%H:%M:%S") if isjson: return logmsg # TODO Why this? Why does isjson exist at all? from restapi.services.authentication import BaseAuthentication as Service ip = Service.get_remote_ip() logmsg['ip_number'] = ip from flask import request # http://localhost:8080/api/pids/<PID> import re endpoint = re.sub(r"https?://[^\/]+", '', request.url) logmsg['program'] = request.method + ':' + endpoint if user is None: user = '******' # TODO: True? Not sure! logmsg['user'] = user # log.pp(logmsg) return logmsg
def handle_response(response: FlaskResponse) -> FlaskResponse: response.headers["_RV"] = str(version) PROJECT_VERSION = get_project_configuration("project.version", default="0") if PROJECT_VERSION is not None: response.headers["Version"] = str(PROJECT_VERSION) data_string = get_data_from_request() url = obfuscate_query_parameters(request.url) if (GZIP_ENABLE and not response.is_streamed and "gzip" in request.headers.get("Accept-Encoding", "").lower()): response.direct_passthrough = False content, headers = ResponseMaker.gzip_response( response.data, response.status_code, response.headers.get("Content-Encoding"), response.headers.get("Content-Type"), ) if content: response.data = content response.headers.update(headers) resp = str(response).replace("<Response ", "").replace(">", "") ip = BaseAuthentication.get_remote_ip(raise_warnings=False) is_healthcheck = (ip == "127.0.0.1" and request.method == "GET" and url == "/api/status") if is_healthcheck: log.debug( "{} {} {}{} -> {} [HEALTHCHECK]", ip, request.method, url, data_string, resp, ) else: log.info( "{} {} {}{} -> {}", ip, request.method, url, data_string, resp, ) return response
def log_event( self, event: Events, target: Optional[Any] = None, payload: Optional[Dict[str, Any]] = None, user: Optional[Any] = None, ) -> None: if not user: user = self.get_user() save_event_log( event=event, target=target, payload=payload, user=user, ip=BaseAuthentication.get_remote_ip(), )
def log_event( self, event: Events, target: Optional[Any] = None, payload: Optional[Dict[str, Any]] = None, user: Optional[Any] = None, ) -> None: if not user: user = self.auth.get_user(user_id=self.authorized_user) save_event_log( event=event, target=target, payload=payload, user=user, ip=BaseAuthentication.get_remote_ip(), url=request.path, )
def put(self, user_id: str, target_user: User, user: User, **kwargs: Any) -> Response: if "password" in kwargs: unhashed_password = kwargs["password"] kwargs["password"] = BaseAuthentication.get_password_hash( kwargs["password"]) else: unhashed_password = None payload = kwargs.copy() roles: List[str] = kwargs.pop("roles", []) # The role is already refused by webards... This is an additional check # to improve the security, but can't be reached if not self.auth.is_admin( user) and Role.ADMIN in roles: # pragma: no cover raise Forbidden("This role is not allowed") group_id = kwargs.pop("group", None) email_notification = kwargs.pop("email_notification", False) self.auth.link_roles(target_user, roles) userdata, extra_userdata = self.auth.custom_user_properties_pre(kwargs) prev_expiration = target_user.expiration # mypy correctly raises errors because update_properties is not defined # in generic Connector instances, but in this case this is an instance # of an auth db and their implementation always contains this method self.auth.db.update_properties(target_user, userdata) # type: ignore self.auth.custom_user_properties_post(target_user, userdata, extra_userdata, self.auth.db) self.auth.save_user(target_user) if group_id is not None: group = self.auth.get_group(group_id=group_id) if not group: # Can't be reached because group_id is prefiltered by marshmallow raise NotFound( "This group cannot be found") # pragma: no cover self.auth.add_user_to_group(target_user, group) if email_notification and unhashed_password is not None: notify_update_credentials_to_user(target_user, unhashed_password) if target_user.expiration: # Set expiration on a previously non-expiring account # or update the expiration by reducing the validity period # In both cases tokens should be invalited to prevent to have tokens # with TTL > account validity # dt_lower (alias for date_lower_than) is a comparison fn that ignores tz if not prev_expiration or dt_lower(target_user.expiration, prev_expiration): for token in self.auth.get_tokens(user=target_user): # Invalidate all tokens with expiration after the account expiration if dt_lower(target_user.expiration, token["expiration"]): self.auth.invalidate_token(token=token["token"]) self.log_event(self.events.modify, target_user, payload) return self.empty_response()
def put(self, user_id: str, **kwargs: Any) -> Response: user = self.auth.get_user(user_id=user_id) if user is None: raise NotFound( "This user cannot be found or you are not authorized") if "password" in kwargs: unhashed_password = kwargs["password"] kwargs["password"] = BaseAuthentication.get_password_hash( kwargs["password"]) else: unhashed_password = None payload = kwargs.copy() roles: List[str] = kwargs.pop("roles", []) group_id = kwargs.pop("group", None) email_notification = kwargs.pop("email_notification", False) self.auth.link_roles(user, roles) userdata, extra_userdata = self.auth.custom_user_properties_pre(kwargs) prev_expiration = user.expiration self.auth.db.update_properties(user, userdata) self.auth.custom_user_properties_post(user, userdata, extra_userdata, self.auth.db) self.auth.save_user(user) if group_id is not None: group = self.auth.get_group(group_id=group_id) if not group: # Can't be reached because grup_id is prefiltered by marshmallow raise NotFound( "This group cannot be found") # pragma: no cover self.auth.add_user_to_group(user, group) if email_notification and unhashed_password is not None: smtp_client = smtp.get_instance() send_notification(smtp_client, user, unhashed_password, is_update=True) if user.expiration: # Set expiration on a previously non-expiring account # or update the expiration by reducing the validity period # In both cases tokens should be invalited to prevent to have tokens # with TTL > account validity # dt_lower (alias for date_lower_than) is a comparison fn that ignores tz if prev_expiration is None or dt_lower(user.expiration, prev_expiration): for token in self.auth.get_tokens(user=user): # Invalidate all tokens with expiration after the account expiration if dt_lower(user.expiration, token["expiration"]): self.auth.invalidate_token(token=token["token"]) self.log_event(self.events.modify, user, payload) return self.empty_response()
def put(self, user_id=None): if user_id is None: raise RestApiException( "Please specify a user id", status_code=hcodes.HTTP_BAD_REQUEST) if not detector.check_availability('neo4j'): log.warning("This endpoint is implemented only for neo4j") return self.empty_response() schema = self.get_endpoint_custom_definition() self.graph = self.get_service_instance('neo4j') is_admin = self.auth.verify_admin() is_group_admin = self.auth.verify_group_admin() if not is_admin and not is_group_admin: raise RestApiException( "You are not authorized: missing privileges", status_code=hcodes.HTTP_BAD_UNAUTHORIZED) v = self.get_input() user = self.graph.User.nodes.get_or_none(uuid=user_id) # user = self.getNode(self.graph.User, user_id, field='uuid') if user is None: raise RestApiException( "This user cannot be found or you are not authorized") current_user = self.get_current_user() is_authorized = self.check_permissions( current_user, user, is_admin, is_group_admin ) if not is_authorized: raise RestApiException( "This user cannot be found or you are not authorized") unhashed_password = None if "password" in v and v["password"] == "": del v["password"] else: v["password"] = BaseAuthentication.hash_password(v["password"]) if "email" in v: v["email"] = v["email"].lower() self.update_properties(user, schema, v) user.save() if 'group' in v: group = self.parse_group(v) if not is_admin: if not group.coordinator.is_connected(current_user): raise RestApiException( "You are allowed to assign users to this group") p = None for p in user.belongs_to.all(): if p == group: continue if p is not None: user.belongs_to.reconnect(p, group) else: user.belongs_to.connect(group) roles = self.parse_roles(v) if not is_admin: allowed_roles = mem.customizer._configurations \ .get('variables', {}) \ .get('backend', {}) \ .get('allowed_roles', []) for r in roles: if r not in allowed_roles: raise RestApiException( "You are allowed to assign users to this role") self.auth.link_roles(user, roles) email_notification = v.get('email_notification', False) if email_notification and unhashed_password is not None: self.send_notification(user, unhashed_password, is_update=True) return self.empty_response()
def test_authentication_abstract_methods(faker: Faker) -> None: # Super trick! # https://clamytoe.github.io/articles/2020/Mar/12/testing-abcs-with-abstract-methods-with-pytest abstractmethods = BaseAuthentication.__abstractmethods__ BaseAuthentication.__abstractmethods__ = frozenset() auth = Connector.get_authentication_instance() user = auth.get_user(username=BaseAuthentication.default_user) group = auth.get_group(name=DEFAULT_GROUP_NAME) role = auth.get_roles()[0] auth = BaseAuthentication() # type: ignore assert (auth.get_user(username=faker.ascii_email(), user_id=faker.pystr()) is None) assert auth.get_users() is None assert auth.save_user(user=user) is None assert auth.delete_user(user=user) is None assert auth.get_group(group_id=faker.pystr(), name=faker.pystr()) is None assert auth.get_groups() is None assert auth.get_user_group(user=user) is None assert auth.get_group_members(group=group) is None assert auth.save_group(group=group) is None assert auth.delete_group(group=group) is None assert auth.get_tokens( user=user, token_jti=faker.pystr(), get_all=True) is None assert auth.verify_token_validity(jti=faker.pystr(), user=user) is None assert (auth.save_token(user=user, token=faker.pystr(), payload={}, token_type=faker.pystr()) is None) assert auth.invalidate_token(token=faker.pystr()) is None assert auth.get_roles() is None assert auth.get_roles_from_user(user=user) is None assert auth.create_role(name=faker.pystr(), description=faker.pystr()) is None assert auth.save_role(role=role) is None assert auth.create_user(userdata={}, roles=[faker.pystr()]) is None assert auth.link_roles(user=user, roles=[faker.pystr()]) is None assert auth.create_group(groupdata={}) is None assert auth.add_user_to_group(user=user, group=group) is None assert (auth.save_login( username=faker.ascii_email(), user=user, failed=True) is None) assert (auth.save_login( username=faker.ascii_email(), user=None, failed=True) is None) assert (auth.save_login( username=faker.ascii_email(), user=user, failed=False) is None) assert (auth.save_login( username=faker.ascii_email(), user=None, failed=False) is None) assert auth.get_logins(username=faker.ascii_email) is None assert auth.flush_failed_logins(username=faker.ascii_email) is None BaseAuthentication.__abstractmethods__ = abstractmethods
def put(self, user_id=None): if user_id is None: raise RestApiException("Please specify a user id", status_code=hcodes.HTTP_BAD_REQUEST) schema = self.get_endpoint_custom_definition() if self.neo4j_enabled: self.graph = self.get_service_instance('neo4j') is_admin = self.auth.verify_admin() is_local_admin = self.auth.verify_local_admin() if not is_admin and not is_local_admin: raise RestApiException( "You are not authorized: missing privileges", status_code=hcodes.HTTP_BAD_UNAUTHORIZED, ) v = self.get_input() user = self.auth.get_users(user_id) if user is None: raise RestApiException( "This user cannot be found or you are not authorized") user = user[0] current_user = self.get_current_user() is_authorized = self.check_permissions(current_user, user, is_admin, is_local_admin) if not is_authorized: raise RestApiException( "This user cannot be found or you are not authorized") if "password" in v and v["password"] == "": del v["password"] if "password" in v: unhashed_password = v["password"] v["password"] = BaseAuthentication.get_password_hash(v["password"]) else: unhashed_password = None if "email" in v: v["email"] = v["email"].lower() roles = self.parse_roles(v) if not is_admin: allowed_roles = get_project_configuration( "variables.backend.allowed_roles", default=[], ) for r in roles: if r not in allowed_roles: raise RestApiException( "You are not allowed to assign users to this role") self.auth.link_roles(user, roles) # Cannot update email address (unique username used to login-in) v.pop('email', None) if self.neo4j_enabled: self.update_properties(user, schema, v) elif self.sql_enabled: self.update_sql_properties(user, schema, v) elif self.mongo_enabled: self.update_mongo_properties(user, schema, v) else: raise RestApiException( "Invalid auth backend, all known db are disabled") self.auth.save_user(user) # FIXME: groups management is only implemented for neo4j if 'group' in v: group = self.parse_group(v) if not is_admin and group.shortname != "default": if not group.coordinator.is_connected(current_user): raise RestApiException( "You are not allowed to assign users to this group") p = None for p in user.belongs_to.all(): if p == group: continue if p is not None: user.belongs_to.reconnect(p, group) else: user.belongs_to.connect(group) email_notification = v.get('email_notification', False) if email_notification and unhashed_password is not None: self.send_notification(user, unhashed_password, is_update=True) return self.empty_response()
assert r.status_code == 200 assert content is not None headers: CaseInsensitiveDict[str] = CaseInsensitiveDict() headers["Authorization"] = f"Bearer {content}" return content, headers # No need to restore the logger after this test because # schemathesis test is the last one! # (just because in alphabetic order there are no other tests) set_logger("WARNING") app = create_app() client = werkzeug.Client(app, werkzeug.wrappers.Response) if Env.get_bool("AUTH_ENABLE"): BaseAuthentication.load_default_user() BaseAuthentication.load_roles() USER = BaseAuthentication.default_user PWD = BaseAuthentication.default_password data = {"username": USER, "password": PWD} token, auth_header = get_auth_token(client, data) # it does not handle custom headers => the endpoint will provide partial schema # due to missing authentication => skipping all private endpoints and schemas # schema = schemathesis.from_wsgi('/api/specs', app) r = client.get(f"/api/specs?access_token={token}") else: r = client.get("/api/specs") assert r.status_code == 200 schema = orjson.loads(r.get_data().decode())
def do_login( cls, client: FlaskClient, USER: Optional[str], PWD: Optional[str], status_code: int = 200, data: Optional[Dict[str, Any]] = None, test_failures: bool = False, ) -> Tuple[Optional[Dict[str, str]], str]: """ Make login and return both token and authorization header """ if USER is None or PWD is None: BaseAuthentication.load_default_user() BaseAuthentication.load_roles() if USER is None: USER = BaseAuthentication.default_user if PWD is None: PWD = BaseAuthentication.default_password assert USER is not None assert PWD is not None if data is None: data = {} data["username"] = USER data["password"] = PWD r = client.post(f"{AUTH_URI}/login", data=data) content = json.loads(r.data.decode("utf-8")) if r.status_code == 403: # This 403 is expected, return an invalid value or you can enter a loop! if status_code == 403: return None, content if isinstance(content, dict) and content.get("actions"): actions = content.get("actions", []) for action in actions: if action == "TOTP": continue if action == "FIRST LOGIN": continue if action == "PASSWORD EXPIRED": continue data = {} if "FIRST LOGIN" in actions or "PASSWORD EXPIRED" in actions: newpwd = cls.faker.password(strong=True) if test_failures: data["new_password"] = newpwd data["password_confirm"] = cls.faker.password( strong=True) if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"): data["totp_code"] = BaseTests.generate_totp(USER) BaseTests.do_login( client, USER, PWD, data=data, status_code=409, ) # Test failure of password change if TOTP is wrong or missing if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"): data["new_password"] = newpwd data["password_confirm"] = newpwd data.pop("totp_code", None) BaseTests.do_login( client, USER, PWD, data=data, status_code=403, ) data["new_password"] = newpwd data["password_confirm"] = newpwd # random int with 6 digits data["totp_code"] = cls.faker.pyint( min_value=100000, max_value=999999) BaseTests.do_login( client, USER, PWD, data=data, status_code=401, ) # Change the password to silence FIRST_LOGIN and PASSWORD_EXPIRED data["new_password"] = newpwd data["password_confirm"] = newpwd if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"): data["totp_code"] = BaseTests.generate_totp(USER) BaseTests.do_login( client, USER, PWD, data=data, ) # Change again to restore the default password # and keep all other tests fully working data["new_password"] = PWD data["password_confirm"] = PWD if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"): data["totp_code"] = BaseTests.generate_totp(USER) return BaseTests.do_login( client, USER, newpwd, data=data, ) # in this case FIRST LOGIN has not been executed # => login by sending the TOTP code if "TOTP" in actions: # Only directly tested => no coverage if test_failures: # pragma: no cover # random int with 6 digits data["totp_code"] = cls.faker.pyint(min_value=100000, max_value=999999) BaseTests.do_login( client, USER, PWD, data=data, status_code=401, ) data["totp_code"] = BaseTests.generate_totp(USER) return BaseTests.do_login( client, USER, PWD, data=data, ) # FOR DEBUGGING WHEN ADVANCED AUTH OPTIONS ARE ON # if r.status_code != 200: # c = json.loads(r.data.decode("utf-8")) # log.error(c) assert r.status_code == status_code # when 200 OK content is the token assert content is not None return {"Authorization": f"Bearer {content}"}, content