Skip to content

Conversation

@samuelveigarangel
Copy link
Collaborator

@samuelveigarangel samuelveigarangel commented Sep 25, 2025

O que esse PR faz?

  • Adiciona uma estrutura de encode token JWT, baseado em RS256. Não há necessidade de user para autenticar (Chave pública, chave privada).
  • Adiciona um trigger para realizar uma requisição post para o endpoint /api/v1/update_colleciton/ de opac_5
  • Realiza melhorias no payload de Collection.
  • REaliza melhorias no modelo de CollectionLogo

Onde a revisão poderia começar?

pelos commits

Como este poderia ser testado manualmente?

Pré-requisitos:

  1. Gerar par de chaves (caso ainda não existam)
# Gerar novo par de chaves se necessário
openssl genrsa -out jwt_private.pem 2048
openssl rsa -in jwt_private.pem -pubout -out jwt_public.pem
  1. Configurar variáveis de ambiente
  • No opac_5
    • Defina JWT_PUBLIC_KEY_PATH com o caminho absoluto da chave pública (jwt_public.pem).
    • Defina OPAC_SERVER_NAME com o endereço acessível do opac_webapp_local (IP do gateway do container) e a porta 8000. Ex.: 172.18.0.1:8000
  • No core:
    • Defina JWT_PRIVATE_KEY_PATH com o caminho absoluto da chave privada (jwt_private.pem).
  1. Configurar coleção no core
  • Adicione uma coleção no core usando:
    • domain: exatamente o mesmo valor de OPAC_SERVER_NAME (por exemplo, 172.18.0.1:8000)
    • Demais campos: iguais aos da coleção existente no opac_5.
  1. Validar o fluxo
  • Ao salvar a coleção no core, será disparado o trigger post_save, que enviará uma requisição HTTP para o domain configurado (valor de OPAC_SERVER_NAME).
  • Verifique nos logs do opac_5 se a requisição foi recebida e processada com sucesso.
  • Se aplicável, valide o efeito esperado no opac (por exemplo, sincronização/atualização dos dados da coleção).

Algum cenário de contexto que queira dar?

Screenshots

N/A

Quais são tickets relevantes?

#339

Referências

N/A

@scieloorg scieloorg deleted a comment from gitguardian bot Sep 25, 2025
@gitguardian
Copy link

gitguardian bot commented Sep 25, 2025

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@samuelveigarangel samuelveigarangel changed the title Issue opac 339 Adiciona trigger para atualizar os dados de coleção no opac_5 Sep 25, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds JWT token generation and a webhook trigger system to automatically notify opac_5 when collection data is updated in the core system. The integration uses RS256 JWT authentication and sends HTTP POST requests to update collection information.

  • Implements JWT token generation using RS256 algorithm for secure service-to-service communication
  • Adds a post-save signal trigger that sends collection data to opac_5 via webhook when collections are created or updated
  • Enhances collection logo model by replacing size field with purpose field for better organization

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
requirements/base.txt Adds JWT dependencies (cryptography and PyJWT)
core/utils/jwt.py New JWT token generation utility using RS256 algorithm
collection/tasks.py Celery tasks for sending collection data webhooks to opac_5
collection/signals.py Post-save signal handler to trigger webhook on collection changes
collection/models.py Updates CollectionLogo model to use purpose instead of size field
collection/api/v1/serializers.py Enhanced collection serializers with logo grouping and URL generation
config/settings/base.py JWT configuration settings and webhook endpoint configuration
.envs/.local/.jwt JWT environment variables for local development

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 587 to 588
with open(JWT_PRIVATE_KEY_PATH, "rb") as f:
JWT_PRIVATE_KEY = f.read()
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the JWT private key file at settings import time will cause the application to crash if the file doesn't exist. This should be moved to a function that's called when needed, or wrapped in a try/except block with appropriate error handling.

Suggested change
with open(JWT_PRIVATE_KEY_PATH, "rb") as f:
JWT_PRIVATE_KEY = f.read()
def get_jwt_private_key():
try:
with open(JWT_PRIVATE_KEY_PATH, "rb") as f:
return f.read()
except (FileNotFoundError, IOError) as e:
# You may want to log the error or handle it differently
return None

Copilot uses AI. Check for mistakes.
for schema in ["http", "https"]:
url = f"{schema}://{host_or_url}"
try:
resp = requests.post(url, timeout=timeout)
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function fetch_with_schema_guess is sending a POST request without any data or headers to test connectivity, but it should use a GET request instead since it's only checking if the URL is reachable.

Suggested change
resp = requests.post(url, timeout=timeout)
resp = requests.get(url, timeout=timeout)

Copilot uses AI. Check for mistakes.
core/models.py Outdated
ordering = ['sort_order', 'language']

def __str__(self):
return f"{self.collection} - {self.language} ({self.size})"
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __str__ method references self.size which was removed from the model. This should be updated to reference the new field or removed entirely.

