Compare commits

...

12 Commits

29 changed files with 221 additions and 39 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.0.2-alpha
current_version = 0.0.6-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]

2
.gitignore vendored
View File

@ -189,5 +189,5 @@ pyvenv.cfg
pip-selfcheck.json
# End of https://www.gitignore.io/api/python,django
/static/*
static/
local.env.yml

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.6-alpha
stage: build
only:
- tags
@ -55,6 +55,7 @@ package-helm:
stage: build
script:
- curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
- helm init --client-only
- helm package helm/passbook
- ./manage.py nexus_upload --method put --url $NEXUS_URL --user $NEXUS_USER --password $NEXUS_PASS --repo helm *.tgz
only:

View File

@ -1,14 +1,25 @@
FROM python:3.6-slim-stretch
# LABEL version="1.8.8"
FROM python:3.6-slim-stretch as build
COPY ./passbook/ /app/passbook
COPY ./static/ /app/static
COPY ./manage.py /app/
COPY ./requirements.txt /app/
WORKDIR /app/
#RUN apk add --no-cache libffi-dev build-base py2-pip python2-dev libxml-dev && \
RUN mkdir /app/static/ && \
pip install -r requirements.txt && \
pip install psycopg2 && \
./manage.py collectstatic --no-input
FROM python:3.6-slim-stretch
COPY ./passbook/ /app/passbook
COPY ./manage.py /app/
COPY ./requirements.txt /app/
COPY --from=build /app/static/* /app/static/
WORKDIR /app/
RUN pip install -r requirements.txt && \
pip install psycopg2 && \
adduser --system --home /app/ passbook && \

View File

@ -1,5 +1,5 @@
apiVersion: v1
appVersion: "0.0.2-alpha"
appVersion: "0.0.6-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.6-alpha'

View File

@ -1,2 +1,2 @@
"""passbook admin"""
__version__ = '0.0.2-alpha'
__version__ = '0.0.6-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.6-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.6-alpha'

View File

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

View File

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

View File

@ -0,0 +1,71 @@
"""passbook nexus_upload management command"""
from getpass import getpass
import requests
from django.core.management.base import BaseCommand
class Command(BaseCommand):
"""Upload debian package to nexus repository"""
url = None
user = None
password = None
def add_arguments(self, parser):
parser.add_argument(
'--repo',
action='store',
help='Repository to upload to',
required=True)
parser.add_argument(
'--url',
action='store',
help='Nexus root URL',
required=True)
parser.add_argument(
'--user',
action='store',
help='Username to use for Nexus upload',
required=True)
parser.add_argument(
'--method',
action='store',
nargs='?',
const='post',
choices=['post', 'put'],
help=('Method used for uploading files to nexus. '
'Apt repositories use post, Helm uses put.'),
required=True)
parser.add_argument(
'--password',
action='store',
help=("Password to use for Nexus upload. "
"If parameter not given, we'll interactively ask"))
# Positional arguments
parser.add_argument('file', nargs='+', type=str)
def handle(self, *args, **options):
"""Upload debian package to nexus repository"""
if options.get('password') is None:
options['password'] = getpass()
responses = {}
url = 'https://%(url)s/repository/%(repo)s//' % options
method = options.get('method')
exit_code = 0
for file in options.get('file'):
if method == 'post':
responses[file] = requests.post(url, data=open(file, mode='rb'),
auth=(options.get('user'), options.get('password')))
else:
responses[file] = requests.put(url+file, data=open(file, mode='rb'),
auth=(options.get('user'), options.get('password')))
self.stdout.write('Upload results:\n')
sep = '-' * 60
self.stdout.write('%s\n' % sep)
for path, response in responses.items():
self.stdout.write('%-55s: %d\n' % (path, response.status_code))
if response.status_code >= 400:
exit_code = 1
self.stdout.write('%s\n' % sep)
exit(exit_code)

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.6-alpha'

View File

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

View File

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

View File

@ -23,7 +23,7 @@ web:
listen: 0.0.0.0
port: 8000
threads: 30
debug: true
debug: false
secure_proxy_header:
HTTP_X_FORWARDED_PROTO: https
redis: localhost

View File

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

View File

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

View File

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

View File

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