Compare commits

...

14 Commits

58 changed files with 707 additions and 540 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.0.7-alpha current_version = 0.0.8-alpha
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\":{\"https://docker.$NEXUS_URL/\":{\"username\":\"$NEXUS_USER\",\"password\":\"$NEXUS_PASS\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"https://docker.$NEXUS_URL/\":{\"username\":\"$NEXUS_USER\",\"password\":\"$NEXUS_PASS\"}}}" > /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.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 stage: build
only: only:
- tags - tags

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1>{% trans "Applications" %}</h1> <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> <span>{% trans "External Applications which use passbook as Identity-Provider, utilizing protocols like OAuth2 and SAML." %}</span>
<hr> <hr>
<a href="{% url 'passbook_admin:application-create' %}" class="btn btn-primary"> <a href="{% url 'passbook_admin:application-create' %}" class="btn btn-primary">
@ -30,8 +30,10 @@
<td>{{ application.name }}</td> <td>{{ application.name }}</td>
<td>{{ application.provider }}</td> <td>{{ application.provider }}</td>
<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"
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:application-delete' pk=application.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> 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> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -8,7 +8,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h1>{% trans "Audit Log" %}</h1> <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"> <div id="pf-list-standard" class="list-group list-view-pf list-view-pf-view">
{% for entry in object_list %} {% for entry in object_list %}
<div class="list-group-item"> <div class="list-group-item">

View File

@ -10,7 +10,7 @@
{% block content %} {% block content %}
<div class="container"> <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> <span>{% trans "Factors required for a user to successfully authenticate." %}</span>
<hr> <hr>
<div class="dropdown"> <div class="dropdown">
@ -20,7 +20,8 @@
</button> </button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown"> <ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %} {% 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 %} {% endfor %}
</ul> </ul>
</div> </div>

View File

