Compare commits

...

62 Commits

Author SHA1 Message Date
ff37ed095c new release: 0.2.4-beta 2019-07-05 15:30:13 +02:00
8623a2c3fc oidc_provider: fix error trying to create RSA Key before migrations are run 2019-07-05 15:27:04 +02:00
23d277eaf1 remove oidc from OAuth2, add dedicated OIDC provider 2019-07-05 15:21:48 +02:00
75ced59451 helm: fix syntax error 2019-07-05 15:21:12 +02:00
bccf424c5e new release: 0.2.3-beta 2019-07-04 16:25:33 +02:00
2f9ae40d20 client-sentry: fix 400 failing upload 2019-07-04 16:25:17 +02:00
11e1eec3fb ci: fix new dependencies not being installed on the fly 2019-07-04 16:21:35 +02:00
765c5633df helm: add appgw to ingress 2019-07-04 16:07:16 +02:00
6344b1aafb helm: add deployment for appgw 2019-07-04 15:25:36 +02:00
ed25801e6e core: revert to cherrypy for main webserver and use daphne only for app_gw 2019-07-04 15:23:05 +02:00
4d0148193f root: migrate to new sentry instance 2019-07-03 17:35:54 +02:00
804ae15c2e new release: 0.2.2-beta 2019-06-25 18:50:41 +02:00
b35a9fad86 Fix linting errors with current build-base image 2019-06-25 18:50:37 +02:00
a4f83bd28a new release: 0.2.1-beta 2019-06-25 18:25:27 +02:00
796f83c3d0 Fix requirements file importing wrong path 2019-06-25 18:24:07 +02:00
2099bbb713 new release: 0.2.0-beta 2019-06-25 18:16:14 +02:00
67beba8f78 Fix wrong URLConf being loaded 2019-06-25 18:11:08 +02:00
a798412e17 separate passbook.core into passbook.root and passbook.core
Move Main Django Project into passbook.root while passbook.core holds core functionality.

passbook.root contains main settings, ASGI & WSGI, celery and URLs.
2019-06-25 18:00:54 +02:00
3b2c2d781f Fix typo 2019-06-25 17:59:51 +02:00
98c844f3d6 improve OIDC compatibility 2019-06-25 17:57:25 +02:00
2645bd0132 new release: 0.1.38-beta 2019-04-29 23:26:18 +02:00
2c4fc56b49 Merge branch '27-rewrite-oauth-client-as-factor' into 'master'
Resolve "Rewrite OAuth Client as Factor"

Closes #27

See merge request BeryJu.org/passbook!14
2019-04-29 21:25:04 +00:00
0ec1468058 remove unused import 2019-04-29 23:22:54 +02:00
5d1a3043b2 create SSOLoginPolicy, which allows factors to be applied when user comes from SSO login
implement SESSIION_IS_SSO_LOGIN for OAuth Client and core MFA
2019-04-29 23:19:37 +02:00
b46958d1f9 send session to task 2019-04-29 23:18:51 +02:00
5daa8d5fe3 fix missing/wrong widget inputs 2019-04-29 23:16:04 +02:00
31846f1d05 Show redirect URL in <pre> element 2019-04-29 22:32:22 +02:00
1fac964b8b increase application close timeout 2019-04-29 22:19:26 +02:00
dfa6ed8ac2 add help to show how SAML Property Mapping substitutes variables 2019-04-29 22:19:13 +02:00
66fe10299e new release: 0.1.37-beta 2019-04-29 21:43:18 +02:00
e0a3ec033f fix IDP-Initiated SAML Login 2019-04-29 21:39:41 +02:00
7033ec0ab9 remove debug print 2019-04-29 21:39:30 +02:00
4004579905 remove nexus_upload command 2019-04-29 21:39:16 +02:00
9fe9e48a5c new release: 0.1.36-beta 2019-04-29 20:57:57 +02:00
595a6c7fe6 remove guardian completely 2019-04-29 20:57:54 +02:00
11b5860d4a new release: 0.1.35-beta 2019-04-29 20:47:32 +02:00
9bdbff4cda fix gitlab-ci using incorrect variables for docker access 2019-04-29 20:42:24 +02:00
e0d597eeac show cache on admin overview, add modal to clear cache, re-add logging to policy 2019-04-29 20:37:44 +02:00
f576985cc9 remove django guardian and check application access via PolicyEngine 2019-04-29 20:07:18 +02:00
22a6aef60b fix wrong import 2019-04-29 20:06:52 +02:00
ec0a6e7854 new release: 0.1.34-beta 2019-04-29 19:22:20 +02:00
6904608e6f fix clicking on administration always loading admin interface 2019-04-29 19:22:14 +02:00
cb3732cb2b filter out connection errors for sentry 2019-04-29 19:16:49 +02:00
57de6cbafc repo cleanup, switch to new docker registry 2019-04-29 17:05:39 +02:00
b1dda764a9 Merge branch 'master' of git.beryju.org:BeryJu.org/passbook
# Conflicts:
#	debian/etc/passbook/config.yml
#	helm/passbook/templates/passbook-configmap.yaml
2019-04-18 11:34:13 +02:00
5ec2102487 Merge branch '39-saml-audience-as-database-field' into 'master'
Resolve "SAML: Audience as Database field"

Closes #39

See merge request BeryJu.org/passbook!25
2019-04-18 09:31:56 +00:00
9f8fb7378a add audience field, switch base processor, better handle processor loading failure 2019-04-18 11:30:21 +02:00
98cd646044 remove unneeded processors 2019-04-18 11:09:12 +02:00
0cba1b4c45 fix SAML RancherProcessor not being loaded by default 2019-04-18 11:01:44 +02:00
53918462b6 new release: 0.1.33-beta 2019-04-18 10:28:12 +02:00
8a7e74b523 better release commit messages 2019-04-18 10:28:09 +02:00
4dc7065e97 prepare 0.1.33 2019-04-18 10:25:22 +02:00
3c93bb9f9f remove included CI file 2019-04-18 10:18:47 +02:00
8143fae2d6 switch to custom CI base image with dependencies installed 2019-04-18 10:15:44 +02:00
3cfe45d3cb fix broken icon for helm chart 2019-04-17 17:53:56 +02:00
8e5c3f2f31 remove unused import 2019-04-17 17:53:45 +02:00
5a3b2fdd49 add Rancher SAML processor 2019-04-17 17:53:15 +02:00
e47b9f0d57 fix SAML Request not being parsed all the time 2019-04-17 17:53:05 +02:00
146dd747f1 fix EntityID being None in SAML Metadata 2019-04-17 17:52:24 +02:00
f2ce56063b bump version: 0.1.31-beta -> 0.1.32-beta 2019-04-17 14:27:41 +02:00
b26f378e4c prepare 0.1.32 2019-04-17 14:27:03 +02:00
9072b836c6 automatically add response_type if not given in OAuth Request 2019-04-17 14:25:51 +02:00
124 changed files with 1098 additions and 1041 deletions

View File

@ -1,10 +1,10 @@
[bumpversion]
current_version = 0.1.31-beta
current_version = 0.2.4-beta
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
serialize = {major}.{minor}.{patch}-{release}
message = bump version: {current_version} -> {new_version}
message = new release: {new_version}
tag_name = version/{new_version}
[bumpversion:part:release]

View File

@ -6,7 +6,6 @@ omit =
manage.py
*/migrations/*
*/apps.py
passbook/management/commands/nexus_upload.py
passbook/management/commands/web.py
passbook/management/commands/worker.py
docs/

View File

@ -9,3 +9,6 @@ insert_final_newline = true
[html]
indent_size = 2
[yaml]
indent_size = 2

View File