Suggested change
return f"{self.collection} - {self.language} ({self.size})"
return f"{self.collection} - {self.language}"

Copilot uses AI. Check for mistakes.
Comment on lines 54 to 58
if resp.status_code != 200:
logging.error(f"Erro ao enviar dados de coleção para {url}. Status: {resp.status_code}. Body: {resp.text}")
resp.raise_for_status()
return resp.json()
except Exception as e:
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error logging occurs before raise_for_status(), which means the function will continue and potentially raise an exception after logging. The logging should be inside an exception handler or the raise_for_status() should be moved before the logging.

Suggested change
if resp.status_code != 200:
logging.error(f"Erro ao enviar dados de coleção para {url}. Status: {resp.status_code}. Body: {resp.text}")
resp.raise_for_status()
return resp.json()
except Exception as e:
resp.raise_for_status()
return resp.json()
except Exception as e:
logging.error(f"Erro ao enviar dados de coleção para {url}. Exception: {e}")

Copilot uses AI. Check for mistakes.
return rendition.url
domain = Site.objects.get(is_default_site=True).hostname
domain = f"http://{domain}"
return f"{domain}{obj.logo.file.url}"
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using obj.logo.file.url directly bypasses Wagtail's image rendition system and may expose internal file paths. Consider using obj.logo.url instead for consistency with the original implementation.

Suggested change
return f"{domain}{obj.logo.file.url}"
return f"{domain}{obj.logo.url}"

Copilot uses AI. Check for mistakes.
{% trans 'Sobre o SciELO' %}
{% endif %}
</span>
</span>
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a duplicate closing </span> tag. The closing span tag on line 94 should be removed as it creates invalid HTML structure.

Suggested change
</span>

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 52 to 57
try:
resp = requests.post(pattern_url, json=payload, headers=headers, timeout=5)
resp.raise_for_status()
return resp.json()
except Exception as e:
logging.error(f"Erro ao enviar dados de coleção para {url}. Status: {resp.status_code}. Body: {resp.text}")
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable resp is referenced before it's guaranteed to be defined. If the exception occurs during the requests.post() call, resp won't exist, causing a NameError.

Suggested change
try:
resp = requests.post(pattern_url, json=payload, headers=headers, timeout=5)
resp.raise_for_status()
return resp.json()
except Exception as e:
logging.error(f"Erro ao enviar dados de coleção para {url}. Status: {resp.status_code}. Body: {resp.text}")
resp = None
try:
resp = requests.post(pattern_url, json=payload, headers=headers, timeout=5)
resp.raise_for_status()
return resp.json()
except Exception as e:
if resp is not None:
logging.error(f"Erro ao enviar dados de coleção para {url}. Status: {resp.status_code}. Body: {resp.text}")
else:
logging.error(f"Erro ao enviar dados de coleção para {url}. Nenhuma resposta recebida. Exception: {e}")

Copilot uses AI. Check for mistakes.
Collection.load(user)


def fetch_with_protocol_guess(host_or_url, timeout=10):
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function is missing a return type annotation and docstring explanation of what happens when both protocols fail.

Copilot uses AI. Check for mistakes.
collection = Collection.objects.get(acron3=collection_acron, is_active=True)

if not collection.domain:
return None
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function returns None when both HTTP and HTTPS protocols fail, but the caller expects a URL string. This could cause issues in _send_payload when concatenating with settings.ENDPOINT_COLLECTION.

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 13
domain = Site.objects.get(is_default_site=True).hostname
domain = f"http://{domain}"
return f"{domain}{obj.logo.url}"
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes there's always a default site. If no default site exists, this will raise a DoesNotExist exception. Consider using get_or_create() or handling the exception.

Suggested change
domain = Site.objects.get(is_default_site=True).hostname
domain = f"http://{domain}"
return f"{domain}{obj.logo.url}"
try:
domain = Site.objects.get(is_default_site=True).hostname
domain = f"http://{domain}"
return f"{domain}{obj.logo.url}"
except Site.DoesNotExist:
return None

Copilot uses AI. Check for mistakes.
Comment on lines 62 to 63
domain = Site.objects.get(is_default_site=True).hostname
domain = f"http://{domain}"
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as in organization serializer - assumes there's always a default site. If no default site exists, this will raise a DoesNotExist exception.

Copilot uses AI. Check for mistakes.
Comment on lines 236 to 237


LOGO_SIZE_CHOICES = [
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOGO_SIZE_CHOICES is still defined but appears to be unused after the refactoring to purpose-based logos. Consider removing this unused constant.

Copilot uses AI. Check for mistakes.
ordering = ['sort_order', 'language']
unique_together = [
("collection", "size", "language"),
('collection', 'language', 'purpose'),
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The unique constraint uses single quotes while other parts of the codebase use double quotes for consistency. Consider using double quotes.

Suggested change
('collection', 'language', 'purpose'),
("collection", "language", "purpose"),

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant