Skip to content

Commit

Permalink
feat: reuse api keys and token as far as not expired
Browse files Browse the repository at this point in the history
  • Loading branch information
qasimgulzar committed Aug 15, 2024
1 parent 662fffe commit b7216ca
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 31 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
python -m pip install --upgrade pip
pip install pylint
pip install -e .
pip install -r requirements/testing.txt
- name: Analysing the code with pylint
run: |
pylint $(git ls-files './openedx_search_api/*.py')
pylint ./openedx_search_api/
9 changes: 2 additions & 7 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
[MESSAGES CONTROL]
disable=
locally-disabled,
logging-format-interpolation,
too-few-public-methods,
too-many-instance-attributes,
import-error
[MASTER]
ignore=openedx_search_api/migrations
74 changes: 51 additions & 23 deletions openedx_search_api/drivers/meilisearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from meilisearch.models.key import Key

from . import BaseDriver
from ..models import SearchEngineToken, SearchApiKeyModel


class BaseIndexConfiguration:
Expand Down Expand Up @@ -62,17 +63,12 @@ def __init__(
request,
meilisearch_url,
meilisearch_public_url,
meilisearch_api_id,
meilisearch_api_key,
meilisearch_master_api_key,
expiry_days=7
): # pylint: disable=too-many-arguments
self._index = None
self.api_key_id = meilisearch_api_id
self.api_key = meilisearch_api_key
self.url = meilisearch_url
self.public_url = meilisearch_public_url
self.meili_api_key_uid = None
self.token_expires_at = datetime.now(tz=timezone.utc) + timedelta(days=expiry_days)
self.request = request
self.client: MeilisearchClient = MeilisearchClient(self.url, meilisearch_master_api_key)
Expand Down Expand Up @@ -101,33 +97,69 @@ def create_key(self) -> Key:
}
)

def _get_meili_api_key_uid(self):
def get_api_key(self):
"""
Helper method to get the UID of the API key we're using for MeiliSearch.
Returns api key pair [api_key_uid, api_key]
:return:
"""
if self.meili_api_key_uid is None:
self.meili_api_key_uid = self.client.get_key(self.api_key_id).uid
return self.meili_api_key_uid
api_key_model_object = SearchApiKeyModel.get_active_api_key(self.request.user)
if not api_key_model_object:
api_key = self.create_key()
SearchApiKeyModel.objects.update_or_create(
user=self.request.user,
defaults={
'uid': api_key.uid,
'name': api_key.name,
'actions': api_key.actions,
'indexes': api_key.indexes,
'expires_at': api_key.expires_at,
'key': api_key.key,
'created_at': api_key.created_at,
'updated_at': api_key.updated_at,
}
)
return api_key.uid, api_key.key
return api_key_model_object.uid, api_key_model_object.key

def get_user_token(self, index_search_rules=None):
"""
Generate a user token for MeiliSearch.
"""
restricted_api_key = self.client.generate_tenant_token(
api_key_uid=self._get_meili_api_key_uid(),
search_rules=index_search_rules or {},
expires_at=self.token_expires_at,
api_key=self.api_key
)

return {
response = {
"url": self.public_url,
"token": restricted_api_key,
"token_type": "Bearer",
"expires_at": self.token_expires_at,
"search_engine": self.SEARCH_ENGINE,
"index_search_rules": index_search_rules
}
token = SearchEngineToken.get_active_token(self.request.user)
if not token:
api_key_uid, key = self.get_api_key()
restricted_api_key = self.client.generate_tenant_token(
api_key_uid=api_key_uid,
search_rules=index_search_rules or {},
expires_at=self.token_expires_at,
api_key=key
)
response.update(token=restricted_api_key)
SearchEngineToken.objects.update_or_create(
defaults={
'token': restricted_api_key,
'token_type': "Bearer",
'expires_at': self.token_expires_at,
'search_engine': self.SEARCH_ENGINE,
'index_search_rules': index_search_rules or {},
},
user=self.request.user
)
else:
response.update(
expires_at=token.expires_at,
token=token.token,
index_search_rules=token.index_search_rules
)

return response

def indexes(self, parameters: Optional[Mapping[str, Any]] = None) -> Dict[str, List[Index]]:
"""
Expand All @@ -142,15 +174,11 @@ def get_instance(cls, request):
"""
meilisearch_url = getattr(settings, 'MEILISEARCH_URL')
meilisearch_public_url = getattr(settings, 'MEILISEARCH_PUBLIC_URL')
meilisearch_api_key_id = getattr(settings, 'MEILISEARCH_API_KEY_ID')
meilisearch_api_key = getattr(settings, 'MEILISEARCH_API_KEY')
meilisearch_master_api_key = getattr(settings, 'MEILISEARCH_MASTER_API_KEY')
return MeiliSearchEngine(
request,
meilisearch_url,
meilisearch_public_url,
meilisearch_api_key_id,
meilisearch_api_key,
meilisearch_master_api_key
)

Expand Down
44 changes: 44 additions & 0 deletions openedx_search_api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 5.0.7 on 2024-08-15 12:32

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='SearchApiKeyModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uid', models.CharField(max_length=255)),
('name', models.CharField(blank=True, max_length=255, null=True)),
('actions', models.JSONField()),
('indexes', models.JSONField()),
('expires_at', models.DateTimeField()),
('key', models.CharField(max_length=500)),
('created_at', models.DateTimeField()),
('updated_at', models.DateTimeField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='SearchEngineToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.TextField()),
('token_type', models.CharField(max_length=255)),
('expires_at', models.DateTimeField()),
('search_engine', models.CharField(max_length=255)),
('index_search_rules', models.JSONField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
58 changes: 58 additions & 0 deletions openedx_search_api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Define you models here
"""
from datetime import datetime

from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()


class SearchEngineToken(models.Model):
"""
It is to store user specific token in database
"""
token = models.TextField()
token_type = models.CharField(max_length=255)
expires_at = models.DateTimeField()
search_engine = models.CharField(max_length=255)
index_search_rules = models.JSONField()
user = models.OneToOneField(User, on_delete=models.CASCADE)

objects = models.Manager()

@classmethod
def get_active_token(cls, user):
"""
Returns active token key object
:param user:
:return:
"""
return cls.objects.filter(user_id=user.id, expires_at__gt=datetime.now()).first()


class SearchApiKeyModel(models.Model):
"""
It is to store user specific api key in database
"""
uid = models.CharField(max_length=255)
name = models.CharField(max_length=255, blank=True, null=True)
actions = models.JSONField()
indexes = models.JSONField()
expires_at = models.DateTimeField()
key = models.CharField(max_length=500)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
user = models.OneToOneField(User, on_delete=models.CASCADE)

objects = models.Manager()

@classmethod
def get_active_api_key(cls, user):
"""
Returns active api key object
:param user:
:return:
"""
return cls.objects.filter(user_id=user.id, expires_at__gt=datetime.now()).first()

0 comments on commit b7216ca

Please sign in to comment.