Rules -> Policies, more things

This commit is contained in:
Jens Langhammer 2019-02-21 16:06:57 +01:00
parent d3d75737ed
commit c941107d42
31 changed files with 333 additions and 175 deletions

View File

@ -1,6 +1,6 @@
"""passbook core source form fields""" """passbook core source form fields"""
# from django import forms # from django import forms
SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled'] SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled', 'policies']
# class SourceForm(forms.Form) # class SourceForm(forms.Form)

View File

@ -8,7 +8,6 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container">
<h1>{% trans "Audit Log" %}</h1> <h1>{% 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 %}
@ -23,27 +22,31 @@
{{ entry.action }} {{ entry.action }}
</div> </div>
<div class="list-group-item-text"> <div class="list-group-item-text">
{{ entry.context }}
</div> </div>
</div> </div>
<div class="list-view-pf-additional-info"> <div class="list-view-pf-additional-info">
<div class="list-view-pf-additional-info-item"> <div class="list-view-pf-additional-info-item">
<span class="pficon pficon-user"></span> <span class="pficon pficon-user"></span>
<strong>{{ entry.user }}</strong> <strong>{{ entry.user }}</strong>
</div> </div>
<div class="list-view-pf-additional-info-item"> <div class="list-view-pf-additional-info-item">
<span class="pficon pficon-screen"></span> <span class="pficon pficon-cluster"></span>
<strong>{{ entry.request_ip }}</strong> <strong>{{ entry.app|default:'-' }}</strong>
</div> </div>
<div class="list-view-pf-additional-info-item"> <div class="list-view-pf-additional-info-item">
<span class="pficon pficon-cluster"></span> <span class="fa fa-clock-o"></span>
<strong>{{ entry.app|default:'-' }}</strong> <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> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
// Row Checkbox Selection // Row Checkbox Selection

View File

@ -4,53 +4,89 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<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="#"><span class="fa fa-shield"></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="#"><span class="pficon pficon-ok"></span>{{ application_count }}</a></span>
</p> </p>
</div> </div>
</div>
</div> </div>
</div> <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="#"><span class="fa fa-shield"></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>{{ source_count }}</a></span>
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ provider_count }}</a></span> </p>
</p> </div>
</div> </div>
</div> </div>
</div> <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="#"><span class="fa fa-shield"></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>{{ provider_count }}</a></span>
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ policy_count }}</a></span> </p>
</p> </div>
</div> </div>
</div> </div>
</div> <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="#"><span class="fa fa-shield"></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>{{ factor_count }}</a></span>
<span class="card-pf-aggregate-status-notification"><a href="#"><span class="pficon pficon-ok"></span>{{ user_count }}</a></span> </p>
</p> </div>
</div> </div>
</div>
<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>
</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>
</p>
</div>
</div>
</div>
<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>
</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>
</p>
</div>
</div>
</div>
<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>
</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>
</p>
</div>
</div>
</div> </div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,7 +2,8 @@
from django.views.generic import TemplateView from django.views.generic import TemplateView
from passbook.admin.mixins import AdminRequiredMixin from passbook.admin.mixins import AdminRequiredMixin
from passbook.core.models import Application, Policy, Provider, User from passbook.core.models import (Application, Factor, Invitation, Policy,
Provider, Source, User)
class AdministrationOverviewView(AdminRequiredMixin, TemplateView): class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
@ -15,4 +16,7 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
kwargs['policy_count'] = len(Policy.objects.all()) kwargs['policy_count'] = len(Policy.objects.all())
kwargs['user_count'] = len(User.objects.all()) kwargs['user_count'] = len(User.objects.all())
kwargs['provider_count'] = len(Provider.objects.all()) kwargs['provider_count'] = len(Provider.objects.all())
kwargs['source_count'] = len(Source.objects.all())
kwargs['factor_count'] = len(Factor.objects.all())
kwargs['invitation_count'] = len(Invitation.objects.all())
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-02-21 12:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='loginattempt',
name='created',
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.1.7 on 2019-02-21 12:40
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('passbook_audit', '0002_auto_20190221_1201'),
]
operations = [
migrations.RemoveField(
model_name='auditentry',
name='_context',
),
migrations.AddField(
model_name='auditentry',
name='context',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
),
]

