Compare commits

...

14 Commits

58 changed files with 707 additions and 540 deletions

View File

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

View File

@ -54,7 +54,7 @@ package-docker:
before_script:
- echo "{\"auths\":{\"https://docker.$NEXUS_URL/\":{\"username\":\"$NEXUS_USER\",\"password\":\"$NEXUS_PASS\"}}}" > /kaniko/.docker/config.json
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.0.7-alpha
- /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.0.8-alpha
stage: build
only:
- tags

View File

@ -1,6 +1,5 @@
"""passbook provider"""
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from allauth_passbook.provider import PassbookProvider
urlpatterns = default_urlpatterns(PassbookProvider)

View File

@ -1,10 +1,10 @@
"""passbook adapter"""
import requests
from allauth.socialaccount import app_settings
from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView)
from allauth_passbook.provider import PassbookProvider

View File

@ -1,5 +1,5 @@
apiVersion: v1
appVersion: "0.0.7-alpha"
appVersion: "0.0.8-alpha"
description: A Helm chart for passbook.
name: passbook
version: 1.0.0

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,2 +1,2 @@
"""passbook admin"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -9,33 +9,35 @@
{% block content %}
<div class="container">
<h1>{% trans "Applications" %}</h1>
<span>{% trans "External Applications which use passbook as Identity-Provider, utilizing protocols like OAuth2 and SAML." %}</span>
<hr>
<a href="{% url 'passbook_admin:application-create' %}" class="btn btn-primary">
{% trans 'Create...' %}
</a>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Provider' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for application in object_list %}
<tr>
<td>{{ application.name }}</td>
<td>{{ application.provider }}</td>
<td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:application-update' pk=application.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:application-delete' pk=application.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h1><span class="pficon-applications"></span> {% trans "Applications" %}</h1>
<span>{% trans "External Applications which use passbook as Identity-Provider, utilizing protocols like OAuth2 and SAML." %}</span>
<hr>
<a href="{% url 'passbook_admin:application-create' %}" class="btn btn-primary">
{% trans 'Create...' %}
</a>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Provider' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for application in object_list %}
<tr>
<td>{{ application.name }}</td>
<td>{{ application.provider }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:application-update' pk=application.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:application-delete' pk=application.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -8,80 +8,80 @@
{% endblock %}
{% block content %}
<h1>{% trans "Audit Log" %}</h1>
<div id="pf-list-standard" class="list-group list-view-pf list-view-pf-view">
<h1><span class="pficon-catalog"></span> {% trans "Audit Log" %}</h1>
<div id="pf-list-standard" class="list-group list-view-pf list-view-pf-view">
{% for entry in object_list %}
<div class="list-group-item">
<div class="list-view-pf-main-info">
<div class="list-view-pf-left">
<span class="fa fa-plane list-view-pf-icon-sm"></span>
<div class="list-view-pf-main-info">
<div class="list-view-pf-left">
<span class="fa fa-plane list-view-pf-icon-sm"></span>
</div>
<div class="list-view-pf-body">
<div class="list-view-pf-description">
<div class="list-group-item-heading">
{{ entry.action }}
</div>
<div class="list-group-item-text">
{{ entry.context }}
</div>
</div>
<div class="list-view-pf-additional-info">
<div class="list-view-pf-additional-info-item">
<span class="pficon pficon-user"></span>
<strong>{{ entry.user }}</strong>
</div>
<div class="list-view-pf-additional-info-item">
<span class="pficon pficon-cluster"></span>
<strong>{{ entry.app|default:'-' }}</strong>
</div>
<div class="list-view-pf-additional-info-item">
<span class="fa fa-clock-o"></span>
<strong>{{ entry.created }}</strong>
</div>
<div class="list-view-pf-additional-info-item">
<span class="pficon pficon-screen"></span>
<strong>{{ entry.request_ip }}</strong>
</div>
</div>
</div>
</div>
<div class="list-view-pf-body">
<div class="list-view-pf-description">
<div class="list-group-item-heading">
{{ entry.action }}
</div>
<div class="list-group-item-text">
{{ entry.context }}
</div>
</div>
<div class="list-view-pf-additional-info">
<div class="list-view-pf-additional-info-item">
<span class="pficon pficon-user"></span>
<strong>{{ entry.user }}</strong>
</div>
<div class="list-view-pf-additional-info-item">
<span class="pficon pficon-cluster"></span>
<strong>{{ entry.app|default:'-' }}</strong>
</div>
<div class="list-view-pf-additional-info-item">
<span class="fa fa-clock-o"></span>
<strong>{{ entry.created }}</strong>
</div>
<div class="list-view-pf-additional-info-item">
<span class="pficon pficon-screen"></span>
<strong>{{ entry.request_ip }}</strong>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<script>
$(document).ready(function () {
// Row Checkbox Selection
$("#pf-list-standard input[type='checkbox']").change(function (e) {
if ($(this).is(":checked")) {
$(this).closest('.list-group-item').addClass("active");
} else {
$(this).closest('.list-group-item').removeClass("active");
}
});
// toggle dropdown menu
$('#pf-list-standard .list-view-pf-actions').on('show.bs.dropdown', function () {
var $this = $(this);
var $dropdown = $this.find('.dropdown');
var space = $(window).height() - $dropdown[0].getBoundingClientRect().top - $this.find('.dropdown-menu').outerHeight(true);
$dropdown.toggleClass('dropup', space < 10);
});
// allow users to select multiple list items with shift key
$('#pf-list-standard .list-group').on('click', '.list-view-pf-checkbox>input', function (event) {
var $list = $('.list-group');
var prevIndex = $list.data('preIndex');
var $listItems = $list.children('.list-group-item');
var $currentItem = $(this).closest('.list-group-item');
if (event.shiftKey && prevIndex > -1 && this.checked) {
var currentIndex = $listItems.index($currentItem);
var $selectScope = currentIndex - prevIndex > 0
? $currentItem.prevAll().not($listItems.eq(prevIndex).prevAll().addBack())
: $listItems.eq(prevIndex).prevAll().not($currentItem.prevAll().addBack());
$selectScope.addClass('active').find('.list-view-pf-checkbox').children('input').prop('checked', true);
}
$list.data('preIndex', this.checked ? $listItems.index($currentItem) : -1);
});
<script>
$(document).ready(function () {
// Row Checkbox Selection
$("#pf-list-standard input[type='checkbox']").change(function (e) {
if ($(this).is(":checked")) {
$(this).closest('.list-group-item').addClass("active");
} else {
$(this).closest('.list-group-item').removeClass("active");
}
});
// toggle dropdown menu
$('#pf-list-standard .list-view-pf-actions').on('show.bs.dropdown', function () {
var $this = $(this);
var $dropdown = $this.find('.dropdown');
var space = $(window).height() - $dropdown[0].getBoundingClientRect().top - $this.find('.dropdown-menu').outerHeight(true);
$dropdown.toggleClass('dropup', space < 10);
});
// allow users to select multiple list items with shift key
$('#pf-list-standard .list-group').on('click', '.list-view-pf-checkbox>input', function (event) {
var $list = $('.list-group');
var prevIndex = $list.data('preIndex');
var $listItems = $list.children('.list-group-item');
var $currentItem = $(this).closest('.list-group-item');
if (event.shiftKey && prevIndex > -1 && this.checked) {
var currentIndex = $listItems.index($currentItem);
var $selectScope = currentIndex - prevIndex > 0
? $currentItem.prevAll().not($listItems.eq(prevIndex).prevAll().addBack())
: $listItems.eq(prevIndex).prevAll().not($currentItem.prevAll().addBack());
$selectScope.addClass('active').find('.list-view-pf-checkbox').children('input').prop('checked', true);
}
$list.data('preIndex', this.checked ? $listItems.index($currentItem) : -1);
});
});
</script>
{% include 'partials/pagination.html' %}
});
</script>
{% include 'partials/pagination.html' %}
</div>
{% endblock %}

View File

@ -10,7 +10,7 @@
{% block content %}
<div class="container">
<h1>{% trans "Factors" %}</h1>
<h1><span class="pficon-plugged"></span> {% trans "Factors" %}</h1>
<span>{% trans "Factors required for a user to successfully authenticate." %}</span>
<hr>
<div class="dropdown">
@ -20,7 +20,8 @@
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:factor-create' %}?type={{ type }}">{{ name }}</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1"
href="{% url 'passbook_admin:factor-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %}
</ul>
</div>

View File

@ -9,32 +9,35 @@
{% block content %}
<div class="container">
<h1>{% trans "Invitations" %}</h1>
<span>{% trans "Create Invitation Links which optionally force a username or expire on a set date." %}</span>
<hr>
<a href="{% url 'passbook_admin:invitation-create' %}" class="btn btn-primary">
{% trans 'Create...' %}
</a>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Expiry' %}</th>
<th>{% trans 'Link' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for invitation in object_list %}
<tr>
<td>{{ invitation.expires|default:"Never" }}</td>
<td><pre>{{ invitation.link }}</pre></td>
<td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:invitation-delete' pk=invitation.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h1><span class="pficon-migration"></span> {% trans "Invitations" %}</h1>
<span>{% trans "Create Invitation Links which optionally force a username or expire on a set date." %}</span>
<hr>
<a href="{% url 'passbook_admin:invitation-create' %}" class="btn btn-primary">
{% trans 'Create...' %}
</a>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Expiry' %}</th>
<th>{% trans 'Link' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for invitation in object_list %}
<tr>
<td>{{ invitation.expires|default:"Never" }}</td>
<td>
<pre>{{ invitation.link }}</pre>
</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:invitation-delete' pk=invitation.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -7,11 +7,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Applications' %}</a>
<a href="{% url 'passbook_admin:applications' %}">
<span class="pficon-applications"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Applications' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ application_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:applications' %}">
<span class="pficon pficon-ok"></span>{{ application_count }}
</a>
</span>
</p>
</div>
</div>
@ -19,11 +26,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Sources' %}</a>
<a href="{% url 'passbook_admin:sources' %}">
<span class="pficon-resource-pool"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Sources' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ source_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:sources' %}">
<span class="pficon pficon-ok"></span>{{ source_count }}
</a>
</span>
</p>
</div>
</div>
@ -31,11 +45,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Providers' %}</a>
<a href="{% url 'passbook_admin:providers' %}">
<span class="pficon-integration"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Providers' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ provider_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:providers' %}">
<span class="pficon pficon-ok"></span>{{ provider_count }}
</a>
</span>
</p>
</div>
</div>
@ -43,11 +64,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Factors' %}</a>
<a href="{% url 'passbook_admin:factors' %}">
<span class="pficon-plugged"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Factors' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ factor_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:factors' %}">
<span class="pficon pficon-ok"></span>{{ factor_count }}
</a>
</span>
</p>
</div>
</div>
@ -55,11 +83,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Invitation' %}</a>
<a href="{% url 'passbook_admin:policies' %}">
<span class="pficon-infrastructure"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Policies' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ invitation_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:policies' %}">
<span class="pficon pficon-ok"></span>{{ policy_count }}
</a>
</span>
</p>
</div>
</div>
@ -67,11 +102,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Policies' %}</a>
<a href="{% url 'passbook_admin:invitations' %}">
<span class="pficon-migration"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Invitation' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ policy_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:invitations' %}">
<span class="pficon pficon-ok"></span>{{ invitation_count }}
</a>
</span>
</p>
</div>
</div>
@ -79,11 +121,18 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#"><span class="fa fa-shield"></span><span class="card-pf-aggregate-status-count"></span> {% trans 'Users' %}</a>
<a href="{% url 'passbook_admin:users' %}">
<span class="pficon-users"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Users' %}
</a>
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ user_count }}</a></span>
<span class="card-pf-aggregate-status-notification">
<a href="{% url 'passbook_admin:users' %}">
<span class="pficon pficon-ok"></span>{{ user_count }}
</a>
</span>
</p>
</div>
</div>

View File

@ -9,42 +9,46 @@
{% block content %}
<div class="container">
<h1>{% trans "Policies" %}</h1>
<span>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Factors." %}</span>
<hr>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown">
{% trans 'Create...' %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %}
</ul>
</div>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Class' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for policy in object_list %}
<tr>
<td>{{ policy.name }}</td>
<td>{{ policy|fieldtype }}</td>
<td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-update' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-test' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-delete' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h1><span class="pficon-infrastructure"></span> {% trans "Policies" %}</h1>
<span>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Factors." %}</span>
<hr>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown">
{% trans 'Create...' %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %}
<li role="presentation"><a role="menuitem" tabindex="-1"
href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %}
</ul>
</div>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Class' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for policy in object_list %}
<tr>
<td>{{ policy.name }}</td>
<td>{{ policy|fieldtype }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:policy-update' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:policy-test' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:policy-delete' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -10,45 +10,49 @@
{% block content %}
<div class="container">
<h1>{% trans "Providers" %}</h1>
<span>{% trans "Authentication Protocol Provider, used as Protocol behind an Application." %}</span>
<hr>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown">
{% trans 'Create...' %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %}
<li role="presentation"><a role="menuitem" tabindex="-1" href="{% url 'passbook_admin:provider-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %}
</ul>
</div>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Class' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for provider in object_list %}
<tr>
<td>{{ provider.name }}</td>
<td>{{ provider|fieldtype }}</td>
<td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:provider-update' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:provider-delete' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
{% get_links provider as links %}
{% for name, href in links.items %}
<a class="btn btn-default btn-sm" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
<h1><span class="pficon-integration"></span> {% trans "Providers" %}</h1>
<span>{% trans "Authentication Protocol Provider, used as Protocol behind an Application." %}</span>
<hr>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="createDropdown" data-toggle="dropdown">
{% trans 'Create...' %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %}
<li role="presentation"><a role="menuitem" tabindex="-1"
href="{% url 'passbook_admin:provider-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</ul>
</div>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Class' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for provider in object_list %}
<tr>
<td>{{ provider.name }}</td>
<td>{{ provider|fieldtype }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:provider-update' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:provider-delete' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
{% get_links provider as links %}
{% for name, href in links.items %}
<a class="btn btn-default btn-sm"
href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="container">
<h1>{% trans "Sources" %}</h1>
<h1><span class="pficon-resource-pool"></span> {% trans "Sources" %}</h1>
<span>{% trans "External Sources which can be used to get Identities into passbook, for example Social Providers like Twiter and GitHub or Enterprise Providers like ADFS and LDAP." %}</span>
<hr>
<div class="dropdown">
@ -27,6 +27,7 @@
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Class' %}</th>
<th>{% trans 'Additional Info' %}</th>
<th></th>
</tr>
</thead>
@ -35,6 +36,7 @@
<tr>
<td>{{ source.name }}</td>
<td>{{ source|fieldtype }}</td>
<td>{{ source.additional_info }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:source-update' pk=source.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>

View File

@ -5,34 +5,36 @@
{% block content %}
<div class="container">
<h1>{% trans "Users" %}</h1>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Username' %}</th>
<th>{% trans 'First Name' %}</th>
<th>{% trans 'Last Name' %}</th>
<th>{% trans 'Active' %}</th>
<th>{% trans 'Last Login' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in object_list %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.first_name|default:'-' }}</td>
<td>{{ user.last_name|default:'-' }}</td>
<td>{{ user.is_active }}</td>
<td>{{ user.last_login }}</td>
<td>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h1><span class="pficon-users"></span> {% trans "Users" %}</h1>
<hr>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Username' %}</th>
<th>{% trans 'First Name' %}</th>
<th>{% trans 'Last Name' %}</th>
<th>{% trans 'Active' %}</th>
<th>{% trans 'Last Login' %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in object_list %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.first_name|default:'-' }}</td>
<td>{{ user.last_name|default:'-' }}</td>
<td>{{ user.is_active }}</td>
<td>{{ user.last_login }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -4,4 +4,8 @@
{% block above_form %}
<h1>{% trans 'Create' %}</h1>
{% endblock %}
{% endblock %}
{% block action %}
{% trans 'Create' %}
{% endblock %}

View File

@ -11,7 +11,7 @@
<form action="" method="post" class="form-horizontal">
{% include 'partials/form.html' with form=form %}
<a class="btn btn-default" href="{% back %}">{% trans "Cancel" %}</a>
<input type="submit" class="btn btn-primary" value="{% trans 'Create' %}" />
<input type="submit" class="btn btn-primary" value="{% block action %}{% endblock %}" />
</form>
</div>
</div>

View File

@ -4,4 +4,8 @@
{% block above_form %}
<h1>{% trans 'Update' %}</h1>
{% endblock %}
{% endblock %}
{% block action %}
{% trans 'Update' %}
{% endblock %}

View File

@ -39,19 +39,18 @@ class FactorCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
source_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type)
factor_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
kwargs['type'] = model._meta.verbose_name
return kwargs
def get_form_class(self):
source_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type)
factor_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
if not model:
raise Http404
return path_to_class(model.form)
class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
"""Update factor"""
@ -61,11 +60,12 @@ class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
success_message = _('Successfully updated Factor')
def get_form_class(self):
source_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type)
if not model:
raise Http404
return path_to_class(model.form)
form_class_path = self.get_object().form
form_class = path_to_class(form_class_path)
return form_class
def get_object(self, queryset=None):
return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
"""Delete factor"""

View File

@ -1,2 +1,2 @@
"""passbook api"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,2 +1,2 @@
"""passbook audit Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -84,6 +84,8 @@ class LoginAttempt(CreatedUpdatedModel):
@staticmethod
def attempt(target_uid, request):
"""Helper function to create attempt or count up existing one"""
if not target_uid:
return
client_ip, _ = get_client_ip(request)
# Since we can only use 254 chars for target_uid, truncate target_uid.
target_uid = target_uid[:254]

View File

@ -1,2 +1,2 @@
"""passbook captcha_factor Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,2 +1,2 @@
"""passbook core"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,9 +1,11 @@
"""passbook multi-factor authentication engine"""
from logging import getLogger
from django.contrib import messages
from django.contrib.auth import authenticate
from django.core.exceptions import PermissionDenied
from django.forms.utils import ErrorList
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from django.views.generic import FormView
@ -21,6 +23,19 @@ class PasswordFactor(FormView, AuthenticationFactor):
form_class = PasswordFactorForm
template_name = 'login/factors/backend.html'
def get_context_data(self, **kwargs):
kwargs['show_password_forget_notice'] = CONFIG.y('passbook.password_reset.enabled')
return super().get_context_data(**kwargs)
def get(self, request, *args, **kwargs):
if 'password-forgotten' in request.GET:
# TODO: Save nonce key in database for password reset
# TODO: Send email to user
self.authenticator.cleanup()
messages.success(request, _('Check your E-Mails for a password reset link.'))
return redirect('passbook_core:auth-login')
return super().get(request, *args, **kwargs)
def form_valid(self, form):
"""Authenticate against django's authentication backend"""
uid_fields = CONFIG.y('passbook.uid_fields')