@ -1,157 +1,113 @@
# Global Variables
before_script:
- "python3 -m pip install -U virtualenv"
- "virtualenv env"
- "source env/bin/activate"
- "pip3 install -U -r requirements-dev.txt"
stages:
- test
- build
- docs
- deploy
image: python:3.6
- build-buildimage
- test
- build
- docs
- deploy
image: docker.beryju.org/passbook/build-base:latest
services:
- postgres:latest
- redis:latest
- postgres:latest
- redis:latest
variables:
POSTGRES_DB: passbook
POSTGRES_USER: passbook
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
POSTGRES_DB: passbook
POSTGRES_USER: passbook
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
include:
- /client-packages/allauth/.gitlab-ci.yml
before_script:
# Ensure all dependencies are installed, even those not included in passbook/build-base
- pip install -r requirements-dev.txt
create-build-image:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile.build-base --destination docker.beryju.org/passbook/build-base:latest --destination docker.beryju.org/passbook/build-base:0.2.4-beta
stage: build-buildimage
only:
refs:
- tags
- /^version/.*$/
isort:
script:
- isort -c -sg env
stage: test
script:
- isort -c -sg env
stage: test
migrations:
script:
- python manage.py migrate
stage: test
script:
- python manage.py migrate
stage: test
prospector:
script:
- prospector
stage: test
script:
- prospector
stage: test
pylint:
script:
- pylint passbook
stage: test
script:
- pylint passbook
stage: test
coverage:
script:
- python manage.py collectstatic --no-input
- coverage run manage.py test
- coverage report
stage: test
script:
- python manage.py collectstatic --no-input
- coverage run manage.py test
- coverage report
stage: test
bandit:
script:
- bandit -r passbook
stage: test
script:
- bandit -r passbook
stage: test
package-docker:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
before_script:
- echo "{\"auths\":{\"docker.$NEXUS_URL\":{\"auth\":\"$NEXUS_AUTH\"}}}" > /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.1.31-beta
stage: build
only:
- tags
- /^version/.*$/
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
before_script:
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
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.2.4-beta
stage: build
only:
- tags
- /^version/.*$/
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 --auth $NEXUS_AUTH --repo helm *.tgz
only:
- tags
- /^version/.*$/
package-debian:
before_script:
- apt update
- apt install -y --no-install-recommends build-essential debhelper devscripts equivs python3 python3-dev python3-pip libsasl2-dev libldap2-dev
- mk-build-deps debian/control
- apt install ./*build-deps*deb -f -y
- python3 -m pip install -U virtualenv pip
- virtualenv env
- source env/bin/activate
- pip3 install -U -r requirements.txt -r requirements-dev.txt
- ./manage.py collectstatic --no-input
image: ubuntu:18.04
script:
- debuild -us -uc
- cp ../passbook*.deb .
- ./manage.py nexus_upload --method post --url $NEXUS_URL --auth $NEXUS_AUTH --repo apt passbook*deb
artifacts:
paths:
- passbook*deb
expire_in: 2 days
stage: build
only:
- tags
- /^version/.*$/
stage: build
script:
- curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
- helm init --client-only
- helm package helm/passbook
artifacts:
paths:
- passbook-*.tgz
expire_in: 2 days
only:
- tags
- /^version/.*$/
package-client-package-allauth:
script:
- cd client-packages/allauth
- python setup.py sdist
- twine upload --username $TWINE_USERNAME --password $TWINE_PASSWORD dist/*
stage: build
only:
refs:
- tags
- /^version/.*$/
changes:
- client-packages/allauth/**
script:
- cd client-packages/allauth
- python setup.py sdist
- twine upload --username $TWINE_USERNAME --password $TWINE_PASSWORD dist/*
stage: build
only:
refs:
- tags
- /^version/.*$/
changes:
- client-packages/allauth/**
package-client-package-sentry:
script:
- cd client-packages/sentry-auth-passbook
- python setup.py sdist
- twine upload --username $TWINE_USERNAME --password $TWINE_PASSWORD dist/*
stage: build
only:
refs:
- tags
- /^version/.*$/
changes:
- client-packages/sentry-auth-passbook/**
# docs:
# stage: docs
# only:
# - master
# - tags
# - /^debian/.*$/
# environment:
# name: docs
# url: "https://passbook.beryju.org/docs/"
# script:
# - apt update
# - apt install -y rsync
# - "mkdir ~/.ssh"
# - "cp .gitlab/known_hosts ~/.ssh/"
# - "pip3 install -U -r requirements-docs.txt"
# - "eval $(ssh-agent -s)"
# - "echo \"${CI_SSH_PRIVATE}\" | ssh-add -"
# - mkdocs build
# - 'rsync -avh --delete web/* "beryjuorg@ory1-web-prod-1.ory1.beryju.org:passbook.beryju.org/"'
# - 'rsync -avh --delete site/* "beryjuorg@ory1-web-prod-1.ory1.beryju.org:passbook.beryju.org/docs/"'
# deploy:
# environment:
# name: production
# url: https://passbook-prod.default.k8s.beryju.org/
# stage: deploy
# only:
# - tags
# - /^version/.*$/
# script:
# - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
# - helm init
# - helm upgrade passbook-prod helm/passbook --devel
script:
- cd client-packages/sentry-auth-passbook
- python setup.py sdist
- twine upload --username $TWINE_USERNAME --password $TWINE_PASSWORD dist/*
stage: build
only:
refs:
- tags
- /^version/.*$/
changes:
- client-packages/sentry-auth-passbook/**

View File

@ -4,6 +4,9 @@
"[html]": {
"editor.tabSize": 2
},
"[yml]": {
"editor.tabSize": 2
},
"cSpell.words": [
"SAML",
"passbook"

12
Dockerfile.build-base Normal file
View File

@ -0,0 +1,12 @@
FROM python:3.6
COPY ./passbook/ /app/passbook
COPY ./client-packages/ /app/client-packages
COPY ./requirements.txt /app/
COPY ./requirements-dev.txt /app/
WORKDIR /app/
RUN apt-get update && apt-get install libssl-dev libffi-dev libpq-dev -y && \
pip install -U -r requirements-dev.txt && \
rm -rf /app/*

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 BeryJu.org
Copyright (c) 2019 BeryJu.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,27 +0,0 @@
# Global Variables
before_script:
- cd allauth/
- "python3 -m pip install -U virtualenv"
- "virtualenv env"
- "source env/bin/activate"
- "pip3 install -U -r requirements-dev.txt"
stages:
- test-allauth
image: python:3.6
isort:
script:
- isort -c -sg env
stage: test-allauth
prospector:
script:
- prospector
stage: test-allauth
pylint:
script:
- pylint passbook
stage: test-allauth
bandit:
script:
- bandit -r allauth_passbook
stage: test-allauth

View File

@ -3,7 +3,7 @@ from setuptools import setup
setup(
name='django-allauth-passbook',
version='0.1.31-beta',
version='0.2.4-beta',
description='passbook support for django-allauth',
# long_description='\n'.join(read_simple('docs/index.md')[2:]),
long_description_content_type='text/markdown',

View File

@ -18,12 +18,13 @@ tests_require = [
setup(
name='sentry-auth-passbook',
version='0.1.31-beta',
version='0.2.4-beta',
author='BeryJu.org',
author_email='support@beryju.org',
url='https://passbook.beryju.org',
description='passbook authentication provider for Sentry',
long_description=__doc__,
long_description_content_type='text/markdown',
license='MIT',
packages=find_packages(exclude=['tests']),
zip_safe=False,

233
debian/changelog vendored
View File

@ -1,233 +0,0 @@
passbook (0.1.31) stable; urgency=medium
* bump version: 0.1.29-beta -> 0.1.30-beta
* allow setting authentication_header to empty string (disabling the header)
* use global urllib Pools
* try to fix app_gw being null
* only enable sentry when not DEBUG
* move logging to separate thread
* move actual proxying logic to separate class
* remove logging to increase speed, add more caching to policy and rewriter
-- Jens Langhammer <jens.langhammer@beryju.org> Sat, 13 Apr 2019 15:56:55 +0000
passbook (0.1.30) stable; urgency=medium
* bump version: 0.1.28-beta -> 0.1.29-beta
* don't use context manager in web command
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 11 Apr 2019 12:21:58 +0000
passbook (0.1.29) stable; urgency=medium
* bump version: 0.1.27-beta -> 0.1.28-beta
* Add libpq-dev dependency so psycopg2 build works
* switch to whitenoise for static files
* replace cherrypy with daphne
* Run collectstatic before coverage, use autoreload on celery worker
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 11 Apr 2019 12:00:27 +0000
passbook (0.1.28) stable; urgency=medium
* bump version: 0.1.26-beta -> 0.1.27-beta
* fix allauth client's formatting
* switch from raven to sentry_sdk
* add ability to have non-expiring nonces, clean up expired nonces
* fully remove raven and switch WSGI and logging to sentry_sdk
* fix failing CI
* trigger autoreload from config files
* Choose upstream more cleverly
* Move code from django-revproxy to app_gw to fix cookie bug
* Implement websocket proxy
* switch kubernetes deployment to daphne server
* set default log level to warn, fix clean_nonces not working
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 11 Apr 2019 08:46:44 +0000
passbook (0.1.27) stable; urgency=medium
* bump version: 0.1.25-beta -> 0.1.26-beta
* fix broken app_gw
-- Jens Langhammer <jens.langhammer@beryju.org> Fri, 22 Mar 2019 13:50:31 +0000
passbook (0.1.26) stable; urgency=medium
* bump version: 0.1.24-beta -> 0.1.25-beta
* always parse url instead of once
* validate upstream in form
* add custom template views
-- Jens Langhammer <jens.langhammer@beryju.org> Fri, 22 Mar 2019 11:47:08 +0000
passbook (0.1.25) stable; urgency=medium
* initial implementation of reverse proxy, using django-revproxy from within a middleware
* fix TypeError: can only concatenate list (not "str") to list
* bump version: 0.1.23-beta -> 0.1.24-beta
* add redis dependency back in for caching
* utilise cache in PolicyEngine
* explicitly use redis db
* invalidate cache when policy is saved
* add redis as service in CI for unittests
* add timeout field to policy to prevent stuck policies
* Don't use LoginRequired for PermissionDenied View
* Check for policies in app_gw
* Better handle policy timeouts
* cleanup post-migration mess
* prevent ZeroDivisionError
* Redirect to login on reverse proxy
* cleanup property_mapping list
* add compiled regex to RewriteRule
* implement actual Rewriting logic
* Invalidate cache when ApplicationGateway instance is saved
* validate server_name in form
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 21 Mar 2019 15:47:58 +0000
passbook (0.1.24) stable; urgency=medium
* bump version: 0.1.22-beta -> 0.1.23-beta
* add modal for OAuth Providers showing the URLs
* remove user field from form. Closes #32
-- Jens Langhammer <jens.langhammer@beryju.org> Wed, 20 Mar 2019 21:59:21 +0000
passbook (0.1.23) stable; urgency=medium
* add support for OpenID-Connect Discovery
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 18 Mar 2019 20:19:27 +0000
passbook (0.1.22) stable; urgency=medium
* bump version: 0.1.20-beta -> 0.1.21-beta
* fix missing debug template
* move icons to single folder, cleanup
* fix layout when on mobile viewport and scrolling
* fix delete form not working
* point to correct icons
* add Azure AD Source
* Fix OAuth Client's disconnect view having invalid URL names
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 14 Mar 2019 20:19:27 +0000
passbook (0.1.21) stable; urgency=medium
* bump version: 0.1.19-beta -> 0.1.20-beta
* add request debug view
* detect HTTPS from reverse proxy
-- Jens Langhammer <jens.langhammer@beryju.org> Thu, 14 Mar 2019 17:01:49 +0000
passbook (0.1.20) stable; urgency=medium
* bump version: 0.1.18-beta -> 0.1.19-beta
* fix GitHub Pretend again
* add user settings for Sources
-- Jens Langhammer <jens.langhammer@beryju.org> Wed, 13 Mar 2019 15:49:44 +0000
passbook (0.1.18) stable; urgency=medium
* bump version: 0.1.16-beta -> 0.1.17-beta
* fix Server Error when downloading metadata
* add sentry client
* fix included yaml file
* adjust versions for client packages, auto build client-packages
* bump version: 0.1.17-beta -> 0.1.18-beta
* fix API Call for sentry-client, add missing template
* fix GitHub Pretend throwing a 500 error
-- Jens Langhammer <jens.langhammer@beryju.org> Wed, 13 Mar 2019 14:14:10 +0000
passbook (0.1.17) stable; urgency=medium
* bump version: 0.1.15-beta -> 0.1.16-beta
* remove Application.user_is_authorized
* don't use celery heartbeat, use TCP keepalive instead
* switch to vertical navigation
-- Jens Langhammer <jens.langhammer@beryju.org> Tue, 12 Mar 2019 14:54:27 +0000
passbook (0.1.16) stable; urgency=medium
* Replace redis with RabbitMQ
* updated debian package to suggest RabbitMQ
* update helm chart to require RabbitMQ
* fix invalid default config in debian package
-- Jens Langhammer <jens.langhammer@beryju.org> Mon, 11 Mar 2019 10:28:36 +0000
passbook (0.1.14) stable; urgency=medium
* bump version: 0.1.11-beta -> 0.1.12-beta
* Fix DoesNotExist error when running PolicyEngine against None user
* allow custom email server for helm installs
* fix UserChangePasswordView not requiring Login
-- Jens Langhammer <jens.langhammer@beryju.org> Mon, 11 Mar 2019 10:28:36 +0000
passbook (0.1.12) stable; urgency=medium
* bump version: 0.1.10-beta -> 0.1.11-beta
* rewrite PasswordFactor to use backends setting instead of trying all backends
* install updated helm release from local folder
* disable automatic k8s deployment for now
* fix OAuth Authorization View not requiring authentication
-- Jens Langhammer <jens.langhammer@beryju.org> Mon, 11 Mar 2019 08:50:29 +0000
passbook (0.1.11) stable; urgency=medium
* add group administration
* bump version: 0.1.9-beta -> 0.1.10-beta
* fix helm labels being on deployments and not pods
* automatically deploy after release
* use Django's Admin FilteredSelectMultiple for Group Membership
* always use FilteredSelectMultiple for many-to-many fields
* Add Group Member policy
* add LDAP Group Membership Policy
-- Jens Langhammer <jens.langhammer@beryju.org> Sun, 10 Mar 2019 18:55:31 +0000
passbook (0.1.10) stable; urgency=high
* bump version: 0.1.7-beta -> 0.1.8-beta
* consistently using PolicyEngine
* add more Verbosity to PolicyEngine, rewrite SAML Authorisation check
* slightly refactor Factor View, add more unittests
* add impersonation middleware, add to templates
* bump version: 0.1.8-beta -> 0.1.9-beta
* fix k8s service routing http traffic to workers
* Fix button on policy test page
* better show loading state when testing a policy
-- Jens Langhammer <jens.langhammer@beryju.org> Sun, 10 Mar 2019 14:52:40 +0000
passbook (0.1.7) stable; urgency=medium
* bump version: 0.1.3-beta -> 0.1.4-beta
* implicitly add kubernetes-healthcheck-host in helm configmap
* fix debian build (again)
* add PropertyMapping Model, add Subclass for SAML, test with AWS
* add custom DynamicArrayField to better handle arrays
* format data before inserting it
* bump version: 0.1.4-beta -> 0.1.5-beta
* fix static files missing for debian package
* fix password not getting set on user import
* remove audit's login attempt
* add passing property to PolicyEngine
* fix captcha factor not loading keys from Factor class
* bump version: 0.1.5-beta -> 0.1.6-beta
* fix MATCH_EXACT not working as intended
* Improve access control for saml
-- Jens Langhammer <jens.langhammer@beryju.org> Fri, 08 Mar 2019 20:37:05 +0000
passbook (0.1.4) stable; urgency=medium
* initial debian package release
-- Jens Langhammer <jens.langhammer@beryju.org> Wed, 06 Mar 2019 18:22:41 +0000

1
debian/compat vendored
View File

@ -1 +0,0 @@
10

20
debian/config vendored
View File

@ -1,20 +0,0 @@
#!/bin/sh
# config maintainer script for passbook
set -e
# source debconf stuff
. /usr/share/debconf/confmodule
dbc_first_version=1.0.0
dbc_dbuser=passbook
dbc_dbname=passbook
# source dbconfig-common shell library, and call the hook function
if [ -f /usr/share/dbconfig-common/dpkg/config.pgsql ]; then
. /usr/share/dbconfig-common/dpkg/config.pgsql
dbc_go passbook "$@"
fi
#DEBHELPER#
exit 0

14
debian/control vendored
View File

@ -1,14 +0,0 @@
Source: passbook
Section: admin
Priority: optional
Maintainer: BeryJu.org <support@beryju.org>
Uploaders: Jens Langhammer <jens@beryju.org>, BeryJu.org <support@beryju.org>
Build-Depends: debhelper (>= 10), dh-systemd (>= 1.5), dh-exec, wget, dh-exec, python3 (>= 3.5) | python3.6 | python3.7, libpq-dev
Standards-Version: 3.9.6
Package: passbook
Architecture: all
Recommends: mysql-server, rabbitmq-server, redis-server
Pre-Depends: adduser, libldap2-dev, libsasl2-dev
Depends: python3 (>= 3.5) | python3.6 | python3.7, python3-pip, dbconfig-pgsql | dbconfig-no-thanks, ${misc:Depends}
Description: Authentication Provider/Proxy supporting protocols like SAML, OAuth, LDAP and more.

22
debian/copyright vendored
View File

@ -1,22 +0,0 @@
MIT License
Copyright (c) 2019 BeryJu.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
debian/dirs vendored
View File

@ -1,4 +0,0 @@
etc/passbook/
etc/passbook/config.d/
var/log/passbook/
usr/share/passbook/

View File

@ -1,81 +0,0 @@
http:
host: 0.0.0.0
port: 8000
secret_key_file: /etc/passbook/secret_key
log:
level:
console: INFO
file: DEBUG
file: /var/log/passbook/passbook.log
debug: false
secure_proxy_header:
HTTP_X_FORWARDED_PROTO: https
rabbitmq: guest:guest@localhost/passbook
redis: localhost/0
# Error reporting, sends stacktrace to sentry.services.beryju.org
error_report_enabled: true
primary_domain: passbook.local
passbook:
sign_up:
# Enables signup, created users are stored in internal Database and created in LDAP if ldap.create_users is true
enabled: true
password_reset:
# Enable password reset, passwords are reset in internal Database and in LDAP if ldap.reset_password is true
enabled: true
# Verification the user has to provide in order to be able to reset passwords. Can be any combination of `email`, `2fa`, `security_questions`
verification:
- email
# Text used in title, on login page and multiple other places
branding: passbook
login:
# Override URL used for logo
logo_url: null
# Override URL used for Background on Login page
bg_url: null
# Optionally add a subtext, placed below logo on the login page
subtext: null
footer:
links:
# Optionally add links to the footer on the login page
# - name: test
# href: https://test
# Specify which fields can be used to authenticate. Can be any combination of `username` and `email`
uid_fields:
- username
- email
session:
remember_age: 2592000 # 60 * 60 * 24 * 30, one month
# Provider-specific settings
ldap:
# Which field from `uid_fields` maps to which LDAP Attribute
login_field_map:
username: sAMAccountName
email: mail # or userPrincipalName
user_attribute_map:
active_directory:
username: "%(sAMAccountName)s"
email: "%(mail)s"
name: "%(displayName)"
oauth_client:
# List of python packages with sources types to load.
types:
- passbook.oauth_client.source_types.discord
- passbook.oauth_client.source_types.facebook
- passbook.oauth_client.source_types.github
- passbook.oauth_client.source_types.google
- passbook.oauth_client.source_types.reddit
- passbook.oauth_client.source_types.supervisr
- passbook.oauth_client.source_types.twitter
saml_idp:
# List of python packages with provider types to load.
types:
- passbook.saml_idp.processors.generic
- passbook.saml_idp.processors.aws
- passbook.saml_idp.processors.gitlab
- passbook.saml_idp.processors.nextcloud
- passbook.saml_idp.processors.salesforce
- passbook.saml_idp.processors.shibboleth
- passbook.saml_idp.processors.wordpress_orange

2
debian/gbp.conf vendored
View File

@ -1,2 +0,0 @@
[buildpackage]
export-dir=../build-area

8
debian/install vendored
View File

@ -1,8 +0,0 @@
passbook /usr/share/passbook/
static /usr/share/passbook/
manage.py /usr/share/passbook/
passbook.sh /usr/share/passbook/
vendor /usr/share/passbook/
debian/etc/passbook /etc/
debian/templates/database.yml /usr/share/passbook/

View File

@ -1,14 +0,0 @@
[Unit]
Description=passbook - Authentication Provider/Proxy (Background worker)
After=network.target
Requires=network.target
[Service]
User=passbook
Group=passbook
WorkingDirectory=/usr/share/passbook
Type=simple
ExecStart=/usr/share/passbook/passbook.sh worker
[Install]
WantedBy=multi-user.target

View File

@ -1,14 +0,0 @@
[Unit]
Description=passbook - Authentication Provider/Proxy
After=network.target
Requires=network.target
[Service]
User=passbook
Group=passbook
WorkingDirectory=/usr/share/passbook
Type=simple
ExecStart=/usr/share/passbook/passbook.sh web
[Install]
WantedBy=multi-user.target

36
debian/postinst vendored
View File

@ -1,36 +0,0 @@
#!/bin/bash
set -e
. /usr/share/debconf/confmodule
. /usr/share/dbconfig-common/dpkg/postinst.pgsql
# you can set the default database encoding to something else
dbc_pgsql_createdb_encoding="UTF8"
dbc_generate_include=template:/etc/passbook/config.d/database.yml
dbc_generate_include_args="-o template_infile=/usr/share/passbook/database.yml"
dbc_go passbook "$@"
if [ -z "`getent group passbook`" ]; then
addgroup --quiet --system passbook
fi
if [ -z "`getent passwd passbook`" ]; then
echo " * Creating user and group passbook..."
adduser --quiet --system --home /usr/share/passbook --shell /bin/false --ingroup passbook --disabled-password --disabled-login --gecos "passbook User" passbook >> /var/log/passbook/passbook.log 2>&1
fi
echo " * Updating binary packages (psycopg2)"
python3 -m pip install --target=/usr/share/passbook/vendor/ --no-cache-dir --upgrade --force-reinstall psycopg2 >> /var/log/passbook/passbook.log 2>&1
if [ ! -f '/etc/passbook/secret_key' ]; then
echo " * Generating Secret Key"
python3 -c 'import random; result = "".join([random.choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)") for i in range(50)]); print(result)' > /etc/passbook/secret_key 2> /dev/null
fi
chown -R passbook: /usr/share/passbook/
chown -R passbook: /etc/passbook/
chown -R passbook: /var/log/passbook/
chmod 440 /etc/passbook/secret_key
echo " * Running Database Migration"
/usr/share/passbook/passbook.sh migrate
echo " * A superuser can be created with this command '/usr/share/passbook/passbook.sh createsuperuser'"
echo " * You should probably also adjust your settings in '/etc/passbook/config.yml'"
#DEBHELPER#

24
debian/postrm vendored
View File

@ -1,24 +0,0 @@
#!/bin/sh
set -e
if [ -f /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
fi
if [ -f /usr/share/dbconfig-common/dpkg/postrm.pgsql ]; then
. /usr/share/dbconfig-common/dpkg/postrm.pgsql
dbc_go passbook "$@"
fi
if [ "$1" = "purge" ]; then
if which ucf >/dev/null 2>&1; then
ucf --purge /etc/passbook/config.d/database.yml
ucfr --purge passbook /etc/passbook/config.d/database.yml
fi
rm -rf /etc/passbook/
rm -rf /usr/share/passbook/
fi
#DEBHELPER#

10
debian/prerm vendored
View File

@ -1,10 +0,0 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
. /usr/share/dbconfig-common/dpkg/prerm.pgsql
dbc_go passbook "$@"
#DEBHELPER#

27
debian/rules vendored
View File

@ -1,27 +0,0 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
# export DH_VERBOSE=1
%:
dh $@ --with=systemd
build-arch:
python3 -m pip install setuptools
python3 -m pip install --target=vendor/ -r requirements.txt
override_dh_strip:
dh_strip --exclude=psycopg2
override_dh_shlibdeps:
dh_shlibdeps --exclude=psycopg2
override_dh_installinit:
dh_installinit --name=passbook
dh_installinit --name=passbook-worker
dh_systemd_enable --name=passbook
dh_systemd_enable --name=passbook-worker
dh_systemd_start
# override_dh_usrlocal to do nothing
override_dh_usrlocal:

View File

@ -1 +0,0 @@
3.0 (native)

View File

@ -1,8 +0,0 @@
databases:
default:
engine: django.db.backends.postgresql
name: _DBC_DBNAME_
user: _DBC_DBUSER_
password: _DBC_DBPASS_
host: _DBC_DBSERVER_
port: _DBC_DBPORT_

View File

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

View File

@ -0,0 +1 @@
# passbook

View File

@ -0,0 +1,98 @@
---
categories:
- Authentication
- SSO
questions:
- default: "true"
variable: config.error_reporting
type: boolean
description: "Enable error-reporting to sentry.services.beryju.org"
group: "passbook Configuration"
label: "Error Reporting"
####################################################################
### PostgreSQL
####################################################################
- variable: postgresql.enabled
default: true
description: "Deploy a database server as part of this deployment, or set to false and configure an external database connection."
type: boolean
required: true
label: Install PostgreSQL
show_subquestion_if: true
group: "Database Settings"
subquestions:
- variable: postgresql.postgresqlDatabase
default: "passbook"
description: "Database name to create"
type: string
label: PostgreSQL Database
- variable: postgresql.postgresqlUsername
default: "passbook"
description: "Database user to create"
type: string
label: PostgreSQL User
- variable: postgresql.postgresqlPassword
default: ""
description: "password will be auto-generated if not specified"
type: password
label: PostgreSQL Password
- variable: externalDatabase.host
default: ""
description: "Host of the external database"
type: string
label: External Database Host
show_if: "postgresql.enabled=false"
group: "Database Settings"
- variable: externalDatabase.user
default: ""
description: "Existing username in the external DB"
type: string
label: External Database username
show_if: "postgresql.enabled=false"
group: "Database Settings"
- variable: externalDatabase.password
default: ""
description: "External database password"
type: password
label: External Database password
show_if: "postgresql.enabled=false"
group: "Database Settings"
- variable: externalDatabase.database
default: ""
description: "Name of the existing database"
type: string
label: External Database
show_if: "postgresql.enabled=false"
group: "Database Settings"
- variable: externalDatabase.port
default: "3306"
description: "External database port number"
type: string
label: External Database Port
show_if: "postgresql.enabled=false"
group: "Database Settings"
- variable: postgresql.persistence.enabled
default: false
description: "Enable persistent volume for PostgreSQL"
type: boolean
required: true
label: PostgreSQL Persistent Volume Enabled
show_if: "postgresql.enabled=true"
show_subquestion_if: true
group: "Database Settings"
subquestions:
- variable: postgresql.master.persistence.size
default: "8Gi"
description: "PostgreSQL Persistent Volume Size"
type: string
label: PostgreSQL Volume Size
- variable: postgresql.master.persistence.storageClass
default: ""
description: "If undefined or null, uses the default StorageClass. Default to null"
type: storageclass
label: Default StorageClass for PostgreSQL
- variable: postgresql.master.persistence.existingClaim
default: ""
description: "If not empty, uses the specified existing PVC instead of creating new one"
type: string
label: Existing Persistent Volume Claim for PostgreSQL

View File

@ -0,0 +1,67 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ include "passbook.fullname" . }}-appgw
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: appgw
spec:
volumes:
- name: config-volume
configMap:
name: {{ include "passbook.fullname" . }}-config
containers:
- name: {{ .Chart.Name }}
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args: ["./manage.py migrate && ./manage.py app_gw_web"]
ports:
- name: http
containerPort: 8000
protocol: TCP
volumeMounts:
- mountPath: /etc/passbook
name: config-volume
livenessProbe:
httpGet:
path: /
port: http
httpHeaders:
- name: Host
value: kubernetes-healthcheck-host
readinessProbe:
httpGet:
path: /
port: http
httpHeaders:
- name: Host
value: kubernetes-healthcheck-host
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}

View File

@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "passbook.fullname" . }}-appgw
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: appgw

View File

@ -134,9 +134,4 @@ data:
# List of python packages with provider types to load.
types:
- passbook.saml_idp.processors.generic
- passbook.saml_idp.processors.aws
- passbook.saml_idp.processors.gitlab
- passbook.saml_idp.processors.nextcloud
- passbook.saml_idp.processors.salesforce
- passbook.saml_idp.processors.shibboleth
- passbook.saml_idp.processors.wordpress_orange

View File

@ -32,7 +32,16 @@ spec:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
serviceName: {{ $fullName }}-web
servicePort: http
{{- end }}
{{- range .Values.ingress.app_gw_hosts }}
- host: {{ . | quote }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}-appgw
servicePort: http
{{- end }}
{{- end }}

View File

@ -26,7 +26,7 @@ spec:
name: {{ include "passbook.fullname" . }}-config
containers:
- name: {{ .Chart.Name }}
image: "docker.pkg.beryju.org/passbook:{{ .Values.image.tag }}"
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c"]
args: ["./manage.py migrate && ./manage.py web"]

View File

@ -1,7 +1,7 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "passbook.fullname" . }}
name: {{ include "passbook.fullname" . }}-web
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
helm.sh/chart: {{ include "passbook.chart" . }}

View File

@ -26,7 +26,7 @@ spec:
name: {{ include "passbook.fullname" . }}-config
containers:
- name: {{ .Chart.Name }}
image: "docker.pkg.beryju.org/passbook:{{ .Values.image.tag }}"
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
command: ["./manage.py", "worker"]
ports:

View File

@ -5,7 +5,7 @@
replicaCount: 1
image:
tag: 0.1.31-beta
tag: 0.2.4-beta
nameOverride: ""
@ -37,6 +37,8 @@ ingress:
path: /
hosts:
- passbook.k8s.local
app_gw_hosts:
- '*.passbook.k8s.local'
defaultHost: passbook.k8s.local
tls: []
# - secretName: chart-example-tls

View File

@ -4,7 +4,7 @@ import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'passbook.core.settings')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'passbook.root.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:

View File

@ -1,7 +0,0 @@
#!/bin/bash
# Check if this file is a symlink, if so, read real base dir
BASE_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
cd $BASE_DIR
PYTHONPATH="${BASE_DIR}/vendor/" python3 manage.py $@

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -1,2 +1,2 @@
"""passbook admin"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -152,10 +152,8 @@
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<a href="#">
<span class="pficon-bundle"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Version' %}
</a>
<span class="pficon-bundle"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Version' %}
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
@ -192,5 +190,59 @@
</div>
</div>
</div>
<div class="col-xs-6 col-sm-2 col-md-2">
<div class="card-pf card-pf-accented card-pf-aggregate-status">
<h2 class="card-pf-title">
<span class="pficon-server"></span>
<span class="card-pf-aggregate-status-count"></span> {% trans 'Cached Policies' %}
</h2>
<div class="card-pf-body">
<p class="card-pf-aggregate-status-notifications">
<span class="card-pf-aggregate-status-notification">
<a href="#" data-toggle="modal" data-target="#clearCacheMOdal">
{% if cached_policies < 1 %}
<span class="pficon-warning-triangle-o" data-toggle="tooltip" data-placement="right"
title="{% trans 'No policies cached. Users may experience slow response times.' %}"></span> {{ cached_policies }}
{% else %}
<span class="pficon pficon-ok"></span>{{ cached_policies }}
{% endif %}
</a>
</span>
</p>
</div>
</div>
</div>
</div>
<div class="modal fade" id="clearCacheMOdal" tabindex="-1" role="dialog" aria-labelledby="clearCacheMOdalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
<h4 class="modal-title" id="clearCacheMOdalLabel">{% trans 'Clear Cache' %}</h4>
</div>
<div class="modal-body">
<form method="post" id="clearForm">
{% csrf_token %}
<input type="hidden" name="clear">
<p>
{% blocktrans %}
Are you sure you want to clear the cache? This includes all user sessions and all cached Policy results.
{% endblocktrans %}
</p>
<h3>
{% blocktrans %}
This will also log you out.
{% endblocktrans %}
</h3>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button form="clearForm" type="submit" type="button" class="btn btn-danger">{% trans 'Clear' %}</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -36,7 +36,7 @@
<tr>
<td>{{ source.name }}</td>
<td>{{ source|fieldtype }}</td>
<td>{{ source.additional_info }}</td>
<td>{{ source.additional_info|safe }}</td>
<td>
<a class="btn btn-default btn-sm"
href="{% url 'passbook_admin:source-update' pk=source.uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>

View File

@ -1,11 +1,13 @@
"""passbook administration overview"""
from django.core.cache import cache
from django.shortcuts import redirect, reverse
from django.views.generic import TemplateView
from passbook.admin.mixins import AdminRequiredMixin
from passbook.core import __version__
from passbook.core.celery import CELERY_APP
from passbook.core.models import (Application, Factor, Invitation, Policy,
Provider, Source, User)
from passbook.root.celery import CELERY_APP
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
@ -13,6 +15,13 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
template_name = 'administration/overview.html'
def post(self, *args, **kwargs):
"""Handle post (clear cache from modal)"""
if 'clear' in self.request.POST:
cache.clear()
return redirect(reverse('passbook_core:auth-login'))
return self.get(*args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['application_count'] = len(Application.objects.all())
kwargs['policy_count'] = len(Policy.objects.all())
@ -25,4 +34,5 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
kwargs['worker_count'] = len(CELERY_APP.control.ping(timeout=0.5))
kwargs['providers_without_application'] = Provider.objects.filter(application=None)
kwargs['policies_without_attachment'] = len(Policy.objects.filter(policymodel__isnull=True))
kwargs['cached_policies'] = len(cache.keys('policy_*'))
return super().get_context_data(**kwargs)

View File

@ -1,2 +1,2 @@
"""passbook api"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -1,2 +1,2 @@
"""passbook Application Security Gateway Header"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -8,6 +8,6 @@ import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.core.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
django.setup()
application = get_default_application()

View File

@ -0,0 +1,30 @@
"""passbook app_gw webserver management command"""
from logging import getLogger
from daphne.cli import CommandLineInterface
from django.core.management.base import BaseCommand
from django.utils import autoreload
from passbook.lib.config import CONFIG
LOGGER = getLogger(__name__)
class Command(BaseCommand):
"""Run Daphne Webserver for app_gw"""
def handle(self, *args, **options):
"""passbook daphne server"""
autoreload.run_with_reloader(self.daphne_server)
def daphne_server(self):
"""Run daphne server within autoreload"""
autoreload.raise_last_exception()
CommandLineInterface().run([
'-p', str(CONFIG.y('app_gw.port', 8000)),
'-b', CONFIG.y('app_gw.listen', '0.0.0.0'), # nosec
'--access-log', '/dev/null',
'--application-close-timeout', '500',
'passbook.app_gw.asgi:application'
])

View File

@ -190,8 +190,6 @@ def cookie_from_string(cookie_string, strict_cookies=False):
cookie_dict['key'], cookie_dict['value'] = \
cookie_parts[0].split('=', 1)
cookie_dict['value'] = cookie_dict['value'].replace('"', '')
# print('aaaaaaaaaaaaaaaaaaaaaaaaaaaa')
# print(cookie_parts[0].split('=', 1))
except ValueError:
logger.warning('Invalid cookie: `%s`', cookie_string)
return None

View File

@ -1,2 +1,2 @@
"""passbook audit Header"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -22,7 +22,7 @@ class AuditEntry(UUIDModel):
ACTION_AUTHORIZE_APPLICATION = 'authorize_application'
ACTION_SUSPICIOUS_REQUEST = 'suspicious_request'
ACTION_SIGN_UP = 'sign_up'
ACTION_PASSWORD_RESET = 'password_reset' # noqa
ACTION_PASSWORD_RESET = 'password_reset' # noqa # nosec
ACTION_INVITE_CREATED = 'invitation_created'
ACTION_INVITE_USED = 'invitation_used'
ACTIONS = (

View File

@ -1,2 +1,2 @@
"""passbook captcha_factor Header"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -1,2 +1,2 @@
"""passbook core"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -14,6 +14,7 @@ class PassbookCoreConfig(AppConfig):
name = 'passbook.core'
label = 'passbook_core'
verbose_name = 'passbook Core'
mountpoint = ''
def ready(self):
import_module('passbook.core.policies')

View File

@ -29,6 +29,7 @@ class AuthenticationView(UserPassesTestMixin, View):
SESSION_PENDING_FACTORS = 'passbook_pending_factors'
SESSION_PENDING_USER = 'passbook_pending_user'
SESSION_USER_BACKEND = 'passbook_user_backend'
SESSION_IS_SSO_LOGIN = 'passbook_sso_login'
pending_user = None
pending_factors = []
@ -79,6 +80,10 @@ class AuthenticationView(UserPassesTestMixin, View):
if AuthenticationView.SESSION_FACTOR not in request.session:
# Case when no factors apply to user, return error denied
if not self.pending_factors:
# Case when user logged in from SSO provider and no more factors apply
if AuthenticationView.SESSION_IS_SSO_LOGIN in request.session:
LOGGER.debug("User authenticated with SSO, logging in...")
return self._user_passed()
return self.user_invalid()
factor_uuid, factor_class = self.pending_factors[0]
else:

View File

@ -27,7 +27,8 @@ class PasswordFactorForm(forms.ModelForm):
'order': forms.NumberInput(),
'policies': FilteredSelectMultiple(_('policies'), False),
'backends': FilteredSelectMultiple(_('backends'), False,
choices=get_authentication_backends())
choices=get_authentication_backends()),
'password_policies': FilteredSelectMultiple(_('password policies'), False),
}
class DummyFactorForm(forms.ModelForm):

View File

@ -5,7 +5,7 @@ from django.utils.translation import gettext as _
from passbook.core.models import (DebugPolicy, FieldMatcherPolicy,
GroupMembershipPolicy, PasswordPolicy,
WebhookPolicy)
SSOLoginPolicy, WebhookPolicy)
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout']
@ -63,6 +63,19 @@ class GroupMembershipPolicyForm(forms.ModelForm):
fields = GENERAL_FIELDS + ['group', ]
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
}
class SSOLoginPolicyForm(forms.ModelForm):
"""Edit SSOLoginPolicy instances"""
class Meta:
model = SSOLoginPolicy
fields = GENERAL_FIELDS
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
}
class PasswordPolicyForm(forms.ModelForm):

View File

@ -1,63 +0,0 @@
"""passbook nexus_upload management command"""
from base64 import b64decode
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(
'--auth',
action='store',
help='base64-encoded string of username:password',
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)
# Positional arguments
parser.add_argument('file', nargs='+', type=str)
def handle(self, *args, **options):
"""Upload debian package to nexus repository"""
auth = tuple(b64decode(options.get('auth')).decode('utf-8').split(':', 1))
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=auth)
else:
responses[file] = requests.put(url+file, data=open(file, mode='rb'), auth=auth)
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

@ -2,11 +2,12 @@
from logging import getLogger
from daphne.cli import CommandLineInterface
import cherrypy
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils import autoreload
from passbook.lib.config import CONFIG
from passbook.root.wsgi import application
LOGGER = getLogger(__name__)
@ -15,15 +16,21 @@ class Command(BaseCommand):
"""Run CherryPy webserver"""
def handle(self, *args, **options):
"""passbook daphne server"""
autoreload.run_with_reloader(self.daphne_server)
def daphne_server(self):
"""Run daphne server within autoreload"""
autoreload.raise_last_exception()
CommandLineInterface().run([
'-p', str(CONFIG.y('web.port', 8000)),
'-b', CONFIG.y('web.listen', '0.0.0.0'), # nosec
'--access-log', '/dev/null',
'passbook.core.asgi:application'
])
"""passbook cherrypy server"""
cherrypy.config.update(CONFIG.get('web'))
cherrypy.tree.graft(application, '/')
# Mount NullObject to serve static files
cherrypy.tree.mount(None, settings.STATIC_URL, config={
'/': {
'tools.staticdir.on': True,
'tools.staticdir.dir': settings.STATIC_ROOT,
'tools.expires.on': True,
'tools.expires.secs': 86400,
'tools.gzip.on': True,
}
})
cherrypy.engine.start()
for file in CONFIG.loaded_file:
cherrypy.engine.autoreload.files.add(file)
LOGGER.info("Added '%s' to autoreload triggers", file)
cherrypy.engine.block()

View File

@ -5,7 +5,7 @@ from logging import getLogger
from django.core.management.base import BaseCommand
from django.utils import autoreload
from passbook.core.celery import CELERY_APP
from passbook.root.celery import CELERY_APP
LOGGER = getLogger(__name__)

View File

@ -0,0 +1,25 @@
# Generated by Django 2.2 on 2019-04-29 21:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('passbook_core', '0023_remove_user_applications'),
]
operations = [
migrations.CreateModel(
name='SSOLoginPolicy',
fields=[
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
],
options={
'verbose_name': 'SSO Login Policy',
'verbose_name_plural': 'SSO Login Policies',
},
bases=('passbook_core.policy',),
),
]

View File

@ -165,9 +165,10 @@ class Source(PolicyModel):
name = models.TextField()
slug = models.SlugField()
form = '' # ModelForm-based class ued to create/edit instance
enabled = models.BooleanField(default=True)
form = '' # ModelForm-based class ued to create/edit instance
objects = InheritanceManager()
@property
@ -409,6 +410,21 @@ class GroupMembershipPolicy(Policy):
verbose_name = _('Group Membership Policy')
verbose_name_plural = _('Group Membership Policies')
class SSOLoginPolicy(Policy):
"""Policy that applies to users that have authenticated themselves through SSO"""
form = 'passbook.core.forms.policies.SSOLoginPolicyForm'
def passes(self, user):
"""Check if user instance passes this policy"""
from passbook.core.auth.view import AuthenticationView
return user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False), ""
class Meta:
verbose_name = _('SSO Login Policy')
verbose_name_plural = _('SSO Login Policies')
class Invitation(UUIDModel):
"""Single-use invitation link"""

View File

@ -1,18 +1,19 @@
"""passbook core policy engine"""
# from logging import getLogger
from logging import getLogger
from amqp.exceptions import UnexpectedFrame
from celery import group
from celery.exceptions import TimeoutError as CeleryTimeoutError
from django.core.cache import cache
from ipware import get_client_ip
from passbook.core.celery import CELERY_APP
from passbook.core.models import Policy, User
from passbook.root.celery import CELERY_APP
# LOGGER = getLogger(__name__)
LOGGER = getLogger(__name__)
def _cache_key(policy, user):
return "%s#%s" % (policy.uuid, user.pk)
return "policy_%s#%s" % (policy.uuid, user.pk)
@CELERY_APP.task()
def _policy_engine_task(user_pk, policy_pk, **kwargs):
@ -23,8 +24,8 @@ def _policy_engine_task(user_pk, policy_pk, **kwargs):
user_obj = User.objects.get(pk=user_pk)
for key, value in kwargs.items():
setattr(user_obj, key, value)
# LOGGER.debug("Running policy `%s`#%s for user %s...", policy_obj.name,
# policy_obj.pk.hex, user_obj)
LOGGER.debug("Running policy `%s`#%s for user %s...", policy_obj.name,
policy_obj.pk.hex, user_obj)
policy_result = policy_obj.passes(user_obj)
# Handle policy result correctly if result, message or just result
message = None
@ -33,10 +34,10 @@ def _policy_engine_task(user_pk, policy_pk, **kwargs):
# Invert result if policy.negate is set
if policy_obj.negate:
policy_result = not policy_result
# LOGGER.debug("Policy %r#%s got %s", policy_obj.name, policy_obj.pk.hex, policy_result)
LOGGER.debug("Policy %r#%s got %s", policy_obj.name, policy_obj.pk.hex, policy_result)
cache_key = _cache_key(policy_obj, user_obj)
cache.set(cache_key, (policy_obj.action, policy_result, message))
# LOGGER.debug("Cached entry as %s", cache_key)
LOGGER.debug("Cached entry as %s", cache_key)
return policy_obj.action, policy_result, message
class PolicyEngine:
@ -73,6 +74,7 @@ class PolicyEngine:
cached_policies = []
kwargs = {
'__password__': getattr(self.__user, '__password__', None),
'session': dict(getattr(self.__request, 'session', {}).items()),
}
if self.__request:
kwargs['remote_ip'], _ = get_client_ip(self.__request)
@ -81,16 +83,16 @@ class PolicyEngine:
for policy in self.policies:
cached_policy = cache.get(_cache_key(policy, self.__user), None)
if cached_policy:
# LOGGER.debug("Taking result from cache for %s", policy.pk.hex)
LOGGER.debug("Taking result from cache for %s", policy.pk.hex)
cached_policies.append(cached_policy)
else:
# LOGGER.debug("Evaluating policy %s", policy.pk.hex)
LOGGER.debug("Evaluating policy %s", policy.pk.hex)
signatures.append(_policy_engine_task.signature(
args=(self.__user.pk, policy.pk.hex),
kwargs=kwargs,
time_limit=policy.timeout))
self.__get_timeout += policy.timeout
# LOGGER.debug("Set total policy timeout to %r", self.__get_timeout)
LOGGER.debug("Set total policy timeout to %r", self.__get_timeout)
# If all policies are cached, we have an empty list here.
if signatures:
self.__group = group(signatures)()
@ -119,7 +121,7 @@ class PolicyEngine:
for policy_action, policy_result, policy_message in result:
passing = (policy_action == Policy.ACTION_ALLOW and policy_result) or \
(policy_action == Policy.ACTION_DENY and not policy_result)
# LOGGER.debug('Action=%s, Result=%r => %r', policy_action, policy_result, passing)
LOGGER.debug('Action=%s, Result=%r => %r', policy_action, policy_result, passing)
if policy_message:
messages.append(policy_message)
if not passing:

View File

@ -6,9 +6,9 @@ from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from passbook.core.celery import CELERY_APP
from passbook.core.models import Nonce
from passbook.lib.config import CONFIG
from passbook.root.celery import CELERY_APP
LOGGER = getLogger(__name__)

View File

@ -0,0 +1,54 @@
{% load static %}
{% load i18n %}
{% load utils %}
<!DOCTYPE html>
<html lang="en" class="layout-pf layout-pf-fixed transitions">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% block title %}
{% title %}
{% endblock %}
</title>
<link rel="icon" type="image/png" href="{% static 'img/logo.png' %}">
<link rel="shortcut icon" type="image/png" href="{% static 'img/logo.png' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/patternfly-additions.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/passbook.css' %}">
<style>
.login-pf {
background-attachment: fixed;
scroll-behavior: smooth;
background-size: cover;
}
</style>
{% block head %}
{% endblock %}
</head>
<body class="login-pf">
{% if 'impersonate_id' in request.session %}
<div class="experimental-pf-bar">
<span id="experimentalBar" class="experimental-pf-text">
{% blocktrans with user=user %}You're currently impersonating {{ user }}.{% endblocktrans %}
<a href="?__unimpersonate=True" id="acceptMessage">{% trans 'Stop impersonation' %}</a>
</span>
</div>
{% endif %}
{% block body %}
{% endblock %}
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/patternfly.min.js' %}"></script>
<script src="{% static 'js/passbook.js' %}"></script>
{% block scripts %}
{% endblock %}
<div class="modals">
{% include 'partials/about_modal.html' %}
</div>
</body>
</html>

View File

@ -1,4 +1,4 @@
{% extends 'base/skeleton.html' %}
{% extends 'base/skeleton_login.html' %}
{% load static %}
{% load i18n %}

View File

@ -68,7 +68,7 @@
{% is_active_app 'passbook_admin' as is_admin %}
{% if user.is_superuser %}
<li class="list-group-item {% is_active_app 'passbook_admin' %} secondary-nav-item-pf">
<a href="{% url 'passbook_admin:overview' %}">
<a>
<span class="pficon pficon-user" data-toggle="tooltip" title=""
data-original-title="{% trans 'Administration' %}"></span>
<span class="list-group-item-value dropdown-title">{% trans 'Administration' %}</span>

View File

@ -1,25 +1,14 @@
"""passbook URL Configuration"""
from logging import getLogger
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
from django.views.generic import RedirectView
from django.urls import path
from passbook.core.auth import view
from passbook.core.views import authentication, error, overview, user
from passbook.lib.utils.reflection import get_apps
from passbook.core.views import authentication, overview, user
LOGGER = getLogger(__name__)
admin.autodiscover()
admin.site.login = RedirectView.as_view(pattern_name='passbook_core:auth-login')
handler400 = error.BadRequestView.as_view()
handler403 = error.ForbiddenView.as_view()
handler404 = error.NotFoundView.as_view()
handler500 = error.ServerErrorView.as_view()
core_urls = [
urlpatterns = [
# Authentication views
path('auth/login/', authentication.LoginView.as_view(), name='auth-login'),
path('auth/logout/', authentication.LogoutView.as_view(), name='auth-logout'),
@ -39,27 +28,3 @@ core_urls = [
# Overview
path('', overview.OverviewView.as_view(), name='overview'),
]
urlpatterns = [
# Core (include our own URLs so namespaces are used everywhere)
path('', include((core_urls, 'passbook_core'), namespace='passbook_core')),
]
for _passbook_app in get_apps():
if hasattr(_passbook_app, 'mountpoint'):
_path = path(_passbook_app.mountpoint, include((_passbook_app.name+'.urls',
_passbook_app.label),
namespace=_passbook_app.label))
urlpatterns.append(_path)
LOGGER.debug("Loaded %s's URLs", _passbook_app.name)
urlpatterns += [
# Administration
path('administration/django/', admin.site.urls),
]
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
] + urlpatterns

View File

@ -71,7 +71,7 @@ class LoginView(UserPassesTestMixin, FormView):
if not pre_user:
# No user found
return self.invalid_login(self.request)
self.request.session.flush()
# self.request.session.flush()
self.request.session[AuthenticationView.SESSION_PENDING_USER] = pre_user.pk
return _redirect_with_qs('passbook_core:auth-process', self.request.GET)

View File

@ -2,7 +2,9 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from guardian.shortcuts import get_objects_for_user
from passbook.core.models import Application
from passbook.core.policies import PolicyEngine
class OverviewView(LoginRequiredMixin, TemplateView):
@ -12,6 +14,11 @@ class OverviewView(LoginRequiredMixin, TemplateView):
template_name = 'overview/index.html'
def get_context_data(self, **kwargs):
kwargs['applications'] = get_objects_for_user(self.request.user,
'passbook_core.view_application')
kwargs['applications'] = []
for application in Application.objects.all():
engine = PolicyEngine(application.policies.all())
engine.for_user(self.request.user).with_request(self.request)
engine.build()
if engine.passing:
kwargs['applications'].append(application)
return super().get_context_data(**kwargs)

View File

@ -1,2 +1,2 @@
"""passbook hibp_policy"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -1,2 +1,2 @@
"""Passbook ldap app Header"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -1,2 +1,2 @@
"""passbook lib"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -137,4 +137,6 @@ def signal_handler(sender, **kwargs):
"""Add all loaded config files to autoreload watcher"""
for path in CONFIG.loaded_file:
sender.watch_file(path)
autoreload_started.connect(signal_handler)

View File

@ -23,9 +23,13 @@ email:
use_ssl: false
from: passbook <passbook@domain.tld>
web:
listen: 0.0.0.0
port: 8000
threads: 30
server.socket_host: 0.0.0.0
server.socket_port: 8000
server.thread_pool: 20
log.screen: false
log.access_file: ''
log.error_file: ''
debug: false
secure_proxy_header:
HTTP_X_FORWARDED_PROTO: https
@ -95,9 +99,7 @@ saml_idp:
# List of python packages with provider types to load.
types:
- passbook.saml_idp.processors.generic
- passbook.saml_idp.processors.aws
- passbook.saml_idp.processors.gitlab
- passbook.saml_idp.processors.nextcloud
- passbook.saml_idp.processors.salesforce
- passbook.saml_idp.processors.shibboleth
- passbook.saml_idp.processors.wordpress_orange
app_gw:
listen: 0.0.0.0
port: 8000

29
passbook/lib/sentry.py Normal file
View File

@ -0,0 +1,29 @@
"""passbook sentry integration"""
from logging import getLogger
LOGGER = getLogger(__name__)
def before_send(event, hint):
"""Check if error is database error, and ignore if so"""
from django_redis.exceptions import ConnectionInterrupted
from django.db import OperationalError, InternalError
from rest_framework.exceptions import APIException
from billiard.exceptions import WorkerLostError
from django.core.exceptions import DisallowedHost
ignored_classes = (
OperationalError,
ConnectionInterrupted,
APIException,
InternalError,
ConnectionResetError,
WorkerLostError,
DisallowedHost,
ConnectionResetError,
)
if 'exc_info' in hint:
_exc_type, exc_value, _ = hint['exc_info']
if isinstance(exc_value, ignored_classes):
LOGGER.info("Supressing error %r", exc_value)
return None
return event

View File

@ -1,2 +1,2 @@
"""passbook oauth_client Header"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -29,14 +29,13 @@ class OAuthSource(Source):
def get_login_button(self):
url = reverse_lazy('passbook_oauth_client:oauth-client-login',
kwargs={'source_slug': self.slug})
# if self.provider_type == 'github':
# return url, 'github-logo', _('GitHub')
return url, self.provider_type, self.name
@property
def additional_info(self):
return "Callback URL: '%s'" % reverse_lazy('passbook_oauth_client:oauth-client-callback',
kwargs={'source_slug': self.slug})
return "Callback URL: <pre>%s</pre>" % \
reverse_lazy('passbook_oauth_client:oauth-client-callback',
kwargs={'source_slug': self.slug})
def has_user_settings(self):
"""Entrypoint to integrate with User settings. Can either return False if no

View File

@ -4,7 +4,7 @@ from logging import getLogger
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate, login
from django.contrib.auth import authenticate
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
@ -12,6 +12,7 @@ from django.urls import reverse
from django.utils.translation import ugettext as _
from django.views.generic import RedirectView, View
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
from passbook.lib.utils.reflection import app
from passbook.oauth_client.clients import get_client
from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection
@ -128,11 +129,6 @@ class OAuthCallback(OAuthClientMixin, View):
"Return url to redirect on login failure."
return settings.LOGIN_URL
# pylint: disable=unused-argument
def get_login_redirect(self, source, user, access, new=False):
"Return url to redirect authenticated users."
return 'passbook_core:overview'
def get_or_create_user(self, source, access, info):
"Create a shell auth.User."
raise NotImplementedError()
@ -149,14 +145,22 @@ class OAuthCallback(OAuthClientMixin, View):
except KeyError:
return None
def handle_login(self, user, source, access):
"""Prepare AuthenticationView, redirect users to remaining Factors"""
user = authenticate(source=access.source,
identifier=access.identifier, request=self.request)
self.request.session[AuthenticationView.SESSION_PENDING_USER] = user.pk
self.request.session[AuthenticationView.SESSION_USER_BACKEND] = user.backend
self.request.session[AuthenticationView.SESSION_IS_SSO_LOGIN] = True
return _redirect_with_qs('passbook_core:auth-process', self.request.GET)
# pylint: disable=unused-argument
def handle_existing_user(self, source, user, access, info):
"Login user and redirect."
login(self.request, user)
messages.success(self.request, _("Successfully authenticated with %(source)s!" % {
'source': self.source.name
}))
return redirect(self.get_login_redirect(source, user, access))
return self.handle_login(user, source, access)
def handle_login_failure(self, source, reason):
"Message user and redirect on error."
@ -176,12 +180,9 @@ class OAuthCallback(OAuthClientMixin, View):
access.user = user
access.save()
UserOAuthSourceConnection.objects.filter(pk=access.pk).update(user=user)
if not was_authenticated:
user = authenticate(source=access.source,
identifier=access.identifier, request=self.request)
login(self.request, user)
if app('passbook_audit'):
pass
# TODO: Create audit entry
# from passbook.audit.models import something
# something.event(user=user,)
# Event.create(
@ -197,10 +198,13 @@ class OAuthCallback(OAuthClientMixin, View):
return redirect(reverse('passbook_oauth_client:oauth-client-user', kwargs={
'source_slug': self.source.slug
}))
# User was not authenticated, new user has been created
user = authenticate(source=access.source,
identifier=access.identifier, request=self.request)
messages.success(self.request, _("Successfully authenticated with %(source)s!" % {
'source': self.source.name
}))
return redirect(self.get_login_redirect(source, user, access, True))
return self.handle_login(user, source, access)
class DisconnectView(LoginRequiredMixin, View):

View File

@ -1,2 +1,2 @@
"""passbook oauth_provider Header"""
__version__ = '0.1.31-beta'
__version__ = '0.2.4-beta'

View File

@ -1,4 +1,4 @@
"""passbook OAuth2 IDP Forms"""
"""passbook OAuth2 Provider Forms"""
from django import forms

View File

@ -25,8 +25,6 @@ class OAuth2Provider(Provider, AbstractApplication):
reverse('passbook_oauth_provider:token')),
'userinfo_url': request.build_absolute_uri(
reverse('passbook_api:openid')),
'openid_url': request.build_absolute_uri(
reverse('passbook_oauth_provider:openid-discovery'))
}
class Meta:

View File

@ -20,6 +20,7 @@ OAUTH2_PROVIDER_APPLICATION_MODEL = 'passbook_oauth_provider.OAuth2Provider'
OAUTH2_PROVIDER = {
# this is the list of available scopes
'SCOPES': {
'openid': 'Access OpenID Userinfo',
'openid:userinfo': 'Access OpenID Userinfo',
# 'write': 'Write scope',
# 'groups': 'Access to your groups',

View File

@ -31,19 +31,10 @@
</div>
</div>
</form>
<hr>
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans 'OpenID Configuration URL' %}</label>
<div class="col-sm-9">
<input type="text"class="form-control" readonly value="{{ openid_url }}">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
from django.urls import path
from oauth2_provider import views
from passbook.oauth_provider.views import oauth2, openid
from passbook.oauth_provider.views import oauth2
urlpatterns = [
# Custom OAuth 2 Authorize View
@ -17,9 +17,4 @@ urlpatterns = [
path("token/", views.TokenView.as_view(), name="token"),
path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"),
path("introspect/", views.IntrospectTokenView.as_view(), name="introspect"),
# OpenID-Connect Discovery
path('.well-known/openid-configuration', openid.OpenIDConfigurationView.as_view(),
name='openid-discovery'),
path('.well-known/jwks.json', openid.JSONWebKeyView.as_view(),
name='openid-jwks'),
]

View File

@ -36,6 +36,13 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView):
_application = None
def _inject_response_type(self):
"""Inject response_type into querystring if not set"""
LOGGER.debug("response_type not set, defaulting to 'code'")
querystring = urlencode(self.request.GET)
querystring += '&response_type=code'
return redirect(reverse('passbook_oauth_provider:oauth2-ok-authorize') + '?' + querystring)
def dispatch(self, request, *args, **kwargs):
"""Update OAuth2Provider's skip_authorization state"""
# Get client_id to get provider, so we can update skip_authorization field
@ -50,11 +57,14 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView):
provider.save()
self._application = application
# Check permissions
passing, policy_meaages = self.user_has_access(self._application, request.user)
passing, policy_messages = self.user_has_access(self._application, request.user)
if not passing:
for policy_meaage in policy_meaages:
messages.error(request, policy_meaage)
for policy_message in policy_messages:
messages.error(request, policy_message)
return redirect('passbook_oauth_provider:oauth2-permission-denied')
# Some clients don't pass response_type, so we default to code
if 'response_type' not in request.GET:
return self._inject_response_type()
actual_response = super().dispatch(request, *args, **kwargs)
if actual_response.status_code == 400:
LOGGER.debug(request.GET.get('redirect_uri'))

View File

@ -1,30 +0,0 @@
"""passbook oauth provider OpenID Views"""
from django.http import HttpRequest, JsonResponse
from django.shortcuts import reverse
from django.views.generic import View
class OpenIDConfigurationView(View):
"""Return OpenID Configuration"""
def get(self, request: HttpRequest):
"""Get Response conform to https://openid.net/specs/openid-connect-discovery-1_0.html"""
return JsonResponse({
'issuer': request.build_absolute_uri(reverse('passbook_core:overview')),
'authorization_endpoint': request.build_absolute_uri(
reverse('passbook_oauth_provider:oauth2-authorize')),
'token_endpoint': request.build_absolute_uri(reverse('passbook_oauth_provider:token')),
"jwks_uri": request.build_absolute_uri(reverse('passbook_oauth_provider:openid-jwks')),
"scopes_supported": [
"openid:userinfo",
],
})
class JSONWebKeyView(View):
"""JSON Web Key View"""
def get(self, request: HttpRequest):
"""JSON Webkeys are not implemented yet, hence return an empty object"""
return JsonResponse({})

View File

View File

@ -0,0 +1,31 @@
"""passbook auth oidc provider app config"""
from logging import getLogger
from django.apps import AppConfig
from django.db.utils import InternalError, OperationalError, ProgrammingError
from django.urls import include, path
LOGGER = getLogger(__name__)
class PassbookOIDCProviderConfig(AppConfig):
"""passbook auth oidc provider app config"""
name = 'passbook.oidc_provider'
label = 'passbook_oidc_provider'
verbose_name = 'passbook OIDC Provider'
def ready(self):
try:
from Cryptodome.PublicKey import RSA
from oidc_provider.models import RSAKey
if not RSAKey.objects.exists():
key = RSA.generate(2048)
rsakey = RSAKey(key=key.exportKey('PEM').decode('utf8'))
rsakey.save()
LOGGER.info("Created key")
except (OperationalError, ProgrammingError, InternalError):
pass
from passbook.root import urls
urls.urlpatterns.append(
path('application/oidc/', include('oidc_provider.urls', namespace='oidc_provider')),
)

View File

@ -0,0 +1,38 @@
"""passbook OIDC IDP Forms"""
from django import forms
from oauth2_provider.generators import (generate_client_id,
generate_client_secret)
from oidc_provider.models import Client
from passbook.oidc_provider.models import OpenIDProvider
class OIDCProviderForm(forms.ModelForm):
"""OpenID Client form"""
def __init__(self, *args, **kwargs):
# Correctly load data from 1:1 rel
if 'instance' in kwargs:
kwargs['instance'] = kwargs['instance'].oidc_client
super().__init__(*args, **kwargs)
self.fields['client_id'].initial = generate_client_id()
self.fields['client_secret'].initial = generate_client_secret()
def save(self, *args, **kwargs):
response = super().save(*args, **kwargs)
# Check if openidprovider class instance exists
if not OpenIDProvider.objects.filter(oidc_client=self.instance).exists():
OpenIDProvider.objects.create(oidc_client=self.instance)
return response
class Meta:
model = Client
fields = [
'name', 'client_type', 'client_id', 'client_secret', 'response_types',
'jwt_alg', 'reuse_consent', 'require_consent', '_redirect_uris', '_scope'
]
# exclude = ['owner', 'website_url', 'terms_url', 'contact_email', 'logo', ]
labels = {
'client_secret': "Client Secret"
}

View File

@ -0,0 +1,30 @@
"""OIDC Permission checking"""
from logging import getLogger
from django.contrib import messages
from django.shortcuts import redirect
from passbook.core.models import Application
from passbook.core.policies import PolicyEngine
LOGGER = getLogger(__name__)
def check_permissions(request, user, client):
"""Check permissions, used for
https://django-oidc-provider.readthedocs.io/en/latest/
sections/settings.html#oidc-after-userlogin-hook"""
try:
application = client.openidprovider.application
except Application.DoesNotExist:
return redirect('passbook_oauth_provider:oauth2-permission-denied')
LOGGER.debug("Checking permissions of %s on application %s...", user, application)
policy_engine = PolicyEngine(application.policies.all())
policy_engine.for_user(user).with_request(request).build()
# Check permissions
passing, policy_messages = policy_engine.result
if not passing:
for policy_message in policy_messages:
messages.error(request, policy_message)
return redirect('passbook_oauth_provider:oauth2-permission-denied')
return None

View File

@ -0,0 +1,25 @@
# Generated by Django 2.2.3 on 2019-07-05 12:16
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('oidc_provider', '0026_client_multiple_response_types'),
('passbook_core', '0024_ssologinpolicy'),
]
operations = [
migrations.CreateModel(
name='OpenIDProvider',
fields=[
('provider_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Provider')),
('oidc_client', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client')),
],
bases=('passbook_core.provider',),
),
]

View File

@ -0,0 +1,45 @@
"""oidc models"""
from django.db import models
from django.shortcuts import reverse
from django.utils.translation import gettext as _
from oidc_provider.models import Client
from passbook.core.models import Provider
class OpenIDProvider(Provider):
"""Proxy model for OIDC Client"""
# Since oidc_provider doesn't currently support swappable models
# (https://github.com/juanifioren/django-oidc-provider/pull/305)
# we have a 1:1 relationship, and update oidc_client when the form is saved.
oidc_client = models.OneToOneField(Client, on_delete=models.CASCADE)
form = 'passbook.oidc_provider.forms.OIDCProviderForm'
@property
def name(self):
"""Name property for UI"""
return self.oidc_client.name
def __str__(self):
return "OpenID Connect Provider %s" % self.oidc_client.__str__()
def html_setup_urls(self, request):
"""return template and context modal with URLs for authorize, token, openid-config, etc"""
return "oidc_provider/setup_url_modal.html", {
'provider': self,
'authorize': request.build_absolute_uri(
reverse('oidc_provider:authorize')),
'token': request.build_absolute_uri(
reverse('oidc_provider:token')),
'userinfo': request.build_absolute_uri(
reverse('oidc_provider:userinfo')),
'provider_info': request.build_absolute_uri(
reverse('oidc_provider:provider-info')),
}
class Meta:
verbose_name = _('OpenID Provider')
verbose_name_plural = _('OpenID Providers')

View File

@ -0,0 +1 @@
django-oidc-provider

View File

@ -0,0 +1,7 @@
"""passbook OIDC Provider"""
INSTALLED_APPS = [
'oidc_provider',
]
OIDC_AFTER_USERLOGIN_HOOK = "passbook.oidc_provider.lib.check_permissions"

View File

@ -0,0 +1,70 @@
{% extends "login/base.html" %}
{% load utils %}
{% load i18n %}
{% block title %}
{% title 'Authorize Application' %}
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans 'Authorize Application' %}</h1>
</header>
<form method="POST">
{% csrf_token %}
{% if not error %}
{% csrf_token %}
{% for field in form %}
{% if field.is_hidden %}
{{ field }}
{% endif %}
{% endfor %}
<div class="form-group">
<p class="subtitle">
{% blocktrans with remote=client.name %}
You're about to sign into {{ remote }}
{% endblocktrans %}
</p>
<p>{% trans "Application requires following permissions" %}</p>
<ul>
{% for scope in scopes %}
<li>{{ scope.name }}</li>
{% endfor %}
</ul>
{{ hidden_inputs }}
{{ form.errors }}
{{ form.non_field_errors }}
<p>
{% blocktrans with user=user %}
You are logged in as {{ user }}. Not you?
{% endblocktrans %}
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a>
</p>
<div class="form-group">
<input type="submit" class="btn btn-success btn-disabled btn-lg click-spinner" name="allow" value="{% trans 'Continue' %}">
<a href="{% back %}" class="btn btn-default btn-lg">{% trans "Cancel" %}</a>
</div>
<div class="form-group spinner-hidden hidden">
<div class="spinner"></div>
</div>
</div>
{% else %}
<div class="login-group">
<p class="subtitle">
{% blocktrans with err=error.error %}Error: {{ err }}{% endblocktrans %}
</p>
<p>{{ error.description }}</p>
</div>
{% endif %}
</form>
{% endblock %}
{% block scripts %}
<script>
$('.click-spinner').on('click', function (e) {
$('.spinner-hidden').removeClass('hidden');
$(e.target).addClass('disabled');
})
</script>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More