@ -9,7 +9,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1>{% trans "Invitations" %}</h1> <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> <span>{% trans "Create Invitation Links which optionally force a username or expire on a set date." %}</span>
<hr> <hr>
<a href="{% url 'passbook_admin:invitation-create' %}" class="btn btn-primary"> <a href="{% url 'passbook_admin:invitation-create' %}" class="btn btn-primary">
@ -28,9 +28,12 @@
{% for invitation in object_list %} {% for invitation in object_list %}
<tr> <tr>
<td>{{ invitation.expires|default:"Never" }}</td> <td>{{ invitation.expires|default:"Never" }}</td>
<td><pre>{{ invitation.link }}</pre></td>
<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> <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> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -7,11 +7,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>
@ -19,11 +26,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>
@ -31,11 +45,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>
@ -43,11 +64,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>
@ -55,11 +83,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>
@ -67,11 +102,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>
@ -79,11 +121,18 @@
<div class="col-xs-6 col-sm-2 col-md-2"> <div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status"> <div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title"> <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> </h2>
<div class="card-pf-body"> <div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications"> <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> </p>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1>{% trans "Policies" %}</h1> <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> <span>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Factors." %}</span>
<hr> <hr>
<div class="dropdown"> <div class="dropdown">
@ -19,7 +19,8 @@
</button> </button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown"> <ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %} {% 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> <li role="presentation"><a role="menuitem" tabindex="-1"
href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -38,9 +39,12 @@
<td>{{ policy.name }}</td> <td>{{ policy.name }}</td>
<td>{{ policy|fieldtype }}</td> <td>{{ policy|fieldtype }}</td>
<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"
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:policy-test' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a> 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-delete' pk=policy.uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</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> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -10,7 +10,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1>{% trans "Providers" %}</h1> <h1><span class="pficon-integration"></span> {% trans "Providers" %}</h1>
<span>{% trans "Authentication Protocol Provider, used as Protocol behind an Application." %}</span> <span>{% trans "Authentication Protocol Provider, used as Protocol behind an Application." %}</span>
<hr> <hr>
<div class="dropdown"> <div class="dropdown">
@ -20,7 +20,8 @@
</button> </button>
<ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown"> <ul class="dropdown-menu" role="menu" aria-labelledby="createDropdown">
{% for type, name in types.items %} {% 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> <li role="presentation"><a role="menuitem" tabindex="-1"
href="{% url 'passbook_admin:provider-create' %}?type={{ type }}">{{ name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -39,11 +40,14 @@
<td>{{ provider.name }}</td> <td>{{ provider.name }}</td>
<td>{{ provider|fieldtype }}</td> <td>{{ provider|fieldtype }}</td>
<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"
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:provider-delete' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> 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 %} {% get_links provider as links %}
{% for name, href in links.items %} {% for name, href in links.items %}
<a class="btn btn-default btn-sm" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a> <a class="btn btn-default btn-sm"
href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %} {% endfor %}
</td> </td>
</tr> </tr>

View File

@ -6,7 +6,7 @@
{% block content %} {% block content %}
<div class="container"> <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> <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> <hr>
<div class="dropdown"> <div class="dropdown">
@ -27,6 +27,7 @@
<tr> <tr>
<th>{% trans 'Name' %}</th> <th>{% trans 'Name' %}</th>
<th>{% trans 'Class' %}</th> <th>{% trans 'Class' %}</th>
<th>{% trans 'Additional Info' %}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -35,6 +36,7 @@
<tr> <tr>
<td>{{ source.name }}</td> <td>{{ source.name }}</td>
<td>{{ source|fieldtype }}</td> <td>{{ source|fieldtype }}</td>
<td>{{ source.additional_info }}</td>
<td> <td>
<a class="btn btn-default btn-sm" <a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:source-update' pk=source.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> href="{% url 'passbook_admin:source-update' pk=source.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>

View File

@ -5,7 +5,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1>{% trans "Users" %}</h1> <h1><span class="pficon-users"></span> {% trans "Users" %}</h1>
<hr> <hr>
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
@ -27,8 +27,10 @@
<td>{{ user.is_active }}</td> <td>{{ user.is_active }}</td>
<td>{{ user.last_login }}</td> <td>{{ user.last_login }}</td>
<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"
<a class="btn btn-default btn-sm" href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> 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> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -5,3 +5,7 @@
{% block above_form %} {% block above_form %}
<h1>{% trans 'Create' %}</h1> <h1>{% trans 'Create' %}</h1>
{% endblock %} {% endblock %}
{% block action %}
{% trans 'Create' %}
{% endblock %}

View File

@ -11,7 +11,7 @@
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
{% include 'partials/form.html' with form=form %} {% include 'partials/form.html' with form=form %}
<a class="btn btn-default" href="{% back %}">{% trans "Cancel" %}</a> <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> </form>
</div> </div>
</div> </div>

View File

@ -5,3 +5,7 @@
{% block above_form %} {% block above_form %}
<h1>{% trans 'Update' %}</h1> <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): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
source_type = self.request.GET.get('type') factor_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type) model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
kwargs['type'] = model._meta.verbose_name kwargs['type'] = model._meta.verbose_name
return kwargs return kwargs
def get_form_class(self): def get_form_class(self):
source_type = self.request.GET.get('type') factor_type = self.request.GET.get('type')
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type) model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView): class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
"""Update factor""" """Update factor"""
@ -61,11 +60,12 @@ class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
success_message = _('Successfully updated Factor') success_message = _('Successfully updated Factor')
def get_form_class(self): def get_form_class(self):
source_type = self.request.GET.get('type') form_class_path = self.get_object().form
model = next(x for x in all_subclasses(Factor) if x.__name__ == source_type) form_class = path_to_class(form_class_path)
if not model: return form_class
raise Http404
return path_to_class(model.form) def get_object(self, queryset=None):
return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
"""Delete factor""" """Delete factor"""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,11 @@
"""passbook multi-factor authentication engine""" """passbook multi-factor authentication engine"""
from logging import getLogger from logging import getLogger
from django.contrib import messages
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.shortcuts import redirect
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView from django.views.generic import FormView
@ -21,6 +23,19 @@ class PasswordFactor(FormView, AuthenticationFactor):
form_class = PasswordFactorForm form_class = PasswordFactorForm
template_name = 'login/factors/backend.html' 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): def form_valid(self, form):
"""Authenticate against django's authentication backend""" """Authenticate against django's authentication backend"""
uid_fields = CONFIG.y('passbook.uid_fields') uid_fields = CONFIG.y('passbook.uid_fields')

