fase 3.5: licenciamiento por plan (clave firmada, planes basico/profesional/enterprise/custom, modulos incluidos y limites de usuarios/sucursales/citas/almacenamiento, modo aviso o bloqueo, panel de licencia) + API keys gestionables con alcances y revocacion (arregla la API publica que estaba rota) + webhooks salientes firmados con outbox, reintentos y bitacora; herramientas de vendedor y docs
This commit is contained in:
125
odoo-addons/skeen_plataforma/controllers/api_keys.py
Normal file
125
odoo-addons/skeen_plataforma/controllers/api_keys.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""CRUD de claves de API de terceros (Configuración -> API)."""
|
||||
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request, Response
|
||||
|
||||
from odoo.addons.skeen_whatsapp.controllers.auth import require_role, require_perm
|
||||
from odoo.addons.skeen_plataforma.models.api_key import SCOPES
|
||||
|
||||
BASE = '/skeen/frontend/v1'
|
||||
|
||||
|
||||
def _json(data, status=200):
|
||||
return Response(
|
||||
json.dumps(data, default=str),
|
||||
status=status,
|
||||
mimetype='application/json',
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _body():
|
||||
try:
|
||||
raw = request.httprequest.get_data(as_text=True)
|
||||
return json.loads(raw) if raw else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _login():
|
||||
return getattr(request.frontend_user, 'login', '') if hasattr(request, 'frontend_user') else ''
|
||||
|
||||
|
||||
class SkeenApiKeysController(http.Controller):
|
||||
|
||||
@http.route(BASE + '/api_keys', type='http', auth='none',
|
||||
methods=['GET', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def list_api_keys(self, **kw):
|
||||
try:
|
||||
keys = request.env['skeen.api.key'].sudo().search([])
|
||||
return _json({
|
||||
'status': 'success',
|
||||
'api_keys': [k.to_public_dict() for k in keys],
|
||||
'scopes': [{'clave': c, 'label': l} for c, l in SCOPES],
|
||||
})
|
||||
except Exception as e:
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/api_keys', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def create_api_key(self, **kw):
|
||||
try:
|
||||
data = _body()
|
||||
nombre = (data.get('name') or '').strip()
|
||||
if not nombre:
|
||||
return _json({'status': 'error', 'message': 'El nombre es obligatorio'}, 400)
|
||||
key, raw = request.env['skeen.api.key'].sudo().generar({
|
||||
'name': nombre,
|
||||
'scopes': data.get('scopes') or [],
|
||||
'vence': data.get('vence') or False,
|
||||
'notas': data.get('notas') or '',
|
||||
}, login=_login())
|
||||
return _json({
|
||||
'status': 'success',
|
||||
'clave': raw, # se muestra UNA sola vez
|
||||
'api_key': key.to_public_dict(),
|
||||
'message': 'Guarda esta clave: no se volverá a mostrar.',
|
||||
})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/api_keys/<int:key_id>', type='http', auth='none',
|
||||
methods=['PUT', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def update_api_key(self, key_id, **kw):
|
||||
try:
|
||||
key = request.env['skeen.api.key'].sudo().browse(key_id)
|
||||
if not key.exists():
|
||||
return _json({'status': 'error', 'message': 'Clave no encontrada'}, 404)
|
||||
data = _body()
|
||||
vals = {}
|
||||
if 'name' in data and (data.get('name') or '').strip():
|
||||
vals['name'] = data['name'].strip()
|
||||
if 'scopes' in data:
|
||||
vals['scopes'] = json.dumps(key._normalizar_scopes(data.get('scopes') or []))
|
||||
if 'activo' in data:
|
||||
vals['activo'] = bool(data['activo'])
|
||||
if 'vence' in data:
|
||||
vals['vence'] = data.get('vence') or False
|
||||
if 'notas' in data:
|
||||
vals['notas'] = data.get('notas') or ''
|
||||
if vals:
|
||||
key.write(vals)
|
||||
return _json({'status': 'success', 'api_key': key.to_public_dict()})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/api_keys/<int:key_id>/revocar', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def revocar_api_key(self, key_id, **kw):
|
||||
try:
|
||||
key = request.env['skeen.api.key'].sudo().browse(key_id)
|
||||
if not key.exists():
|
||||
return _json({'status': 'error', 'message': 'Clave no encontrada'}, 404)
|
||||
key.action_revocar(login=_login())
|
||||
return _json({'status': 'success', 'message': 'Clave revocada.',
|
||||
'api_key': key.to_public_dict()})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
Reference in New Issue
Block a user