From f0c2df2d50385831ad64fd021b5ce20c42ddbb3a Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Thu, 10 Feb 2022 14:56:08 +0100 Subject: [PATCH 01/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f048eefc..6f70b9f8 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ index.search( ## 🤖 Compatibility with Meilisearch -This package only guarantees the compatibility with the [version v0.25.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.25.0). +This package only guarantees the compatibility with the [version v0.26.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.26.0). ## 💡 Learn More From 06330e4d58bece454c044cdc2bd30960cff87a5e Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Tue, 15 Feb 2022 18:50:01 +0100 Subject: [PATCH 02/21] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40a93887..6bbb13c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ Each PR should pass the tests, mypy type checking, and the linter to be accepted ```bash # Tests curl -L https://install.meilisearch.com | sh # download Meilisearch -./meilisearch --master-key=masterKey --no-analytics=true # run Meilisearch +./meilisearch --master-key=masterKey --no-analytics # run Meilisearch pipenv run pytest meilisearch # MyPy pipenv run mypy meilisearch @@ -51,7 +51,7 @@ Optionally tox can be used to run test on all supported version of Python, mypy, ```bash docker pull getmeili/meilisearch:latest # Fetch the latest version of Meilisearch image from Docker Hub -docker run -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics=true +docker run -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics pipenv run tox ``` From 3bdbdc66c3ca3eeca92a71727a9bf334d40169b3 Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Tue, 15 Feb 2022 18:50:02 +0100 Subject: [PATCH 03/21] Update .github/workflows/tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d785c73..4833c362 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: - name: Install dependencies run: pipenv install --dev - name: Meilisearch (latest version) setup with Docker - run: docker run -d -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --no-analytics=true --master-key=masterKey + run: docker run -d -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --no-analytics --master-key=masterKey - name: Test with pytest run: pipenv run pytest From 66fa5f8008364dad311805c40b8ef17159f54750 Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Tue, 15 Feb 2022 18:50:02 +0100 Subject: [PATCH 04/21] Update .github/workflows/pre-release-tests.yml --- .github/workflows/pre-release-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-release-tests.yml b/.github/workflows/pre-release-tests.yml index deb858d7..fc4c8900 100644 --- a/.github/workflows/pre-release-tests.yml +++ b/.github/workflows/pre-release-tests.yml @@ -29,6 +29,6 @@ jobs: - name: Get the latest Meilisearch RC run: echo "MEILISEARCH_VERSION=$(curl https://raw.githubusercontent.com/meilisearch/integration-guides/main/scripts/get-latest-meilisearch-rc.sh | bash)" >> $GITHUB_ENV - name: Meilisearch (${{ env.MEILISEARCH_VERSION }}) setup with Docker - run: docker run -d -p 7700:7700 getmeili/meilisearch:${{ env.MEILISEARCH_VERSION }} ./meilisearch --master-key=masterKey --no-analytics=true + run: docker run -d -p 7700:7700 getmeili/meilisearch:${{ env.MEILISEARCH_VERSION }} ./meilisearch --master-key=masterKey --no-analytics - name: Test with pytest run: pipenv run pytest From 36e21614d89a8e3423da762e4486466fba609fb1 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 16 Feb 2022 18:04:11 +0100 Subject: [PATCH 05/21] Adding generate_tenant_token method --- meilisearch/client.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/meilisearch/client.py b/meilisearch/client.py index 845d2ad2..d961b666 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -5,6 +5,10 @@ from meilisearch.task import get_task, get_tasks, wait_for_task from meilisearch._httprequests import HttpRequests from meilisearch.errors import MeiliSearchError +import base64 +import hashlib +import hmac +import json class Client(): """ @@ -464,3 +468,34 @@ def wait_for_task( An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors """ return wait_for_task(self.config, uid, timeout_in_ms, interval_in_ms) + + def base64url_encode( + self, + input: bytes + ) -> str: + return base64.urlsafe_b64encode(input).decode('utf-8').replace('=','') + + def generate_tenant_token( + self, + parentApiKey: str, + payload: str + ) -> str: + + header = { + "typ": "JWT", + "alg": "HS256" + } + + payload['apiKeyPrefix'] = parentApiKey[0:8] + + json_header = json.dumps(header, separators=(",",":")).encode() + json_payload = json.dumps(payload, separators=(",",":")).encode() + + header_encode = self.base64url_encode(json_header) + header_payload = self.base64url_encode(json_payload) + + secret_encoded = parentApiKey.encode() + signature = hmac.new(secret_encoded, (header_encode + "." + header_payload).encode(), hashlib.sha256).digest() + jwt_token = header_encode + '.' + header_payload + '.' + self.base64url_encode(signature) + + return jwt_token From 1500c514722487a352b3676828d532d54c60a5d3 Mon Sep 17 00:00:00 2001 From: alallema Date: Thu, 17 Feb 2022 10:53:35 +0100 Subject: [PATCH 06/21] Changes of parameters --- meilisearch/client.py | 60 +++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index d961b666..2e4ef29b 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -1,14 +1,14 @@ from typing import Any, Dict, List, Optional +import base64 +import hashlib +import hmac +import json from meilisearch.index import Index from meilisearch.config import Config from meilisearch.task import get_task, get_tasks, wait_for_task from meilisearch._httprequests import HttpRequests from meilisearch.errors import MeiliSearchError -import base64 -import hashlib -import hmac -import json class Client(): """ @@ -469,33 +469,55 @@ def wait_for_task( """ return wait_for_task(self.config, uid, timeout_in_ms, interval_in_ms) - def base64url_encode( - self, - input: bytes - ) -> str: - return base64.urlsafe_b64encode(input).decode('utf-8').replace('=','') - def generate_tenant_token( self, - parentApiKey: str, - payload: str + options: Dict[str, Any], + parent_api_key: Optional[str] = None ) -> str: + """Generate a JWT token for the use of multitenancy. + + Parameters + ---------- + options: + Options, the information to generate the token (ex: { 'searchRules': ['*'], 'exp': '1645089029' }). + `searchRules`: A Dictionary which contains the rules to be enforced at search time for all or specific + accessible indexes for the signing API Key. + In the specific case of you want to have any restrictions you can also use a array ["*"]. + Note that if an exp value is included it should a `timestamp`. + Returns + ------- + jwt_token: + A string containing the jwt tenant token. + + Raises + ------ + MeiliSearchApiError + An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors + """ header = { "typ": "JWT", "alg": "HS256" } - payload['apiKeyPrefix'] = parentApiKey[0:8] + api_key = str(self.config.api_key) if parent_api_key is None else str(parent_api_key) + + options['apiKeyPrefix'] = api_key[0:8] json_header = json.dumps(header, separators=(",",":")).encode() - json_payload = json.dumps(payload, separators=(",",":")).encode() + json_options = json.dumps(options, separators=(",",":")).encode() - header_encode = self.base64url_encode(json_header) - header_payload = self.base64url_encode(json_payload) + header_encode = self._base64url_encode(json_header) + header_options = self._base64url_encode(json_options) - secret_encoded = parentApiKey.encode() - signature = hmac.new(secret_encoded, (header_encode + "." + header_payload).encode(), hashlib.sha256).digest() - jwt_token = header_encode + '.' + header_payload + '.' + self.base64url_encode(signature) + secret_encoded = api_key.encode() + signature = hmac.new(secret_encoded, (header_encode + "." + header_options).encode(), hashlib.sha256).digest() + jwt_token = header_encode + '.' + header_options + '.' + self._base64url_encode(signature) return jwt_token + + @staticmethod + def _base64url_encode( + data: bytes + ) -> str: + return base64.urlsafe_b64encode(data).decode('utf-8').replace('=','') From 7d411517e0e3f2acc1c9b4b131cc04a4fda61a8a Mon Sep 17 00:00:00 2001 From: alallema Date: Mon, 21 Feb 2022 18:57:59 +0100 Subject: [PATCH 07/21] Changes function declaration and add tests --- meilisearch/client.py | 38 ++++++++++++------ tests/client/test_client_token.py | 67 +++++++++++++++++++++++++++++++ tests/conftest.py | 6 +++ 3 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 tests/client/test_client_token.py diff --git a/meilisearch/client.py b/meilisearch/client.py index 2e4ef29b..4e27ec49 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -471,19 +471,23 @@ def wait_for_task( def generate_tenant_token( self, - options: Dict[str, Any], - parent_api_key: Optional[str] = None + search_rules: Dict[str, Any], + expired_at: Optional[float] = None, + api_key: Optional[str] = None ) -> str: """Generate a JWT token for the use of multitenancy. Parameters ---------- - options: - Options, the information to generate the token (ex: { 'searchRules': ['*'], 'exp': '1645089029' }). - `searchRules`: A Dictionary which contains the rules to be enforced at search time for all or specific + search_rules: + A Dictionary or an object which contains the rules to be enforced at search time for all or specific accessible indexes for the signing API Key. In the specific case of you want to have any restrictions you can also use a array ["*"]. - Note that if an exp value is included it should a `timestamp`. + expired_at (optional): + Date and time when the key will expire. + Note that if an expired_at value is included it should a `timestamp`. + api_key (optional): + The API key parent of the token. If you let it empty the client API Key will be used. Returns ------- @@ -495,24 +499,34 @@ def generate_tenant_token( MeiliSearchApiError An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors """ + # Standard JWT header for encryption with SHA256/HS256 algorithm header = { "typ": "JWT", "alg": "HS256" } - api_key = str(self.config.api_key) if parent_api_key is None else str(parent_api_key) + api_key = str(self.config.api_key) if api_key is None else str(api_key) - options['apiKeyPrefix'] = api_key[0:8] + # Add the required fields to the payload + payload = { + 'apiKeyPrefix': api_key[0:8], + 'searchRules': search_rules, + 'exp': expired_at if expired_at is not None else None + } + # Serialize the header and the payload json_header = json.dumps(header, separators=(",",":")).encode() - json_options = json.dumps(options, separators=(",",":")).encode() + json_payload = json.dumps(payload, separators=(",",":")).encode() + # Encode the header and the payload to Base64Url String header_encode = self._base64url_encode(json_header) - header_options = self._base64url_encode(json_options) + header_payload = self._base64url_encode(json_payload) secret_encoded = api_key.encode() - signature = hmac.new(secret_encoded, (header_encode + "." + header_options).encode(), hashlib.sha256).digest() - jwt_token = header_encode + '.' + header_options + '.' + self._base64url_encode(signature) + # Create Signature Hash + signature = hmac.new(secret_encoded, (header_encode + "." + header_payload).encode(), hashlib.sha256).digest() + # Create JWT + jwt_token = header_encode + '.' + header_payload + '.' + self._base64url_encode(signature) return jwt_token diff --git a/tests/client/test_client_token.py b/tests/client/test_client_token.py new file mode 100644 index 00000000..d2b8bc4e --- /dev/null +++ b/tests/client/test_client_token.py @@ -0,0 +1,67 @@ +# pylint: disable=invalid-name + +from re import search +import meilisearch +from tests import BASE_URL, MASTER_KEY +import datetime + +def test_generate_tenant_token_with_search_rules(get_private_key, index_with_documents): + """Tests create a tenant token with only search rules.""" + index_with_documents() + key = get_private_key + + client = meilisearch.Client(BASE_URL, key['key']) + + token = client.generate_tenant_token(search_rules=["*"]) + + token_client = meilisearch.Client(BASE_URL, token) + response = token_client.index('indexUID').search('') + assert isinstance(response, dict) + assert len(response['hits']) == 20 + assert response['query'] == '' + +def test_generate_tenant_token_with_api_key(client, get_private_key, index_with_documents): + """Tests create a tenant token with only search rules.""" + index_with_documents() + key = get_private_key + + token = client.generate_tenant_token(search_rules=["*"], api_key=key['key']) + + token_client = meilisearch.Client(BASE_URL, token) + response = token_client.index('indexUID').search('') + assert isinstance(response, dict) + assert len(response['hits']) == 20 + assert response['query'] == '' + +def test_generate_tenant_token_with_expired_at(client, get_private_key, index_with_documents): + """Tests create a tenant token with only search rules.""" + index_with_documents() + key = get_private_key + + client = meilisearch.Client(BASE_URL, key['key']) + + tomorrow = datetime.datetime.now() + datetime.timedelta(days=1, hours=3) + timestamp = datetime.datetime.timestamp(tomorrow) + + token = client.generate_tenant_token(search_rules=["*"], expired_at=int(timestamp)) + + token_client = meilisearch.Client(BASE_URL, token) + response = token_client.index('indexUID').search('') + assert isinstance(response, dict) + assert len(response['hits']) == 20 + assert response['query'] == '' + +def test_generate_tenant_token_without_search_rules(get_private_key, index_with_documents): + """Tests create a tenant token with only search rules.""" + index_with_documents() + key = get_private_key + + client = meilisearch.Client(BASE_URL, key['key']) + + token = client.generate_tenant_token(search_rules='') + + token_client = meilisearch.Client(BASE_URL, token) + response = token_client.index('indexUID').search('') + assert isinstance(response, dict) + assert len(response['hits']) == 20 + assert response['query'] == '' diff --git a/tests/conftest.py b/tests/conftest.py index 60f0861e..b506e56f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -109,3 +109,9 @@ def test_key_info(client): client.delete_key(key['key']) except MeiliSearchApiError: pass + +@fixture(scope='function') +def get_private_key(client): + keys = client.get_keys()['results'] + key = next(x for x in keys if 'Default Admin API' in x['description']) + return key From f254acacd04cf40213e893d9561048f11fb2ccab Mon Sep 17 00:00:00 2001 From: alallema Date: Tue, 22 Feb 2022 11:57:20 +0100 Subject: [PATCH 08/21] Adding tests --- meilisearch/client.py | 15 +++---- ...t_token.py => test_client_tenant_token.py} | 41 +++++++++++++++---- 2 files changed, 40 insertions(+), 16 deletions(-) rename tests/client/{test_client_token.py => test_client_tenant_token.py} (62%) diff --git a/meilisearch/client.py b/meilisearch/client.py index 4e27ec49..d916e137 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -472,7 +472,7 @@ def wait_for_task( def generate_tenant_token( self, search_rules: Dict[str, Any], - expired_at: Optional[float] = None, + expires_at: Optional[int] = None, api_key: Optional[str] = None ) -> str: """Generate a JWT token for the use of multitenancy. @@ -483,9 +483,9 @@ def generate_tenant_token( A Dictionary or an object which contains the rules to be enforced at search time for all or specific accessible indexes for the signing API Key. In the specific case of you want to have any restrictions you can also use a array ["*"]. - expired_at (optional): + expires_at (optional): Date and time when the key will expire. - Note that if an expired_at value is included it should a `timestamp`. + Note that if an expires_at value is included it should a `timestamp`. api_key (optional): The API key parent of the token. If you let it empty the client API Key will be used. @@ -493,11 +493,8 @@ def generate_tenant_token( ------- jwt_token: A string containing the jwt tenant token. - - Raises - ------ - MeiliSearchApiError - An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors + Note: If your token does not work remember that the searchrules is madatory and should be well formatted. + `exp` must be a timestamp in the future. """ # Standard JWT header for encryption with SHA256/HS256 algorithm header = { @@ -511,7 +508,7 @@ def generate_tenant_token( payload = { 'apiKeyPrefix': api_key[0:8], 'searchRules': search_rules, - 'exp': expired_at if expired_at is not None else None + 'exp': expires_at if expires_at is not None else None } # Serialize the header and the payload diff --git a/tests/client/test_client_token.py b/tests/client/test_client_tenant_token.py similarity index 62% rename from tests/client/test_client_token.py rename to tests/client/test_client_tenant_token.py index d2b8bc4e..14db574e 100644 --- a/tests/client/test_client_token.py +++ b/tests/client/test_client_tenant_token.py @@ -1,8 +1,10 @@ # pylint: disable=invalid-name from re import search +import pytest import meilisearch from tests import BASE_URL, MASTER_KEY +from meilisearch.errors import MeiliSearchApiError import datetime def test_generate_tenant_token_with_search_rules(get_private_key, index_with_documents): @@ -33,17 +35,17 @@ def test_generate_tenant_token_with_api_key(client, get_private_key, index_with_ assert len(response['hits']) == 20 assert response['query'] == '' -def test_generate_tenant_token_with_expired_at(client, get_private_key, index_with_documents): +def test_generate_tenant_token_with_expires_at(client, get_private_key, index_with_documents): """Tests create a tenant token with only search rules.""" index_with_documents() key = get_private_key client = meilisearch.Client(BASE_URL, key['key']) - tomorrow = datetime.datetime.now() + datetime.timedelta(days=1, hours=3) + tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) timestamp = datetime.datetime.timestamp(tomorrow) - token = client.generate_tenant_token(search_rules=["*"], expired_at=int(timestamp)) + token = client.generate_tenant_token(search_rules=["*"], expires_at=int(timestamp)) token_client = meilisearch.Client(BASE_URL, token) response = token_client.index('indexUID').search('') @@ -61,7 +63,32 @@ def test_generate_tenant_token_without_search_rules(get_private_key, index_with_ token = client.generate_tenant_token(search_rules='') token_client = meilisearch.Client(BASE_URL, token) - response = token_client.index('indexUID').search('') - assert isinstance(response, dict) - assert len(response['hits']) == 20 - assert response['query'] == '' + with pytest.raises(MeiliSearchApiError): + token_client.index('indexUID').search('') + +def test_generate_tenant_token_with_master_key(client, get_private_key, index_with_documents): + """Tests create a tenant token with only search rules.""" + index_with_documents() + key = get_private_key + + token = client.generate_tenant_token(search_rules=['*']) + + token_client = meilisearch.Client(BASE_URL, token) + with pytest.raises(MeiliSearchApiError): + token_client.index('indexUID').search('') + +def test_generate_tenant_token_with_bad_expires_at(client, get_private_key, index_with_documents): + """Tests create a tenant token with only search rules.""" + index_with_documents() + key = get_private_key + + client = meilisearch.Client(BASE_URL, key['key']) + + yesterday = datetime.datetime.now() + datetime.timedelta(days=-1) + timestamp = datetime.datetime.timestamp(yesterday) + + token = client.generate_tenant_token(search_rules=["*"], expires_at=int(timestamp)) + + token_client = meilisearch.Client(BASE_URL, token) + with pytest.raises(MeiliSearchApiError): + token_client.index('indexUID').search('') From 985db067248acf28f768f6ea30b59b640f73f874 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 10:14:48 +0100 Subject: [PATCH 09/21] Changes due to review --- meilisearch/client.py | 13 +++++++------ tests/client/test_client_tenant_token.py | 8 +++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index d916e137..ff3c9a8e 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -9,6 +9,7 @@ from meilisearch.task import get_task, get_tasks, wait_for_task from meilisearch._httprequests import HttpRequests from meilisearch.errors import MeiliSearchError +from typing import Any, Dict, List, Optional, Union class Client(): """ @@ -471,7 +472,7 @@ def wait_for_task( def generate_tenant_token( self, - search_rules: Dict[str, Any], + search_rules: Union[Dict[str, Any], List[str]], expires_at: Optional[int] = None, api_key: Optional[str] = None ) -> str: @@ -480,20 +481,20 @@ def generate_tenant_token( Parameters ---------- search_rules: - A Dictionary or an object which contains the rules to be enforced at search time for all or specific + A Dictionary or list of string which contains the rules to be enforced at search time for all or specific accessible indexes for the signing API Key. - In the specific case of you want to have any restrictions you can also use a array ["*"]. + In the specific case where you do not want to have any restrictions you can also use a list ["*"]. expires_at (optional): Date and time when the key will expire. Note that if an expires_at value is included it should a `timestamp`. api_key (optional): - The API key parent of the token. If you let it empty the client API Key will be used. + The API key parent of the token. If you leave it empty the client API Key will be used. Returns ------- jwt_token: A string containing the jwt tenant token. - Note: If your token does not work remember that the searchrules is madatory and should be well formatted. + Note: If your token does not work remember that the search_rules is mandatory and should be well formatted. `exp` must be a timestamp in the future. """ # Standard JWT header for encryption with SHA256/HS256 algorithm @@ -508,7 +509,7 @@ def generate_tenant_token( payload = { 'apiKeyPrefix': api_key[0:8], 'searchRules': search_rules, - 'exp': expires_at if expires_at is not None else None + 'exp': expires_at } # Serialize the header and the payload diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 14db574e..6135a011 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -36,7 +36,7 @@ def test_generate_tenant_token_with_api_key(client, get_private_key, index_with_ assert response['query'] == '' def test_generate_tenant_token_with_expires_at(client, get_private_key, index_with_documents): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token with search rules and expiration date.""" index_with_documents() key = get_private_key @@ -54,7 +54,7 @@ def test_generate_tenant_token_with_expires_at(client, get_private_key, index_wi assert response['query'] == '' def test_generate_tenant_token_without_search_rules(get_private_key, index_with_documents): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token without search rules.""" index_with_documents() key = get_private_key @@ -67,10 +67,8 @@ def test_generate_tenant_token_without_search_rules(get_private_key, index_with_ token_client.index('indexUID').search('') def test_generate_tenant_token_with_master_key(client, get_private_key, index_with_documents): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token with master key.""" index_with_documents() - key = get_private_key - token = client.generate_tenant_token(search_rules=['*']) token_client = meilisearch.Client(BASE_URL, token) From 0ac90d7e8648a2f4b4e46c553ec020ec0bdb36de Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 11:29:00 +0100 Subject: [PATCH 10/21] Changes due to review --- meilisearch/client.py | 21 ++++--- tests/client/test_client_tenant_token.py | 71 +++++++++++++----------- tests/conftest.py | 2 +- 3 files changed, 54 insertions(+), 40 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index ff3c9a8e..5bd94b74 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -1,15 +1,14 @@ -from typing import Any, Dict, List, Optional - import base64 import hashlib import hmac import json +import datetime +from typing import Any, Dict, List, Optional, Union from meilisearch.index import Index from meilisearch.config import Config from meilisearch.task import get_task, get_tasks, wait_for_task from meilisearch._httprequests import HttpRequests from meilisearch.errors import MeiliSearchError -from typing import Any, Dict, List, Optional, Union class Client(): """ @@ -473,7 +472,7 @@ def wait_for_task( def generate_tenant_token( self, search_rules: Union[Dict[str, Any], List[str]], - expires_at: Optional[int] = None, + expires_at: Optional[datetime.datetime] = None, api_key: Optional[str] = None ) -> str: """Generate a JWT token for the use of multitenancy. @@ -486,7 +485,6 @@ def generate_tenant_token( In the specific case where you do not want to have any restrictions you can also use a list ["*"]. expires_at (optional): Date and time when the key will expire. - Note that if an expires_at value is included it should a `timestamp`. api_key (optional): The API key parent of the token. If you leave it empty the client API Key will be used. @@ -495,8 +493,17 @@ def generate_tenant_token( jwt_token: A string containing the jwt tenant token. Note: If your token does not work remember that the search_rules is mandatory and should be well formatted. - `exp` must be a timestamp in the future. + `exp` must be a `datetime` in the future. It's not possible to create a token from the master key. """ + if api_key == '' or api_key is None and self.config.api_key is None: + raise Exception('An api key is required in the client or should be passed as an argument.') + if isinstance(search_rules, Dict) and search_rules == {} or search_rules == {''}: + raise Exception('The search_rules field is mandatory and should be defined.') + if isinstance(search_rules, List) and search_rules == [] or search_rules == ['']: + raise Exception('The search_rules field is mandatory and should be defined.') + if expires_at and expires_at < datetime.datetime.now(): + raise Exception('The date expires_at should be in the future.') + # Standard JWT header for encryption with SHA256/HS256 algorithm header = { "typ": "JWT", @@ -509,7 +516,7 @@ def generate_tenant_token( payload = { 'apiKeyPrefix': api_key[0:8], 'searchRules': search_rules, - 'exp': expires_at + 'exp': int(datetime.datetime.timestamp(expires_at)) if expires_at is not None else None } # Serialize the header and the payload diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 6135a011..593bc654 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -10,9 +10,7 @@ def test_generate_tenant_token_with_search_rules(get_private_key, index_with_documents): """Tests create a tenant token with only search rules.""" index_with_documents() - key = get_private_key - - client = meilisearch.Client(BASE_URL, key['key']) + client = meilisearch.Client(BASE_URL, get_private_key['key']) token = client.generate_tenant_token(search_rules=["*"]) @@ -25,9 +23,7 @@ def test_generate_tenant_token_with_search_rules(get_private_key, index_with_doc def test_generate_tenant_token_with_api_key(client, get_private_key, index_with_documents): """Tests create a tenant token with only search rules.""" index_with_documents() - key = get_private_key - - token = client.generate_tenant_token(search_rules=["*"], api_key=key['key']) + token = client.generate_tenant_token(search_rules=["*"], api_key=get_private_key['key']) token_client = meilisearch.Client(BASE_URL, token) response = token_client.index('indexUID').search('') @@ -38,14 +34,10 @@ def test_generate_tenant_token_with_api_key(client, get_private_key, index_with_ def test_generate_tenant_token_with_expires_at(client, get_private_key, index_with_documents): """Tests create a tenant token with search rules and expiration date.""" index_with_documents() - key = get_private_key - - client = meilisearch.Client(BASE_URL, key['key']) - + client = meilisearch.Client(BASE_URL, get_private_key['key']) tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) - timestamp = datetime.datetime.timestamp(tomorrow) - token = client.generate_tenant_token(search_rules=["*"], expires_at=int(timestamp)) + token = client.generate_tenant_token(search_rules=["*"], expires_at=tomorrow) token_client = meilisearch.Client(BASE_URL, token) response = token_client.index('indexUID').search('') @@ -53,40 +45,55 @@ def test_generate_tenant_token_with_expires_at(client, get_private_key, index_wi assert len(response['hits']) == 20 assert response['query'] == '' -def test_generate_tenant_token_without_search_rules(get_private_key, index_with_documents): +def test_generate_tenant_token_with_empty_search_rules_in_list(get_private_key, index_with_documents): """Tests create a tenant token without search rules.""" - index_with_documents() - key = get_private_key + client = meilisearch.Client(BASE_URL, get_private_key['key']) + + with pytest.raises(Exception): + client.generate_tenant_token(search_rules=['']) - client = meilisearch.Client(BASE_URL, key['key']) +def test_generate_tenant_token_without_search_rules_in_list(get_private_key, index_with_documents): + """Tests create a tenant token without search rules.""" + client = meilisearch.Client(BASE_URL, get_private_key['key']) - token = client.generate_tenant_token(search_rules='') + with pytest.raises(Exception): + client.generate_tenant_token(search_rules=[]) - token_client = meilisearch.Client(BASE_URL, token) - with pytest.raises(MeiliSearchApiError): - token_client.index('indexUID').search('') +def test_generate_tenant_token_without_search_rules_in_dict(get_private_key, index_with_documents): + """Tests create a tenant token without search rules.""" + client = meilisearch.Client(BASE_URL, get_private_key['key']) + + with pytest.raises(Exception): + client.generate_tenant_token(search_rules={}) -def test_generate_tenant_token_with_master_key(client, get_private_key, index_with_documents): +def test_generate_tenant_token_wit_empty_search_rules_in_dict(get_private_key, index_with_documents): + """Tests create a tenant token without search rules.""" + client = meilisearch.Client(BASE_URL, get_private_key['key']) + + with pytest.raises(Exception): + client.generate_tenant_token(search_rules={''}) + +def test_generate_tenant_token_with_master_key(client, index_with_documents): """Tests create a tenant token with master key.""" - index_with_documents() token = client.generate_tenant_token(search_rules=['*']) token_client = meilisearch.Client(BASE_URL, token) with pytest.raises(MeiliSearchApiError): token_client.index('indexUID').search('') -def test_generate_tenant_token_with_bad_expires_at(client, get_private_key, index_with_documents): +def test_generate_tenant_token_with_bad_expires_at(client, get_private_key): """Tests create a tenant token with only search rules.""" - index_with_documents() - key = get_private_key - - client = meilisearch.Client(BASE_URL, key['key']) + client = meilisearch.Client(BASE_URL, get_private_key['key']) yesterday = datetime.datetime.now() + datetime.timedelta(days=-1) - timestamp = datetime.datetime.timestamp(yesterday) - token = client.generate_tenant_token(search_rules=["*"], expires_at=int(timestamp)) + with pytest.raises(Exception): + client.generate_tenant_token(search_rules=["*"], expires_at=yesterday) + +def test_generate_tenant_token_with_no_api_key(client): + """Tests create a tenant token with only search rules.""" + client = meilisearch.Client(BASE_URL) + + with pytest.raises(Exception): + client.generate_tenant_token(search_rules=["*"]) - token_client = meilisearch.Client(BASE_URL, token) - with pytest.raises(MeiliSearchApiError): - token_client.index('indexUID').search('') diff --git a/tests/conftest.py b/tests/conftest.py index b506e56f..7de3fb45 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -113,5 +113,5 @@ def test_key_info(client): @fixture(scope='function') def get_private_key(client): keys = client.get_keys()['results'] - key = next(x for x in keys if 'Default Admin API' in x['description']) + key = next(x for x in keys if 'Default Search API' in x['description']) return key From 5e214deb1a09836355db7e69f402526080b5984d Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 11:32:26 +0100 Subject: [PATCH 11/21] Changes due to review --- meilisearch/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/meilisearch/client.py b/meilisearch/client.py index 5bd94b74..69e4dbdf 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -472,6 +472,7 @@ def wait_for_task( def generate_tenant_token( self, search_rules: Union[Dict[str, Any], List[str]], + *, expires_at: Optional[datetime.datetime] = None, api_key: Optional[str] = None ) -> str: From 433966d7c2e8825772ffdbd3f4796fffcfd8a7c5 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 12:04:17 +0100 Subject: [PATCH 12/21] Changes due to review --- meilisearch/client.py | 13 +++++++++++-- tests/client/test_client_tenant_token.py | 7 ++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 69e4dbdf..f13a5ff2 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -496,6 +496,7 @@ def generate_tenant_token( Note: If your token does not work remember that the search_rules is mandatory and should be well formatted. `exp` must be a `datetime` in the future. It's not possible to create a token from the master key. """ + # Validate all fields if api_key == '' or api_key is None and self.config.api_key is None: raise Exception('An api key is required in the client or should be passed as an argument.') if isinstance(search_rules, Dict) and search_rules == {} or search_rules == {''}: @@ -505,14 +506,22 @@ def generate_tenant_token( if expires_at and expires_at < datetime.datetime.now(): raise Exception('The date expires_at should be in the future.') + api_key = str(self.config.api_key) if api_key is None else api_key + + # Check if the api_key is not the master key + try: + client = Client(self.config.url, api_key) + client.get_keys() + raise Exception('The master key can be used to generated a token.') + except MeiliSearchError: + pass + # Standard JWT header for encryption with SHA256/HS256 algorithm header = { "typ": "JWT", "alg": "HS256" } - api_key = str(self.config.api_key) if api_key is None else str(api_key) - # Add the required fields to the payload payload = { 'apiKeyPrefix': api_key[0:8], diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 593bc654..94fefa83 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -75,11 +75,8 @@ def test_generate_tenant_token_wit_empty_search_rules_in_dict(get_private_key, i def test_generate_tenant_token_with_master_key(client, index_with_documents): """Tests create a tenant token with master key.""" - token = client.generate_tenant_token(search_rules=['*']) - - token_client = meilisearch.Client(BASE_URL, token) - with pytest.raises(MeiliSearchApiError): - token_client.index('indexUID').search('') + with pytest.raises(Exception): + client.generate_tenant_token(search_rules=['*']) def test_generate_tenant_token_with_bad_expires_at(client, get_private_key): """Tests create a tenant token with only search rules.""" From 77294cbd370c45b719c8505c7d4c8a86d5105b78 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 14:41:21 +0100 Subject: [PATCH 13/21] Changes due to review --- meilisearch/client.py | 4 +-- tests/client/test_client_tenant_token.py | 39 ++++++++++++++++-------- tests/conftest.py | 6 ++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index f13a5ff2..4f5a17dd 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -499,9 +499,7 @@ def generate_tenant_token( # Validate all fields if api_key == '' or api_key is None and self.config.api_key is None: raise Exception('An api key is required in the client or should be passed as an argument.') - if isinstance(search_rules, Dict) and search_rules == {} or search_rules == {''}: - raise Exception('The search_rules field is mandatory and should be defined.') - if isinstance(search_rules, List) and search_rules == [] or search_rules == ['']: + if not search_rules or search_rules == ['']: raise Exception('The search_rules field is mandatory and should be defined.') if expires_at and expires_at < datetime.datetime.now(): raise Exception('The date expires_at should be in the future.') diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 94fefa83..22c7a70b 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -14,26 +14,42 @@ def test_generate_tenant_token_with_search_rules(get_private_key, index_with_doc token = client.generate_tenant_token(search_rules=["*"]) + token_client = meilisearch.Client(BASE_URL, token) + response = token_client.index('indexUID').search('', { + 'limit': 5 + }) + assert isinstance(response, dict) + assert len(response['hits']) == 5 + assert response['query'] == '' + +def test_generate_tenant_token_with_search_rules_on_one_index(get_private_key, empty_index): + """Tests create a tenant token with only search rules.""" + empty_index() + empty_index('tenant_token') + client = meilisearch.Client(BASE_URL, get_private_key['key']) + + token = client.generate_tenant_token(search_rules=['indexUID']) + token_client = meilisearch.Client(BASE_URL, token) response = token_client.index('indexUID').search('') assert isinstance(response, dict) - assert len(response['hits']) == 20 assert response['query'] == '' + with pytest.raises(MeiliSearchApiError): + response = token_client.index('tenant_token').search('') -def test_generate_tenant_token_with_api_key(client, get_private_key, index_with_documents): +def test_generate_tenant_token_with_api_key(client, get_private_key, empty_index): """Tests create a tenant token with only search rules.""" - index_with_documents() + empty_index() token = client.generate_tenant_token(search_rules=["*"], api_key=get_private_key['key']) token_client = meilisearch.Client(BASE_URL, token) response = token_client.index('indexUID').search('') assert isinstance(response, dict) - assert len(response['hits']) == 20 assert response['query'] == '' -def test_generate_tenant_token_with_expires_at(client, get_private_key, index_with_documents): +def test_generate_tenant_token_with_expires_at(client, get_private_key, empty_index): """Tests create a tenant token with search rules and expiration date.""" - index_with_documents() + empty_index() client = meilisearch.Client(BASE_URL, get_private_key['key']) tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) @@ -42,38 +58,37 @@ def test_generate_tenant_token_with_expires_at(client, get_private_key, index_wi token_client = meilisearch.Client(BASE_URL, token) response = token_client.index('indexUID').search('') assert isinstance(response, dict) - assert len(response['hits']) == 20 assert response['query'] == '' -def test_generate_tenant_token_with_empty_search_rules_in_list(get_private_key, index_with_documents): +def test_generate_tenant_token_with_empty_search_rules_in_list(get_private_key): """Tests create a tenant token without search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) with pytest.raises(Exception): client.generate_tenant_token(search_rules=['']) -def test_generate_tenant_token_without_search_rules_in_list(get_private_key, index_with_documents): +def test_generate_tenant_token_without_search_rules_in_list(get_private_key): """Tests create a tenant token without search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) with pytest.raises(Exception): client.generate_tenant_token(search_rules=[]) -def test_generate_tenant_token_without_search_rules_in_dict(get_private_key, index_with_documents): +def test_generate_tenant_token_without_search_rules_in_dict(get_private_key): """Tests create a tenant token without search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) with pytest.raises(Exception): client.generate_tenant_token(search_rules={}) -def test_generate_tenant_token_wit_empty_search_rules_in_dict(get_private_key, index_with_documents): +def test_generate_tenant_token_wit_empty_search_rules_in_dict(get_private_key): """Tests create a tenant token without search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) with pytest.raises(Exception): client.generate_tenant_token(search_rules={''}) -def test_generate_tenant_token_with_master_key(client, index_with_documents): +def test_generate_tenant_token_with_master_key(client): """Tests create a tenant token with master key.""" with pytest.raises(Exception): client.generate_tenant_token(search_rules=['*']) diff --git a/tests/conftest.py b/tests/conftest.py index 7de3fb45..43124544 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ from tests import common import meilisearch from meilisearch.errors import MeiliSearchApiError +from typing import Optional @fixture(scope='session') def client(): @@ -67,8 +68,9 @@ def songs_ndjson(): return song_ndjson_file.read().encode('utf-8') @fixture(scope='function') -def empty_index(client): - def index_maker(index_name=common.INDEX_UID): +def empty_index(client, index_uid: Optional[str] = None): + index_uid = index_uid if index_uid else common.INDEX_UID + def index_maker(index_name=index_uid): task = client.create_index(uid=index_name) client.wait_for_task(task['uid']) return client.get_index(uid=index_name) From 7579c8fa83a8a100653b47c4cdd8608ef8383d90 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 15:48:43 +0100 Subject: [PATCH 14/21] Changes due to review --- meilisearch/client.py | 12 ++---------- tests/client/test_client_tenant_token.py | 5 ----- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 4f5a17dd..1bd3ee3c 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -504,22 +504,14 @@ def generate_tenant_token( if expires_at and expires_at < datetime.datetime.now(): raise Exception('The date expires_at should be in the future.') - api_key = str(self.config.api_key) if api_key is None else api_key - - # Check if the api_key is not the master key - try: - client = Client(self.config.url, api_key) - client.get_keys() - raise Exception('The master key can be used to generated a token.') - except MeiliSearchError: - pass - # Standard JWT header for encryption with SHA256/HS256 algorithm header = { "typ": "JWT", "alg": "HS256" } + api_key = str(self.config.api_key) if api_key is None else api_key + # Add the required fields to the payload payload = { 'apiKeyPrefix': api_key[0:8], diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 22c7a70b..f28c9643 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -88,11 +88,6 @@ def test_generate_tenant_token_wit_empty_search_rules_in_dict(get_private_key): with pytest.raises(Exception): client.generate_tenant_token(search_rules={''}) -def test_generate_tenant_token_with_master_key(client): - """Tests create a tenant token with master key.""" - with pytest.raises(Exception): - client.generate_tenant_token(search_rules=['*']) - def test_generate_tenant_token_with_bad_expires_at(client, get_private_key): """Tests create a tenant token with only search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) From 5c81b201333a7c63e59df4dad61fc31e3b338e13 Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 17:54:06 +0100 Subject: [PATCH 15/21] Changes due to review --- meilisearch/client.py | 4 ++-- tests/client/test_client_tenant_token.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 1bd3ee3c..85aea7e3 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -485,7 +485,7 @@ def generate_tenant_token( accessible indexes for the signing API Key. In the specific case where you do not want to have any restrictions you can also use a list ["*"]. expires_at (optional): - Date and time when the key will expire. + Date and time when the key will expire. Note that if an expires_at value is included it should be in UTC time. api_key (optional): The API key parent of the token. If you leave it empty the client API Key will be used. @@ -501,7 +501,7 @@ def generate_tenant_token( raise Exception('An api key is required in the client or should be passed as an argument.') if not search_rules or search_rules == ['']: raise Exception('The search_rules field is mandatory and should be defined.') - if expires_at and expires_at < datetime.datetime.now(): + if expires_at and expires_at < datetime.datetime.utcnow(): raise Exception('The date expires_at should be in the future.') # Standard JWT header for encryption with SHA256/HS256 algorithm diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index f28c9643..83c28e08 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -92,7 +92,7 @@ def test_generate_tenant_token_with_bad_expires_at(client, get_private_key): """Tests create a tenant token with only search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) - yesterday = datetime.datetime.now() + datetime.timedelta(days=-1) + yesterday = datetime.datetime.utcnow() + datetime.timedelta(days=-1) with pytest.raises(Exception): client.generate_tenant_token(search_rules=["*"], expires_at=yesterday) From 09577a5f8d60dc9420add841f3ec69364027f5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie?= Date: Wed, 23 Feb 2022 19:01:34 +0100 Subject: [PATCH 16/21] Update tests/client/test_client_tenant_token.py Co-authored-by: Bruno Casali --- tests/client/test_client_tenant_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 83c28e08..2c31b795 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -81,7 +81,7 @@ def test_generate_tenant_token_without_search_rules_in_dict(get_private_key): with pytest.raises(Exception): client.generate_tenant_token(search_rules={}) -def test_generate_tenant_token_wit_empty_search_rules_in_dict(get_private_key): +def test_generate_tenant_token_with_empty_search_rules_in_dict(get_private_key): """Tests create a tenant token without search rules.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) From 7f10221a9f36ec98a9f5c5fda261bd932bb53a1b Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 23 Feb 2022 19:08:35 +0100 Subject: [PATCH 17/21] Changes due to review --- tests/client/test_client_tenant_token.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 2c31b795..037b07b9 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -23,7 +23,7 @@ def test_generate_tenant_token_with_search_rules(get_private_key, index_with_doc assert response['query'] == '' def test_generate_tenant_token_with_search_rules_on_one_index(get_private_key, empty_index): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token with search rules set for one index.""" empty_index() empty_index('tenant_token') client = meilisearch.Client(BASE_URL, get_private_key['key']) @@ -38,7 +38,7 @@ def test_generate_tenant_token_with_search_rules_on_one_index(get_private_key, e response = token_client.index('tenant_token').search('') def test_generate_tenant_token_with_api_key(client, get_private_key, empty_index): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token with search rules and an api key.""" empty_index() token = client.generate_tenant_token(search_rules=["*"], api_key=get_private_key['key']) @@ -89,7 +89,7 @@ def test_generate_tenant_token_with_empty_search_rules_in_dict(get_private_key): client.generate_tenant_token(search_rules={''}) def test_generate_tenant_token_with_bad_expires_at(client, get_private_key): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token with a bad expires at.""" client = meilisearch.Client(BASE_URL, get_private_key['key']) yesterday = datetime.datetime.utcnow() + datetime.timedelta(days=-1) @@ -98,7 +98,7 @@ def test_generate_tenant_token_with_bad_expires_at(client, get_private_key): client.generate_tenant_token(search_rules=["*"], expires_at=yesterday) def test_generate_tenant_token_with_no_api_key(client): - """Tests create a tenant token with only search rules.""" + """Tests create a tenant token with no api key.""" client = meilisearch.Client(BASE_URL) with pytest.raises(Exception): From 66485e91813b5ed7fb28dc125aa8922dbb99cd6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie?= Date: Wed, 2 Mar 2022 18:31:21 +0100 Subject: [PATCH 18/21] Changes bad naming Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com> --- meilisearch/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 85aea7e3..1bb6f9c4 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -525,7 +525,7 @@ def generate_tenant_token( # Encode the header and the payload to Base64Url String header_encode = self._base64url_encode(json_header) - header_payload = self._base64url_encode(json_payload) + payload_encode = self._base64url_encode(json_payload) secret_encoded = api_key.encode() # Create Signature Hash From bc01c678abe82e7eda341c6ba2e223373887c0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie?= Date: Wed, 2 Mar 2022 18:31:28 +0100 Subject: [PATCH 19/21] Changes bad naming Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com> --- meilisearch/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 1bb6f9c4..ade2d792 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -529,9 +529,9 @@ def generate_tenant_token( secret_encoded = api_key.encode() # Create Signature Hash - signature = hmac.new(secret_encoded, (header_encode + "." + header_payload).encode(), hashlib.sha256).digest() + signature = hmac.new(secret_encoded, (header_encode + "." + payload_encode).encode(), hashlib.sha256).digest() # Create JWT - jwt_token = header_encode + '.' + header_payload + '.' + self._base64url_encode(signature) + jwt_token = header_encode + '.' + payload_encode + '.' + self._base64url_encode(signature) return jwt_token From d236af9e7f602a536a815a96239a4d2cd1d97b0f Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 9 Mar 2022 13:38:53 +0100 Subject: [PATCH 20/21] Update code sample for generateTenantToken method --- .code-samples.meilisearch.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 86798d9e..1dd118a1 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -473,3 +473,15 @@ security_guide_delete_key_1: |- authorization_header_1: |- client = Client('http://127.0.0.1:7700', 'masterKey') client.get_keys() +tenant_token_guide_generate_sdk_1: |- + api_key = 'B5KdX2MY2jV6EXfUs6scSfmC...' + expires_at = datetime(2025, 12, 20) + search_rules = { + 'patient_medical_records': { + 'filter': 'user_id = 1' + } + } + token = client.generate_tenant_token(search_rules=["*"], api_key=api_key, expires_at=expires_at) +tenant_token_guide_search_sdk_1: |- + front_end_client = Client('http://127.0.0.1:7700', token) + front_end_client.index('patient_medical_records').search('blood test') From 3b1672529cdb4b809cbdac8b64e61eb2dc37b8dd Mon Sep 17 00:00:00 2001 From: alallema Date: Wed, 9 Mar 2022 15:30:10 +0100 Subject: [PATCH 21/21] Fix test and code sample --- .code-samples.meilisearch.yaml | 2 +- tests/client/test_client_tenant_token.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 1dd118a1..2609b3b7 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -481,7 +481,7 @@ tenant_token_guide_generate_sdk_1: |- 'filter': 'user_id = 1' } } - token = client.generate_tenant_token(search_rules=["*"], api_key=api_key, expires_at=expires_at) + token = client.generate_tenant_token(search_rules=search_rules, api_key=api_key, expires_at=expires_at) tenant_token_guide_search_sdk_1: |- front_end_client = Client('http://127.0.0.1:7700', token) front_end_client.index('patient_medical_records').search('blood test') diff --git a/tests/client/test_client_tenant_token.py b/tests/client/test_client_tenant_token.py index 037b07b9..d90c5335 100644 --- a/tests/client/test_client_tenant_token.py +++ b/tests/client/test_client_tenant_token.py @@ -51,7 +51,7 @@ def test_generate_tenant_token_with_expires_at(client, get_private_key, empty_in """Tests create a tenant token with search rules and expiration date.""" empty_index() client = meilisearch.Client(BASE_URL, get_private_key['key']) - tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) + tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1) token = client.generate_tenant_token(search_rules=["*"], expires_at=tomorrow)