View File

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

View File

@ -11,7 +11,7 @@ class PasswordFactorForm(forms.ModelForm):
class Meta: class Meta:
model = PasswordFactor model = PasswordFactor
fields = GENERAL_FIELDS + ['backends'] fields = GENERAL_FIELDS + ['backends', 'password_policies']
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),
'order': forms.NumberInput(), '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.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from passbook.core.signals import password_changed
from passbook.lib.models import CreatedUpdatedModel, UUIDModel from passbook.lib.models import CreatedUpdatedModel, UUIDModel
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
@ -38,6 +40,12 @@ class User(AbstractUser):
sources = models.ManyToManyField('Source', through='UserSourceConnection') sources = models.ManyToManyField('Source', through='UserSourceConnection')
applications = models.ManyToManyField('Application') applications = models.ManyToManyField('Application')
groups = models.ManyToManyField('Group') 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): class Provider(models.Model):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
@ -87,6 +95,7 @@ class PasswordFactor(Factor):
"""Password-based Django-backend Authentication Factor""" """Password-based Django-backend Authentication Factor"""
backends = ArrayField(models.TextField()) backends = ArrayField(models.TextField())
password_policies = models.ManyToManyField('Policy', blank=True)
type = 'passbook.core.auth.factors.password.PasswordFactor' type = 'passbook.core.auth.factors.password.PasswordFactor'
form = 'passbook.core.forms.factors.PasswordFactorForm' form = 'passbook.core.forms.factors.PasswordFactorForm'
@ -94,6 +103,13 @@ class PasswordFactor(Factor):
def has_user_settings(self): def has_user_settings(self):
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password' 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): def __str__(self):
return "Password Factor %s" % self.slug return "Password Factor %s" % self.slug
@ -155,10 +171,15 @@ class Source(PolicyModel):
return False return False
@property @property
def get_url(self): def get_login_button(self):
"""Return URL used for logging in""" """Return a tuple of URL, Icon name and Name"""
raise NotImplementedError 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): def __str__(self):
return self.name return self.name

View File

@ -73,6 +73,7 @@ INSTALLED_APPS = [
'passbook.saml_idp.apps.PassbookSAMLIDPConfig', 'passbook.saml_idp.apps.PassbookSAMLIDPConfig',
'passbook.otp.apps.PassbookOTPConfig', 'passbook.otp.apps.PassbookOTPConfig',
'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig', 'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig',
'passbook.hibp_policy.apps.PassbookHIBPConfig',
] ]
# Message Tag fix for bootstrap CSS Classes # 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']) user_signed_up = Signal(providing_args=['request', 'user'])
invitation_created = Signal(providing_args=['request', 'invitation']) invitation_created = Signal(providing_args=['request', 'invitation'])
invitation_used = Signal(providing_args=['request', 'invitation', 'user']) invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
password_changed = Signal(providing_args=['user', 'password'])

View File

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

View File

