passerelle/passerelle/apps/proxy/models.py

95 lines
3.4 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from urllib.parse import parse_qsl
from django.db import models
from django.http.response import HttpResponse
from django.utils.translation import gettext_lazy as _
from passerelle.base.models import BaseResource, HTTPResource
from passerelle.utils.api import endpoint
PASS_HEADERS_REQUEST = (
'accept',
'accept-encoding',
'accept-language',
'cookie',
'user-agent',
)
class Resource(BaseResource, HTTPResource):
category = _('Misc')
upstream_base_url = models.URLField(_('Upstream Service Base URL'))
http_timeout = models.PositiveIntegerField(_('Timeout on upstream (in seconds)'), default=20)
forced_headers = models.TextField(
_('Headers'),
blank=True,
help_text=_('Headers to always add (one per line, format "Header-Name: value")'),
)
class Meta:
verbose_name = _('Proxy')
@endpoint(
name='request',
perm='can_access',
methods=['get', 'post', 'delete', 'put', 'patch'],
pattern=r'^(?P<path>.*)$',
description=_('Make a request'),
example_pattern='{path}',
parameters={
'path': {
'description': _('request will be made on Upstream Service Base URL + path'),
'example_value': 'foo/bar',
}
},
)
def request(self, request, path, *args, **kwargs):
params = parse_qsl(request.META.get('QUERY_STRING'))
if params and params[-1][0] == 'signature':
# remove Publik signature parts: orig, algo, timestamp, nonce, signature
params = [
(k, v) for k, v in params if k not in ('orig', 'algo', 'timestamp', 'nonce', 'signature')
]
headers = {k: v for k, v in request.headers.items() if v and k.lower() in PASS_HEADERS_REQUEST}
if request.method != 'GET':
headers['Content-Type'] = request.headers.get('content-type')
for header in self.forced_headers.splitlines():
header = header.strip()
if header.startswith('#'):
continue
header = header.split(':', 1)
if len(header) == 2:
headers[header[0].strip()] = header[1].strip()
upstream = self.requests.request(
method=request.method,
url=self.upstream_base_url + path,
headers=headers,
params=params,
data=request.body,
timeout=self.http_timeout,
)
response = HttpResponse(
upstream.content,
content_type=upstream.headers.get('Content-Type'),
status=upstream.status_code,
reason=upstream.reason,
)
return response