Compare commits

...

23 Commits

Author SHA1 Message Date
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
a53a269a8c bump version: 0.1.19-beta -> 0.1.20-beta 2019-03-13 16:51:43 +01:00
59565a5286 prepare 0.1.20 2019-03-13 16:51:38 +01:00
ae3c092238 add user settings for Sources 2019-03-13 16:49:30 +01:00
e98e5e4e3e fix GitHub Pretend again 2019-03-13 15:52:05 +01:00
83 changed files with 344 additions and 178 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.1.19-beta current_version = 0.1.23-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.19-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.23-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.19-beta', version='0.1.23-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.19-beta', version='0.1.23-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.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
* bump version: 0.1.18-beta -> 0.1.19-beta
* fix GitHub Pretend again
* add user settings for Sources
-- Jens Langhammer <jens.langhammer@beryju.org> Wed, 13 Mar 2019 15:49:44 +0000
passbook (0.1.18) stable; urgency=medium passbook (0.1.18) stable; urgency=medium
* bump version: 0.1.16-beta -> 0.1.17-beta * bump version: 0.1.16-beta -> 0.1.17-beta

View File

@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
appVersion: "0.1.19-beta" appVersion: "0.1.23-beta"
description: A Helm chart for passbook. description: A Helm chart for passbook.
name: passbook name: passbook
version: "0.1.19-beta" version: "0.1.23-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.19-beta tag: 0.1.23-beta
nameOverride: "" nameOverride: ""

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""passbook admin""" """passbook admin"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-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