@ -2,3 +2,8 @@
{% load i18n %} {% 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> <h1>{% trans title %}</h1>
</header> </header>
<form method="POST"> <form method="POST">
{% csrf_token %}
{% block above_form %} {% block above_form %}
{% endblock %} {% endblock %}
{% include 'partials/form_login.html' %} {% include 'partials/form_login.html' %}
{% block beneath_form %}
{% endblock %}
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button> <button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button>
</form> </form>
{% if show_sign_up_notice %} {% 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,45 +1,71 @@
{% extends 'login/base.html' %} {% extends 'base/skeleton.html' %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block row %} {% 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' %} {% include 'partials/messages.html' %}
<div class="col-md-6"> </div>
<div class="card-pf"> <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"> <header class="login-pf-header">
<h1>{% trans title %}</h1> <h1>{% trans title %}</h1>
</header> </header>
<section class="login-pf-social-section" role="contentinfo" aria-label="Log in to your patternfly account">
<form method="POST"> <form method="POST">
{% csrf_token %}
{% block above_form %} {% block above_form %}
{% endblock %} {% endblock %}
{% include 'partials/form_login.html' %} {% include 'partials/form_login.html' %}
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button> <button type="submit" class="btn btn-primary btn-block btn-lg">{% trans primary_action %}</button>
</form> </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 %} {% if show_sign_up_notice %}
<p class="login-pf-signup"> <p class="login-pf-signup">
{% trans 'Need an account?' %} {% trans 'Need an account?' %}
<a href="{% url 'passbook_core:auth-sign-up' %}">{% trans 'Sign up' %}</a> <a href="{% url 'passbook_core:auth-sign-up' %}">{% trans 'Sign up' %}</a>
</p> </p>
{% endif %} {% endif %}
</div> </div><!-- card -->
</div> <div class="row">
<div class="col-md-6"> <div class="col-md-6 col-md-offset-3">
<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"> <footer class="login-pf-page-footer">
<ul class="login-pf-page-footer-links list-unstyled"> <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="#">Terms of Use</a></li>
@ -48,4 +74,6 @@
</ul> </ul>
</footer> </footer>
</div> </div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -5,6 +5,9 @@
{% load is_active %} {% load is_active %}
{% block body %} {% block body %}
<div class="toast-notifications-list-pf">
{% include 'partials/messages.html' %}
</div>
<nav class="navbar navbar-default navbar-pf" role="navigation"> <nav class="navbar navbar-default navbar-pf" role="navigation">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
@ -20,8 +23,8 @@
<div class="collapse navbar-collapse navbar-collapse-1"> <div class="collapse navbar-collapse navbar-collapse-1">
<ul class="nav navbar-nav navbar-utility"> <ul class="nav navbar-nav navbar-utility">
<li class="dropdown"> <li class="dropdown">
<button class="btn btn-link nav-item-iconic" id="horizontalDropdownMenu1" data-toggle="dropdown" aria-haspopup="true" <button class="btn btn-link nav-item-iconic" id="horizontalDropdownMenu1" data-toggle="dropdown"
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 dropdown-title"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="horizontalDropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="horizontalDropdownMenu1">
@ -66,9 +69,6 @@
</div> </div>
</nav> </nav>
<div class="container-fluid container-cards-pf"> <div class="container-fluid container-cards-pf">
<div class="container">
{% include 'partials/messages.html' %}
</div>
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>

View File

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

View File

@ -1,6 +1,6 @@
{% if messages %} {% if messages %}
{% for msg in messages %} {% for msg in messages %}
<div class="alert alert-{{ msg.level_tag }}"> <div class="toast-pf alert alert-dismissable alert-{{ msg.level_tag }}">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span class="pficon pficon-close"></span> <span class="pficon pficon-close"></span>
</button> </button>
@ -13,7 +13,7 @@
{% elif msg.level_tag == 'info' %} {% elif msg.level_tag == 'info' %}
<span class="pficon pficon-info"></span> <span class="pficon pficon-info"></span>
{% endif %} {% endif %}
<strong>{{ msg.message|safe }}</strong> {{ msg.message|safe }}
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% 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['title'] = _('Log in to your account')
kwargs['primary_action'] = _('Log in') kwargs['primary_action'] = _('Log in')
kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled') kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled')
kwargs['show_password_forget_notice'] = CONFIG.y('passbook.password_reset.enabled') kwargs['sources'] = []
kwargs['sources'] = Source.objects.filter(enabled=True).select_subclasses() sources = Source.objects.filter(enabled=True).select_subclasses()
if any(source.is_link for source in kwargs['sources']): if any(source.is_link for source in sources):
self.template_name = 'login/test.html' for source in sources:
kwargs['sources'].append(source.get_login_button)
self.template_name = 'login/with_sources.html'
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_user(self, uid_value) -> User: 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""" """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' form = 'passbook.ldap.forms.LDAPSourceForm'
@property @property
def get_url(self): def get_login_button(self):
raise NotImplementedError() raise NotImplementedError()
class Meta: class Meta:

View File

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

View File

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

View File

@ -26,8 +26,16 @@ class OAuthSource(Source):
return True return True
@property @property
def get_url(self): def get_login_button(self):
return reverse_lazy('passbook_oauth_client:oauth-client-login', 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}) kwargs={'source_slug': self.slug})
class Meta: class Meta:

View File

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

View File

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

View File

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