diff --git a/passbook/core/templates/base/skeleton_login.html b/passbook/core/templates/base/skeleton_login.html
new file mode 100644
index 000000000..a747775c9
--- /dev/null
+++ b/passbook/core/templates/base/skeleton_login.html
@@ -0,0 +1,54 @@
+{% load static %}
+{% load i18n %}
+{% load utils %}
+
+
+
+
+
+
+
+
+ {% block title %}
+ {% title %}
+ {% endblock %}
+
+
+
+
+
+
+
+ {% block head %}
+ {% endblock %}
+
+
+
+ {% if 'impersonate_id' in request.session %}
+
+ {% endif %}
+ {% block body %}
+ {% endblock %}
+
+
+
+
+ {% block scripts %}
+ {% endblock %}
+
+ {% include 'partials/about_modal.html' %}
+
+
+
+
diff --git a/passbook/core/templates/login/base.html b/passbook/core/templates/login/base.html
index fa9f79215..7450204ff 100644
--- a/passbook/core/templates/login/base.html
+++ b/passbook/core/templates/login/base.html
@@ -1,4 +1,4 @@
-{% extends 'base/skeleton.html' %}
+{% extends 'base/skeleton_login.html' %}
{% load static %}
{% load i18n %}
diff --git a/passbook/oauth_provider/forms.py b/passbook/oauth_provider/forms.py
index 1da6ee820..895291ff6 100644
--- a/passbook/oauth_provider/forms.py
+++ b/passbook/oauth_provider/forms.py
@@ -1,4 +1,4 @@
-"""passbook OAuth2 IDP Forms"""
+"""passbook OAuth2 Provider Forms"""
from django import forms
diff --git a/passbook/oauth_provider/models.py b/passbook/oauth_provider/models.py
index a318eb88e..9dff16202 100644
--- a/passbook/oauth_provider/models.py
+++ b/passbook/oauth_provider/models.py
@@ -25,8 +25,6 @@ class OAuth2Provider(Provider, AbstractApplication):
reverse('passbook_oauth_provider:token')),
'userinfo_url': request.build_absolute_uri(
reverse('passbook_api:openid')),
- 'openid_url': request.build_absolute_uri(
- reverse('passbook_oauth_provider:openid-discovery'))
}
class Meta:
diff --git a/passbook/oauth_provider/templates/oauth2_provider/setup_url_modal.html b/passbook/oauth_provider/templates/oauth2_provider/setup_url_modal.html
index b17f9ed7f..902107626 100644
--- a/passbook/oauth_provider/templates/oauth2_provider/setup_url_modal.html
+++ b/passbook/oauth_provider/templates/oauth2_provider/setup_url_modal.html
@@ -31,19 +31,10 @@
-
-
-
\ No newline at end of file
+
diff --git a/passbook/oauth_provider/urls.py b/passbook/oauth_provider/urls.py
index 5badf8aa3..1fd14cc2d 100644
--- a/passbook/oauth_provider/urls.py
+++ b/passbook/oauth_provider/urls.py
@@ -3,7 +3,7 @@
from django.urls import path
from oauth2_provider import views
-from passbook.oauth_provider.views import oauth2, openid
+from passbook.oauth_provider.views import oauth2
urlpatterns = [
# Custom OAuth 2 Authorize View
@@ -17,9 +17,4 @@ urlpatterns = [
path("token/", views.TokenView.as_view(), name="token"),
path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"),
path("introspect/", views.IntrospectTokenView.as_view(), name="introspect"),
- # OpenID-Connect Discovery
- path('.well-known/openid-configuration', openid.OpenIDConfigurationView.as_view(),
- name='openid-discovery'),
- path('.well-known/jwks.json', openid.JSONWebKeyView.as_view(),
- name='openid-jwks'),
]
diff --git a/passbook/oauth_provider/views/oauth2.py b/passbook/oauth_provider/views/oauth2.py
index a582f57a5..eae14a028 100644
--- a/passbook/oauth_provider/views/oauth2.py
+++ b/passbook/oauth_provider/views/oauth2.py
@@ -57,10 +57,10 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView):
provider.save()
self._application = application
# Check permissions
- passing, policy_meaages = self.user_has_access(self._application, request.user)
+ passing, policy_messages = self.user_has_access(self._application, request.user)
if not passing:
- for policy_meaage in policy_meaages:
- messages.error(request, policy_meaage)
+ for policy_message in policy_messages:
+ messages.error(request, policy_message)
return redirect('passbook_oauth_provider:oauth2-permission-denied')
# Some clients don't pass response_type, so we default to code
if 'response_type' not in request.GET:
diff --git a/passbook/oauth_provider/views/openid.py b/passbook/oauth_provider/views/openid.py
deleted file mode 100644
index 5b72b40a0..000000000
--- a/passbook/oauth_provider/views/openid.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""passbook oauth provider OpenID Views"""
-
-from django.http import HttpRequest, JsonResponse
-from django.shortcuts import reverse
-from django.views.generic import View
-
-
-class OpenIDConfigurationView(View):
- """Return OpenID Configuration"""
-
- def get_issuer_url(self, request):
- """Get correct issuer URL"""
- full_url = request.build_absolute_uri(reverse('passbook_oauth_provider:openid-discovery'))
- return full_url.replace(".well-known/openid-configuration", "")
-
- def get(self, request: HttpRequest):
- """Get Response conform to https://openid.net/specs/openid-connect-discovery-1_0.html"""
- return JsonResponse({
- 'issuer': self.get_issuer_url(request),
- 'authorization_endpoint': request.build_absolute_uri(
- reverse('passbook_oauth_provider:oauth2-authorize')),
- 'token_endpoint': request.build_absolute_uri(reverse('passbook_oauth_provider:token')),
- "jwks_uri": request.build_absolute_uri(reverse('passbook_oauth_provider:openid-jwks')),
- "scopes_supported": [
- "openid",
- ],
- })
-
-
-class JSONWebKeyView(View):
- """JSON Web Key View"""
-
- def get(self, request: HttpRequest):
- """JSON Webkeys are not implemented yet, hence return an empty object"""
- return JsonResponse({})
diff --git a/passbook/oidc_provider/__init__.py b/passbook/oidc_provider/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/passbook/oidc_provider/apps.py b/passbook/oidc_provider/apps.py
new file mode 100644
index 000000000..c6066356b
--- /dev/null
+++ b/passbook/oidc_provider/apps.py
@@ -0,0 +1,27 @@
+"""passbook auth oidc provider app config"""
+from logging import getLogger
+
+from django.apps import AppConfig
+from django.urls import include, path
+
+LOGGER = getLogger(__name__)
+
+class PassbookOIDCProviderConfig(AppConfig):
+ """passbook auth oidc provider app config"""
+
+ name = 'passbook.oidc_provider'
+ label = 'passbook_oidc_provider'
+ verbose_name = 'passbook OIDC Provider'
+
+ def ready(self):
+ from Cryptodome.PublicKey import RSA
+ from oidc_provider.models import RSAKey
+ if not RSAKey.objects.exists():
+ key = RSA.generate(2048)
+ rsakey = RSAKey(key=key.exportKey('PEM').decode('utf8'))
+ rsakey.save()
+ LOGGER.info("Created key")
+ from passbook.root import urls
+ urls.urlpatterns.append(
+ path('application/oidc/', include('oidc_provider.urls', namespace='oidc_provider')),
+ )
diff --git a/passbook/oidc_provider/forms.py b/passbook/oidc_provider/forms.py
new file mode 100644
index 000000000..4b2f84ca2
--- /dev/null
+++ b/passbook/oidc_provider/forms.py
@@ -0,0 +1,38 @@
+"""passbook OIDC IDP Forms"""
+
+from django import forms
+from oauth2_provider.generators import (generate_client_id,
+ generate_client_secret)
+from oidc_provider.models import Client
+
+from passbook.oidc_provider.models import OpenIDProvider
+
+
+class OIDCProviderForm(forms.ModelForm):
+ """OpenID Client form"""
+
+ def __init__(self, *args, **kwargs):
+ # Correctly load data from 1:1 rel
+ if 'instance' in kwargs:
+ kwargs['instance'] = kwargs['instance'].oidc_client
+ super().__init__(*args, **kwargs)
+ self.fields['client_id'].initial = generate_client_id()
+ self.fields['client_secret'].initial = generate_client_secret()
+
+ def save(self, *args, **kwargs):
+ response = super().save(*args, **kwargs)
+ # Check if openidprovider class instance exists
+ if not OpenIDProvider.objects.filter(oidc_client=self.instance).exists():
+ OpenIDProvider.objects.create(oidc_client=self.instance)
+ return response
+
+ class Meta:
+ model = Client
+ fields = [
+ 'name', 'client_type', 'client_id', 'client_secret', 'response_types',
+ 'jwt_alg', 'reuse_consent', 'require_consent', '_redirect_uris', '_scope'
+ ]
+ # exclude = ['owner', 'website_url', 'terms_url', 'contact_email', 'logo', ]
+ labels = {
+ 'client_secret': "Client Secret"
+ }
diff --git a/passbook/oidc_provider/lib.py b/passbook/oidc_provider/lib.py
new file mode 100644
index 000000000..07c17edcd
--- /dev/null
+++ b/passbook/oidc_provider/lib.py
@@ -0,0 +1,30 @@
+"""OIDC Permission checking"""
+from logging import getLogger
+
+from django.contrib import messages
+from django.shortcuts import redirect
+
+from passbook.core.models import Application
+from passbook.core.policies import PolicyEngine
+
+LOGGER = getLogger(__name__)
+
+def check_permissions(request, user, client):
+ """Check permissions, used for
+ https://django-oidc-provider.readthedocs.io/en/latest/
+ sections/settings.html#oidc-after-userlogin-hook"""
+ try:
+ application = client.openidprovider.application
+ except Application.DoesNotExist:
+ return redirect('passbook_oauth_provider:oauth2-permission-denied')
+ LOGGER.debug("Checking permissions of %s on application %s...", user, application)
+ policy_engine = PolicyEngine(application.policies.all())
+ policy_engine.for_user(user).with_request(request).build()
+
+ # Check permissions
+ passing, policy_messages = policy_engine.result
+ if not passing:
+ for policy_message in policy_messages:
+ messages.error(request, policy_message)
+ return redirect('passbook_oauth_provider:oauth2-permission-denied')
+ return None
diff --git a/passbook/oidc_provider/migrations/0001_initial.py b/passbook/oidc_provider/migrations/0001_initial.py
new file mode 100644
index 000000000..0e810eed7
--- /dev/null
+++ b/passbook/oidc_provider/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.2.3 on 2019-07-05 12:16
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('oidc_provider', '0026_client_multiple_response_types'),
+ ('passbook_core', '0024_ssologinpolicy'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='OpenIDProvider',
+ 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')),
+ ('oidc_client', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client')),
+ ],
+ bases=('passbook_core.provider',),
+ ),
+ ]
diff --git a/passbook/oidc_provider/migrations/__init__.py b/passbook/oidc_provider/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/passbook/oidc_provider/models.py b/passbook/oidc_provider/models.py
new file mode 100644
index 000000000..091b1ef8b
--- /dev/null
+++ b/passbook/oidc_provider/models.py
@@ -0,0 +1,45 @@
+"""oidc models"""
+from django.db import models
+from django.shortcuts import reverse
+from django.utils.translation import gettext as _
+from oidc_provider.models import Client
+
+from passbook.core.models import Provider
+
+
+class OpenIDProvider(Provider):
+ """Proxy model for OIDC Client"""
+ # Since oidc_provider doesn't currently support swappable models
+ # (https://github.com/juanifioren/django-oidc-provider/pull/305)
+ # we have a 1:1 relationship, and update oidc_client when the form is saved.
+
+ oidc_client = models.OneToOneField(Client, on_delete=models.CASCADE)
+
+ form = 'passbook.oidc_provider.forms.OIDCProviderForm'
+
+ @property
+ def name(self):
+ """Name property for UI"""
+ return self.oidc_client.name
+
+ def __str__(self):
+ return "OpenID Connect Provider %s" % self.oidc_client.__str__()
+
+ def html_setup_urls(self, request):
+ """return template and context modal with URLs for authorize, token, openid-config, etc"""
+ return "oidc_provider/setup_url_modal.html", {
+ 'provider': self,
+ 'authorize': request.build_absolute_uri(
+ reverse('oidc_provider:authorize')),
+ 'token': request.build_absolute_uri(
+ reverse('oidc_provider:token')),
+ 'userinfo': request.build_absolute_uri(
+ reverse('oidc_provider:userinfo')),
+ 'provider_info': request.build_absolute_uri(
+ reverse('oidc_provider:provider-info')),
+ }
+
+ class Meta:
+
+ verbose_name = _('OpenID Provider')
+ verbose_name_plural = _('OpenID Providers')
diff --git a/passbook/oidc_provider/requirements.txt b/passbook/oidc_provider/requirements.txt
new file mode 100644
index 000000000..615ae1804
--- /dev/null
+++ b/passbook/oidc_provider/requirements.txt
@@ -0,0 +1 @@
+django-oidc-provider
diff --git a/passbook/oidc_provider/settings.py b/passbook/oidc_provider/settings.py
new file mode 100644
index 000000000..dfde0f058
--- /dev/null
+++ b/passbook/oidc_provider/settings.py
@@ -0,0 +1,7 @@
+"""passbook OIDC Provider"""
+
+INSTALLED_APPS = [
+ 'oidc_provider',
+]
+
+OIDC_AFTER_USERLOGIN_HOOK = "passbook.oidc_provider.lib.check_permissions"
diff --git a/passbook/oidc_provider/templates/oidc_provider/authorize.html b/passbook/oidc_provider/templates/oidc_provider/authorize.html
new file mode 100644
index 000000000..d6392bf1e
--- /dev/null
+++ b/passbook/oidc_provider/templates/oidc_provider/authorize.html
@@ -0,0 +1,70 @@
+{% extends "login/base.html" %}
+
+{% load utils %}
+{% load i18n %}
+
+{% block title %}
+{% title 'Authorize Application' %}
+{% endblock %}
+
+{% block card %}
+
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
diff --git a/passbook/oidc_provider/templates/oidc_provider/setup_url_modal.html b/passbook/oidc_provider/templates/oidc_provider/setup_url_modal.html
new file mode 100644
index 000000000..46ba06b3e
--- /dev/null
+++ b/passbook/oidc_provider/templates/oidc_provider/setup_url_modal.html
@@ -0,0 +1,49 @@
+{% load i18n %}
+
+
+
diff --git a/passbook/root/settings.py b/passbook/root/settings.py
index 9115b31e5..0f87ecba1 100644
--- a/passbook/root/settings.py
+++ b/passbook/root/settings.py
@@ -82,6 +82,7 @@ INSTALLED_APPS = [
'passbook.ldap.apps.PassbookLdapConfig',
'passbook.oauth_client.apps.PassbookOAuthClientConfig',
'passbook.oauth_provider.apps.PassbookOAuthProviderConfig',
+ 'passbook.oidc_provider.apps.PassbookOIDCProviderConfig',
'passbook.saml_idp.apps.PassbookSAMLIDPConfig',
'passbook.otp.apps.PassbookOTPConfig',
'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig',
diff --git a/requirements.txt b/requirements.txt
index c92c9854f..8f2ab6fd8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,3 +8,4 @@
-r passbook/admin/requirements.txt
-r passbook/api/requirements.txt
-r passbook/app_gw/requirements.txt
+-r passbook/oidc_provider/requirements.txt