View File

@ -111,7 +111,7 @@ class AuthenticationView(UserPassesTestMixin, View):
"""Show error message, user cannot login.
This should only be shown if user authenticated successfully, but is disabled/locked/etc"""
LOGGER.debug("User invalid")
self._cleanup()
self.cleanup()
return redirect(reverse('passbook_core:auth-denied'))
def _user_passed(self):
@ -121,13 +121,13 @@ class AuthenticationView(UserPassesTestMixin, View):
login(self.request, self.pending_user, backend=backend)
LOGGER.debug("Logged in user %s", self.pending_user)
# Cleanup
self._cleanup()
self.cleanup()
next_param = self.request.GET.get('next', None)
if next_param and is_url_absolute(next_param):
return redirect(next_param)
return redirect(reverse('passbook_core:overview'))
def _cleanup(self):
def cleanup(self):
"""Remove temporary data from session"""
session_keys = [self.SESSION_FACTOR, self.SESSION_PENDING_FACTORS,
self.SESSION_PENDING_USER, self.SESSION_USER_BACKEND, ]

View File

@ -11,7 +11,7 @@ class PasswordFactorForm(forms.ModelForm):
class Meta:
model = PasswordFactor
fields = GENERAL_FIELDS + ['backends']
fields = GENERAL_FIELDS + ['backends', 'password_policies']
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),