View File

@ -1,10 +1,10 @@
"""passbook audit models""" """passbook audit models"""
from datetime import timedelta from datetime import timedelta
from json import dumps, loads
from logging import getLogger from logging import getLogger
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.contrib.postgres.fields import JSONField
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@ -43,18 +43,10 @@ class AuditEntry(UUIDModel):
action = models.TextField(choices=ACTIONS) action = models.TextField(choices=ACTIONS)
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
app = models.TextField() app = models.TextField()
_context = models.TextField() context = JSONField(default=dict, blank=True)
_context_cache = None
request_ip = models.GenericIPAddressField() request_ip = models.GenericIPAddressField()
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
@property
def context(self):
"""Load context data and load json"""
if not self._context_cache:
self._context_cache = loads(self._context)
return self._context_cache
@staticmethod @staticmethod
def create(action, request, **kwargs): def create(action, request, **kwargs):
"""Create AuditEntry from arguments""" """Create AuditEntry from arguments"""
@ -67,7 +59,7 @@ class AuditEntry(UUIDModel):
user=user, user=user,
# User 255.255.255.255 as fallback if IP cannot be determined # User 255.255.255.255 as fallback if IP cannot be determined
request_ip=client_ip or '255.255.255.255', request_ip=client_ip or '255.255.255.255',
_context=dumps(kwargs)) context=kwargs)
LOGGER.debug("Logged %s from %s (%s)", action, request.user, client_ip) LOGGER.debug("Logged %s from %s (%s)", action, request.user, client_ip)
return entry return entry

View File

@ -15,8 +15,9 @@ class AuthenticationFactor(TemplateView):
form = None form = None
required = True required = True
authenticator = None authenticator = None
pending_user = None
request = None request = None
template_name = 'login/form.html' template_name = 'login/factors/base.html'
def __init__(self, authenticator): def __init__(self, authenticator):
self.authenticator = authenticator self.authenticator = authenticator
@ -26,4 +27,5 @@ class AuthenticationFactor(TemplateView):
kwargs['is_login'] = True kwargs['is_login'] = True
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['pending_user'] = self.pending_user
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -67,6 +67,7 @@ class AuthenticationView(UserPassesTestMixin, View):
# Instantiate Next Factor and pass request # Instantiate Next Factor and pass request
factor = path_to_class(factor_class) factor = path_to_class(factor_class)
self._current_factor = factor(self) self._current_factor = factor(self)
self._current_factor.pending_user = self.pending_user
self._current_factor.request = request self._current_factor.request = request
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@ -93,7 +94,8 @@ class AuthenticationView(UserPassesTestMixin, View):
self.pending_factors self.pending_factors
self.request.session[AuthenticationView.SESSION_FACTOR] = next_factor self.request.session[AuthenticationView.SESSION_FACTOR] = next_factor
LOGGER.debug("Rendering Factor is %s", next_factor) LOGGER.debug("Rendering Factor is %s", next_factor)
return redirect(reverse('passbook_core:auth-process', kwargs={'factor': next_factor})) # return redirect(reverse('passbook_core:auth-process', kwargs={'factor': next_factor}))
return redirect(reverse('passbook_core:auth-process'))
# User passed all factors # User passed all factors
LOGGER.debug("User passed all factors, logging in") LOGGER.debug("User passed all factors, logging in")
return self._user_passed() return self._user_passed()
@ -102,6 +104,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()
return redirect(reverse('passbook_core:auth-denied')) return redirect(reverse('passbook_core:auth-denied'))
def _user_passed(self): def _user_passed(self):

View File