@ -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.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""passbook audit Header""" """passbook audit Header"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""passbook captcha_factor Header""" """passbook captcha_factor Header"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""passbook core""" """passbook core"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -186,6 +186,12 @@ class Source(PolicyModel):
"""Return additional Info, such as a callback URL. Show in the administration interface.""" """Return additional Info, such as a callback URL. Show in the administration interface."""
return None return None
def has_user_settings(self):
"""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
is the name the item has, the second string is the icon and the third is the view-name."""
return False
def __str__(self): def __str__(self):
return self.name return self.name

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

@ -26,7 +26,7 @@
<li class="dropdown"> <li class="dropdown">
<button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu1" data-toggle="dropdown" <button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu1" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true"> aria-haspopup="true" aria-expanded="true">
<span title="Help" class="fa pficon-help dropdown-title"></span> <span title="Help" class="fa pficon-help"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
{% comment %} <li><a href="#0">Help</a></li> {% endcomment %} {% comment %} <li><a href="#0">Help</a></li> {% endcomment %}
@ -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

@ -2,6 +2,7 @@
{% load i18n %} {% load i18n %}
{% load is_active %} {% load is_active %}
{% load static %}
{% load passbook_user_settings %} {% load passbook_user_settings %}
{% block content %} {% block content %}
@ -24,6 +25,15 @@
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
<li class="nav-divider"></li>
{% user_sources as us %}
{% for name, icon, link in us %}
<li class="{% if link == request.get_full_path %} active {% endif %}">
<a href="{{ link }}">
<i class="{{ icon }}"></i> {{ name }}
</a>
</li>
{% endfor %}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
from django import template from django import template
from passbook.core.models import Factor from passbook.core.models import Factor, Source
from passbook.core.policies import PolicyEngine from passbook.core.policies import PolicyEngine
register = template.Library() register = template.Library()
@ -20,3 +20,17 @@ def user_factors(context):
if policy_engine.passing and _link: if policy_engine.passing and _link:
matching_factors.append(_link) matching_factors.append(_link)
return matching_factors return matching_factors
@register.simple_tag(takes_context=True)
def user_sources(context):
"""Return a list of all sources which are enabled for the user"""
user = context.get('request').user
_all_sources = Source.objects.filter(enabled=True).select_subclasses()
matching_sources = []
for factor in _all_sources:
_link = factor.has_user_settings()
policy_engine = PolicyEngine(factor.policies.all())
policy_engine.for_user(user).with_request(context.get('request')).build()
if policy_engine.passing and _link:
matching_sources.append(_link)
return matching_sources

View File

@ -1,2 +1,2 @@
"""passbook hibp_policy""" """passbook hibp_policy"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""Passbook ldap app Header""" """Passbook ldap app Header"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""passbook lib""" """passbook lib"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-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.19-beta' __version__ = '0.1.23-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

@ -1,7 +1,7 @@
"""OAuth Client models""" """OAuth Client models"""
from django.db import models from django.db import models
from django.urls import reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from passbook.core.models import Source, UserSourceConnection from passbook.core.models import Source, UserSourceConnection
@ -29,15 +29,28 @@ 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):
return "Callback URL: '%s'" % reverse_lazy('passbook_oauth_client:oauth-client-callback', return "Callback URL: '%s'" % reverse_lazy('passbook_oauth_client:oauth-client-callback',
kwargs={'source_slug': self.slug}) kwargs={'source_slug': self.slug})
def has_user_settings(self):
"""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
is the name the item has, the second string is the icon and the third is the view-name."""
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'
return self.name, icon_class, reverse((view_name), kwargs={
'source_slug': self.slug
})
class Meta: class Meta:
verbose_name = _('Generic OAuth Source') verbose_name = _('Generic OAuth Source')
@ -103,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

@ -1,6 +0,0 @@
{% load passbook_oauth_client %}
{% any_provider as enabled %}
{% if enabled %}
<div class="btn-group btn-primary btn-block">
{% endif %}

View File

@ -1,6 +0,0 @@
{% load passbook_oauth_client %}
{% provider_exists 'facebook' as facebook_enabled %}
{% if facebook_enabled %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider='facebook' %}" class="btn" style="background-color:#4267b2;color:white;margin-top:10px;width:100%;"><i class="fa fa-facebook-official" aria-hidden="true"></i></a>
{% endif %}

View File

@ -1,6 +0,0 @@
{% load passbook_oauth_client %}
{% provider_exists 'twitter' as twitter_enabled %}
{% if twitter_enabled %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider='twitter' %}" class="btn" style="background-color:#55ACEE;color:white;margin-top:10px;width:100%;"><i class="fa fa-twitter" aria-hidden="true"></i></a>
{% endif %}

View File

@ -1,7 +0,0 @@
{% load passbook_oauth_client %}
{% load static %}
{% provider_exists 'google' as google_enabled %}
{% if google_enabled %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider='google' %}" class="btn" style="background-color:white;color:black;margin-top:10px;width:100%;"><img src="{% static 'img/google.svg' %}" style="height:12px"></a>
{% endif %}

View File

@ -1,6 +0,0 @@
{% load passbook_oauth_client %}
{% provider_exists 'github' as github_enabled %}
{% if github_enabled %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider='github' %}" class="btn" style="background-color:#444444;color:white;margin-top:10px;width:100%;"><i class="fa fa-github" aria-hidden="true"></i></a>
{% endif %}

View File

@ -1,7 +0,0 @@
{% load passbook_oauth_client %}
{% load static %}
{% provider_exists 'discord' as discord_enabled %}
{% if discord_enabled %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider='discord' %}" class="btn" style="background-color:#2C2F33;color:white;margin-top:10px;width:100%;"><img src="{% static 'img/discord.svg' %}" style="height:12px"></a>
{% endif %}

View File

@ -1,7 +0,0 @@
{% load passbook_oauth_client %}
{% load static %}
{% provider_exists 'reddit' as reddit_enabled %}
{% if reddit_enabled %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider='reddit' %}" class="btn" style="background-color:#ff4500;color:white;margin-top:10px;width:100%;"><img src="{% static 'img/reddit.svg' %}" style="height:20px;margin-top:-5px;"></a>
{% endif %}

View File

@ -1,6 +0,0 @@
{% load passbook_oauth_client %}
{% any_provider as enabled %}
{% if enabled %}
</div>
{% endif %}

View File

@ -1,54 +0,0 @@
{% extends "user/base.html" %}
{% load utils %}
{% load i18n %}
{% block title %}
{% title "Overview" %}
{% endblock %}
{% block content %}
<h1><clr-icon shape="connect" size="48"></clr-icon>{% trans "OAuth2" %}</h1>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
{% trans "Connected Accounts" %}
</div>
<div class="card-footer">
{% if provider_state %}
<table class="table">
<thead>
<th>
<th>{% trans 'Provider' %}</th>
<th>{% trans 'Status' %}</th>
<th>{% trans 'Action' %}</th>
<th>{% trans 'ID' %}</th>
</th>
</thead>
<tbody>
{% for data in provider_state %}
<tr>
<td></td>
<td>{% trans data.provider.ui_name %}</td>
<td>{{ data.state|yesno:"Connected,Not Connected" }}</td>
<td>
{% if data.state == False %}
<a href="{% url 'passbook_oauth_client:oauth-client-login' provider=data.provider.name %}">Connect</a>
{% else %}
<a href="{% url 'passbook_oauth_client:oauth-client-disconnect' provider=data.provider.name %}">Disconnect</a>
{% endif %}
</td>
<td>{{ data.aas.first.identifier }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans "No Providers configured!" %}</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "user/base.html" %}
{% load i18n %}
{% block page %}
<h1>{{ source.name }}</h1>
{% if connections.exists %}
<p>{% trans 'Connected.' %}</p>
<a class="btn btn-danger" href="{% url 'passbook_oauth_client:oauth-client-disconnect' source_slug=source.slug %}">
{% trans 'Disconnect' %}
</a>
{% else %}
<p>Not connected.</p>
<a class="btn btn-primary" href="{% url 'passbook_oauth_client:oauth-client-login' source_slug=source.slug %}">
{% trans 'Connect' %}
</a>
{% endif %}
{% endblock %}

View File

@ -3,7 +3,7 @@
from django.urls import path from django.urls import path
from passbook.oauth_client.source_types.manager import RequestKind from passbook.oauth_client.source_types.manager import RequestKind
from passbook.oauth_client.views import core, dispatcher from passbook.oauth_client.views import core, dispatcher, user
urlpatterns = [ urlpatterns = [
path('login/<slug:source_slug>/', dispatcher.DispatcherView.as_view( path('login/<slug:source_slug>/', dispatcher.DispatcherView.as_view(
@ -12,4 +12,6 @@ urlpatterns = [
kind=RequestKind.callback), name='oauth-client-callback'), kind=RequestKind.callback), name='oauth-client-callback'),
path('disconnect/<slug:source_slug>/', core.DisconnectView.as_view(), path('disconnect/<slug:source_slug>/', core.DisconnectView.as_view(),
name='oauth-client-disconnect'), name='oauth-client-disconnect'),
path('user/<slug:source_slug>/', user.UserSettingsView.as_view(),
name='oauth-client-user'),
] ]

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

@ -0,0 +1,20 @@
"""passbook oauth_client user views"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection
class UserSettingsView(LoginRequiredMixin, TemplateView):
"""Show user current connection state"""
template_name = 'oauth_client/user.html'
def get_context_data(self, **kwargs):
source = get_object_or_404(OAuthSource, slug=self.kwargs.get('source_slug'))
connections = UserOAuthSourceConnection.objects.filter(user=self.request.user,
source=source)
kwargs['source'] = source
kwargs['connections'] = connections
return super().get_context_data(**kwargs)

View File

@ -1,2 +1,2 @@
"""passbook oauth_provider Header""" """passbook oauth_provider Header"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

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.19-beta' __version__ = '0.1.23-beta'

View File

@ -1,2 +1,2 @@
"""passbook password_expiry""" """passbook password_expiry"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'

View File

@ -10,7 +10,7 @@ class GitHubUserView(View):
def verify_access_token(self): def verify_access_token(self):
"""Verify access token manually since github uses /user?access_token=...""" """Verify access token manually since github uses /user?access_token=..."""
token = get_object_or_404(AccessToken, token=self.request.get('access_token', '')) token = get_object_or_404(AccessToken, token=self.request.GET.get('access_token', ''))
return token.user return token.user
def get(self, request): def get(self, request):

View File

@ -1,2 +1,2 @@
"""passbook saml_idp Header""" """passbook saml_idp Header"""
__version__ = '0.1.19-beta' __version__ = '0.1.23-beta'