redo models again
This commit is contained in:
		| @ -1,13 +1,12 @@ | ||||
| # Generated by Django 2.1.3 on 2018-11-11 14:06 | ||||
|  | ||||
| import uuid | ||||
| # Generated by Django 2.1.3 on 2018-11-16 10:21 | ||||
|  | ||||
| from django.conf import settings | ||||
| import django.contrib.auth.models | ||||
| import django.contrib.auth.validators | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| import django.utils.timezone | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
| import uuid | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
| @ -33,7 +32,6 @@ class Migration(migrations.Migration): | ||||
|                 ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), | ||||
|                 ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), | ||||
|                 ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), | ||||
|                 ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'user', | ||||
| @ -58,6 +56,12 @@ class Migration(migrations.Migration): | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='Provider', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|             ], | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='Rule', | ||||
|             fields=[ | ||||
| @ -114,6 +118,21 @@ class Migration(migrations.Migration): | ||||
|             name='application', | ||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Application'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='application', | ||||
|             name='provider', | ||||
|             field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='applications', | ||||
|             field=models.ManyToManyField(to='passbook_core.Application'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='groups', | ||||
|             field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='sources', | ||||
|  | ||||
| @ -16,6 +16,13 @@ class User(AbstractUser): | ||||
|     """Custom User model to allow easier adding o f user-based settings""" | ||||
|  | ||||
|     sources = models.ManyToManyField('Source', through='UserSourceConnection') | ||||
|     applications = models.ManyToManyField('Application') | ||||
|  | ||||
| @reversion.register() | ||||
| class Provider(models.Model): | ||||
|     """Application-independant Provider instance. For example SAML2 Remote, OAuth2 Application""" | ||||
|  | ||||
|     # This class defines no field for easier inheritance | ||||
|  | ||||
| @reversion.register() | ||||
| class Application(UUIDModel, CreatedUpdatedModel): | ||||
| @ -26,6 +33,7 @@ class Application(UUIDModel, CreatedUpdatedModel): | ||||
|     name = models.TextField() | ||||
|     launch_url = models.URLField(null=True, blank=True) | ||||
|     icon_url = models.TextField(null=True, blank=True) | ||||
|     provider = models.ForeignKey('Provider', null=True, default=None, on_delete=models.SET_DEFAULT) | ||||
|  | ||||
|     objects = InheritanceManager() | ||||
|  | ||||
|  | ||||
| @ -53,9 +53,9 @@ INSTALLED_APPS = [ | ||||
|     'django.contrib.messages', | ||||
|     'django.contrib.staticfiles', | ||||
|     'reversion', | ||||
|     'rest_framework', | ||||
|     'passbook.core', | ||||
|     'passbook.admin', | ||||
|     'rest_framework', | ||||
|     'passbook.lib', | ||||
|     'passbook.ldap', | ||||
|     'passbook.oauth_client', | ||||
|  | ||||
| @ -1,11 +1,16 @@ | ||||
| {% load static %} | ||||
| {% load i18n %} | ||||
| {% load utils %} | ||||
|  | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <title>{% trans 'passbook' %}</title> | ||||
|     <title> | ||||
|       {% block title %} | ||||
|         {% title %} | ||||
|       {% endblock %} | ||||
|     </title> | ||||
|     <link rel="stylesheet" type="text/css" href="{% static 'css/patternfly.min.css' %}"> | ||||
|     <link rel="stylesheet" type="text/css" href="{% static 'css/patternfly-additions.min.css' %}"> | ||||
|   </head> | ||||
|  | ||||
| @ -24,9 +24,12 @@ urlpatterns = [ | ||||
|          include(('passbook.admin.urls', 'passbook_admin'), namespace='passbook_admin')), | ||||
|     path('source/oauth/', include(('passbook.oauth_client.urls', | ||||
|                                    'passbook_oauth_client'), namespace='passbook_oauth_client')), | ||||
|     path('application/oauth', include(('passbook.oauth_provider.urls', | ||||
|                                        'passbook_oauth_provider'), | ||||
|                                         namespace='passbook_oauth_provider')), | ||||
|     path('application/oauth/', include(('passbook.oauth_provider.urls', | ||||
|                                         'passbook_oauth_provider'), | ||||
|                                        namespace='passbook_oauth_provider')), | ||||
|     path('application/saml/', include(('passbook.saml_idp.urls', | ||||
|                                        'passbook_saml_idp'), | ||||
|                                       namespace='passbook_saml_idp')), | ||||
| ] | ||||
|  | ||||
| if settings.DEBUG: | ||||
|  | ||||
| @ -75,7 +75,7 @@ class LoginView(UserPassesTestMixin, FormView): | ||||
|         login(request, user) | ||||
|  | ||||
|         if cleaned_data.get('remember') is True: | ||||
|             request.session.set_expiry(CONFIG.get('passbook').get('session').get('remember_age')) | ||||
|             request.session.set_expiry(CONFIG.y('passbook.session.remember_age')) | ||||
|         else: | ||||
|             request.session.set_expiry(0)  # Expires when browser is closed | ||||
|         messages.success(request, _("Successfully logged in!")) | ||||
| @ -98,4 +98,5 @@ class LoginView(UserPassesTestMixin, FormView): | ||||
|             context = { | ||||
|                 'reason': 'invalid', | ||||
|             } | ||||
|         raise NotImplementedError() | ||||
|         return render(request, 'login/invalid.html', context) | ||||
|  | ||||
| @ -9,3 +9,7 @@ class OverviewView(LoginRequiredMixin, TemplateView): | ||||
|     and is not being forwarded""" | ||||
|  | ||||
|     template_name = 'overview/index.html' | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         kwargs['applications'] = self.request.user.applications.objects.all() | ||||
|         return super().get_context_data(**kwargs) | ||||
|  | ||||
| @ -76,7 +76,7 @@ def pick(cont, arg, fallback=''): | ||||
| @register.simple_tag(takes_context=True) | ||||
| def title(context, *title): | ||||
|     """Return either just branding or title - branding""" | ||||
|     branding = Setting.get('branding', default='passbook') | ||||
|     branding = CONFIG.y('passbook.branding', 'passbook') | ||||
|     if not title: | ||||
|         return branding | ||||
|     # Include App Title in title | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| # Generated by Django 2.1.3 on 2018-11-11 14:06 | ||||
| # Generated by Django 2.1.3 on 2018-11-16 10:21 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
| @ -9,7 +9,7 @@ class Migration(migrations.Migration): | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '0001_initial'), | ||||
|         ('passbook_core', '__first__'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| # Generated by Django 2.1.3 on 2018-11-14 18:35 | ||||
| # Generated by Django 2.1.3 on 2018-11-16 10:21 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
| @ -11,19 +11,16 @@ class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL), | ||||
|         ('passbook_core', '0001_initial'), | ||||
|         ('passbook_core', '__first__'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='OAuth2Application', | ||||
|             name='OAuth2Provider', | ||||
|             fields=[ | ||||
|                 ('application_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Application')), | ||||
|                 ('oauth2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)), | ||||
|                 ('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')), | ||||
|                 ('oauth2_app', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|             bases=('passbook_core.application',), | ||||
|             bases=('passbook_core.provider',), | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| """Oauth2 provider product extension""" | ||||
|  | ||||
| from django.db import models | ||||
| from oauth2_provider.models import Application as _OAuth2Application | ||||
| from oauth2_provider.models import Application | ||||
|  | ||||
| from passbook.core.models import Application | ||||
| from passbook.core.models import Provider | ||||
|  | ||||
|  | ||||
| class OAuth2Application(Application): | ||||
| class OAuth2Provider(Provider): | ||||
|     """Associate an OAuth2 Application with a Product""" | ||||
|  | ||||
|     oauth2 = models.ForeignKey(_OAuth2Application, on_delete=models.CASCADE) | ||||
|     oauth2_app = models.ForeignKey(Application, on_delete=models.CASCADE) | ||||
|  | ||||
| @ -8,5 +8,5 @@ urlpatterns = [ | ||||
|     # Custom OAuth 2 Authorize View | ||||
|     # path('authorize/', oauth2.PassbookAuthorizationView.as_view(), name="oauth2-authorize"), | ||||
|     # OAuth API | ||||
|     path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')), | ||||
|     path('', include('oauth2_provider.urls', namespace='oauth2_provider')), | ||||
| ] | ||||
|  | ||||
							
								
								
									
										29
									
								
								passbook/saml_idp/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								passbook/saml_idp/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| # Generated by Django 2.1.3 on 2018-11-16 10:21 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('passbook_core', '__first__'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='SAMLApplication', | ||||
|             fields=[ | ||||
|                 ('application_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Application')), | ||||
|                 ('acs_url', models.URLField()), | ||||
|                 ('processor_path', models.CharField(max_length=255)), | ||||
|                 ('skip_authorization', models.BooleanField(default=False)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|             bases=('passbook_core.application',), | ||||
|         ), | ||||
|     ] | ||||
| @ -7,7 +7,7 @@ from passbook.lib.utils.reflection import class_to_path | ||||
| from passbook.saml_idp.base import Processor | ||||
|  | ||||
|  | ||||
| class SAMLRemote(Application): | ||||
| class SAMLApplication(Application): | ||||
|     """Model to save information about a Remote SAML Endpoint""" | ||||
|  | ||||
|     acs_url = models.URLField() | ||||
| @ -20,4 +20,4 @@ class SAMLRemote(Application): | ||||
|         self._meta.get_field('processor_path').choices = processors | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "SAMLRemote %s (processor=%s)" % (self.name, self.processor_path) | ||||
|         return "SAMLApplication %s (processor=%s)" % (self.name, self.processor_path) | ||||
|  | ||||
| @ -3,7 +3,7 @@ from logging import getLogger | ||||
|  | ||||
| from passbook.lib.utils.reflection import path_to_class | ||||
| from passbook.saml_idp.exceptions import CannotHandleAssertion | ||||
| from passbook.saml_idp.models import SAMLRemote | ||||
| from passbook.saml_idp.models import SAMLApplication | ||||
|  | ||||
| LOGGER = getLogger(__name__) | ||||
|  | ||||
| @ -16,7 +16,7 @@ def get_processor(remote): | ||||
|  | ||||
| def find_processor(request): | ||||
|     """Returns the Processor instance that is willing to handle this request.""" | ||||
|     for remote in SAMLRemote.objects.all(): | ||||
|     for remote in SAMLApplication.objects.all(): | ||||
|         proc = get_processor(remote) | ||||
|         try: | ||||
|             if proc.can_handle(request): | ||||
|  | ||||
| @ -8,5 +8,5 @@ urlpatterns = [ | ||||
|     url(r'^login/process/$', views.login_process, name='saml_login_process'), | ||||
|     url(r'^logout/$', views.logout, name="saml_logout"), | ||||
|     url(r'^metadata/xml/$', views.descriptor, name='metadata_xml'), | ||||
|     url(r'^settings/$', views.IDPSettingsView.as_view(), name='admin_settings'), | ||||
|     # url(r'^settings/$', views.IDPSettingsView.as_view(), name='admin_settings'), | ||||
| ] | ||||
|  | ||||
| @ -17,22 +17,19 @@ from OpenSSL.crypto import FILETYPE_PEM | ||||
| from OpenSSL.crypto import Error as CryptoError | ||||
| from OpenSSL.crypto import load_certificate | ||||
|  | ||||
| from passbook.core.models import Event, Setting, UserAcquirableRelationship | ||||
| from passbook.core.utils import render_to_string | ||||
| from passbook.core.views.common import ErrorResponseView | ||||
| from passbook.core.views.settings import GenericSettingView | ||||
| from passbook.mod.auth.saml.idp import exceptions, registry, xml_signing | ||||
| from passbook.mod.auth.saml.idp.forms.settings import IDPSettingsForm | ||||
| # from passbook.core.models import Event, Setting, UserAcquirableRelationship | ||||
| from passbook.lib.utils.template import render_to_string | ||||
| # from passbook.core.views.common import ErrorResponseView | ||||
| # from passbook.core.views.settings import GenericSettingView | ||||
| from passbook.saml_idp import exceptions, registry, xml_signing | ||||
|  | ||||
| LOGGER = getLogger(__name__) | ||||
| URL_VALIDATOR = URLValidator(schemes=('http', 'https')) | ||||
|  | ||||
|  | ||||
| def _generate_response(request, processor, remote): | ||||
|     """ | ||||
|     Generate a SAML response using processor and return it in the proper Django | ||||
|     response. | ||||
|     """ | ||||
|     """Generate a SAML response using processor and return it in the proper Django | ||||
|     response.""" | ||||
|     try: | ||||
|         ctx = processor.generate_response() | ||||
|         ctx['remote'] = remote | ||||
| @ -49,10 +46,8 @@ def render_xml(request, template, ctx): | ||||
|  | ||||
| @csrf_exempt | ||||
| def login_begin(request): | ||||
|     """ | ||||
|     Receives a SAML 2.0 AuthnRequest from a Service Provider and | ||||
|     stores it in the session prior to enforcing login. | ||||
|     """ | ||||
|     """Receives a SAML 2.0 AuthnRequest from a Service Provider and | ||||
|     stores it in the session prior to enforcing login.""" | ||||
|     if request.method == 'POST': | ||||
|         source = request.POST | ||||
|     else: | ||||
| @ -65,13 +60,11 @@ def login_begin(request): | ||||
|         return HttpResponseBadRequest('the SAML request payload is missing') | ||||
|  | ||||
|     request.session['RelayState'] = source.get('RelayState', '') | ||||
|     return redirect(reverse('passbook_mod_auth_saml_idp:saml_login_process')) | ||||
|     return redirect(reverse('passbook_saml_idp:saml_login_process')) | ||||
|  | ||||
|  | ||||
| def redirect_to_sp(request, acs_url, saml_response, relay_state): | ||||
|     """ | ||||
|     Return autosubmit form | ||||
|     """ | ||||
|     """Return autosubmit form""" | ||||
|     return render(request, 'core/autosubmit_form.html', { | ||||
|         'url': acs_url, | ||||
|         'attrs': { | ||||
| @ -83,10 +76,8 @@ def redirect_to_sp(request, acs_url, saml_response, relay_state): | ||||
|  | ||||
| @login_required | ||||
| def login_process(request): | ||||
|     """ | ||||
|     Processor-based login continuation. | ||||
|     Presents a SAML 2.0 Assertion for POSTing back to the Service Provider. | ||||
|     """ | ||||
|     """Processor-based login continuation. | ||||
|     Presents a SAML 2.0 Assertion for POSTing back to the Service Provider.""" | ||||
|     LOGGER.debug("Request: %s", request) | ||||
|     proc, remote = registry.find_processor(request) | ||||
|     # Check if user has access | ||||
| @ -141,11 +132,9 @@ def login_process(request): | ||||
|  | ||||
| @csrf_exempt | ||||
| def logout(request): | ||||
|     """ | ||||
|     Allows a non-SAML 2.0 URL to log out the user and | ||||
|     """Allows a non-SAML 2.0 URL to log out the user and | ||||
|     returns a standard logged-out page. (SalesForce and others use this method, | ||||
|     though it's technically not SAML 2.0). | ||||
|     """ | ||||
|     though it's technically not SAML 2.0).""" | ||||
|     auth.logout(request) | ||||
|  | ||||
|     redirect_url = request.GET.get('redirect_to', '') | ||||
| @ -163,10 +152,8 @@ def logout(request): | ||||
| @login_required | ||||
| @csrf_exempt | ||||
| def slo_logout(request): | ||||
|     """ | ||||
|     Receives a SAML 2.0 LogoutRequest from a Service Provider, | ||||
|     logs out the user and returns a standard logged-out page. | ||||
|     """ | ||||
|     """Receives a SAML 2.0 LogoutRequest from a Service Provider, | ||||
|     logs out the user and returns a standard logged-out page.""" | ||||
|     request.session['SAMLRequest'] = request.POST['SAMLRequest'] | ||||
|     # TODO: Parse SAML LogoutRequest from POST data, similar to login_process(). | ||||
|     # TODO: Add a URL dispatch for this view. | ||||
| @ -179,12 +166,10 @@ def slo_logout(request): | ||||
|  | ||||
|  | ||||
| def descriptor(request): | ||||
|     """ | ||||
|     Replies with the XML Metadata IDSSODescriptor. | ||||
|     """ | ||||
|     entity_id = Setting.get('issuer') | ||||
|     slo_url = request.build_absolute_uri(reverse('passbook_mod_auth_saml_idp:saml_logout')) | ||||
|     sso_url = request.build_absolute_uri(reverse('passbook_mod_auth_saml_idp:saml_login_begin')) | ||||
|     """Replies with the XML Metadata IDSSODescriptor.""" | ||||
|     entity_id = CONFIG.y('saml_idp.issuer') | ||||
|     slo_url = request.build_absolute_uri(reverse('passbook_saml_idp:saml_logout')) | ||||
|     sso_url = request.build_absolute_uri(reverse('passbook_saml_idp:saml_login_begin')) | ||||
|     pubkey = xml_signing.load_certificate(strip=True) | ||||
|     ctx = { | ||||
|         'entity_id': entity_id, | ||||
| @ -194,25 +179,25 @@ def descriptor(request): | ||||
|     } | ||||
|     metadata = render_to_string('saml/xml/metadata.xml', ctx) | ||||
|     response = HttpResponse(metadata, content_type='application/xml') | ||||
|     response['Content-Disposition'] = 'attachment; filename="sv_metadata.xml' | ||||
|     response['Content-Disposition'] = 'attachment; filename="passbook_metadata.xml' | ||||
|     return response | ||||
|  | ||||
|  | ||||
| class IDPSettingsView(GenericSettingView): | ||||
|     """IDP Settings""" | ||||
| # class IDPSettingsView(GenericSettingView): | ||||
| #     """IDP Settings""" | ||||
|  | ||||
|     form = IDPSettingsForm | ||||
|     template_name = 'saml/idp/settings.html' | ||||
| #     form = IDPSettingsForm | ||||
| #     template_name = 'saml/idp/settings.html' | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         self.extra_data['metadata'] = escape(descriptor(request).content.decode('utf-8')) | ||||
| #     def dispatch(self, request, *args, **kwargs): | ||||
| #         self.extra_data['metadata'] = escape(descriptor(request).content.decode('utf-8')) | ||||
|  | ||||
|         # Show the certificate fingerprint | ||||
|         sha1_fingerprint = _('<failed to parse certificate>') | ||||
|         try: | ||||
|             cert = load_certificate(FILETYPE_PEM, Setting.get('certificate')) | ||||
|             sha1_fingerprint = cert.digest("sha1") | ||||
|         except CryptoError: | ||||
|             pass | ||||
|         self.extra_data['fingerprint'] = sha1_fingerprint | ||||
|         return super().dispatch(request, *args, **kwargs) | ||||
| #         # Show the certificate fingerprint | ||||
| #         sha1_fingerprint = _('<failed to parse certificate>') | ||||
| #         try: | ||||
| #             cert = load_certificate(FILETYPE_PEM, CONFIG.y('saml_idp.certificate')) | ||||
| #             sha1_fingerprint = cert.digest("sha1") | ||||
| #         except CryptoError: | ||||
| #             pass | ||||
| #         self.extra_data['fingerprint'] = sha1_fingerprint | ||||
| #         return super().dispatch(request, *args, **kwargs) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer