Compare commits

...

25 Commits

Author SHA1 Message Date
6f7b917c38 bump version: 0.1.23-beta -> 0.1.24-beta 2019-03-20 23:00:33 +01:00
1456ee6d3e prepare 0.1.24 2019-03-20 23:00:22 +01:00
5155204283 Merge branch '32-automatically-set-owner-field-when-creating-oauth-provider' into 'master'
Resolve "Automatically set owner field when creating OAuth Provider"

Closes #32

See merge request BeryJu.org/passbook!16
2019-03-20 21:20:54 +00:00
5509ec9b0f Merge branch '29-oauth-provider-add-extra-info-button-to-show-urls' into 'master'
Resolve "OAuth Provider: Add extra info button to show URLs"

Closes #29

See merge request BeryJu.org/passbook!15
2019-03-20 21:17:36 +00:00
d6f9b2e47d remove user field from form. Closes #32 2019-03-20 20:09:27 +01:00
67aa4aef11 add modal for OAuth Providers showing the URLs 2019-03-20 20:03:28 +01:00
9e46c8bfec bump version: 0.1.22-beta -> 0.1.23-beta 2019-03-18 20:54:31 +01:00
1eaa9b9733 prepare 0.1.23 2019-03-18 20:54:23 +01:00
ee05834b69 Merge branch '28-openid-connect-discovery' into 'master'
set issuer to root address instead of well-known path

Closes #28

See merge request BeryJu.org/passbook!13
2019-03-18 19:52:13 +00:00
fccc8f4959 set issuer to root address instead of well-known path 2019-03-18 20:42:32 +01:00
c721620f96 Merge branch '28-openid-connect-discovery' into 'master'
Resolve "OpenID Connect Discovery support"

Closes #28

See merge request BeryJu.org/passbook!12
2019-03-18 19:42:08 +00:00
c9f73d718e start implementing openid connect discovery 2019-03-18 20:35:11 +01:00
bfa58be721 bump version: 0.1.21-beta -> 0.1.22-beta 2019-03-14 21:22:15 +01:00
4bb602149e perapre 0.1.22 2019-03-14 21:20:55 +01:00
81ab9092fc Fix OAuth Client's disconnect view having invalid URL names 2019-03-14 21:19:14 +01:00
29d5962c4c add Azure AD Source 2019-03-14 21:18:55 +01:00
5c75339946 point to correct icons 2019-03-14 21:18:13 +01:00
4774d9a46c fix delete form not working 2019-03-14 21:17:41 +01:00
dbe16ba4fd fix layout when on mobile viewport and scrolling 2019-03-14 21:17:28 +01:00
6972cf00a0 move icons to single folder, cleanup 2019-03-14 21:17:07 +01:00
0445be9712 fix missing debug template 2019-03-14 21:16:27 +01:00
89dbdd9585 bump version: 0.1.20-beta -> 0.1.21-beta 2019-03-14 18:08:02 +01:00
da88ce7150 prepare 0.1.21 2019-03-14 18:02:13 +01:00
5f50fcfcf5 detect HTTPS from reverse proxy 2019-03-14 18:01:41 +01:00
96be087221 add request debug view 2019-03-14 18:01:27 +01:00
73 changed files with 355 additions and 73 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.1.20-beta current_version = 0.1.24-beta
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)

View File

@ -54,7 +54,7 @@ package-docker:
before_script: before_script:
- echo "{\"auths\":{\"docker.$NEXUS_URL\":{\"auth\":\"$NEXUS_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.$NEXUS_URL\":{\"auth\":\"$NEXUS_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.pkg.beryju.org/passbook:latest --destination docker.pkg.beryju.org/passbook:0.1.20-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.pkg.beryju.org/passbook:latest --destination docker.pkg.beryju.org/passbook:0.1.24-beta
stage: build stage: build
only: only:
- tags - tags

View File