View File

@ -0,0 +1,25 @@
# Generated by Django 2.1.7 on 2019-02-25 14:38
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0010_auto_20190224_1016'),
]
operations = [
migrations.AddField(
model_name='passwordfactor',
name='password_policies',
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
),
migrations.AddField(
model_name='user',
name='password_change_date',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@ -9,9 +9,11 @@ from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.urls import reverse_lazy
from django.utils.timezone import now
from django.utils.translation import gettext as _
from model_utils.managers import InheritanceManager
from passbook.core.signals import password_changed
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
LOGGER = getLogger(__name__)
@ -38,6 +40,12 @@ class User(AbstractUser):
sources = models.ManyToManyField('Source', through='UserSourceConnection')
applications = models.ManyToManyField('Application')
groups = models.ManyToManyField('Group')
password_change_date = models.DateTimeField(auto_now_add=True)
def set_password(self, password):
password_changed.send(sender=self, user=self, password=password)
self.password_change_date = now()
return super().set_password(password)
class Provider(models.Model):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
@ -87,6 +95,7 @@ class PasswordFactor(Factor):
"""Password-based Django-backend Authentication Factor"""
backends = ArrayField(models.TextField())
password_policies = models.ManyToManyField('Policy', blank=True)
type = 'passbook.core.auth.factors.password.PasswordFactor'
form = 'passbook.core.forms.factors.PasswordFactorForm'
@ -94,6 +103,13 @@ class PasswordFactor(Factor):
def has_user_settings(self):
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password'
def password_passes(self, user: User) -> bool:
"""Return true if user's password passes, otherwise False or raise Exception"""
for policy in self.policies.all():
if not policy.passes(user):
return False
return True
def __str__(self):
return "Password Factor %s" % self.slug
@ -155,10 +171,15 @@ class Source(PolicyModel):
return False
@property
def get_url(self):
"""Return URL used for logging in"""
def get_login_button(self):
"""Return a tuple of URL, Icon name and Name"""
raise NotImplementedError
@property
def additional_info(self):
"""Return additional Info, such as a callback URL. Show in the administration interface."""
return None
def __str__(self):
return self.name

View File

@ -73,6 +73,7 @@ INSTALLED_APPS = [
'passbook.saml_idp.apps.PassbookSAMLIDPConfig',
'passbook.otp.apps.PassbookOTPConfig',
'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig',
'passbook.hibp_policy.apps.PassbookHIBPConfig',
]
# Message Tag fix for bootstrap CSS Classes

View File

@ -9,3 +9,4 @@ from django.core.signals import Signal
user_signed_up = Signal(providing_args=['request', 'user'])
invitation_created = Signal(providing_args=['request', 'invitation'])
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
password_changed = Signal(providing_args=['user', 'password'])

View File

@ -23,16 +23,16 @@
{% endblock %}
{% block body %}
<div class="toast-notifications-list-pf">
{% include 'partials/messages.html' %}
</div>
<div class="login-pf-page">
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
{% include 'partials/messages.html' %}
</div>
<div class="col-sm-6 col-sm-offset-3 col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4">
<header class="login-pf-page-header">
<img class="login-pf-brand" style="max-height: 10rem;" src="{% static 'img/logo.svg' %}"
alt="PatternFly logo" />
alt="passbook logo" />
{% if config.login.subtext %}
<p>{{ config.login.subtext }}</p>
{% endif %}

View File

@ -2,3 +2,8 @@
{% load i18n %}
{% block beneath_form %}
{% if show_password_forget_notice %}
<a href="{% url 'passbook_core:auth-process' %}?password-forgotten">{% trans 'Forgot password?' %}</a>
{% endif %}
{% endblock %}

View File

@ -8,10 +8,11 @@
<h1>{% trans title %}</h1>
</header>
<form method="POST">
{% csrf_token %}
{% block above_form %}
{% endblock %}
{% include 'partials/form_login.html' %}
{% block beneath_form %}
{% endblock %}
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button>
</form>
{% if show_sign_up_notice %}

View File

@ -1,131 +0,0 @@
{% load static %}
<!DOCTYPE html>
<!--[if IE 9]><html lang="en-us" class="ie9 login-pf"><![endif]-->
<!--[if gt IE 9]><!-->
<html lang="en-us" class="login-pf">
<!--<![endif]-->
<head>
<title>Login Social Account (two column) - Red Hat&reg; Common User Experience</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}">
<!-- iPad retina icon -->
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{% static 'img/apple-touch-icon-precomposed-152.png' %}">
<!-- iPad retina icon (iOS < 7) -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{% static 'img/apple-touch-icon-precomposed-144.png' %}">
<!-- iPad non-retina icon -->
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="{% static 'img/apple-touch-icon-precomposed-76.png' %}">
<!-- iPad non-retina icon (iOS < 7) -->
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="{% static 'img/apple-touch-icon-precomposed-72.png' %}">
<!-- iPhone 6 Plus icon -->
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="{% static 'img/apple-touch-icon-precomposed-180.png' %}">
<!-- iPhone retina icon (iOS < 7) -->
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="{% static 'img/apple-touch-icon-precomposed-114.png' %}">
<!-- iPhone non-retina icon (iOS < 7) -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="{% static 'img/apple-touch-icon-precomposed-57.png' %}">
<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' %}">
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.12.2/js/bootstrap-select.min.js"></script>
<script src="{% static 'js/patternfly.min.js' %}"></script>
</head>
<div class="toast-notifications-list-pf">
<div class="toast-pf alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-warning-triangle-o"></span>
These examples are included for development testing purposes. For official documentation, see <a href="https://www.patternfly.org" class="alert-link">https://www.patternfly.org</a> and <a href="http://getbootstrap.com" class="alert-link">http://getbootstrap.com</a>.
</div>
</div>
<body>
<div class="login-pf-page login-pf-page-accounts">
<header class="login-pf-page-header">
<img class="login-pf-brand" src="/" alt="Red Hat&reg; logo" />
</header>
<div class="card-pf login-pf-accounts">
<header class="login-pf-header">
<select class="selectpicker">
<option>English</option>
<option>French</option>
<option>Italian</option>
</select>
<h1>Log In to Your Account</h1>
</header>
<section class="login-pf-social-section" role="contentinfo" aria-label="Log in to your patternfly account">
<form>
<div class="form-group">
<label class="sr-only" for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control input-lg" id="exampleInputEmail1" placeholder="Email address">
</div>
<div class="form-group">
<label class="sr-only" for="exampleInputPassword1">Password
</label>
<input type="password" class="form-control input-lg" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="login-pf-settings">
<label class="checkbox-label">
<input type="checkbox"> Keep me logged in for 30 days
</label>
<a href="#">Forgot password?</a>
</div>
<button type="submit" class="btn btn-primary btn-block btn-lg">Log In</button>
</form>
</section><!--login-pf-section-->
<section class="login-pf-social-section" role="contentinfo" aria-label="Log in with third party account">
<ul class="login-pf-social login-pf-social-double-col list-unstyled">
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/google-logo.svg' %}" alt="Google account login">Google</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/github-logo.svg' %}" alt="github account login">Github</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/facebook-logo.svg' %}" alt="Facebook account login">Facebook</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/twitter-logo.svg' %}" alt="Twitter account login">Twitter</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/linkedin-logo.svg' %}" alt="LinkIn account login">LinkIn</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/stack-exchange-logo.svg' %}" alt="Stack Exchange logo">Stack Exchange</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/open-id-logo.svg' %}" alt="Open ID account login">Open ID</a></li>
<li class="login-pf-social-link"><a href="#"><img src="{% static 'img/instagram-logo.png' %}" alt="Instagram account login">Instagram</a></li>
<li class="login-pf-social-link login-pf-social-link-more"><a href="#"><img src="{% static 'img/git-logo.svg' %}" alt="Git account login">Git</a></li>
<li class="login-pf-social-link login-pf-social-link-more"><a href="#"><img src="{% static 'img/dropbox-logo.svg' %}" alt="dropbox account login">Dropbox</a></li>
<li class="login-pf-social-link login-pf-social-link-more"><a href="#"><img src="{% static 'img/fedora-logo.png' %}" alt="fedora account login">Fedora</a></li>
<li class="login-pf-social-link login-pf-social-link-more"><a href="#"><img src="{% static 'img/skype-logo.svg' %}" alt="skype account logingit ">Skype</a></li>
</ul>
<button type="button" id="socialAccountsToggle" class="btn btn-link login-pf-social-toggle">More<span class="caret"></span></button>
</section><!--login-pf-section-->
<p class="login-pf-signup">Need an account?<a href="#">Sign up</a></p>
</div><!-- card -->
<div class="row">
<div class="col-md-6 col-md-offset-3">
<footer class="login-pf-page-footer">
<ul class="login-pf-page-footer-links list-unstyled">
<li><a class="login-pf-page-footer-link" href="#">Terms of Use</a></li>
<li><a class="login-pf-page-footer-link" href="#">Help</a></li>
<li><a class="login-pf-page-footer-link" href="#">Privacy Policy</a></li>
</ul>
</footer>
</div>
</div>
</div><!-- login-pf-page -->
<script>
$("#socialAccountsToggle").on("click", function(e) {
var $toggle = $(e.target);
var text = $toggle.contents().first()[0];
var socialContainer = $('.login-pf-social-section > .login-pf-social');
if ($toggle.hasClass('login-pf-social-toggle-active')) {
$toggle.removeClass('login-pf-social-toggle-active');
text.textContent = 'More';
socialContainer.removeClass('login-pf-social-all');
} else {
$toggle.addClass('login-pf-social-toggle-active');
text.textContent = 'Less';
socialContainer.addClass('login-pf-social-all');
}
});
</script>
</body>
</html>

