Compare commits

..

5 Commits

24 changed files with 128 additions and 30 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.0.3-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.3-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.3-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.3-alpha' __version__ = '0.0.4-alpha'

View File

@ -1,2 +1,2 @@
"""passbook admin""" """passbook admin"""
__version__ = '0.0.3-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.3-alpha' __version__ = '0.0.4-alpha'

View File

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

View File

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

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.3-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.3-alpha' __version__ = '0.0.4-alpha'

View File

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

View File

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

View File

@ -1,2 +1,2 @@
"""passbook totp Header""" """passbook totp Header"""
__version__ = '0.0.3-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