@ -1,5 +1,6 @@
"""passbook Core Application forms""" """passbook Core Application forms"""
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _
from passbook.core.models import Application, Provider from passbook.core.models import Application, Provider
@ -19,3 +20,7 @@ class ApplicationForm(forms.ModelForm):
'launch_url': forms.TextInput(), 'launch_url': forms.TextInput(),
'icon_url': forms.TextInput(), 'icon_url': forms.TextInput(),
} }
labels = {
'launch_url': _('Launch URL'),
'icon_url': _('Icon URL'),
}

View File

@ -18,6 +18,11 @@ class LoginForm(forms.Form):
uid_field = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _('UID')})) uid_field = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _('UID')}))
remember_me = forms.BooleanField(required=False) remember_me = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if CONFIG.y('passbook.uid_fields') == ['email']:
self.fields['uid_field'] = forms.EmailField()
def clean_uid_field(self): def clean_uid_field(self):
"""Validate uid_field after EmailValidator if 'email' is the only selected uid_fields""" """Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
if CONFIG.y('passbook.uid_fields') == ['email']: if CONFIG.y('passbook.uid_fields') == ['email']:

View File

@ -17,7 +17,7 @@ class FactorForm(forms.ModelForm):
class Meta: class Meta:
model = Factor model = Factor
fields = ['name', 'slug', 'order', 'policies', 'type', 'enabled'] fields = ['name', 'slug', 'order', 'policies', 'type', 'enabled', 'arguments']
widgets = { widgets = {
'type': forms.Select(choices=get_factors()), 'type': forms.Select(choices=get_factors()),
'name': forms.TextInput(), 'name': forms.TextInput(),

View File

@ -0,0 +1,28 @@
# Generated by Django 2.1.7 on 2019-02-21 12:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0004_auto_20190216_1013'),
]
operations = [
migrations.AlterField(
model_name='policy',
name='created',
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name='policymodel',
name='created',
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name='usersourceconnection',
name='created',
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.1.7 on 2019-02-21 12:32
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0005_auto_20190221_1201'),
]
operations = [
migrations.AddField(
model_name='factor',
name='arguments',
field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.1.7 on 2019-02-21 12:33
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0006_factor_arguments'),
]
operations = [
migrations.AlterField(
model_name='factor',
name='arguments',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
),
]

View File

@ -6,6 +6,7 @@ from time import sleep
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import JSONField
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.translation import gettext as _ from django.utils.translation import gettext as _
@ -69,6 +70,7 @@ class Factor(PolicyModel):
order = models.IntegerField() order = models.IntegerField()
type = models.TextField(unique=True) type = models.TextField(unique=True)
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
arguments = JSONField(default=dict, blank=True)
def __str__(self): def __str__(self):
return "Factor %s" % self.slug return "Factor %s" % self.slug

View File

@ -32,7 +32,7 @@ class PolicyEngine:
"""Check policies for user""" """Check policies for user"""
signatures = [] signatures = []
kwargs = { kwargs = {
'__password__': getattr(user, '__password__') '__password__': getattr(user, '__password__', None)
} }
for policy in self.policies: for policy in self.policies:
signatures.append(_policy_engine_task.s(user.pk, policy.pk.hex, **kwargs)) signatures.append(_policy_engine_task.s(user.pk, policy.pk.hex, **kwargs))

View File

@ -61,7 +61,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'rest_framework', 'rest_framework',
'rest_framework_swagger', 'drf_yasg',
'passbook.core.apps.PassbookCoreConfig', 'passbook.core.apps.PassbookCoreConfig',
'passbook.admin.apps.PassbookAdminConfig', 'passbook.admin.apps.PassbookAdminConfig',
'passbook.api.apps.PassbookAPIConfig', 'passbook.api.apps.PassbookAPIConfig',

View File

@ -26,7 +26,7 @@
<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-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3"> <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' %}" alt="PatternFly logo" /> <img class="login-pf-brand" style="max-height: 10rem;" src="{% static 'img/logo.svg' %}" alt="PatternFly logo" />
{% if config.login.subtext %} {% if config.login.subtext %}

View File

@ -1,8 +1,4 @@
{% extends 'login/form.html' %} {% extends 'login/factors/base.html' %}
{% load i18n %} {% load i18n %}
{% block above_form %}
<span class="pficon pficon-unlocked"></span>
{% trans "This is a text" %}
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends 'login/form.html' %}
{% load i18n %}
{% load utils %}
{% block head %}
{{ block.super }}
<style>
.login-pf-settings img {
max-height: 32px;
border-radius: 100%;
border-width: 1px;
border-color: #000;
}
.login-pf-settings a {
padding-top: 3px;
padding-bottom: 3px;
line-height: 32px;
}
</style>
{% endblock %}
{% block above_form %}
<div class="form-group login-pf-settings">
<p class="form-control-static">
<img src="{% gravatar pending_user.email %}" alt="">
{{ pending_user.username }}
</p>
<a href="{% url 'passbook_core:auth-login' %}">{% trans 'Not you?' %}</a>
</div>
{% endblock %}

View File

@ -1,68 +0,0 @@
{% extends "base/skeleton.html" %}
{% load static %}
{% block body %}
<div class="login-pf-page">
<div class="container-fluid">
<div class="row">
<div class="col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3">
<header class="login-pf-page-header">
<img class="login-pf-brand" src="{% static 'img/Logo_Horizontal_Reversed.svg' %}" alt=" logo" />
</header>
<div class="row">
<div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2">
<div class="card-pf">
<header class="login-pf-header">
<select class="selectpicker">
<option>English</option>
<option>French</option>
<option>Italian</option>
</select>
<h1>Single Sign-On</h1>
<p class="text-center">Log in to <strong>Application</strong></p>
</header>
<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="form-group 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>
<p class="login-pf-signup">Need an account?<a href="#">Sign up</a></p>
</div><!-- card -->
<footer class="login-pf-page-footer">
<div class="login-pf-page-footer-sso-services">
<p>One account for all your company services</p>
<ul class="login-pf-page-footer-sso-services-logos">
<li><img src="{% static 'img/google-drive.svg' %}" alt="google drive icon" /></li>
<li><img src="{% static 'img/gmail.svg' %}" alt="gmail icon" /></li>
<li><img src="{% static 'img/google-calendar.svg' %}" alt="google calendar icon" /></li>
</ul>
</div>
<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><!-- col -->
</div><!-- row -->
</div><!-- col -->
</div><!-- login-pf-page -->
</div>
<!--row-->
</div>
<!--container-->
{% endblock %}

View File

@ -14,7 +14,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="/"> <a class="navbar-brand" href="/">
<img src="{% static 'img/brand.svg' %}" alt="PatternFly Enterprise Application" /> <img src="{% static 'img/brand.svg' %}" alt="passbook" />
</a> </a>
</div> </div>
<div class="collapse navbar-collapse navbar-collapse-1"> <div class="collapse navbar-collapse navbar-collapse-1">
@ -63,7 +63,9 @@
</div> </div>
</nav> </nav>
<div class="container-fluid container-cards-pf"> <div class="container-fluid container-cards-pf">
{% include 'partials/messages.html' %} <div class="container">
{% include 'partials/messages.html' %}
</div>
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>

View File

@ -9,10 +9,18 @@
<div class="nav-category"> <div class="nav-category">
<h2>{% trans 'User Profile'%}</h2> <h2>{% trans 'User Profile'%}</h2>
<ul class="nav nav-pills nav-stacked"> <ul class="nav nav-pills nav-stacked">
<li class="{% is_active 'passbook_core:user-settings' %}"><a href="{% url 'passbook_core:user-settings' %}"><i class="fa fa-desktop"></i>{% trans 'Details' %}</a></li> <li class="{% is_active 'passbook_core:user-settings' %}">
<li><a href="#"><i class="fa fa-cog"></i>System Services</a></li> <a href="{% url 'passbook_core:user-settings' %}">
<li><a href="#"><i class="fa fa-file-text-o"></i>Journal</a></li> <i class="fa fa-desktop"></i> {% trans 'Details' %}
<li><a href="#"><i class="fa fa-cloud"></i>Storage</a></li> </a>
</li>
<li class="{% is_active 'passbook_core:user-settings' %}">
<a href="{% url 'passbook_core:user-settings' %}">
<i class="pficon pficon-locked"></i> {% trans 'Change Password' %}
</a>
</li>
<li><a href="#"><i class="fa fa-file-text-o"></i> Journal</a></li>
<li><a href="#"><i class="fa fa-cloud"></i> Storage</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -0,0 +1,14 @@
{% extends "user/base.html" %}
{% load i18n %}
{% block page %}
<h1>{% trans 'Change Password' %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% include 'partials/form.html' %}
<input class="btn btn-primary" type="submit" value="{% trans 'Update' %}">
<a class="btn btn-danger"
href="{% url 'passbook_core:user-delete' %}?back={{ request.get_full_path }}">{% trans 'Delete user' %}</a>
</form>
{% endblock %}

View File

@ -13,6 +13,7 @@ class OverviewView(LoginRequiredMixin, TemplateView):
template_name = 'overview/index.html' template_name = 'overview/index.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
print(self.request.session.keys())
kwargs['applications'] = self.request.user.applications.all() kwargs['applications'] = self.request.user.applications.all()
if self.request.user.is_superuser: if self.request.user.is_superuser:
kwargs['applications'] = Application.objects.all() kwargs['applications'] = Application.objects.all()

View File

@ -1,6 +1,7 @@
"""passbook LDAP Forms""" """passbook LDAP Forms"""
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _
from passbook.admin.forms.source import SOURCE_FORM_FIELDS from passbook.admin.forms.source import SOURCE_FORM_FIELDS
from passbook.ldap.models import LDAPSource from passbook.ldap.models import LDAPSource
@ -14,7 +15,7 @@ class LDAPSourceForm(forms.ModelForm):
model = LDAPSource model = LDAPSource
fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password', fields = SOURCE_FORM_FIELDS + ['server_uri', 'bind_cn', 'bind_password',
'type', 'domain', 'base_dn', 'create_user', 'type', 'domain', 'base_dn', 'create_user',
'reset_password', 'policies'] 'reset_password']
widgets = { widgets = {
'name': forms.TextInput(), 'name': forms.TextInput(),
'server_uri': forms.TextInput(), 'server_uri': forms.TextInput(),
@ -23,6 +24,11 @@ class LDAPSourceForm(forms.ModelForm):
'domain': forms.TextInput(), 'domain': forms.TextInput(),
'base_dn': forms.TextInput(), 'base_dn': forms.TextInput(),
} }
labels = {
'server_uri': _('Server URI'),
'bind_cn': _('Bind CN'),
'base_dn': _('Base DN'),
}
# class GeneralSettingsForm(SettingsForm): # class GeneralSettingsForm(SettingsForm):
# """general settings form""" # """general settings form"""

View File

@ -59,6 +59,7 @@ passbook:
uid_fields: uid_fields:
- username - username
- email - email
# Factors to load
factors: factors:
- passbook.core.auth.factors.backend - passbook.core.auth.factors.backend
- passbook.core.auth.factors.dummy - passbook.core.auth.factors.dummy
@ -67,22 +68,6 @@ passbook:
remember_age: 2592000 # 60 * 60 * 24 * 30, one month remember_age: 2592000 # 60 * 60 * 24 * 30, one month
# Provider-specific settings # Provider-specific settings
ldap: ldap:
# # Completely enable or disable LDAP provider
# enabled: false
# # AD Domain, used to generate `userPrincipalName`
# domain: corp.contoso.com
# # Base DN in which passbook should look for users
# base_dn: dn=corp,dn=contoso,dn=com
# # LDAP field which is used to set the django username
# username_field: sAMAccountName
# # LDAP server to connect to, can be set to `<domain_name>`
# server:
# name: corp.contoso.com
# use_tls: false
# # Bind credentials, used for account creation
# bind:
# username: Administraotr@corp.contoso.com
# password: VerySecurePassword!
# Which field from `uid_fields` maps to which LDAP Attribute # Which field from `uid_fields` maps to which LDAP Attribute
login_field_map: login_field_map:
username: sAMAccountName username: sAMAccountName
@ -93,10 +78,6 @@ ldap:
mail: email mail: email
given_name: first_name given_name: first_name
name: last_name name: last_name
# # Create new users in LDAP upon sign-up
# create_users: true
# # Reset LDAP password when user reset their password
# reset_password: true
oauth_client: oauth_client:
# List of python packages with sources types to load. # List of python packages with sources types to load.
types: types:
@ -108,7 +89,6 @@ oauth_client:
- passbook.oauth_client.source_types.supervisr - passbook.oauth_client.source_types.supervisr
- passbook.oauth_client.source_types.twitter - passbook.oauth_client.source_types.twitter
saml_idp: saml_idp:
issuer: passbook
# List of python packages with provider types to load. # List of python packages with provider types to load.
types: types:
- passbook.saml_idp.processors.generic - passbook.saml_idp.processors.generic

View File

@ -6,7 +6,7 @@ from django.db import models
class CreatedUpdatedModel(models.Model): class CreatedUpdatedModel(models.Model):
"""Base Abstract Model to save created and update""" """Base Abstract Model to save created and update"""
created = models.DateField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True) last_updated = models.DateTimeField(auto_now=True)
class Meta: class Meta:

View File

@ -2,8 +2,9 @@
import glob import glob
import os import os
import socket import socket
from hashlib import md5
from importlib import import_module from importlib import import_module
from urllib.parse import urljoin from urllib.parse import urlencode, urljoin
from django import template from django import template
from django.apps import apps from django.apps import apps
@ -11,6 +12,7 @@ from django.conf import settings
from django.db.models import Model from django.db.models import Model
from django.template.loaders.app_directories import get_app_template_dirs from django.template.loaders.app_directories import get_app_template_dirs
from django.urls import reverse from django.urls import reverse
from django.utils.html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from passbook.lib.config import CONFIG from passbook.lib.config import CONFIG
@ -178,3 +180,30 @@ def app_versions():
ver = '.'.join([str(x) for x in ver]) ver = '.'.join([str(x) for x in ver])
app_versions[app.verbose_name] = ver app_versions[app.verbose_name] = ver
return app_versions return app_versions
@register.simple_tag
def gravatar(email, size=None, rating=None):
"""
Generates a Gravatar URL for the given email address.
Syntax::
{% gravatar <email> [size] [rating] %}
Example::
{% gravatar someone@example.com 48 pg %}
"""
# gravatar uses md5 for their URLs, so md5 can't be avoided
gravatar_url = "%savatar/%s" % ('https://secure.gravatar.com/',
md5(email.encode('utf-8')).hexdigest()) # nosec
parameters = [p for p in (
('s', size or '158'),
('r', rating or 'g'),
) if p[1]]
if parameters:
gravatar_url += '?' + urlencode(parameters, doseq=True)
return escape(gravatar_url)

View File

@ -19,7 +19,7 @@ class OAuthSource(Source):
consumer_key = models.TextField() consumer_key = models.TextField()
consumer_secret = models.TextField() consumer_secret = models.TextField()
form = 'passbook.oauth_client.forms.GitHubOAuthSourceForm' form = 'passbook.oauth_client.forms.OAuthSourceForm'
@property @property
def is_link(self): def is_link(self):