Compare commits

..

7 Commits

25 changed files with 132 additions and 33 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.0.2-alpha current_version = 0.0.4-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>.*)
@ -20,6 +20,8 @@ values =
[bumpversion:file:passbook/__init__.py] [bumpversion:file:passbook/__init__.py]
[bumpversion:file:passbook/api/__init__.py]
[bumpversion:file:passbook/core/__init__.py] [bumpversion:file:passbook/core/__init__.py]
[bumpversion:file:passbook/admin/__init__.py] [bumpversion:file:passbook/admin/__init__.py]
@ -30,6 +32,8 @@ values =
[bumpversion:file:passbook/ldap/__init__.py] [bumpversion:file:passbook/ldap/__init__.py]
[bumpversion:file:passbook/lib/__init__.py]
[bumpversion:file:passbook/saml_idp/__init__.py] [bumpversion:file:passbook/saml_idp/__init__.py]
[bumpversion:file:passbook/audit/__init__.py] [bumpversion:file:passbook/audit/__init__.py]

View File

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

View File

@ -1,5 +1,5 @@
apiVersion: v1 apiVersion: v1
appVersion: "0.0.2-alpha" appVersion: "0.0.4-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.2-alpha' __version__ = '0.0.4-alpha'

View File

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

View File

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

View File

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

View File

@ -9,3 +9,4 @@ class PassbookAPIConfig(AppConfig):
name = 'passbook.api' name = 'passbook.api'
label = 'passbook_api' label = 'passbook_api'
mountpoint = '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""" """passbook audit Header"""
__version__ = '0.0.2-alpha' __version__ = '0.0.4-alpha'

View File

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

View File

@ -1,2 +1,2 @@
"""passbook core""" """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 # 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.models
import django.contrib.auth.validators import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import uuid from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): 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: def user_is_authorized(self, user: User) -> bool:
"""Check if user is authorized to use this application""" """Check if user is authorized to use this application"""
from passbook.core.rules import RuleEngine 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): def __str__(self):
return self.name return self.name
@ -160,6 +160,7 @@ class FieldMatcherRule(Rule):
MATCH_CONTAINS = 'contains' MATCH_CONTAINS = 'contains'
MATCH_REGEXP = 'regexp' MATCH_REGEXP = 'regexp'
MATCH_EXACT = 'exact' MATCH_EXACT = 'exact'
MATCHES = ( MATCHES = (
(MATCH_STARTSWITH, _('Starts with')), (MATCH_STARTSWITH, _('Starts with')),
(MATCH_ENDSWITH, _('Ends with')), (MATCH_ENDSWITH, _('Ends with')),
@ -169,13 +170,13 @@ class FieldMatcherRule(Rule):
) )
USER_FIELDS = ( USER_FIELDS = (
('username', 'username',), ('username', _('Username'),),
('first_name', 'first_name',), ('first_name', _('First Name'),),
('last_name', 'last_name',), ('last_name', _('Last Name'),),
('email', 'email',), ('email', _('E-Mail'),),
('is_staff', 'is_staff',), ('is_staff', _('Is staff'),),
('is_active', 'is_active',), ('is_active', _('Is active'),),
('data_joined', 'data_joined',), ('data_joined', _('Date joined'),),
) )
user_field = models.TextField(choices=USER_FIELDS) user_field = models.TextField(choices=USER_FIELDS)
@ -218,6 +219,41 @@ class FieldMatcherRule(Rule):
verbose_name = _('Field matcher Rule') verbose_name = _('Field matcher Rule')
verbose_name_plural = _('Field matcher Rules') 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() @reversion.register()
class WebhookRule(Rule): class WebhookRule(Rule):
"""Rule that asks webhook""" """Rule that asks webhook"""

View File

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

View File

@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
import importlib import importlib
import os import os
import sys
from django.contrib import messages 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = CONFIG.get('debug') DEBUG = CONFIG.get('debug')
INTERNAL_IPS = ['127.0.0.1'] INTERNAL_IPS = ['127.0.0.1']
ALLOWED_HOSTS = CONFIG.get('domains') ALLOWED_HOSTS = CONFIG.get('domains', [])
LOGIN_URL = 'passbook_core:auth-login' LOGIN_URL = 'passbook_core:auth-login'
# CSRF_FAILURE_VIEW = 'passbook.core.views.errors.CSRFErrorView.as_view' # CSRF_FAILURE_VIEW = 'passbook.core.views.errors.CSRFErrorView.as_view'
@ -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'] _DISALLOWED_ITEMS = ['INSTALLED_APPS', 'MIDDLEWARE', 'AUTHENTICATION_BACKENDS']
# Load subapps's INSTALLED_APPS # Load subapps's INSTALLED_APPS
for _app in INSTALLED_APPS: for _app in INSTALLED_APPS:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +1,2 @@
"""passbook totp Header""" """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/audit/requirements.txt
-r passbook/captcha_factor/requirements.txt -r passbook/captcha_factor/requirements.txt
-r passbook/admin/requirements.txt -r passbook/admin/requirements.txt
-r passbook/api/requirements.txt