Compare commits

..

7 Commits

25 changed files with 132 additions and 33 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.0.2-alpha
current_version = 0.0.4-alpha
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
@ -20,6 +20,8 @@ values =
[bumpversion:file:passbook/__init__.py]
[bumpversion:file:passbook/api/__init__.py]
[bumpversion:file:passbook/core/__init__.py]
[bumpversion:file:passbook/admin/__init__.py]
@ -30,6 +32,8 @@ values =
[bumpversion:file:passbook/ldap/__init__.py]
[bumpversion:file:passbook/lib/__init__.py]
[bumpversion:file:passbook/saml_idp/__init__.py]
[bumpversion:file:passbook/audit/__init__.py]

View File

@ -46,7 +46,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.2-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.4-alpha
stage: build
only:
- tags

View File

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

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook admin"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -8,3 +8,4 @@ class PassbookAdminConfig(AppConfig):
name = 'passbook.admin'
label = 'passbook_admin'
mountpoint = 'administration/'
verbose_name = 'passbook Admin'

View File

@ -1,2 +1,2 @@
"""passbook api"""
__version__ = '0.0.1-alpha'
__version__ = '0.0.4-alpha'

View File

@ -9,3 +9,4 @@ class PassbookAPIConfig(AppConfig):
name = 'passbook.api'
label = 'passbook_api'
mountpoint = 'api/'
verbose_name = 'passbook API'

View File

@ -0,0 +1,3 @@
django-rest-framework
drf_yasg
django-filters

View File