View File

@ -1,51 +1,79 @@
{% extends 'login/base.html' %}
{% extends 'base/skeleton.html' %}
{% load static %}
{% load i18n %}
{% block row %}
{% include 'partials/messages.html' %}
<div class="col-md-6">
<div class="card-pf">
{% block head %}
<style>
.login-pf-page .login-pf-page-footer-links {
padding: 15px;
background-color: #fff;
border-top: 2px solid transparent;
box-shadow: 0 1px 1px rgba(3, 3, 3, .175);
}
.login-pf-page .login-pf-page-footer-link {
color: #72767b;
}
.login-pf-page .login-pf-page-footer-links li:not(:last-of-type):after {
color: #72767b;
}
</style>
{% endblock %}
{% block body %}
<div class="toast-notifications-list-pf">
{% include 'partials/messages.html' %}
</div>
<div class="login-pf-page login-pf-page-accounts">
<header class="login-pf-page-header">
<img class="login-pf-brand" style="max-height: 10rem;" src="{% static 'img/logo.svg' %}" alt="passbook logo" />
{% if config.login.subtext %}
<p>{{ config.login.subtext }}</p>
{% endif %}
</header>
<div class="card-pf login-pf-accounts">
<header class="login-pf-header">
<h1>{% trans title %}</h1>
</header>
<form method="POST">
{% csrf_token %}
{% block above_form %}
{% endblock %}
{% include 'partials/form_login.html' %}
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button>
</form>
<section class="login-pf-social-section" role="contentinfo" aria-label="Log in to your patternfly account">
<form method="POST">
{% block above_form %}
{% endblock %}
{% include 'partials/form_login.html' %}
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button>
</form>
</section>
<!--login-pf-section-->
<section class="login-pf-social-section" role="contentinfo" aria-label="Log in with third party account">
<ul class="login-pf-social login-pf-social-double-col list-unstyled">
{% for url, icon, name in sources %}
<li class="login-pf-social-link">
<a href="{{ url }}">
<img src="{% static 'img/' %}{{ icon }}.svg" alt="{{ name }}"> {{ name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% if show_sign_up_notice %}
<p class="login-pf-signup">
{% trans 'Need an account?' %}
<a href="{% url 'passbook_core:auth-sign-up' %}">{% trans 'Sign up' %}</a>
</p>
{% endif %}
</div><!-- card -->
<div class="row">
<div class="col-md-6 col-md-offset-3">
<footer class="login-pf-page-footer">
<ul class="login-pf-page-footer-links list-unstyled">
<li><a class="login-pf-page-footer-link" href="#">Terms of Use</a></li>
<li><a class="login-pf-page-footer-link" href="#">Help</a></li>
<li><a class="login-pf-page-footer-link" href="#">Privacy Policy</a></li>
</ul>
</footer>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card-pf">
<header class="login-pf-header">
<h1>{% trans title %}</h1>
<ul>
{% for source in sources %}
<li>
<a class="btn btn-block btn-primary" href="{{ source.get_url }}">{{ source }}</a>
</li>
{% endfor %}
</ul>
</header>
</div>
</div>
<div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2">
<footer class="login-pf-page-footer">
<ul class="login-pf-page-footer-links list-unstyled">
<li><a class="login-pf-page-footer-link" href="#">Terms of Use</a></li>
<li><a class="login-pf-page-footer-link" href="#">Help</a></li>
<li><a class="login-pf-page-footer-link" href="#">Privacy Policy</a></li>
</ul>
</footer>
</div>
{% endblock %}

View File

@ -5,83 +5,83 @@
{% load is_active %}
{% block body %}
<div class="toast-notifications-list-pf">
{% include 'partials/messages.html' %}
</div>
<nav class="navbar navbar-default navbar-pf" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
<span class="sr-only">{% trans 'Toggle navigation' %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">
<img src="{% static 'img/brand.svg' %}" alt="passbook" />
</a>
</div>
<div class="collapse navbar-collapse navbar-collapse-1">
<ul class="nav navbar-nav navbar-utility">
<li class="dropdown">
<button class="btn btn-link nav-item-iconic" id="horizontalDropdownMenu1" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<span title="Help" class="fa pficon-help dropdown-title"></span>
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
<span class="sr-only">{% trans 'Toggle navigation' %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="horizontalDropdownMenu1">
{% comment %} <li><a href="#0">Help</a></li> {% endcomment %}
<li><a data-toggle="modal" data-target="#about-modal" href="#0">{% trans 'About' %}</a></li>
<a class="navbar-brand" href="/">
<img src="{% static 'img/brand.svg' %}" alt="passbook" />
</a>
</div>
<div class="collapse navbar-collapse navbar-collapse-1">
<ul class="nav navbar-nav navbar-utility">
<li class="dropdown">
<button class="btn btn-link nav-item-iconic" id="horizontalDropdownMenu1" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<span title="Help" class="fa pficon-help dropdown-title"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="horizontalDropdownMenu1">
{% comment %} <li><a href="#0">Help</a></li> {% endcomment %}
<li><a data-toggle="modal" data-target="#about-modal" href="#0">{% trans 'About' %}</a></li>
</ul>
</li>
<li class="dropdown">
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">
<span class="pficon pficon-user"></span>
<span class="dropdown-title">
{{ user.username }} <b class="caret"></b>
</span>
</button>
<ul class="dropdown-menu">
<li>
<a href="{% url 'passbook_core:user-settings' %}">{% trans 'User Settings' %}</a>
</li>
<li>
<a href="{% url 'passbook_core:user-change-password' %}">{% trans 'Change Password' %}</a>
</li>
<li class="divider"></li>
<li>
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="dropdown">
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">
<span class="pficon pficon-user"></span>
<span class="dropdown-title">
{{ user.username }} <b class="caret"></b>
</span>
</button>
<ul class="dropdown-menu">
<li>
<a href="{% url 'passbook_core:user-settings' %}">{% trans 'User Settings' %}</a>
</li>
<li>
<a href="{% url 'passbook_core:user-change-password' %}">{% trans 'Change Password' %}</a>
</li>
<li class="divider"></li>
<li>
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a>
</li>
{% is_active_app 'passbook_admin' as is_admin %}
<ul class="nav navbar-nav navbar-primary {% if is_admin == 'active' %}persistent-secondary{% endif %}">
<li class="{% is_active_url 'passbook_core:overview' %}">
<a href="{% url 'passbook_core:overview' %}">{% trans 'Overview' %}</a>
</li>
{% if user.is_superuser %}
<li class="{% is_active_app 'passbook_admin' %}">
<a href="{% url 'passbook_admin:overview' %}">{% trans 'Administration' %}</a>
{% block nav_secondary %}
{% endblock %}
</li>
{% endif %}
</ul>
</li>
</ul>
{% is_active_app 'passbook_admin' as is_admin %}
<ul class="nav navbar-nav navbar-primary {% if is_admin == 'active' %}persistent-secondary{% endif %}">
<li class="{% is_active_url 'passbook_core:overview' %}">
<a href="{% url 'passbook_core:overview' %}">{% trans 'Overview' %}</a>
</li>
{% if user.is_superuser %}
<li class="{% is_active_app 'passbook_admin' %}">
<a href="{% url 'passbook_admin:overview' %}">{% trans 'Administration' %}</a>
{% block nav_secondary %}
{% endblock %}
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container-fluid container-cards-pf">
<div class="container">
{% include 'partials/messages.html' %}
</div>
{% block content %}
{% endblock %}
{% block content %}
{% endblock %}
</div>
{% endblock %}
{% block scripts %}
<script>
$(document).ready(function () {
// initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
$(document).ready(function () {
// initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
// Initialize the vertical navigation
$().setupVerticalNavigation(true);
});
// Initialize the vertical navigation
$().setupVerticalNavigation(true);
});
</script>
{% endblock %}

View File

@ -25,9 +25,6 @@
<label class="checkbox-label">
{{ field }} {{ field.label }}
</label>
{% if show_password_forget_notice %}
<a href="#">{% trans 'Forgot password?' %}</a>
{% endif %}
{% else %}
<label class="col-sm-2 sr-only" {% if field.field.required %}class="required"{% endif %} for="{{ field.name }}-{{ forloop.counter0 }}">
{{ field.label }}

View File

@ -1,19 +1,19 @@
{% if messages %}
{% for msg in messages %}
<div class="alert alert-{{ msg.level_tag }}">
{% for msg in messages %}
<div class="toast-pf alert alert-dismissable alert-{{ msg.level_tag }}">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span class="pficon pficon-close"></span>
<span class="pficon pficon-close"></span>
</button>
{% if msg.level_tag == 'danger' %}
<span class="pficon pficon-error-circle-o"></span>
<span class="pficon pficon-error-circle-o"></span>
{% elif msg.level_tag == 'warning' %}
<span class="pficon pficon-warning-triangle-o"></span>
<span class="pficon pficon-warning-triangle-o"></span>
{% elif msg.level_tag == 'success' %}
<span class="pficon pficon-ok"></span>
<span class="pficon pficon-ok"></span>
{% elif msg.level_tag == 'info' %}
<span class="pficon pficon-info"></span>
<span class="pficon pficon-info"></span>
{% endif %}
<strong>{{ msg.message|safe }}</strong>
</div>
{% endfor %}
{{ msg.message|safe }}
</div>
{% endfor %}
{% endif %}

View File

View File

@ -0,0 +1,10 @@
"""passbook core login test"""
from django.test import TestCase
class LoginTest(TestCase):
"""Test login"""
def test(self):
"""Stub test"""

View File

@ -41,10 +41,12 @@ class LoginView(UserPassesTestMixin, FormView):
kwargs['title'] = _('Log in to your account')
kwargs['primary_action'] = _('Log in')
kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled')
kwargs['show_password_forget_notice'] = CONFIG.y('passbook.password_reset.enabled')
kwargs['sources'] = Source.objects.filter(enabled=True).select_subclasses()
if any(source.is_link for source in kwargs['sources']):
self.template_name = 'login/test.html'
kwargs['sources'] = []
sources = Source.objects.filter(enabled=True).select_subclasses()
if any(source.is_link for source in sources):
for source in sources:
kwargs['sources'].append(source.get_login_button)
self.template_name = 'login/with_sources.html'
return super().get_context_data(**kwargs)
def get_user(self, uid_value) -> User:

View File

@ -0,0 +1,2 @@
"""passbook hibp_policy"""
__version__ = '0.0.7-alpha'

View File

@ -0,0 +1,5 @@
"""Passbook HIBP Admin"""
from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_hibp_policy')

View File

@ -0,0 +1,11 @@
"""Passbook hibp app config"""
from django.apps import AppConfig
class PassbookHIBPConfig(AppConfig):
"""Passbook hibp app config"""
name = 'passbook.hibp_policy'
label = 'passbook_hibp_policy'
verbose_name = 'passbook HaveIBeenPwned Policy'

View File

@ -0,0 +1,19 @@
"""passbook HaveIBeenPwned Policy forms"""
from django import forms
from passbook.core.forms.policies import GENERAL_FIELDS
from passbook.hibp_policy.models import HaveIBeenPwendPolicy
class HaveIBeenPwnedPolicyForm(forms.ModelForm):
"""Edit HaveIBeenPwendPolicy instances"""
class Meta:
model = HaveIBeenPwendPolicy
fields = GENERAL_FIELDS + ['allowed_count']
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
}

View File

@ -0,0 +1,28 @@
# Generated by Django 2.1.7 on 2019-02-25 15:50
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('passbook_core', '0011_auto_20190225_1438'),
]
operations = [
migrations.CreateModel(
name='HaveIBeenPwendPolicy',
fields=[
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
('allowed_count', models.IntegerField(default=0)),
],
options={
'verbose_name': 'HaveIBeenPwned Policy',
'verbose_name_plural': 'HaveIBeenPwned Policies',
},
bases=('passbook_core.policy',),
),
]

View File

@ -0,0 +1,43 @@
"""passbook HIBP Models"""
from hashlib import sha1
from django.db import models
from django.utils.translation import gettext as _
from requests import get
from passbook.core.models import Policy, User
class HaveIBeenPwendPolicy(Policy):
"""Check if password is on HaveIBeenPwned's list by upload the first
5 characters of the SHA1 Hash."""
allowed_count = models.IntegerField(default=0)
form = 'passbook.hibp_policy.forms.HaveIBeenPwnedPolicyForm'
def passes(self, user: User) -> bool:
"""Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5
characters of Password in request and checks if full hash is in response. Returns 0
if Password is not in result otherwise the count of how many times it was used."""
# Only check if password is being set
if not hasattr(user, '__password__'):
return True
password = getattr(user, '__password__')
pw_hash = sha1(password.encode('utf-8')).hexdigest() # nosec
url = 'https://api.pwnedpasswords.com/range/%s' % pw_hash[:5]
result = get(url).text
final_count = 0
for line in result.split('\r\n'):
full_hash, count = line.split(':')
if pw_hash[5:] == full_hash.lower():
final_count = int(count)
if final_count > self.allowed_count:
return False
return True
class Meta:
verbose_name = _('have i been pwned Policy')
verbose_name_plural = _('have i been pwned Policies')

View File

@ -1,2 +1,2 @@
"""Passbook ldap app Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -29,7 +29,7 @@ class LDAPSource(Source):
form = 'passbook.ldap.forms.LDAPSourceForm'
@property
def get_url(self):
def get_login_button(self):
raise NotImplementedError()
class Meta:

View File

@ -1,2 +1,2 @@
"""passbook lib"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,2 +1,2 @@
"""passbook oauth_client Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -26,9 +26,17 @@ class OAuthSource(Source):
return True
@property
def get_url(self):
return reverse_lazy('passbook_oauth_client:oauth-client-login',
kwargs={'source_slug': self.slug})
def get_login_button(self):
url = reverse_lazy('passbook_oauth_client:oauth-client-login',
kwargs={'source_slug': self.slug})
if self.provider_type == 'github':
return url, 'github-logo', _('GitHub')
return url, 'generic', _('Generic')
@property
def additional_info(self):
return "Callback URL: '%s'" % reverse_lazy('passbook_oauth_client:oauth-client-callback',
kwargs={'source_slug': self.slug})
class Meta:

View File

@ -1,2 +1,2 @@
"""passbook oauth_provider Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,2 +1,2 @@
"""passbook otp Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'

View File

@ -1,2 +1,2 @@
"""passbook saml_idp Header"""
__version__ = '0.0.7-alpha'
__version__ = '0.0.8-alpha'