Merge branch 'master' into guardian

# Conflicts:
#	Pipfile
#	Pipfile.lock
#	passbook/core/models.py
This commit is contained in:
Langhammer, Jens 2019-10-10 17:27:52 +02:00
commit 143a575369
24 changed files with 188 additions and 40 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.6.3-beta current_version = 0.6.4-beta
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>.*)

View File

@ -27,7 +27,7 @@ create-base-image:
before_script: before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest --destination docker.beryju.org/passbook/base:0.6.3-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest
stage: build-base-image stage: build-base-image
only: only:
refs: refs:
@ -41,7 +41,7 @@ build-dev-image:
before_script: before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest --destination docker.beryju.org/passbook/dev:0.6.3-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest
stage: build-dev-image stage: build-dev-image
only: only:
refs: refs:
@ -70,13 +70,13 @@ migrations:
# services: # services:
# - postgres:latest # - postgres:latest
# - redis:latest # - redis:latest
# pylint: pylint:
# script: script:
# - pylint passbook - pylint passbook
# stage: test stage: test
# services: services:
# - postgres:latest - postgres:latest
# - redis:latest - redis:latest
coverage: coverage:
script: script:
- coverage run manage.py test - coverage run manage.py test
@ -95,7 +95,7 @@ build-passbook-server:
before_script: before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.6.3-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.6.4-beta
only: only:
- tags - tags
- /^version/.*$/ - /^version/.*$/
@ -107,7 +107,7 @@ build-passbook-static:
before_script: before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script: script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.6.3-beta - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.6.4-beta
only: only:
- tags - tags
- /^version/.*$/ - /^version/.*$/

View File

@ -1,6 +1,6 @@
FROM docker.beryju.org/passbook/base:latest FROM docker.beryju.org/passbook/base:latest
COPY --chown=passbook:passbook ./passbook/ /app/passbook COPY ./passbook/ /app/passbook
COPY ./manage.py /app/ COPY ./manage.py /app/
COPY ./docker/uwsgi.ini /app/ COPY ./docker/uwsgi.ini /app/

View File

@ -35,18 +35,18 @@ service_identity = "*"
signxml = "*" signxml = "*"
urllib3 = {extras = ["secure"],version = "*"} urllib3 = {extras = ["secure"],version = "*"}
structlog = "*" structlog = "*"
pyuwsgi = "*"
django-guardian = "*" django-guardian = "*"
[requires] [requires]
python_version = "3.7" python_version = "3.7"
[dev-packages] [dev-packages]
astroid = "==2.2.5"
coverage = "*" coverage = "*"
isort = "*" isort = "*"
pylint = "==2.3.1" pylint = "==2.3.1"
pylint-django = "==2.0.10" pylint-django = "*"
prospector = "==1.1.7" prospector = "*"
django-debug-toolbar = "*" django-debug-toolbar = "*"
bumpversion = "*" bumpversion = "*"
unittest-xml-reporting = "*" unittest-xml-reporting = "*"

