*(minor): small refactor

This commit is contained in:
Langhammer, Jens
2019-10-07 16:33:48 +02:00
parent d21ec6c9a5
commit f2acc154cd
300 changed files with 1420 additions and 1788 deletions

View File

View File

@ -0,0 +1,5 @@
"""passbook Application Security Gateway model admin"""
from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_providers_app_gw')

View File

@ -0,0 +1,11 @@
"""passbook Application Security Gateway app"""
from django.apps import AppConfig
class PassbookApplicationApplicationGatewayConfig(AppConfig):
"""passbook app_gw app"""
name = 'passbook.providers.app_gw'
label = 'passbook_providers_app_gw'
verbose_name = 'passbook Providers.Application Security Gateway'
mountpoint = 'application/gateway/'

View File

@ -0,0 +1,67 @@
"""passbook Application Security Gateway Forms"""
from urllib.parse import urlparse
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms import ValidationError
from django.utils.translation import gettext as _
from passbook.lib.fields import DynamicArrayField
from passbook.providers.app_gw.models import (ApplicationGatewayProvider,
RewriteRule)
class ApplicationGatewayProviderForm(forms.ModelForm):
"""Security Gateway Provider form"""
def clean_server_name(self):
"""Check if server_name is in DB already, since
Postgres ArrayField doesn't suppport keys."""
current = self.cleaned_data.get('server_name')
if ApplicationGatewayProvider.objects \
.filter(server_name__overlap=current) \
.exclude(pk=self.instance.pk).exists():
raise ValidationError(_("Server Name already in use."))
return current
def clean_upstream(self):
"""Check that upstream begins with http(s)"""
for upstream in self.cleaned_data.get('upstream'):
_parsed_url = urlparse(upstream)
if _parsed_url.scheme not in ('http', 'https'):
raise ValidationError(_("URL Scheme must be either http or https"))
return self.cleaned_data.get('upstream')
class Meta:
model = ApplicationGatewayProvider
fields = ['server_name', 'upstream', 'enabled', 'authentication_header',
'default_content_type', 'upstream_ssl_verification', 'property_mappings']
widgets = {
'authentication_header': forms.TextInput(),
'default_content_type': forms.TextInput(),
'property_mappings': FilteredSelectMultiple(_('Property Mappings'), False)
}
field_classes = {
'server_name': DynamicArrayField,
'upstream': DynamicArrayField
}
labels = {
'upstream_ssl_verification': _('Verify upstream SSL Certificates?'),
'property_mappings': _('Rewrite Rules')
}
class RewriteRuleForm(forms.ModelForm):
"""Rewrite Rule Form"""
class Meta:
model = RewriteRule
fields = ['name', 'match', 'halt', 'replacement', 'redirect', 'conditions']
widgets = {
'name': forms.TextInput(),
'match': forms.TextInput(attrs={'data-is-monospace': True}),
'replacement': forms.TextInput(attrs={'data-is-monospace': True}),
'conditions': FilteredSelectMultiple(_('Conditions'), False)
}

View File

@ -0,0 +1,55 @@
import time
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
from django.utils.cache import patch_vary_headers
from django.utils.http import cookie_date
from structlog import get_logger
from passbook.factors.view import AuthenticationView
LOGGER = get_logger()
class SessionHostDomainMiddleware(SessionMiddleware):
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
except AttributeError:
pass
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
request.session.save()
hosts = [request.get_host().split(':')[0]]
if AuthenticationView.SESSION_FORCE_COOKIE_HOSTNAME in request.session:
hosts.append(request.session[AuthenticationView.SESSION_FORCE_COOKIE_HOSTNAME])
LOGGER.debug("Setting hosts for session", hosts=hosts)
for host in hosts:
response.set_cookie(settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age,
expires=expires, domain=host,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None)
return response

View File

@ -0,0 +1,50 @@
# Generated by Django 2.2.6 on 2019-10-07 14:07
import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('passbook_core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ApplicationGatewayProvider',
fields=[
('provider_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Provider')),
('server_name', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
('upstream', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
('enabled', models.BooleanField(default=True)),
('authentication_header', models.TextField(blank=True, default='X-Remote-User')),
('default_content_type', models.TextField(default='application/octet-stream')),
('upstream_ssl_verification', models.BooleanField(default=True)),
],
options={
'verbose_name': 'Application Gateway Provider',
'verbose_name_plural': 'Application Gateway Providers',
},
bases=('passbook_core.provider',),
),
migrations.CreateModel(
name='RewriteRule',
fields=[
('propertymapping_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PropertyMapping')),
('match', models.TextField()),
('halt', models.BooleanField(default=False)),
('replacement', models.TextField()),
('redirect', models.CharField(choices=[('internal', 'Internal'), (301, 'Moved Permanently'), (302, 'Found')], max_length=50)),
('conditions', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
],
options={
'verbose_name': 'Rewrite Rule',
'verbose_name_plural': 'Rewrite Rules',
},
bases=('passbook_core.propertymapping',),
),
]