@ -1,2 +1,2 @@
"""passbook audit Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook captcha_factor Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook core"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,12 +1,13 @@
# Generated by Django 2.1.5 on 2019-02-08 10:42
from django.conf import settings
import uuid
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -0,0 +1,35 @@
# Generated by Django 2.1.5 on 2019-02-08 15:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PasswordPolicyRule',
fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Rule')),
('amount_uppercase', models.IntegerField(default=0)),
('amount_lowercase', models.IntegerField(default=0)),
('amount_symbols', models.IntegerField(default=0)),
('length_min', models.IntegerField(default=0)),
('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')),
],
options={
'verbose_name': 'Password Policy Rule',
'verbose_name_plural': 'Password Policy Rules',
},
bases=('passbook_core.rule',),
),
migrations.AlterField(
model_name='fieldmatcherrule',
name='user_field',
field=models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]),
),
]

View File

@ -82,7 +82,7 @@ class Application(RuleModel):
def user_is_authorized(self, user: User) -> bool:
"""Check if user is authorized to use this application"""
from passbook.core.rules import RuleEngine
return RuleEngine(self).for_user(user).result
return RuleEngine(self.rules.all()).for_user(user).result
def __str__(self):
return self.name
@ -160,6 +160,7 @@ class FieldMatcherRule(Rule):
MATCH_CONTAINS = 'contains'
MATCH_REGEXP = 'regexp'
MATCH_EXACT = 'exact'
MATCHES = (
(MATCH_STARTSWITH, _('Starts with')),
(MATCH_ENDSWITH, _('Ends with')),
@ -169,13 +170,13 @@ class FieldMatcherRule(Rule):
)
USER_FIELDS = (
('username', 'username',),
('first_name', 'first_name',),
('last_name', 'last_name',),
('email', 'email',),
('is_staff', 'is_staff',),
('is_active', 'is_active',),
('data_joined', 'data_joined',),
('username', _('Username'),),
('first_name', _('First Name'),),
('last_name', _('Last Name'),),
('email', _('E-Mail'),),
('is_staff', _('Is staff'),),
('is_active', _('Is active'),),
('data_joined', _('Date joined'),),
)
user_field = models.TextField(choices=USER_FIELDS)
@ -218,6 +219,41 @@ class FieldMatcherRule(Rule):
verbose_name = _('Field matcher Rule')
verbose_name_plural = _('Field matcher Rules')
@reversion.register()
class PasswordPolicyRule(Rule):
"""Rule to make sure passwords have certain properties"""
amount_uppercase = models.IntegerField(default=0)
amount_lowercase = models.IntegerField(default=0)
amount_symbols = models.IntegerField(default=0)
length_min = models.IntegerField(default=0)
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
form = 'passbook.core.forms.rules.PasswordPolicyRuleForm'
def passes(self, user: User) -> bool:
# Only check if password is being set
if not hasattr(user, '__password__'):
return True
password = getattr(user, '__password__')
filter_regex = r''
if self.amount_lowercase > 0:
filter_regex += r'[a-z]{%d,}' % self.amount_lowercase
if self.amount_uppercase > 0:
filter_regex += r'[A-Z]{%d,}' % self.amount_uppercase
if self.amount_symbols > 0:
filter_regex += r'[%s]{%d,}' % (self.symbol_charset, self.amount_symbols)
result = bool(re.compile(filter_regex).match(password))
LOGGER.debug("User got %r", result)
return result
class Meta:
verbose_name = _('Password Policy Rule')
verbose_name_plural = _('Password Policy Rules')
@reversion.register()
class WebhookRule(Rule):
"""Rule that asks webhook"""

View File

@ -9,27 +9,32 @@ from passbook.core.models import Rule, User
LOGGER = getLogger(__name__)
@CELERY_APP.task()
def _rule_engine_task(user_pk, rule_pk):
def _rule_engine_task(user_pk, rule_pk, **kwargs):
"""Task wrapper to run rule checking"""
rule_obj = Rule.objects.filter(pk=rule_pk).select_subclasses().first()
user_obj = User.objects.get(pk=user_pk)
for key, value in kwargs.items():
setattr(user_obj, key, value)
LOGGER.debug("Running rule `%s`#%s for user %s...", rule_obj.name, rule_obj.pk.hex, user_obj)
return rule_obj.passes(user_obj)
class RuleEngine:
"""Orchestrate rule checking, launch tasks and return result"""
_rule_model = None
rules = None
_group = None
def __init__(self, rule_model):
self._rule_model = rule_model
def __init__(self, rules):
self.rules = rules
def for_user(self, user):
"""Check rules for user"""
signatures = []
for rule in self._rule_model.rules.all():
signatures.append(_rule_engine_task.s(user.pk, rule.pk.hex))
kwargs = {
'__password__': getattr(user, '__password__')
}
for rule in self.rules:
signatures.append(_rule_engine_task.s(user.pk, rule.pk.hex, **kwargs))
self._group = group(signatures)()
return self

View File

@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
import importlib
import os
import sys
from django.contrib import messages
@ -33,7 +34,7 @@ SECRET_KEY = CONFIG.get('secret_key')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = CONFIG.get('debug')
INTERNAL_IPS = ['127.0.0.1']
ALLOWED_HOSTS = CONFIG.get('domains')
ALLOWED_HOSTS = CONFIG.get('domains', [])
LOGIN_URL = 'passbook_core:auth-login'
# CSRF_FAILURE_VIEW = 'passbook.core.views.errors.CSRFErrorView.as_view'
@ -288,6 +289,16 @@ with CONFIG.cd('log'):
}
}
TEST = False
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
TEST_OUTPUT_VERBOSE = 2
TEST_OUTPUT_FILE_NAME = 'unittest.xml'
if any('test' in arg for arg in sys.argv):
LOGGING = None
TEST = True
_DISALLOWED_ITEMS = ['INSTALLED_APPS', 'MIDDLEWARE', 'AUTHENTICATION_BACKENDS']
# Load subapps's INSTALLED_APPS
for _app in INSTALLED_APPS:

View File

@ -1,2 +1,2 @@
"""Passbook ldap app Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook lib"""
__version__ = '0.0.1-alpha'
__version__ = '0.0.4-alpha'

View File

@ -7,3 +7,4 @@ class PassbookLibConfig(AppConfig):
name = 'passbook.lib'
label = 'passbook_lib'
verbose_name = 'passbook lib'

View File

@ -1,2 +1,2 @@
"""passbook oauth_client Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook oauth_provider Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook saml_idp Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook totp Header"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.4-alpha'

View File

@ -7,3 +7,4 @@
-r passbook/audit/requirements.txt
-r passbook/captcha_factor/requirements.txt
-r passbook/admin/requirements.txt
-r passbook/api/requirements.txt