From ade7c77754888d0870fa654df56bafbcc70324d8 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 17 Jan 2022 13:28:17 +0200 Subject: [PATCH] Fix bugs --- .../migrations/create_tokens_json.py | 2 +- .../resources/api_auth/app_tokens.py | 30 +++++---- .../resources/api_auth/new_device.py | 24 +++---- .../resources/api_auth/recovery_token.py | 67 ++++++++++--------- selfprivacy_api/utils/auth.py | 26 +++++-- tests/test_common.py | 5 ++ 6 files changed, 90 insertions(+), 64 deletions(-) diff --git a/selfprivacy_api/migrations/create_tokens_json.py b/selfprivacy_api/migrations/create_tokens_json.py index 7404301..3198c9c 100644 --- a/selfprivacy_api/migrations/create_tokens_json.py +++ b/selfprivacy_api/migrations/create_tokens_json.py @@ -45,7 +45,7 @@ class CreateTokensJson(Migration): "tokens": [ { "token": token, - "name": "Master Token", + "name": "primary_token", "date": str(datetime.now()), } ] diff --git a/selfprivacy_api/resources/api_auth/app_tokens.py b/selfprivacy_api/resources/api_auth/app_tokens.py index 29d7a27..36db1ab 100644 --- a/selfprivacy_api/resources/api_auth/app_tokens.py +++ b/selfprivacy_api/resources/api_auth/app_tokens.py @@ -8,6 +8,8 @@ from selfprivacy_api.utils.auth import ( delete_token, get_tokens_info, delete_token, + is_token_name_exists, + is_token_name_pair_valid, refresh_token, is_token_valid, ) @@ -46,15 +48,15 @@ class Tokens(Resource): - bearerAuth: [] parameters: - in: body - name: token - required: true - description: Token to delete - schema: - type: object - properties: - token: - type: string - description: Token to delete + name: token + required: true + description: Token's name to delete + schema: + type: object + properties: + token: + type: string + description: Token name to delete responses: 200: description: Token deleted @@ -64,14 +66,14 @@ class Tokens(Resource): description: Token not found """ parser = reqparse.RequestParser() - parser.add_argument("token", type=str, required=True, help="Token to delete") + parser.add_argument("token_name", type=str, required=True, help="Token to delete") args = parser.parse_args() - token = args["token"] - if request.headers.get("Authorization") == f"Bearer {token}": + token_name = args["token"] + if is_token_name_pair_valid(token_name, request.headers.get("Authorization").split(" ")[1]): return {"message": "Cannot delete caller's token"}, 400 - if not is_token_valid(token): + if not is_token_name_exists(token_name): return {"message": "Token not found"}, 404 - delete_token(token) + delete_token(token_name) return {"message": "Token deleted"}, 200 def post(self): diff --git a/selfprivacy_api/resources/api_auth/new_device.py b/selfprivacy_api/resources/api_auth/new_device.py index ba64639..61195c2 100644 --- a/selfprivacy_api/resources/api_auth/new_device.py +++ b/selfprivacy_api/resources/api_auth/new_device.py @@ -45,18 +45,18 @@ class AuthorizeDevice(Resource): - Tokens parameters: - in: body - name: data - required: true - description: Who is authorizing - schema: - type: object - properties: - token: - type: string - description: Mnemonic token to authorize - device: - type: string - description: Device to authorize + name: data + required: true + description: Who is authorizing + schema: + type: object + properties: + token: + type: string + description: Mnemonic token to authorize + device: + type: string + description: Device to authorize responses: 200: description: Device authorized diff --git a/selfprivacy_api/resources/api_auth/recovery_token.py b/selfprivacy_api/resources/api_auth/recovery_token.py index 0d84cda..a9e6e96 100644 --- a/selfprivacy_api/resources/api_auth/recovery_token.py +++ b/selfprivacy_api/resources/api_auth/recovery_token.py @@ -87,18 +87,18 @@ class RecoveryToken(Resource): - bearerAuth: [] parameters: - in: body - name: data - required: true - description: Token data - schema: - type: object - properties: - expiration: - type: string - description: Token expiration date - uses: - type: integer - description: Token uses + name: data + required: true + description: Token data + schema: + type: object + properties: + expiration: + type: string + description: Token expiration date + uses: + type: integer + description: Token uses responses: 200: description: Recovery token generated @@ -113,17 +113,20 @@ class RecoveryToken(Resource): """ parser = reqparse.RequestParser() parser.add_argument( - "expiration", type=str, required=True, help="Token expiration date" + "expiration", type=str, required=False, help="Token expiration date" ) - parser.add_argument("uses", type=int, required=True, help="Token uses") + parser.add_argument("uses", type=int, required=False, help="Token uses") args = parser.parse_args() # Convert expiration date to datetime and return 400 if it is not valid - try: - expiration = datetime.strptime(args["expiration"], "%Y-%m-%dT%H:%M:%S.%fZ") - except ValueError: - return { - "error": "Invalid expiration date. Use YYYY-MM-DDTHH:MM:SS.SSSZ" - }, 400 + if args["expiration"]: + try: + expiration = datetime.strptime(args["expiration"], "%Y-%m-%dT%H:%M:%S.%fZ") + except ValueError: + return { + "error": "Invalid expiration date. Use YYYY-MM-DDTHH:MM:SS.SSSZ" + }, 400 + else: + expiration = None # Generate recovery token token = generate_recovery_token(expiration, args["uses"]) return {"token": token} @@ -142,18 +145,18 @@ class UseRecoveryToken(Resource): - Tokens parameters: - in: body - name: data - required: true - description: Token data - schema: - type: object - properties: - token: - type: string - description: Mnemonic recovery token - device: - type: string - description: Device to authorize + name: data + required: true + description: Token data + schema: + type: object + properties: + token: + type: string + description: Mnemonic recovery token + device: + type: string + description: Device to authorize responses: 200: description: Recovery token used diff --git a/selfprivacy_api/utils/auth.py b/selfprivacy_api/utils/auth.py index b3d7ae7..65d2125 100644 --- a/selfprivacy_api/utils/auth.py +++ b/selfprivacy_api/utils/auth.py @@ -70,6 +70,18 @@ def is_token_valid(token): return True return False +def is_token_name_exists(token_name): + """Check if token name exists""" + with ReadUserData(UserDataFiles.TOKENS) as tokens: + return token_name in [t["name"] for t in tokens["tokens"]] + +def is_token_name_pair_valid(token_name, token): + """Check if token name and token pair exists""" + with ReadUserData(UserDataFiles.TOKENS) as tokens: + for t in tokens["tokens"]: + if t["name"] == token_name and t["token"] == token: + return True + return False def get_tokens_info(): """Get all tokens info without tokens themselves""" @@ -90,21 +102,22 @@ def _generate_token(): def create_token(name): """Create new token""" token = _generate_token() + name = _validate_token_name(name) with WriteUserData(UserDataFiles.TOKENS) as tokens: tokens["tokens"].append( { "token": token, - "name": _validate_token_name(name), + "name": name, "date": str(datetime.now()), } ) return token -def delete_token(token): +def delete_token(token_name): """Delete token""" with WriteUserData(UserDataFiles.TOKENS) as tokens: - tokens["tokens"] = [t for t in tokens["tokens"] if t["token"] != token] + tokens["tokens"] = [t for t in tokens["tokens"] if t["name"] != token_name] def refresh_token(token): @@ -198,6 +211,8 @@ def use_mnemonic_recoverery_token(mnemonic_phrase, name): Substract 1 from uses_left if it exists. mnemonic_phrase is a string representation of the mnemonic word list. """ + if not is_recovery_token_valid(): + return None recovery_token_str = _get_recovery_token() if recovery_token_str is None: return None @@ -208,11 +223,12 @@ def use_mnemonic_recoverery_token(mnemonic_phrase, name): if phrase_bytes != recovery_token: return None token = _generate_token() + name = _validate_token_name(name) with WriteUserData(UserDataFiles.TOKENS) as tokens: tokens["tokens"].append( { "token": token, - "name": _validate_token_name(name), + "name": name, "date": str(datetime.now()), } ) @@ -226,7 +242,7 @@ def get_new_device_auth_token(): """Generate a new device auth token which is valid for 10 minutes and return a mnemonic phrase representation Write token to the new_device of the tokens.json file. """ - token = secrets.token_bytes(24) + token = secrets.token_bytes(16) token_str = token.hex() with WriteUserData(UserDataFiles.TOKENS) as tokens: tokens["new_device"] = { diff --git a/tests/test_common.py b/tests/test_common.py index f8aa36b..c473956 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -14,3 +14,8 @@ def test_get_api_version_unauthorized(client): response = client.get("/api/version") assert response.status_code == 200 assert "version" in response.get_json() + +def test_get_swagger_json(authorized_client): + response = authorized_client.get("/api/swagger.json") + assert response.status_code == 200 + assert "swagger" in response.get_json()