View File

@ -0,0 +1,74 @@
"""passbook app_gw models"""
import re
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext as _
from passbook.core.models import Policy, PropertyMapping, Provider
class ApplicationGatewayProvider(Provider):
"""Virtual server which proxies requests to any hostname in server_name to upstream"""
server_name = ArrayField(models.TextField())
upstream = ArrayField(models.TextField())
enabled = models.BooleanField(default=True)
authentication_header = models.TextField(default='X-Remote-User', blank=True)
default_content_type = models.TextField(default='application/octet-stream')
upstream_ssl_verification = models.BooleanField(default=True)
form = 'passbook.providers.app_gw.forms.ApplicationGatewayProviderForm'
@property
def name(self):
"""since this model has no name property, return a joined list of server_names as name"""
return ', '.join(self.server_name)
def __str__(self):
return "Application Gateway %s" % ', '.join(self.server_name)
class Meta:
verbose_name = _('Application Gateway Provider')
verbose_name_plural = _('Application Gateway Providers')
class RewriteRule(PropertyMapping):
"""Rewrite requests matching `match` with `replacement`, if all polcies in `conditions` apply"""
REDIRECT_INTERNAL = 'internal'
REDIRECT_PERMANENT = 301
REDIRECT_FOUND = 302
REDIRECTS = (
(REDIRECT_INTERNAL, _('Internal')),
(REDIRECT_PERMANENT, _('Moved Permanently')),
(REDIRECT_FOUND, _('Found')),
)
match = models.TextField()
halt = models.BooleanField(default=False)
conditions = models.ManyToManyField(Policy, blank=True)
replacement = models.TextField() # python formatted strings, use {match.1}
redirect = models.CharField(max_length=50, choices=REDIRECTS)
form = 'passbook.providers.app_gw.forms.RewriteRuleForm'
_matcher = None
@property
def compiled_matcher(self):
"""Cache the compiled regex in memory"""
if not self._matcher:
self._matcher = re.compile(self.match)
return self._matcher
def __str__(self):
return "Rewrite Rule %s" % self.name
class Meta:
verbose_name = _('Rewrite Rule')
verbose_name_plural = _('Rewrite Rules')

View File

@ -0,0 +1,8 @@
"""passbook app_gw urls"""
from django.urls import path
from passbook.providers.app_gw.views import NginxCheckView
urlpatterns = [
path('nginx/', NginxCheckView.as_view())
]

View File

@ -0,0 +1,36 @@
"""passbook app_gw views"""
from pprint import pprint
from urllib.parse import urlparse
from django.http import HttpRequest, HttpResponse
from django.views import View
from structlog import get_logger
from passbook.core.views.access import AccessMixin
from passbook.providers.app_gw.models import ApplicationGatewayProvider
ORIGINAL_URL = 'HTTP_X_ORIGINAL_URL'
LOGGER = get_logger()
class NginxCheckView(AccessMixin, View):
def dispatch(self, request: HttpRequest) -> HttpResponse:
pprint(request.META)
parsed_url = urlparse(request.META.get(ORIGINAL_URL))
# request.session[AuthenticationView.SESSION_ALLOW_ABSOLUTE_NEXT] = True
# request.session[AuthenticationView.SESSION_FORCE_COOKIE_HOSTNAME] = parsed_url.hostname
print(request.user)
if not request.user.is_authenticated:
return HttpResponse(status=401)
matching = ApplicationGatewayProvider.objects.filter(
server_name__contains=[parsed_url.hostname])
if not matching.exists():
LOGGER.debug("Couldn't find matching application", host=parsed_url.hostname)
return HttpResponse(status=403)
application = self.provider_to_application(matching.first())
has_access, _ = self.user_has_access(application, request.user)
if has_access:
return HttpResponse(status=202)
LOGGER.debug("User not passing", user=request.user)
return HttpResponse(status=401)