@ -3,7 +3,7 @@ from setuptools import setup
setup( setup(
name='django-allauth-passbook', name='django-allauth-passbook',
version='0.1.20-beta', version='0.1.24-beta',
description='passbook support for django-allauth', description='passbook support for django-allauth',
# long_description='\n'.join(read_simple('docs/index.md')[2:]), # long_description='\n'.join(read_simple('docs/index.md')[2:]),
long_description_content_type='text/markdown', long_description_content_type='text/markdown',

View File

@ -18,7 +18,7 @@ tests_require = [
setup( setup(
name='sentry-auth-passbook', name='sentry-auth-passbook',
version='0.1.20-beta', version='0.1.24-beta',
author='BeryJu.org', author='BeryJu.org',
author_email='support@beryju.org', author_email='support@beryju.org',
url='https://passbook.beryju.org', url='https://passbook.beryju.org',

35
debian/changelog vendored
View File

@ -1,3 +1,38 @@
passbook (0.1.24) stable; urgency=medium
* bump version: 0.1.22-beta -> 0.1.23-beta
* add modal for OAuth Providers showing the URLs
* remove user field from form. Closes #32
-- Jens Langhammer <jens.langhammer@beryju.org> Wed, 20 Mar 2019 21:59:21 +0000
passbook (0.1.23) stable; urgency=medium
* add support for OpenID-Connect Discovery
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 18 Mar 2019 20:19:27 +0000
passbook (0.1.22) stable; urgency=medium
* bump version: 0.1.20-beta -> 0.1.21-beta
* fix missing debug template
* move icons to single folder, cleanup
* fix layout when on mobile viewport and scrolling
* fix delete form not working
* point to correct icons
* add Azure AD Source
* Fix OAuth Client's disconnect view having invalid URL names
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 14 Mar 2019 20:19:27 +0000
passbook (0.1.21) stable; urgency=medium
* bump version: 0.1.19-beta -> 0.1.20-beta
* add request debug view
* detect HTTPS from reverse proxy
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 14 Mar 2019 17:01:49 +0000
passbook (0.1.20) stable; urgency=medium passbook (0.1.20) stable; urgency=medium
* bump version: 0.1.18-beta -> 0.1.19-beta * bump version: 0.1.18-beta -> 0.1.19-beta

View File

@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
appVersion: "0.1.20-beta" appVersion: "0.1.24-beta"
description: A Helm chart for passbook. description: A Helm chart for passbook.
name: passbook name: passbook
version: "0.1.20-beta" version: "0.1.24-beta"
icon: https://passbook.beryju.org/images/logo.png icon: https://passbook.beryju.org/images/logo.png

View File

@ -123,6 +123,7 @@ data:
- passbook.oauth_client.source_types.reddit - passbook.oauth_client.source_types.reddit
- passbook.oauth_client.source_types.supervisr - passbook.oauth_client.source_types.supervisr
- passbook.oauth_client.source_types.twitter - passbook.oauth_client.source_types.twitter
- passbook.oauth_client.source_types.azure_ad
saml_idp: saml_idp:
signing: true signing: true
autosubmit: false autosubmit: false

View File

@ -5,7 +5,7 @@
replicaCount: 1 replicaCount: 1
image: image:
tag: 0.1.20-beta tag: 0.1.24-beta
nameOverride: "" nameOverride: ""

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook admin""" """passbook admin"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -0,0 +1,31 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load utils %}
{% block title %}
{% title %}
{% endblock %}
{% block content %}
<div class="container">
<h1><span class="pficon-applications"></span> {% trans "Request" %}</h1>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Key' %}</th>
<th>{% trans 'Value' %}</th>
</tr>
</thead>
<tbody>
{% for key, value in request_dict.items %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -57,6 +57,10 @@
<a class="btn btn-default btn-sm" <a class="btn btn-default btn-sm"
href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a> href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %} {% endfor %}
{% get_htmls provider as htmls %}
{% for html in htmls %}
{{ html|safe }}
{% endfor %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -5,6 +5,8 @@ from logging import getLogger
from django import template from django import template
from django.db.models import Model from django.db.models import Model
from passbook.lib.utils.template import render_to_string
register = template.Library() register = template.Library()
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
@ -29,3 +31,24 @@ def get_links(model_instance):
pass pass
return links return links
@register.simple_tag(takes_context=True)
def get_htmls(context, model_instance):
"""Find all html_ methods on an object instance, run them and return as dict"""
prefix = 'html_'
htmls = []
if not isinstance(model_instance, Model):
LOGGER.warning("Model %s is not instance of Model", model_instance)
return htmls
try:
for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod):
if name.startswith(prefix):
template, _context = method(context.get('request'))
htmls.append(render_to_string(template, _context))
except NotImplementedError:
pass
return htmls

View File

@ -1,7 +1,7 @@
"""passbook URL Configuration""" """passbook URL Configuration"""
from django.urls import include, path from django.urls import include, path
from passbook.admin.views import (applications, audit, factors, groups, from passbook.admin.views import (applications, audit, debug, factors, groups,
invitations, overview, policy, invitations, overview, policy,
property_mapping, providers, sources, users) property_mapping, providers, sources, users)
@ -77,5 +77,7 @@ urlpatterns = [
# Groups # Groups
path('groups/', groups.GroupListView.as_view(), name='groups'), path('groups/', groups.GroupListView.as_view(), name='groups'),
# API # API
path('api/', include('passbook.admin.api.urls')) path('api/', include('passbook.admin.api.urls')),
# Debug
path('debug/request/', debug.DebugRequestView.as_view(), name='debug-request'),
] ]

View File

@ -0,0 +1,17 @@
"""passbook administration debug views"""
from django.views.generic import TemplateView
from passbook.admin.mixins import AdminRequiredMixin
class DebugRequestView(AdminRequiredMixin, TemplateView):
"""Show debug info about request"""
template_name = 'administration/debug/request.html'
def get_context_data(self, **kwargs):
kwargs['request_dict'] = {}
for key in dir(self.request):
kwargs['request_dict'][key] = getattr(self.request, key)
return super().get_context_data(**kwargs)

View File

@ -1,2 +1,2 @@
"""passbook api""" """passbook api"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook audit Header""" """passbook audit Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook captcha_factor Header""" """passbook captcha_factor Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook core""" """passbook core"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -35,6 +35,7 @@ SECRET_KEY = CONFIG.get('secret_key')
DEBUG = CONFIG.get('debug') DEBUG = CONFIG.get('debug')
INTERNAL_IPS = ['127.0.0.1'] INTERNAL_IPS = ['127.0.0.1']
ALLOWED_HOSTS = CONFIG.get('domains', []) ALLOWED_HOSTS = CONFIG.get('domains', [])
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
LOGIN_URL = 'passbook_core:auth-login' LOGIN_URL = 'passbook_core:auth-login'
# CSRF_FAILURE_VIEW = 'passbook.core.views.errors.CSRFErrorView.as_view' # CSRF_FAILURE_VIEW = 'passbook.core.views.errors.CSRFErrorView.as_view'

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="20px" height="20px" viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FBBB00;}
.st1{fill:#518EF8;}
.st2{fill:#28B446;}
.st3{fill:#F14336;}
</style>
<path class="st0" d="M4.4,12.1l-0.7,2.6l-2.5,0.1C0.4,13.3,0,11.7,0,10c0-1.7,0.4-3.2,1.1-4.6h0l2.3,0.4l1,2.3
C4.2,8.7,4.1,9.3,4.1,10C4.1,10.7,4.2,11.4,4.4,12.1z"/>
<path class="st1" d="M19.8,8.1C19.9,8.7,20,9.4,20,10c0,0.7-0.1,1.4-0.2,2.1c-0.5,2.3-1.8,4.3-3.5,5.7l0,0l-2.9-0.1L13,15.1
c1.2-0.7,2.1-1.8,2.6-3h-5.3v-4h5.4H19.8L19.8,8.1z"/>
<path class="st2" d="M16.3,17.8L16.3,17.8C14.5,19.2,12.4,20,10,20c-3.8,0-7.1-2.1-8.8-5.3l3.2-2.7c0.8,2.3,3,3.9,5.6,3.9
c1.1,0,2.1-0.3,3-0.8L16.3,17.8z"/>
<path class="st3" d="M16.4,2.3L13.1,5c-0.9-0.6-2-0.9-3.1-0.9c-2.6,0-4.8,1.7-5.6,4L1.1,5.4h0C2.8,2.2,6.1,0,10,0
C12.4,0,14.7,0.9,16.4,2.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"><title>MS-SymbolLockup</title><rect x="1" y="1" width="9" height="9" fill="#f25022"/><rect x="1" y="11" width="9" height="9" fill="#00a4ef"/><rect x="11" y="1" width="9" height="9" fill="#7fba00"/><rect x="11" y="11" width="9" height="9" fill="#ffb900"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 750 B

View File

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

View File

Before

Width:  |  Height:  |  Size: 788 B

After

Width:  |  Height:  |  Size: 788 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 783 B

After

Width:  |  Height:  |  Size: 783 B

View File

Before

Width:  |  Height:  |  Size: 688 B

After

Width:  |  Height:  |  Size: 688 B

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 889 B

After

Width:  |  Height:  |  Size: 889 B

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -3,7 +3,7 @@
{% load utils %} {% load utils %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" class="layout-pf layout-pf-fixed transitions">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">

View File

@ -16,6 +16,7 @@
Are you sure you want to delete {{ object_type }} "{{ object }}"? Are you sure you want to delete {{ object_type }} "{{ object }}"?
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<input type="hidden" name="confirmdelete" value="yes">
<a href="{% back %}" class="btn btn-default">{% trans 'Back' %}</a> <a href="{% back %}" class="btn btn-default">{% trans 'Back' %}</a>
<input type="submit" class="btn btn-danger" value="{% trans 'Delete' %}" /> <input type="submit" class="btn btn-danger" value="{% trans 'Delete' %}" />
</form> </form>

View File

@ -51,7 +51,7 @@
{% for url, icon, name in sources %} {% for url, icon, name in sources %}
<li class="login-pf-social-link"> <li class="login-pf-social-link">
<a href="{{ url }}"> <a href="{{ url }}">
<img src="{% static 'img/' %}{{ icon }}.svg" alt="{{ name }}"> {{ name }} <img src="{% static 'img/logos/' %}{{ icon }}.svg" alt="{{ name }}"> {{ name }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}

View File

@ -172,19 +172,27 @@
</span> </span>
</a> </a>
</li> </li>
<li class="list-group-item {% is_active 'passbook_admin:debug-request' %}">
<a href="{% url 'passbook_admin:debug-request' %}">
<span class="list-group-item-value">
{% trans 'Debug' %}
</span>
</a>
</li>
</ul> </ul>
</div> </div>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
<div class="container-fluid container-cards-pf"> <div class="container-fluid container-cards-pf container-pf-nav-pf-vertical hide-nav-pf">
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{{ block.super }}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
// initialize tooltips // initialize tooltips

View File

@ -30,7 +30,7 @@
{% for name, icon, link in us %} {% for name, icon, link in us %}
<li class="{% if link == request.get_full_path %} active {% endif %}"> <li class="{% if link == request.get_full_path %} active {% endif %}">
<a href="{{ link }}"> <a href="{{ link }}">
<img src="{% static icon %}" alt=""> {{ name }} <i class="{{ icon }}"></i> {{ name }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}

View File

@ -1,2 +1,2 @@
"""passbook hibp_policy""" """passbook hibp_policy"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""Passbook ldap app Header""" """Passbook ldap app Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook lib""" """passbook lib"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -85,6 +85,7 @@ oauth_client:
- passbook.oauth_client.source_types.reddit - passbook.oauth_client.source_types.reddit
- passbook.oauth_client.source_types.supervisr - passbook.oauth_client.source_types.supervisr
- passbook.oauth_client.source_types.twitter - passbook.oauth_client.source_types.twitter
- passbook.oauth_client.source_types.azure_ad
saml_idp: saml_idp:
# List of python packages with provider types to load. # List of python packages with provider types to load.
types: types:

View File

@ -1,2 +1,2 @@
"""passbook oauth_client Header""" """passbook oauth_client Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -108,3 +108,17 @@ class GoogleOAuthSourceForm(OAuthSourceForm):
'access_token_url': 'https://accounts.google.com/o/oauth2/token', 'access_token_url': 'https://accounts.google.com/o/oauth2/token',
'profile_url': ' https://www.googleapis.com/oauth2/v1/userinfo', 'profile_url': ' https://www.googleapis.com/oauth2/v1/userinfo',
} }
class AzureADOAuthSourceForm(OAuthSourceForm):
"""OAuth Source form with pre-determined URL for AzureAD"""
class Meta(OAuthSourceForm.Meta):
overrides = {
'provider_type': 'azure_ad',
'request_token_url': '',
'authorization_url': 'https://login.microsoftonline.com/common/oauth2/authorize',
'access_token_url': 'https://login.microsoftonline.com/common/oauth2/token',
'profile_url': ' https://graph.windows.net/myorganization/me?api-version=1.6',
}

View File

@ -29,9 +29,9 @@ class OAuthSource(Source):
def get_login_button(self): def get_login_button(self):
url = reverse_lazy('passbook_oauth_client:oauth-client-login', url = reverse_lazy('passbook_oauth_client:oauth-client-login',
kwargs={'source_slug': self.slug}) kwargs={'source_slug': self.slug})
if self.provider_type == 'github': # if self.provider_type == 'github':
return url, 'github-logo', _('GitHub') # return url, 'github-logo', _('GitHub')
return url, 'generic', _('Generic') return url, self.provider_type, self.name
@property @property
def additional_info(self): def additional_info(self):
@ -42,9 +42,12 @@ class OAuthSource(Source):
"""Entrypoint to integrate with User settings. Can either return False if no """Entrypoint to integrate with User settings. Can either return False if no
user settings are available, or a tuple or string, string, string where the first string user settings are available, or a tuple or string, string, string where the first string
is the name the item has, the second string is the icon and the third is the view-name.""" is the name the item has, the second string is the icon and the third is the view-name."""
icon = 'img/%s.svg' % self.get_login_button[1] icon_type = self.provider_type
if icon_type == 'azure ad':
icon_type = 'windows'
icon_class = 'fa fa-%s' % icon_type
view_name = 'passbook_oauth_client:oauth-client-user' view_name = 'passbook_oauth_client:oauth-client-user'
return self.name, icon, reverse((view_name), kwargs={ return self.name, icon_class, reverse((view_name), kwargs={
'source_slug': self.slug 'source_slug': self.slug
}) })
@ -113,6 +116,19 @@ class GoogleOAuthSource(OAuthSource):
verbose_name = _('Google OAuth Source') verbose_name = _('Google OAuth Source')
verbose_name_plural = _('Google OAuth Sources') verbose_name_plural = _('Google OAuth Sources')
class AzureADOAuthSource(OAuthSource):
"""Abstract subclass of OAuthSource to specify AzureAD Form"""
form = 'passbook.oauth_client.forms.AzureADOAuthSourceForm'
class Meta:
abstract = True
verbose_name = _('Azure AD OAuth Source')
verbose_name_plural = _('Azure AD OAuth Sources')
class UserOAuthSourceConnection(UserSourceConnection): class UserOAuthSourceConnection(UserSourceConnection):
"""Authorized remote OAuth provider.""" """Authorized remote OAuth provider."""

View File

@ -0,0 +1,52 @@
"""AzureAD OAuth2 Views"""
import json
import uuid
from logging import getLogger
from requests.exceptions import RequestException
from passbook.oauth_client.clients import OAuth2Client
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
from passbook.oauth_client.utils import user_get_or_create
from passbook.oauth_client.views.core import OAuthCallback
LOGGER = getLogger(__name__)
class AzureADOAuth2Client(OAuth2Client):
"""AzureAD OAuth2 Client"""
def get_profile_info(self, raw_token):
"Fetch user profile information."
try:
token = json.loads(raw_token)['access_token']
headers = {
'Authorization': 'Bearer %s' % token
}
response = self.request('get', self.source.profile_url,
headers=headers)
response.raise_for_status()
except RequestException as exc:
LOGGER.warning('Unable to fetch user profile: %s', exc)
return None
else:
return response.json() or response.text
@MANAGER.source(kind=RequestKind.callback, name='Azure AD')
class AzureADOAuthCallback(OAuthCallback):
"""AzureAD OAuth2 Callback"""
client_class = AzureADOAuth2Client
def get_user_id(self, source, info):
return uuid.UUID(info.get('objectId')).int
def get_or_create_user(self, source, access, info):
user_data = {
'username': info.get('displayName'),
'email': info.get('mail', None) or info.get('otherMails')[0],
'name': info.get('displayName'),
'password': None,
}
return user_get_or_create(**user_data)

View File

@ -194,7 +194,9 @@ class OAuthCallback(OAuthClientMixin, View):
messages.success(self.request, _("Successfully linked %(source)s!" % { messages.success(self.request, _("Successfully linked %(source)s!" % {
'source': self.source.name 'source': self.source.name
})) }))
return redirect(reverse('user_settings')) return redirect(reverse('passbook_oauth_client:oauth-client-user', kwargs={
'source_slug': self.source.slug
}))
messages.success(self.request, _("Successfully authenticated with %(source)s!" % { messages.success(self.request, _("Successfully authenticated with %(source)s!" % {
'source': self.source.name 'source': self.source.name
})) }))
@ -207,26 +209,28 @@ class DisconnectView(LoginRequiredMixin, View):
source = None source = None
aas = None aas = None
def dispatch(self, request, source): def dispatch(self, request, source_slug):
self.source = get_object_or_404(OAuthSource, name=source) self.source = get_object_or_404(OAuthSource, slug=source_slug)
self.aas = get_object_or_404(UserOAuthSourceConnection, self.aas = get_object_or_404(UserOAuthSourceConnection,
source=self.source, user=request.user) source=self.source, user=request.user)
return super().dispatch(request, source) return super().dispatch(request, source_slug)
def post(self, request, source): def post(self, request, source_slug):
"""Delete connection object""" """Delete connection object"""
if 'confirmdelete' in request.POST: if 'confirmdelete' in request.POST:
# User confirmed deletion # User confirmed deletion
self.aas.delete() self.aas.delete()
messages.success(request, _('Connection successfully deleted')) messages.success(request, _('Connection successfully deleted'))
return redirect(reverse('user_settings')) return redirect(reverse('passbook_oauth_client:oauth-client-user', kwargs={
return self.get(request, source) 'source_slug': self.source.slug
}))
return self.get(request, source_slug)
def get(self, request, source): def get(self, request, source):
"""Show delete form""" """Show delete form"""
return render(request, 'generic/delete.html', { return render(request, 'generic/delete.html', {
'object': 'OAuth Connection with %s' % self.source.name, 'object': self.source,
'delete_url': reverse('oauth-client-disconnect', kwargs={ 'delete_url': reverse('passbook_oauth_client:oauth-client-disconnect', kwargs={
'source': self.source.name, 'source_slug': self.source.slug,
}) })
}) })

View File

@ -1,2 +1,2 @@
"""passbook oauth_provider Header""" """passbook oauth_provider Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -11,5 +11,5 @@ class OAuth2ProviderForm(forms.ModelForm):
class Meta: class Meta:
model = OAuth2Provider model = OAuth2Provider
fields = ['name', 'user', 'redirect_uris', 'client_type', fields = ['name', 'redirect_uris', 'client_type',
'authorization_grant_type', 'client_id', 'client_secret', ] 'authorization_grant_type', 'client_id', 'client_secret', ]

View File

@ -1,5 +1,6 @@
"""Oauth2 provider product extension""" """Oauth2 provider product extension"""
from django.shortcuts import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from oauth2_provider.models import AbstractApplication from oauth2_provider.models import AbstractApplication
@ -14,6 +15,20 @@ class OAuth2Provider(Provider, AbstractApplication):
def __str__(self): def __str__(self):
return "OAuth2 Provider %s" % self.name return "OAuth2 Provider %s" % self.name
def html_setup_urls(self, request):
"""return template and context modal with URLs for authorize, token, openid-config, etc"""
return "oauth2_provider/setup_url_modal.html", {
'provider': self,
'authorize_url': request.build_absolute_uri(
reverse('passbook_oauth_provider:oauth2-authorize')),
'token_url': request.build_absolute_uri(
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: class Meta:
verbose_name = _('OAuth2 Provider') verbose_name = _('OAuth2 Provider')

View File

@ -0,0 +1,49 @@
{% load i18n %}
<button class="btn btn-default btn-sm" data-toggle="modal" data-target="#{{ provider.pk }}">{% trans 'View Setup URLs' %}</button>
<div class="modal fade" id="{{ provider.pk }}" tabindex="-1" role="dialog" aria-labelledby="{{ provider.pk }}Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">
<span class="pficon pficon-close"></span>
</button>
<h4 class="modal-title" id="{{ provider.pk }}Label">{% trans 'Setup URLs' %}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans 'Authroize URL' %}</label>
<div class="col-sm-9">
<input type="text"class="form-control" readonly value="{{ authorize_url }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans 'Token URL' %}</label>
<div class="col-sm-9">
<input type="text" class="form-control" readonly value="{{ token_url }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans 'Userinfo Endpoint' %}</label>
<div class="col-sm-9">
<input type="text" class="form-control" readonly value="{{ userinfo_url }}">
</div>
</div>
</form>
<hr>
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans 'OpenID Configuration URL' %}</label>
<div class="col-sm-9">
<input type="text"class="form-control" readonly value="{{ openid_url }}">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
from django.urls import path from django.urls import path
from oauth2_provider import views from oauth2_provider import views
from passbook.oauth_provider.views import oauth2 from passbook.oauth_provider.views import oauth2, openid
urlpatterns = [ urlpatterns = [
# Custom OAuth 2 Authorize View # Custom OAuth 2 Authorize View
@ -14,8 +14,12 @@ urlpatterns = [
path('authorize/permission_denied/', oauth2.OAuthPermissionDenied.as_view(), path('authorize/permission_denied/', oauth2.OAuthPermissionDenied.as_view(),
name='oauth2-permission-denied'), name='oauth2-permission-denied'),
# OAuth API # OAuth API
path("authorize/", views.AuthorizationView.as_view(), name="authorize"),
path("token/", views.TokenView.as_view(), name="token"), path("token/", views.TokenView.as_view(), name="token"),
path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"), path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"),
path("introspect/", views.IntrospectTokenView.as_view(), name="introspect"), 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'),
] ]

View File

@ -0,0 +1,30 @@
"""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(self, request: HttpRequest):
"""Get Response conform to https://openid.net/specs/openid-connect-discovery-1_0.html"""
return JsonResponse({
'issuer': request.build_absolute_uri(reverse('passbook_core:overview')),
'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:userinfo",
],
})
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({})

View File

@ -1,2 +1,2 @@
"""passbook otp Header""" """passbook otp Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook password_expiry""" """passbook password_expiry"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'

View File

@ -1,2 +1,2 @@
"""passbook saml_idp Header""" """passbook saml_idp Header"""
__version__ = '0.1.20-beta' __version__ = '0.1.24-beta'