39
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "1636ead76bcc61736245f5255a6dfafbf261c3b37b1a2b2665db50919b4cb1ea" "sha256": "587f6d2958f73bf9ae1026c03d123b66937fa642ccd2877f86c4c9b453d15fae"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -101,10 +101,10 @@
}, },
"cheroot": { "cheroot": {
"hashes": [ "hashes": [
"sha256:709259ea832932fb4e1c040b87836a260d386155098c7e138fc317937763e7ae", "sha256:3ff64073efa35b39d5e107410f5c79664dc8c6c5990651e970740c80ab8878a8",
"sha256:abba64f5f0d09b3b8bddf98fa18df1176cd83728b220a995e061c1a766e20a45" "sha256:d523a1525258730026aa35b86c8c47c8d0e3892fb89f0f39157d4b32a50edf05"
], ],
"version": "==8.0.0" "version": "==8.1.0"
}, },
"cherrypy": { "cherrypy": {
"hashes": [ "hashes": [
@ -572,6 +572,36 @@
], ],
"version": "==2019.3" "version": "==2019.3"
}, },
"pyuwsgi": {
"hashes": [
"sha256:15a4626740753b0d0dfeeac7d367f9b2e89ab6af16c195927e60f75359fc1bbc",
"sha256:24c40c3b889eb9f283d43feffbc0f7c7fc024e914451425156ddb68af3df1e71",
"sha256:393737bd43a7e38f0a4a1601a37a69c4bf893635b37665ff958170fdb604fdb7",
"sha256:5a08308f87e639573c1efaa5966a6d04410cd45a73c4586a932fe3ee4b56369d",
"sha256:5f4b36c0dbb9931c4da8008aa423158be596e3b4a23cec95a958631603a94e45",
"sha256:7c31794f71bbd0ccf542cab6bddf38aa69e84e31ae0f9657a2e18ebdc150c01a",
"sha256:802ec6dad4b6707b934370926ec1866603abe31ba03c472f56149001b3533ba1",
"sha256:814d73d4569add69a6c19bb4a27cd5adb72b196e5e080caed17dbda740402072",
"sha256:829299cd117cf8abe837796bf587e61ce6bfe18423a3a1c510c21e9825789c2c",
"sha256:85f2210ceae5f48b7d8fad2240d831f4b890cac85cd98ca82683ac6aa481dfc8",
"sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a",
"sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e",
"sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c",
"sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7",
"sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357",
"sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456",
"sha256:bcb04d6ec644b3e08d03c64851e06edd7110489261e50627a4bcadf66ff6920e",
"sha256:bebfebb9ee83d7cf37668bf54275b677b7ae283e84a944f9f3ac6a4b66f95d4b",
"sha256:c29892dafc65a8b6eb95823fa4bac7754ca3fd1c28ab8d2a973289531b340a27",
"sha256:cb296b50b51ba022b0090b28d032ff1dd395a6db03672b65a39e83532edad527",
"sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4",
"sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee",
"sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6",
"sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe"
],
"index": "pypi",
"version": "==2.0.18.post0"
},
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
@ -745,7 +775,6 @@
"sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
"sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"
], ],
"index": "pypi",
"version": "==2.2.5" "version": "==2.2.5"
}, },
"autopep8": { "autopep8": {

View File

@ -1,18 +1,19 @@
FROM python:3.7-slim-stretch FROM python:3.7-slim-buster as locker
COPY ./Pipfile /app/ COPY ./Pipfile /app/
COPY ./Pipfile.lock /app/ COPY ./Pipfile.lock /app/
WORKDIR /app/ WORKDIR /app/
RUN apt-get update && \ RUN pip install pipenv && \
apt-get install -y --no-install-recommends build-essential && \ pipenv lock -r > requirements.txt && \
pip install pipenv uwsgi --no-cache-dir && \ pipenv lock -rd > requirements-dev.txt
apt-get remove -y --purge build-essential && \
apt-get autoremove -y --purge && \
rm -rf /var/lib/apt/lists/*
RUN pipenv lock -r > requirements.txt && \ FROM python:3.7-slim-buster
pipenv --rm && \
pip install -r requirements.txt --no-cache-dir && \ COPY --from=locker /app/requirements.txt /app/
WORKDIR /app/
RUN pip install -r requirements.txt --no-cache-dir && \
adduser --system --no-create-home --uid 1000 --group --home /app passbook adduser --system --no-create-home --uid 1000 --group --home /app passbook

View File

@ -1,5 +1,3 @@
FROM docker.beryju.org/passbook/base:latest FROM docker.beryju.org/passbook/base:latest
RUN pipenv lock --dev -r > requirements-dev.txt && \ RUN pip install -r /app/requirements-dev.txt --no-cache-dir
pipenv --rm && \
pip install -r /app/requirements-dev.txt --no-cache-dir

View File

@ -49,6 +49,7 @@ services:
- -E - -E
- -B - -B
- -A=passbook.root.celery - -A=passbook.root.celery
- -s=/tmp/celerybeat-schedule
networks: networks:
- internal - internal
labels: labels:

View File

@ -39,7 +39,7 @@ http {
gzip on; gzip on;
gzip_types application/javascript image/* text/css; gzip_types application/javascript image/* text/css;
gunzip on; gunzip on;
add_header X-passbook-Version 0.6.3-beta; add_header X-passbook-Version 0.6.4-beta;
add_header Vary X-passbook-Version; add_header Vary X-passbook-Version;
root /data/; root /data/;

View File

@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
appVersion: "0.6.3-beta" appVersion: "0.6.4-beta"
description: A Helm chart for passbook. description: A Helm chart for passbook.
name: passbook name: passbook
version: "0.6.3-beta" version: "0.6.4-beta"
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png

View File

@ -36,6 +36,7 @@ spec:
- -E - -E
- -B - -B
- -A=passbook.root.celery - -A=passbook.root.celery
- -s=/tmp/celerybeat-schedule
volumeMounts: volumeMounts:
- mountPath: /etc/passbook - mountPath: /etc/passbook
name: config-volume name: config-volume

View File

@ -2,7 +2,7 @@
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
image: image:
tag: 0.6.3-beta tag: 0.6.4-beta
nameOverride: "" nameOverride: ""

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = '0.6.3-beta' __version__ = '0.6.4-beta'

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.6 on 2019-10-10 11:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='nonce',
name='description',
field=models.TextField(blank=True, default=''),
),
]

View File

@ -77,6 +77,7 @@ class Provider(models.Model):
return getattr(self, 'name') return getattr(self, 'name')
return super().__str__() return super().__str__()
class PolicyModel(UUIDModel, CreatedUpdatedModel): class PolicyModel(UUIDModel, CreatedUpdatedModel):
"""Base model which can have policies applied to it""" """Base model which can have policies applied to it"""
@ -262,21 +263,29 @@ class Invitation(UUIDModel):
verbose_name = _('Invitation') verbose_name = _('Invitation')
verbose_name_plural = _('Invitations') verbose_name_plural = _('Invitations')
class Nonce(UUIDModel): class Nonce(UUIDModel):
"""One-time link for password resets/sign-up-confirmations""" """One-time link for password resets/sign-up-confirmations"""
expires = models.DateTimeField(default=default_nonce_duration) expires = models.DateTimeField(default=default_nonce_duration)
user = models.ForeignKey('User', on_delete=models.CASCADE) user = models.ForeignKey('User', on_delete=models.CASCADE)
expiring = models.BooleanField(default=True) expiring = models.BooleanField(default=True)
description = models.TextField(default='', blank=True)
@property
def is_expired(self) -> bool:
"""Check if nonce is expired yet."""
return now() > self.expires
def __str__(self): def __str__(self):
return f"Nonce f{self.uuid.hex} (expires={self.expires})" return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})"
class Meta: class Meta:
verbose_name = _('Nonce') verbose_name = _('Nonce')
verbose_name_plural = _('Nonces') verbose_name_plural = _('Nonces')
class PropertyMapping(UUIDModel): class PropertyMapping(UUIDModel):
"""User-defined key -> x mapping which can be used by providers to expose extra data.""" """User-defined key -> x mapping which can be used by providers to expose extra data."""

View File

11
passbook/recovery/apps.py Normal file
View File

@ -0,0 +1,11 @@
"""passbook Recovery app config"""
from django.apps import AppConfig
class PassbookRecoveryConfig(AppConfig):
"""passbook Recovery app config"""
name = 'passbook.recovery'
label = 'passbook_recovery'
verbose_name = 'passbook Recovery'
mountpoint = 'recovery/'

View File

View File

@ -0,0 +1,46 @@
"""passbook recovery createkey command"""
from datetime import timedelta
from getpass import getuser
from django.core.management.base import BaseCommand
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext as _
from structlog import get_logger
from passbook.core.models import Nonce, User
from passbook.lib.config import CONFIG
LOGGER = get_logger()
class Command(BaseCommand):
"""Create Nonce used to recover access"""
help = _('Create a Key which can be used to restore access to passbook.')
def add_arguments(self, parser):
parser.add_argument('duration', default=1, action='store',
help='How long the token is valid for (in years).')
parser.add_argument('user', action='store',
help='Which user the Token gives access to.')
def get_url(self, nonce: Nonce) -> str:
"""Get full recovery link"""
path = reverse('passbook_recovery:use-nonce', kwargs={'uuid': str(nonce.uuid)})
return f"https://{CONFIG.y('domain')}{path}"
def handle(self, *args, **options):
"""Create Nonce used to recover access"""
duration = int(options.get('duration', 1))
delta = timedelta(days=duration * 365.2425)
_now = now()
expiry = _now + delta
user = User.objects.get(username=options.get('user'))
nonce = Nonce.objects.create(
expires=expiry,
user=user,
description=f'Recovery Nonce generated by {getuser()} on {_now}')
self.stdout.write((f"Store this link safely, as it will allow"
f" anyone to access passbook as {user}."))
self.stdout.write(self.get_url(nonce))

View File

@ -0,0 +1,9 @@
"""recovery views"""
from django.urls import path
from passbook.recovery.views import UseNonceView
urlpatterns = [
path('use-nonce/<uuid:uuid>/', UseNonceView.as_view(), name='use-nonce'),
]

View File

@ -0,0 +1,24 @@
"""recovery views"""
from django.contrib import messages
from django.contrib.auth import login
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext as _
from django.views import View
from passbook.core.models import Nonce
class UseNonceView(View):
"""Use nonce to login"""
def get(self, request: HttpRequest, uuid: str) -> HttpResponse:
"""Check if nonce exists, log user in and delete nonce."""
nonce: Nonce = get_object_or_404(Nonce, pk=uuid)
if nonce.is_expired:
nonce.delete()
raise Http404
login(request, nonce.user, backend='django.contrib.auth.backends.ModelBackend')
nonce.delete()
messages.warning(request, _("Used recovery-link to authenticate."))
return redirect('passbook_core:overview')

View File

@ -74,6 +74,7 @@ INSTALLED_APPS = [
'passbook.api.apps.PassbookAPIConfig', 'passbook.api.apps.PassbookAPIConfig',
'passbook.lib.apps.PassbookLibConfig', 'passbook.lib.apps.PassbookLibConfig',
'passbook.audit.apps.PassbookAuditConfig', 'passbook.audit.apps.PassbookAuditConfig',
'passbook.recovery.apps.PassbookRecoveryConfig',
'passbook.sources.ldap.apps.PassbookSourceLDAPConfig', 'passbook.sources.ldap.apps.PassbookSourceLDAPConfig',
'passbook.sources.oauth.apps.PassbookSourceOAuthConfig', 'passbook.sources.oauth.apps.PassbookSourceOAuthConfig',

View File