Compare commits

...

206 Commits

Author SHA1 Message Date
73497a27cc new release: 0.12.6-stable 2020-10-23 18:42:29 +02:00
f3098418f2 core: fix backup task not being registered, add fallback for api to remove info on ImportError
celery only discovers tasks from installed apps, which `lib` is not, hence the schedule didn't trigger it
2020-10-23 18:32:28 +02:00
a5197963b2 build(deps-dev): bump pytest-django from 4.0.0 to 4.1.0 (#293)
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/master/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.0.0...v4.1.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-23 09:38:49 +02:00
e4634bcc78 build(deps): bump boto3 from 1.16.2 to 1.16.3 (#294)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.2 to 1.16.3.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.2...1.16.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-23 08:01:43 +02:00
74da44a6a9 helm: add readme, general cleanup 2020-10-22 17:25:30 +02:00
3324473cd0 new release: 0.12.5-stable 2020-10-22 14:22:32 +02:00
39d8038533 e2e: Fix @retry decorator not truncating database 2020-10-22 14:05:29 +02:00
bbcf58705f lib: add configurable avatars, set to none mode for tests 2020-10-22 14:03:31 +02:00
7b5a0964b2 outposts: handle docker connection error on init 2020-10-22 12:50:06 +02:00
8eca76e464 root: fix docker permission error 2020-10-22 11:54:23 +02:00
fb9ab368f8 root: fix typo in docker-compose 2020-10-22 11:30:53 +02:00
877279b2ee build(deps): bump rollup in /passbook/static/static (#292)
Bumps [rollup](https://github.com/rollup/rollup) from 2.32.0 to 2.32.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.32.0...v2.32.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-22 11:30:03 +02:00
301be4b411 build(deps): bump boto3 from 1.16.1 to 1.16.2 (#291)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.1 to 1.16.2.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.1...1.16.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-22 08:05:29 +02:00
728f527ccb build(deps): bump drf-yasg2 from 1.19.2 to 1.19.3 (#290)
Bumps [drf-yasg2](https://github.com/JoelLefkowitz/drf-yasg) from 1.19.2 to 1.19.3.
- [Release notes](https://github.com/JoelLefkowitz/drf-yasg/releases)
- [Changelog](https://github.com/JoelLefkowitz/drf-yasg/blob/master/docs/changelog.rst)
- [Commits](https://github.com/JoelLefkowitz/drf-yasg/compare/1.19.2...1.19.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-21 09:51:39 +02:00
3f1c790b1d build(deps): bump boto3 from 1.16.0 to 1.16.1 (#289)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.0...1.16.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-21 09:12:13 +02:00
b00573bde2 new release: 0.12.4-stable 2020-10-20 22:31:31 +02:00
aeee3ad7f9 e2e: add @retry decorator to make e2e tests more reliable 2020-10-20 18:51:17 +02:00
ef021495ef flows: revert evaluate_on_call rename for backwards compatibility 2020-10-20 15:41:50 +02:00
061eab4b36 docs: fix keys for example flows 2020-10-20 15:14:41 +02:00
870e01f836 flows: rename re_evaluate_policies to evaluate_on_call, add evaluate_on_plan 2020-10-20 15:06:36 +02:00
e2ca72adf0 stages/user_login: only show successful login message at login stage 2020-10-20 12:11:59 +02:00
395ef43eae policies/expression: fix ip_network not being imported by default 2020-10-20 12:05:56 +02:00
a4cc653757 new release: 0.12.3-stable 2020-10-20 10:24:45 +02:00
db4ff20906 outposts: fix service using incorrect pod selector 2020-10-20 10:18:05 +02:00
1f0fbd33b6 build(deps): bump urllib3 from 1.25.10 to 1.25.11 (#287)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.10 to 1.25.11.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.25.10...1.25.11)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-20 10:17:46 +02:00
5de8d2721e build(deps): bump uvicorn from 0.12.1 to 0.12.2 (#286)
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.12.1 to 0.12.2.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.12.1...0.12.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-20 10:09:37 +02:00
0d65da9a9e build(deps): bump boto3 from 1.15.18 to 1.16.0 (#288)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.18 to 1.16.0.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.18...1.16.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-20 09:34:55 +02:00
4316ee4330 root: implement db backups with monitored task, update docs 2020-10-19 22:17:47 +02:00
2ed9a1dbe3 */tasks: update phrasing 2020-10-19 21:35:31 +02:00
8e03824d20 lib: always set task's UID, even for unexpected errors 2020-10-19 21:30:21 +02:00
754dbdd0e5 outpost: fix logs for kubernetes controller 2020-10-19 21:29:58 +02:00
e13d348315 new release: 0.12.2-stable 2020-10-19 19:36:36 +02:00
169f3ebe5b outposts: fix logger again 2020-10-19 18:52:17 +02:00
f8ad604e85 outposts: add more tests 2020-10-19 17:47:51 +02:00
774b9c8a61 outposts: update kubernetes controller to use pk as identifier instead of name 2020-10-19 17:39:12 +02:00
d8c522233e outposts: fix outpost mangling log output 2020-10-19 16:54:11 +02:00
82d50f7eaa outposts: fix list showing questionmark when only one outpost is registered 2020-10-19 16:34:16 +02:00
1c426c5136 outposts: trigger deployment re-create when selector changes 2020-10-19 16:21:39 +02:00
d6e14cc551 proxy: show version on startup 2020-10-19 16:21:13 +02:00
c3917ebc2e lifecycle: fix formatting 2020-10-19 16:13:45 +02:00
7203bd37a3 outposts: replace migration with string backup handler 2020-10-19 16:04:38 +02:00
597188c7ee lifecycle: fix migration trying to load all classes 2020-10-19 15:55:16 +02:00
ac4c314042 new release: 0.12.1-stable 2020-10-19 15:30:27 +02:00
05866d3544 providers/proxy: fix creation of ingress 2020-10-19 15:06:50 +02:00
6596bc6034 helm: fix permissions for ingresses in networking 2020-10-19 14:55:14 +02:00
c6661ef4d2 lifecycle: add migration to 0.12 which removes old outpost state from cache 2020-10-19 14:35:38 +02:00
386e23dfac core: fix api signature for view_key 2020-10-19 14:35:22 +02:00
5d7220ca70 helm: fix keys for s3 backup 2020-10-19 14:30:44 +02:00
5de0d03acf new release: 0.12.0-stable 2020-10-19 12:15:25 +02:00
b0cc91f343 ci: disable code-ql while django check is broken 2020-10-19 12:15:17 +02:00
029a78f108 build(deps): bump sentry-sdk from 0.19.0 to 0.19.1 (#285)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.19.0 to 0.19.1.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGES.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.19.0...0.19.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 12:02:33 +02:00
3f4a8dc4f6 docs: update example helm values file 2020-10-19 11:31:36 +02:00
32f6ba6302 ci: install python3.8 for code ql 2020-10-19 11:12:57 +02:00
8da0b14f29 docs: update to-012 2020-10-19 11:12:57 +02:00
83eb4aff02 build(deps): bump rollup in /passbook/static/static (#282)
Bumps [rollup](https://github.com/rollup/rollup) from 2.31.0 to 2.32.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.31.0...v2.32.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 11:12:46 +02:00
927d02f591 build(deps): bump celery from 5.0.0 to 5.0.1 (#279)
Bumps [celery](https://github.com/celery/celery) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/celery/celery/releases)
- [Changelog](https://github.com/celery/celery/blob/master/Changelog.rst)
- [Commits](https://github.com/celery/celery/compare/v5.0.0...v5.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 11:12:34 +02:00
d04afcd6d0 build(deps): bump chart.js in /passbook/static/static (#283)
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 2.9.3 to 2.9.4.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v2.9.3...v2.9.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 10:44:05 +02:00
89c6db66fd build(deps): bump drf-yasg2 from 1.18.5 to 1.19.2 (#284)
Bumps [drf-yasg2](https://github.com/JoelLefkowitz/drf-yasg) from 1.18.5 to 1.19.2.
- [Release notes](https://github.com/JoelLefkowitz/drf-yasg/releases)
- [Changelog](https://github.com/JoelLefkowitz/drf-yasg/blob/master/docs/changelog.rst)
- [Commits](https://github.com/JoelLefkowitz/drf-yasg/compare/1.18.5...1.19.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 10:43:52 +02:00
e6ffa65a7e build(deps): bump lxml from 4.5.2 to 4.6.1 (#280)
Bumps [lxml](https://github.com/lxml/lxml) from 4.5.2 to 4.6.1.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.5.2...lxml-4.6.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-19 08:38:58 +02:00
8a2f982a77 admin: fix html of overview 2020-10-19 00:46:31 +02:00
16cf6315e3 docs: add active directory docs 2020-10-18 23:03:46 +02:00
1d85874f41 stages/user_write: don't update session hash after password change when impersonating 2020-10-18 22:58:05 +02:00
ff64182ae8 stages/prompt: skip password equality check when previous checks failed 2020-10-18 22:54:40 +02:00
a9ee67bf2d sources/ldap: adjust order or fields in form 2020-10-18 22:39:13 +02:00
e87d52a76b providers/proxy: implement Ingress diff checking 2020-10-18 21:34:45 +02:00
8b09cf55a2 root: upgrade to traefik 2.3 2020-10-18 18:48:19 +02:00
0203d20759 providers/proxy: add traefik labels to outposts deployed with docker integration 2020-10-18 17:46:20 +02:00
7861e2e0bd provider/proxy: add K8s ingress support 2020-10-18 17:13:44 +02:00
ad29d54bbf outposts: simplify k8s controller add more extensibility 2020-10-18 17:09:02 +02:00
c698ba37d9 core: add ability for users to create tokens 2020-10-18 15:42:16 +02:00
6a53069653 *: make generic template's base parameterised 2020-10-18 15:35:27 +02:00
152b2d863d api: add fallback for proxies < 0.12 which send authorization without b64 2020-10-18 15:14:00 +02:00
ee670d5e19 core: add key field to token for easier rotation 2020-10-18 14:34:22 +02:00
36e095671c proxy: fix WS Authorization Header being sent with the wrong format 2020-10-18 14:04:12 +02:00
1088b947a8 audit: remove duplicate date column, add search 2020-10-17 22:26:35 +02:00
c4a30c50ac stages/consent: add fallback template 2020-10-17 18:18:29 +02:00
2831df45a0 docs: add note about high cpu usage of proxy 2020-10-17 17:06:57 +02:00
ee5bac099f outposts: fix migration not having access to token property 2020-10-17 17:06:08 +02:00
69f7b41044 e2e: use dockercontroller to test proxy 2020-10-17 17:03:10 +02:00
f9cede7b31 proxy: add random reload offset for HA 2020-10-17 16:48:53 +02:00
903cdeaa7f proxy: fix high CPU when websocket not connected 2020-10-17 16:44:53 +02:00
e909e7fa8a outposts: kill container on down 2020-10-17 16:33:38 +02:00
bee38551f3 outposts: fix tokens without identifier not loading in the UI 2020-10-17 16:33:23 +02:00
c0ec6388df outposts: give container time to boot when newly created 2020-10-16 23:38:46 +02:00
8f08836885 outposts: ensure log is also written to stdout 2020-10-16 23:36:59 +02:00
dd0d7e7481 root: switch from drf-yasg to drf_yasg2 and up rest_framework 2020-10-16 23:32:35 +02:00
25d0ac6534 ci: bump pyright version 2020-10-16 22:29:59 +02:00
971713d1aa outposts: call controller.down on outpost pre_delete 2020-10-16 22:27:00 +02:00
5135d828b4 outposts: rename run to up, add down method for deleting 2020-10-16 22:22:15 +02:00
b2c571bf1b helm: add service account for controller, add option to enable it 2020-10-16 21:55:24 +02:00
6b1d30d230 outposts: improve logging from k8s controller 2020-10-16 21:31:55 +02:00
3454760731 *: ensure TaskResult uid is slugified to prevent URL errors 2020-10-16 21:31:12 +02:00
96846220c3 outposts: trigger reconcile on save 2020-10-16 21:08:35 +02:00
a4f5678144 docs: update admin screenshot 2020-10-16 20:26:09 +02:00
a18baa3cb3 static: simplify Message update trigger 2020-10-16 20:13:57 +02:00
dfedd4a7f1 admin: improve overview, re-add links
closes #270
2020-10-16 20:07:56 +02:00
897f64600a static: dynamically add messages instead of replacing 2020-10-16 19:22:44 +02:00
c6eb015d18 static: fix shell card missing on small screens 2020-10-16 19:13:39 +02:00
54088239ab sources/ldap: fix MonitoredTask not using uid 2020-10-16 16:43:40 +02:00
aa9c7a6567 flow: re-add FlowShell as Web Component 2020-10-16 16:36:18 +02:00
6c0c12c90a static: fix messages update only working once 2020-10-16 16:30:38 +02:00
c49b57ad1d stages/email: fix make_msgid call 2020-10-16 16:07:59 +02:00
2339e855bb *: Improve MonitoredTasks' error capture 2020-10-16 16:00:24 +02:00
bdc019c7cf outposts: skip post_save during migrations and unittests 2020-10-16 15:58:28 +02:00
5e2fb6d56e static: replace server-side alerts with webcomponent 2020-10-16 15:26:51 +02:00
3b9524cdfc *: ensure unittests wait on tasks 2020-10-16 14:53:14 +02:00
7154f19668 admin: fix task list not being sorted 2020-10-16 14:53:00 +02:00
8fedd9ec07 stages/email: Implement MonitoredTask, but only for failed emails 2020-10-16 14:31:01 +02:00
4ac87d8739 sources/saml: Implement MonitoredTask 2020-10-16 14:30:44 +02:00
e4f45eba0a policies/reputation: implement MonitoredTask 2020-10-16 14:20:41 +02:00
4b3e0f0f96 sources/ldap: implement MonitoredTask 2020-10-16 14:20:07 +02:00
482da81522 admin: add button to retry task 2020-10-16 14:10:27 +02:00
c5226fd0e8 admin: add API to list tasks and schedule retry 2020-10-16 14:10:11 +02:00
7806cff96f lib: save task's call arguments for manual retry 2020-10-16 13:35:40 +02:00
fa504e4bf9 outposts: pass outpost reference instead of PK, implement TaskResult.uid 2020-10-16 12:54:52 +02:00
86cfb10b9b outposts: implement .run_wuth_logs() which returns logs, add task monitoring 2020-10-16 11:38:49 +02:00
f6b8171624 outposts: improve controller error handling 2020-10-16 11:31:31 +02:00
91ce7f7363 root: implement monitored tasks 2020-10-16 11:28:54 +02:00
17060238f0 build(deps): bump rollup in /passbook/static/static (#278)
Bumps [rollup](https://github.com/rollup/rollup) from 2.30.0 to 2.31.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.30.0...v2.31.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-16 10:01:01 +02:00
c392c2a74b build(deps): bump boto3 from 1.15.16 to 1.15.17 (#277)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.16 to 1.15.17.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.16...1.15.17)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-16 09:39:12 +02:00
8cbaec8ba8 build(deps): bump kubernetes from 11.0.0 to 12.0.0 (#276)
Bumps [kubernetes](https://github.com/kubernetes-client/python) from 11.0.0 to 12.0.0.
- [Release notes](https://github.com/kubernetes-client/python/releases)
- [Changelog](https://github.com/kubernetes-client/python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes-client/python/compare/v11.0.0...v12.0.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-15 09:40:27 +02:00
4750f8c653 ci: fix typo 2020-10-14 20:28:24 +02:00
69d2a1cf3b providers/proxy: add more kubernetes tests 2020-10-14 20:21:47 +02:00
635f6c1ef2 ci: add k3d cluster for kubernetes controller tests 2020-10-14 20:21:36 +02:00
18da7565c2 outposts: improve performance by running related check in worker, fix tokens being left over on outpost delete 2020-10-14 18:41:16 +02:00
45699a1a69 outpost: rewrite kubernetes controller 2020-10-14 17:49:09 +02:00
5556e9f8e7 outposts: always save state, even without version 2020-10-14 12:15:40 +02:00
327bb09dd4 build(deps): bump rollup in /passbook/static/static (#275)
Bumps [rollup](https://github.com/rollup/rollup) from 2.29.0 to 2.30.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.29.0...v2.30.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-14 11:47:30 +02:00
8ca23451c6 outposts: rewrite state logic, use cache to expire old channels, support multiple instances 2020-10-14 11:32:33 +02:00
b99e2b10fe docs: add note about vcenter and AD Join 2020-10-14 11:32:33 +02:00
e966dff1a7 Revert "flows: rewrite shell to webcomponents"
This reverts commit b03a508475.
2020-10-14 11:32:33 +02:00
481fbedef2 build(deps): bump sentry-sdk from 0.18.0 to 0.19.0 (#274)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGES.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.18.0...0.19.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-14 11:27:35 +02:00
d104012eee build(deps-dev): bump colorama from 0.4.3 to 0.4.4 (#273)
Bumps [colorama](https://github.com/tartley/colorama) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/tartley/colorama/releases)
- [Changelog](https://github.com/tartley/colorama/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tartley/colorama/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-13 11:55:05 +02:00
b03a508475 flows: rewrite shell to webcomponents 2020-10-12 17:53:35 +02:00
8ede4b6a13 build(deps): bump boto3 from 1.15.15 to 1.15.16 (#272)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.15 to 1.15.16.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.15...1.15.16)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-12 10:32:52 +02:00
41323afccc docs: add upgrade instructions for 0.11 2020-10-11 23:42:38 +02:00
4a10b4999b core: fix navbar icon not showing in firefox 2020-10-11 23:38:22 +02:00
20ee634cda admin: add buttons to disable and enable users 2020-10-11 21:54:00 +02:00
713025d218 new release: 0.11.0-stable 2020-10-11 19:57:03 +02:00
58ae159835 outposts: disable Kubernetes selection for now 2020-10-11 19:40:22 +02:00
c95efe3cde docs: fix usage of user's groups 2020-10-11 19:29:22 +02:00
b6eb0bf53d providers/oauth2: add missing property_mapping template 2020-10-11 19:29:13 +02:00
610b6c7f70 policies: add PolicyAccessView, which does complete access checking 2020-10-11 19:26:20 +02:00
1ea2d99ff2 ci: run rollup build 2020-10-09 11:33:02 +02:00
67be43679c build(deps): bump boto3 from 1.15.14 to 1.15.15 (#268)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.14 to 1.15.15.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.14...1.15.15)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-09 11:32:02 +02:00
fd42389bd5 build(deps-dev): bump rollup-plugin-sourcemaps (#267)
Bumps [rollup-plugin-sourcemaps](https://github.com/maxdavidson/rollup-plugin-sourcemaps) from 0.6.2 to 0.6.3.
- [Release notes](https://github.com/maxdavidson/rollup-plugin-sourcemaps/releases)
- [Changelog](https://github.com/maxdavidson/rollup-plugin-sourcemaps/blob/master/CHANGELOG.md)
- [Commits](https://github.com/maxdavidson/rollup-plugin-sourcemaps/compare/v0.6.2...v0.6.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-09 11:31:47 +02:00
71b1df2fec build(deps): bump rollup in /passbook/static/static (#269)
Bumps [rollup](https://github.com/rollup/rollup) from 2.28.2 to 2.29.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.28.2...v2.29.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-09 09:08:39 +02:00
7a3122f25c docs: add reverse-proxy example config, fix outpost docker-compose 2020-10-08 09:27:28 +02:00
63041d788b core: update application list API to show applications accessible by policy 2020-10-08 09:26:50 +02:00
bfc1bae0bb build(deps): bump boto3 from 1.15.13 to 1.15.14 (#266)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.13 to 1.15.14.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.13...1.15.14)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-08 09:18:06 +02:00
8ab7f7fcbb core: make passbook title navigate to overview
closes #264
2020-10-07 19:27:20 +02:00
c1eb8317f7 providers/proxy: update phrasing for basic_auth_* attributes
closes #265
2020-10-07 19:27:06 +02:00
7a578e5e83 admin: dont show check when outpost hasnt connected
closes #263
2020-10-07 19:19:25 +02:00
b10912d8ba proxy: cleanup addHeadersForProxying 2020-10-07 18:02:57 +02:00
ef24b1cde2 docs: update screenshots 2020-10-07 18:02:26 +02:00
26cacc2a06 build(deps): bump boto3 from 1.15.12 to 1.15.13 (#259)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.12 to 1.15.13.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.12...1.15.13)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-07 11:54:32 +02:00
ca0e89c799 build(deps): bump @patternfly/patternfly in /passbook/static/static (#261)
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 4.42.2 to 4.50.4.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/patternfly/patternfly/compare/prerelease-v4.42.2...prerelease-v4.50.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-07 09:10:47 +02:00
17950119ad build(deps): bump django-otp from 1.0.0 to 1.0.1 (#260)
Bumps [django-otp](https://github.com/django-otp/django-otp) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/django-otp/django-otp/releases)
- [Changelog](https://github.com/django-otp/django-otp/blob/master/CHANGES.rst)
- [Commits](https://github.com/django-otp/django-otp/compare/v1.0.0...v1.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-07 09:10:25 +02:00
876618c1ec build(deps): bump @fortawesome/fontawesome-free (#258)
Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 5.15.0 to 5.15.1.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/master/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/5.15.0...5.15.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-06 10:19:05 +02:00
2293ab69b9 build(deps): bump boto3 from 1.15.11 to 1.15.12 (#257)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.11 to 1.15.12.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.11...1.15.12)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-06 09:55:56 +02:00
9df00e09a4 root: fix static docker's rollup build 2020-10-06 00:06:53 +02:00
cf6ce9c915 audit: optimize eventaction, 2020-10-05 23:43:56 +02:00
3b61191614 outpost: enable docker controller 2020-10-05 23:11:44 +02:00
9954eeac86 proxy: fix broken docker healthcheck 2020-10-05 22:53:26 +02:00
ac88bd5d44 core: hide token value by default 2020-10-05 22:40:30 +02:00
2406a619df root: fix lockfile in dockerfile 2020-10-05 22:37:53 +02:00
63087c9393 root: run backups in server contianer 2020-10-05 22:30:13 +02:00
da9aaf69df admin: add metrics and charts 2020-10-05 22:10:03 +02:00
ae125dd1f0 root: fix missing docker dependency 2020-10-04 15:04:07 +02:00
f636595230 static: add fetch-fill-slot to load data for admin interface 2020-10-04 13:09:03 +02:00
d506e8f1a3 outposts: implement docker controller 2020-10-04 00:41:12 +02:00
d3a96ac7aa outposts: load token async 2020-10-04 00:29:18 +02:00
189b0ec324 admin: expose info as API 2020-10-04 00:28:58 +02:00
c5a6b4961f core: Add Token identifier as sudo-primary key 2020-10-04 00:28:43 +02:00
b590589324 root: add base template for api 2020-10-03 23:20:33 +02:00
9fb1ac98ec Backup/Restore (#256)
* lifecycle: move s3 backup settings to s3 name

* providers/oauth2: fix for alerting for missing certificatekeypair

* lifecycle: add backup commands

see #252

* lifecycle: install postgres-client for 11 and 12

* root: migrate to DBBACKUP_STORAGE_OPTIONS, add region setting

* lifecycle: auto-clean last backups

* helm: add s3 region parameter, add cronjob for backups

* docs: add backup docs

* root: remove backup scheduled task for now
2020-10-03 20:36:36 +02:00
195d8fe71f core: move name field to base Provider 2020-10-03 20:05:16 +02:00
b0602a3215 admin: implement search for all views
see #253
2020-10-03 19:32:01 +02:00
0150a5c58c admin: add SearchListMixin mixin and partial template 2020-10-03 19:05:20 +02:00
b35d27c83e admin: fix pagination template, ensure template is placed correctly in footer 2020-10-03 17:50:17 +02:00
801bb90806 root: lock pyright version 2020-10-03 15:34:53 +02:00
55a83abb26 *: remove deprecated providing_args 2020-10-02 11:18:14 +02:00
c09b4e9713 e2e: fix invalid proxy image being pulled 2020-10-02 10:30:56 +02:00
247015e955 stages/otp_*: Remove duplicate validation for OTP Codes 2020-10-02 10:30:43 +02:00
fe3634be64 build(deps): bump django from 3.1.1 to 3.1.2 (#255)
Bumps [django](https://github.com/django/django) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.1...3.1.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-02 09:50:18 +02:00
ead20b03aa build(deps): bump boto3 from 1.15.9 to 1.15.10 (#254)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.9 to 1.15.10.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.9...1.15.10)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-02 09:32:39 +02:00
932a475af7 docs: add notice about vcenter launch URL 2020-10-01 20:01:58 +02:00
e9a1a18ba3 providers/oauth2: ensure that when rs256 is selected, a certificate key pair is selected 2020-10-01 20:01:45 +02:00
6cd9edd38a providers/oauth2: add missing token_validity field to Forms and API 2020-10-01 20:01:28 +02:00
9b5f9167cd root: always enable dbbackup 2020-10-01 13:41:40 +02:00
1f30bcd335 root: lock postgresql to 12 in docker-compose 2020-10-01 10:42:38 +02:00
94eaeb5a60 new release: 0.10.9-stable 2020-10-01 10:24:16 +02:00
a5420fe019 providers/saml: lowercase acs URLs before checking
closes #249
2020-10-01 10:04:20 +02:00
2e1849a732 providers/oauth2: lowercase all uris before checking redirect URI
see #249
2020-10-01 10:00:44 +02:00
4039e96803 build(deps): bump boto3 from 1.15.8 to 1.15.9 (#250)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.8 to 1.15.9.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.8...1.15.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-01 09:50:04 +02:00
8f585eca70 stages/identification: replace buggy FilteredSelectMultiple with ArrayFieldSelectMultiple 2020-09-30 23:58:01 +02:00
516455f482 stages/identification: add case_insensitive_matching
closes #248
2020-09-30 23:48:53 +02:00
719099a5af ci: remove deploy as --recreate is deprecated 2020-09-30 23:00:05 +02:00
7f74d32253 docs: update phrasing on tautulli docs 2020-09-30 21:19:04 +02:00
525d271535 *: apply new black styling 2020-09-30 19:34:22 +02:00
9ef39f1e04 root: update black version 2020-09-30 16:39:15 +02:00
9099dc5713 root: fix missing dependencies of uvicorn 2020-09-30 16:11:28 +02:00
c3c525a3f0 lib: re-add Websockets error 2020-09-30 15:55:59 +02:00
e699dfe88c ci: fix CD not working correctly 2020-09-30 15:41:04 +02:00
c0b334eb02 lib: ignore ChannelFull error 2020-09-30 15:40:54 +02:00
815ad26b91 root: add hard uvloop and httptools dependency 2020-09-30 15:37:15 +02:00
304 changed files with 6981 additions and 2333 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.10.8-stable current_version = 0.12.6-stable
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

@ -6,12 +6,16 @@ updates:
interval: daily interval: daily
time: "04:00" time: "04:00"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: npm - package-ecosystem: npm
directory: "/passbook/static/static" directory: "/passbook/static/static"
schedule: schedule:
interval: daily interval: daily
time: "04:00" time: "04:00"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: pip - package-ecosystem: pip
directory: "/" directory: "/"
schedule: schedule:

View File

@ -1,54 +0,0 @@
name: "CodeQL"
on:
push:
branches: [master, admin-more-info, ci-deploy-dev, gh-pages, provider-saml-v2]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 20 * * 2'
jobs:
analyse:
name: Analyse
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image - name: Building Docker Image
run: docker build run: docker build
--no-cache --no-cache
-t beryju/passbook:0.10.8-stable -t beryju/passbook:0.12.6-stable
-t beryju/passbook:latest -t beryju/passbook:latest
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.10.8-stable run: docker push beryju/passbook:0.12.6-stable
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest run: docker push beryju/passbook:latest
build-proxy: build-proxy:
@ -48,11 +48,11 @@ jobs:
cd proxy cd proxy
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/passbook-proxy:0.10.8-stable \ -t beryju/passbook-proxy:0.12.6-stable \
-t beryju/passbook-proxy:latest \ -t beryju/passbook-proxy:latest \
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-proxy:0.10.8-stable run: docker push beryju/passbook-proxy:0.12.6-stable
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-proxy:latest run: docker push beryju/passbook-proxy:latest
build-static: build-static:
@ -77,11 +77,11 @@ jobs:
run: docker build run: docker build
--no-cache --no-cache
--network=$(docker network ls | grep github | awk '{print $1}') --network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.10.8-stable -t beryju/passbook-static:0.12.6-stable
-t beryju/passbook-static:latest -t beryju/passbook-static:latest
-f static.Dockerfile . -f static.Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.10.8-stable run: docker push beryju/passbook-static:0.12.6-stable
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest run: docker push beryju/passbook-static:latest
test-release: test-release:
@ -114,5 +114,5 @@ jobs:
SENTRY_PROJECT: passbook SENTRY_PROJECT: passbook
SENTRY_URL: https://sentry.beryju.org SENTRY_URL: https://sentry.beryju.org
with: with:
tagName: 0.10.8-stable tagName: 0.12.6-stable
environment: beryjuorg-prod environment: beryjuorg-prod

View File

@ -16,12 +16,23 @@ COPY --from=locker /app/requirements.txt /
COPY --from=locker /app/requirements-dev.txt / COPY --from=locker /app/requirements-dev.txt /
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends postgresql-client-11 build-essential && \ apt-get install -y --no-install-recommends curl ca-certificates gnupg && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
apt-get update && \
apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential && \
apt-get clean && \ apt-get clean && \
pip install -r /requirements.txt --no-cache-dir && \ pip install -r /requirements.txt --no-cache-dir && \
apt-get remove --purge -y build-essential && \ apt-get remove --purge -y build-essential && \
apt-get autoremove --purge -y && \ apt-get autoremove --purge -y && \
adduser --system --no-create-home --uid 1000 --group --home /passbook passbook # This is quite hacky, but docker has no guaranteed Group ID
# we could instead check for the GID of the socket and add the user dynamically,
# but then we have to drop permmissions later
groupadd -g 998 docker_998 && \
groupadd -g 999 docker_999 && \
adduser --system --no-create-home --uid 1000 --group --home /passbook passbook && \
usermod -a -G docker_998 passbook && \
usermod -a -G docker_999 passbook
COPY ./passbook/ /passbook COPY ./passbook/ /passbook
COPY ./manage.py / COPY ./manage.py /

View File

@ -11,7 +11,7 @@ lint-fix:
black passbook e2e lifecycle black passbook e2e lifecycle
lint: lint:
pyright pyright e2e lifecycle pyright passbook e2e lifecycle
bandit -r passbook e2e lifecycle bandit -r passbook e2e lifecycle
pylint passbook e2e lifecycle pylint passbook e2e lifecycle
prospector prospector

10
Pipfile
View File

@ -17,10 +17,10 @@ django-otp = "*"
django-prometheus = "*" django-prometheus = "*"
django-recaptcha = "*" django-recaptcha = "*"
django-redis = "*" django-redis = "*"
djangorestframework = "==3.11.1" djangorestframework = "*"
django-storages = "*" django-storages = "*"
djangorestframework-guardian = "*" djangorestframework-guardian = "*"
drf-yasg = "*" drf_yasg2 = "*"
facebook-sdk = "*" facebook-sdk = "*"
ldap3 = "*" ldap3 = "*"
lxml = "*" lxml = "*"
@ -28,7 +28,7 @@ packaging = "*"
psycopg2-binary = "*" psycopg2-binary = "*"
pycryptodome = "*" pycryptodome = "*"
pyjwkest = "*" pyjwkest = "*"
uvicorn = "*" uvicorn = {extras = ["standard"],version = "*"}
gunicorn = "*" gunicorn = "*"
pyyaml = "*" pyyaml = "*"
qrcode = "*" qrcode = "*"
@ -43,6 +43,7 @@ dacite = "*"
channels = "*" channels = "*"
channels-redis = "*" channels-redis = "*"
kubernetes = "*" kubernetes = "*"
docker = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"
@ -50,12 +51,11 @@ python_version = "3.8"
[dev-packages] [dev-packages]
autopep8 = "*" autopep8 = "*"
bandit = "*" bandit = "*"
black = "==19.10b0" black = "==20.8b1"
bumpversion = "*" bumpversion = "*"
colorama = "*" colorama = "*"
coverage = "*" coverage = "*"
django-debug-toolbar = "*" django-debug-toolbar = "*"
docker = "*"
pylint = "*" pylint = "*"
pylint-django = "*" pylint-django = "*"
selenium = "*" selenium = "*"

566
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "39e0a747699dc7e528a215395cc505b380e40e6bd0295fdf4c373a871a9bde96" "sha256": "d1a9883d864e25f18e34b298b72b58db333a037571c7a20cefb7ba7a4037a434"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -16,24 +16,6 @@
] ]
}, },
"default": { "default": {
"aiohttp": {
"hashes": [
"sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e",
"sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326",
"sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a",
"sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654",
"sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a",
"sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4",
"sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17",
"sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec",
"sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd",
"sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48",
"sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59",
"sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"
],
"markers": "python_version >= '3.6'",
"version": "==3.6.2"
},
"aioredis": { "aioredis": {
"hashes": [ "hashes": [
"sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a", "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
@ -92,18 +74,18 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:348cddfd56be6c8b759544f99d20d633cc6a65d346651700ad8ac93a5214c032", "sha256:270ac22a66ce3313e908946193df6e0fb3e81cdf60f5113d62da1d8991b75030",
"sha256:c4ccd1f260660603f965bcc145de87e09dd1229040784fe119cd08caeb00dbe9" "sha256:e2857738affb394bbe96473de2ed01331685d6e313bb1a3328fd5f47841429cc"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.15.8" "version": "==1.16.3"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:07b399997d8050d3ed1150d4d657b46558999f75246eb5b02cee78b9798b3bd5", "sha256:4ea4c74d244c1b4701387fd1abe6a5e1833dc621c6d39f8888f0bfa95ddd82f5",
"sha256:53a778e6b715ad2ae39bf98e088962e8d524133fb458d83f080964254adc9885" "sha256:f5084376a8519332a200737f5cd80e87f47868b7da4d57fc192397670e0af022"
], ],
"version": "==1.18.8" "version": "==1.19.3"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -114,11 +96,11 @@
}, },
"celery": { "celery": {
"hashes": [ "hashes": [
"sha256:313930fddde703d8e37029a304bf91429cd11aeef63c57de6daca9d958e1f255", "sha256:7aa4ee46ed318bc177900ae7c01500354aee62d723255b0925db0754bcd4d390",
"sha256:72138dc3887f68dc58e1a2397e477256f80f1894c69fa4337f8ed70be460375b" "sha256:e3e8956d74af986b1e9770e0a294338b259618bf70283d6157416328e50c2bd6"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.0.0" "version": "==5.0.1"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -281,11 +263,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:59c8125ca873ed3bdae9c12b146fbbd6ed8d0f743e4cf5f5817af50c51f1fc2f", "sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc",
"sha256:b5fbb818e751f660fa2d576d9f40c34a4c615c8b48dd383f5216e609f383371f" "sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.1.1" "version": "==3.1.2"
}, },
"django-cors-middleware": { "django-cors-middleware": {
"hashes": [ "hashes": [
@ -328,11 +310,11 @@
}, },
"django-otp": { "django-otp": {
"hashes": [ "hashes": [
"sha256:0c9edbb3f4abc9ac6e43daf0a9e0e293e99ad917641cf8d7dbc49d613bcb5cd4", "sha256:2fb1c8dbd7e7ae76a65b63d89d3d8c3e1105a48bc29830b81c6e417a89380658",
"sha256:ace831f3a0f2c2267e4f7219c78deeb3b41c2dc8ae44b03daebb4fb85dabeb43" "sha256:fef1f2de9a52bc37e16211b98b4323e5b34fa24739116fbe3d1ff018c17ebea8"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.0" "version": "==1.0.1"
}, },
"django-prometheus": { "django-prometheus": {
"hashes": [ "hashes": [
@ -367,11 +349,11 @@
}, },
"djangorestframework": { "djangorestframework": {
"hashes": [ "hashes": [
"sha256:6dd02d5a4bd2516fb93f80360673bf540c3b6641fec8766b1da2870a5aa00b32", "sha256:5c5071fcbad6dce16f566d492015c829ddb0df42965d488b878594aabc3aed21",
"sha256:8b1ac62c581dbc5799b03e535854b92fc4053ecfe74bad3f9c05782063d4196b" "sha256:d54452aedebb4b650254ca092f9f4f5df947cb1de6ab245d817b08b4f4156249"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.11.1" "version": "==3.12.1"
}, },
"djangorestframework-guardian": { "djangorestframework-guardian": {
"hashes": [ "hashes": [
@ -381,13 +363,21 @@
"index": "pypi", "index": "pypi",
"version": "==0.3.0" "version": "==0.3.0"
}, },
"drf-yasg": { "docker": {
"hashes": [ "hashes": [
"sha256:5572e9d5baab9f6b49318169df9789f7399d0e3c7bdac8fdb8dfccf1d5d2b1ca", "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828",
"sha256:7d7af27ad16e18507e9392b2afd6b218fbffc432ec8dbea053099a2241e184ff" "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.1" "version": "==4.3.1"
},
"drf-yasg2": {
"hashes": [
"sha256:65826bf19e5222d38b84380468303c8c389d0b9e2335ee6efa4151ba87ca0a3f",
"sha256:6c662de6e0ffd4f74c49c06a88b8a9d1eb4bc9d7bfe82dac9f80a51a23cacecb"
],
"index": "pypi",
"version": "==1.19.3"
}, },
"eight": { "eight": {
"hashes": [ "hashes": [
@ -412,10 +402,10 @@
}, },
"google-auth": { "google-auth": {
"hashes": [ "hashes": [
"sha256:a73e6fb6d232ed1293ef9a5301e6f8aada7880d19c65d7f63e130dc50ec05593", "sha256:712dd7d140a9a1ea218e5688c7fcb04af71b431a29ec9ce433e384c60e387b98",
"sha256:e86e72142d939a8d90a772947268aacc127ab7a1d1d6f3e0fecca7a8d74d8257" "sha256:9c0f71789438d703f77b94aad4ea545afaec9a65f10e6cc1bc8b89ce242244bb"
], ],
"version": "==1.22.0" "version": "==1.22.1"
}, },
"gunicorn": { "gunicorn": {
"hashes": [ "hashes": [
@ -427,10 +417,10 @@
}, },
"h11": { "h11": {
"hashes": [ "hashes": [
"sha256:311dc5478c2568cc07262e0381cdfc5b9c6ba19775905736c87e81ae6662b9fd", "sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab",
"sha256:9eecfbafc980976dbff26a01dd3487644dd5d00f8038584451fc64a660f7c502" "sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"
], ],
"version": "==0.10.0" "version": "==0.11.0"
}, },
"hiredis": { "hiredis": {
"hashes": [ "hashes": [
@ -483,6 +473,23 @@
], ],
"version": "==1.1.0" "version": "==1.1.0"
}, },
"httptools": {
"hashes": [
"sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be",
"sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d",
"sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce",
"sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2",
"sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6",
"sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f",
"sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009",
"sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce",
"sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a",
"sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c",
"sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4",
"sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437"
],
"version": "==0.1.1"
},
"hyperlink": { "hyperlink": {
"hashes": [ "hashes": [
"sha256:47fcc7cd339c6cb2444463ec3277bdcfe142c8b1daf2160bdd52248deec815af", "sha256:47fcc7cd339c6cb2444463ec3277bdcfe142c8b1daf2160bdd52248deec815af",
@ -548,11 +555,11 @@
}, },
"kubernetes": { "kubernetes": {
"hashes": [ "hashes": [
"sha256:1a2472f8b01bc6aa87e3a34781f859bded5a5c8ff791a53d889a8bd6cc550430", "sha256:72f095a1cd593401ff26b3b8d71749340394ca6d8413770ea28ce18efd5bcf4c",
"sha256:4af81201520977139a143f96123fb789fa351879df37f122916b9b6ed050bbaf" "sha256:9a339a32d6c79e6461cb6050c3662cb4e33058b508d8d34ee5d5206add395828"
], ],
"index": "pypi", "index": "pypi",
"version": "==11.0.0" "version": "==12.0.0"
}, },
"ldap3": { "ldap3": {
"hashes": [ "hashes": [
@ -564,40 +571,45 @@
}, },
"lxml": { "lxml": {
"hashes": [ "hashes": [
"sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f", "sha256:0e89f5d422988c65e6936e4ec0fe54d6f73f3128c80eb7ecc3b87f595523607b",
"sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730", "sha256:189ad47203e846a7a4951c17694d845b6ade7917c47c64b29b86526eefc3adf5",
"sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f", "sha256:1d87936cb5801c557f3e981c9c193861264c01209cb3ad0964a16310ca1b3301",
"sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1", "sha256:211b3bcf5da70c2d4b84d09232534ad1d78320762e2c59dedc73bf01cb1fc45b",
"sha256:1fa21263c3aba2b76fd7c45713d4428dbcc7644d73dcf0650e9d344e433741b3", "sha256:2358809cc64394617f2719147a58ae26dac9e21bae772b45cfb80baa26bfca5d",
"sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7", "sha256:23c83112b4dada0b75789d73f949dbb4e8f29a0a3511647024a398ebd023347b",
"sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a", "sha256:24e811118aab6abe3ce23ff0d7d38932329c513f9cef849d3ee88b0f848f2aa9",
"sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe", "sha256:2d5896ddf5389560257bbe89317ca7bcb4e54a02b53a3e572e1ce4226512b51b",
"sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1", "sha256:2d6571c48328be4304aee031d2d5046cbc8aed5740c654575613c5a4f5a11311",
"sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e", "sha256:2e311a10f3e85250910a615fe194839a04a0f6bc4e8e5bb5cac221344e3a7891",
"sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d", "sha256:302160eb6e9764168e01d8c9ec6becddeb87776e81d3fcb0d97954dd51d48e0a",
"sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20", "sha256:3a7a380bfecc551cfd67d6e8ad9faa91289173bdf12e9cfafbd2bdec0d7b1ec1",
"sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae", "sha256:3d9b2b72eb0dbbdb0e276403873ecfae870599c83ba22cadff2db58541e72856",
"sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5", "sha256:475325e037fdf068e0c2140b818518cf6bc4aa72435c407a798b2db9f8e90810",
"sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba", "sha256:4b7572145054330c8e324a72d808c8c8fbe12be33368db28c39a255ad5f7fb51",
"sha256:8f0ec6b9b3832e0bd1d57af41f9238ea7709bbd7271f639024f2fc9d3bb01293", "sha256:4fff34721b628cce9eb4538cf9a73d02e0f3da4f35a515773cce6f5fe413b360",
"sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a", "sha256:56eff8c6fb7bc4bcca395fdff494c52712b7a57486e4fbde34c31bb9da4c6cc4",
"sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6", "sha256:573b2f5496c7e9f4985de70b9bbb4719ffd293d5565513e04ac20e42e6e5583f",
"sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88", "sha256:7ecaef52fd9b9535ae5f01a1dd2651f6608e4ec9dc136fc4dfe7ebe3c3ddb230",
"sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed", "sha256:803a80d72d1f693aa448566be46ffd70882d1ad8fc689a2e22afe63035eb998a",
"sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843", "sha256:8862d1c2c020cb7a03b421a9a7b4fe046a208db30994fc8ff68c627a7915987f",
"sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443", "sha256:9b06690224258db5cd39a84e993882a6874676f5de582da57f3df3a82ead9174",
"sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0", "sha256:a71400b90b3599eb7bf241f947932e18a066907bf84617d80817998cee81e4bf",
"sha256:c9d317efde4bafbc1561509bfa8a23c5cab66c44d49ab5b63ff690f5159b2304", "sha256:bb252f802f91f59767dcc559744e91efa9df532240a502befd874b54571417bd",
"sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258", "sha256:be1ebf9cc25ab5399501c9046a7dcdaa9e911802ed0e12b7d620cd4bbf0518b3",
"sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6", "sha256:be7c65e34d1b50ab7093b90427cbc488260e4b3a38ef2435d65b62e9fa3d798a",
"sha256:cfd7c5dd3c35c19cec59c63df9571c67c6d6e5c92e0fe63517920e97f61106d1", "sha256:c0dac835c1a22621ffa5e5f999d57359c790c52bbd1c687fe514ae6924f65ef5",
"sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481", "sha256:c152b2e93b639d1f36ec5a8ca24cde4a8eefb2b6b83668fcd8e83a67badcb367",
"sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef", "sha256:d182eada8ea0de61a45a526aa0ae4bcd222f9673424e65315c35820291ff299c",
"sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd", "sha256:d18331ea905a41ae71596502bd4c9a2998902328bbabd29e3d0f5f8569fabad1",
"sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee" "sha256:d20d32cbb31d731def4b1502294ca2ee99f9249b63bc80e03e67e8f8e126dea8",
"sha256:d4ad7fd3269281cb471ad6c7bafca372e69789540d16e3755dd717e9e5c9d82f",
"sha256:d6f8c23f65a4bfe4300b85f1f40f6c32569822d08901db3b6454ab785d9117cc",
"sha256:d84d741c6e35c9f3e7406cb7c4c2e08474c2a6441d59322a00dcae65aac6315d",
"sha256:e65c221b2115a91035b55a593b6eb94aa1206fa3ab374f47c6dc10d364583ff9",
"sha256:f98b6f256be6cec8dd308a8563976ddaff0bdc18b730720f6f4bee927ffe926f"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.5.2" "version": "==4.6.1"
}, },
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
@ -660,28 +672,6 @@
], ],
"version": "==1.0.0" "version": "==1.0.0"
}, },
"multidict": {
"hashes": [
"sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a",
"sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000",
"sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2",
"sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507",
"sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5",
"sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7",
"sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d",
"sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463",
"sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19",
"sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3",
"sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b",
"sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c",
"sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87",
"sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7",
"sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430",
"sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255",
"sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"
],
"version": "==4.7.6"
},
"oauthlib": { "oauthlib": {
"hashes": [ "hashes": [
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
@ -706,10 +696,10 @@
}, },
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:822f4605f28f7d2ba6b0b09a31e25e140871e96364d1d377667b547bb3bf4489", "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
"sha256:83074ee28ad4ba6af190593d4d4c607ff525272a504eb159199b6dd9f950c950" "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
], ],
"version": "==3.0.7" "version": "==3.0.8"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
@ -723,6 +713,7 @@
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
"sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
"sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83",
"sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
"sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
"sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
@ -892,6 +883,13 @@
], ],
"version": "==2.8.1" "version": "==2.8.1"
}, },
"python-dotenv": {
"hashes": [
"sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d",
"sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"
],
"version": "==0.14.0"
},
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
@ -982,6 +980,8 @@
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c", "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988", "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f", "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
"sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
"sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1", "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2", "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f" "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
@ -998,11 +998,11 @@
}, },
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
"sha256:1d91a0059d2d8bb980bec169578035c2f2d4b93cd8a4fb5b85c81904d33e221a", "sha256:0eea248408d36e8e7037c7b73827bea20b13a4375bf1719c406cae6fcbc094e3",
"sha256:6222cf623e404c3e62b8e0e81c6db866ac2d12a663b7c1f7963350e3f397522a" "sha256:5cf36eb6b1dc62d55f3c64289792cbaebc8ffa5a9da14474f49b46d20caa7fc8"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.18.0" "version": "==0.19.1"
}, },
"service-identity": { "service-identity": {
"hashes": [ "hashes": [
@ -1029,10 +1029,10 @@
}, },
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
], ],
"version": "==0.3.1" "version": "==0.4.1"
}, },
"structlog": { "structlog": {
"hashes": [ "hashes": [
@ -1100,20 +1100,37 @@
"secure" "secure"
], ],
"hashes": [ "hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
], ],
"index": "pypi", "index": "pypi",
"markers": null, "markers": null,
"version": "==1.25.10" "version": "==1.25.11"
}, },
"uvicorn": { "uvicorn": {
"extras": [
"standard"
],
"hashes": [ "hashes": [
"sha256:9a8f3501d977dedf77a540a0ec3cfadf409fe48eafca2c100d45d843ac62bc7b", "sha256:8ff7495c74b8286a341526ff9efa3988ebab9a4b2f561c7438c3cb420992d7dd",
"sha256:fbe9d1b764bc1f4599e1f150a0974feea0fd6380bec889c0d907ebd0a2e896a7" "sha256:e5dbed4a8a44c7b04376021021d63798d6a7bcfae9c654a0b153577b93854fba"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.12.0" "version": "==0.12.2"
},
"uvloop": {
"hashes": [
"sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd",
"sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e",
"sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09",
"sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726",
"sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891",
"sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7",
"sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5",
"sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95",
"sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"
],
"version": "==0.14.0"
}, },
"vine": { "vine": {
"hashes": [ "hashes": [
@ -1122,6 +1139,13 @@
], ],
"version": "==5.0.0" "version": "==5.0.0"
}, },
"watchgod": {
"hashes": [
"sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a",
"sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858"
],
"version": "==0.6"
},
"wcwidth": { "wcwidth": {
"hashes": [ "hashes": [
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
@ -1136,72 +1160,77 @@
], ],
"version": "==0.57.0" "version": "==0.57.0"
}, },
"yarl": { "websockets": {
"hashes": [ "hashes": [
"sha256:04a54f126a0732af75e5edc9addeaa2113e2ca7c6fce8974a63549a70a25e50e", "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5",
"sha256:3cc860d72ed989f3b1f3abbd6ecf38e412de722fb38b8f1b1a086315cf0d69c5", "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5",
"sha256:5d84cc36981eb5a8533be79d6c43454c8e6a39ee3118ceaadbd3c029ab2ee580", "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308",
"sha256:5e447e7f3780f44f890360ea973418025e8c0cdcd7d6a1b221d952600fd945dc", "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb",
"sha256:61d3ea3c175fe45f1498af868879c6ffeb989d4143ac542163c45538ba5ec21b", "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a",
"sha256:67c5ea0970da882eaf9efcf65b66792557c526f8e55f752194eff8ec722c75c2", "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c",
"sha256:6f6898429ec3c4cfbef12907047136fd7b9e81a6ee9f105b45505e633427330a", "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170",
"sha256:7ce35944e8e61927a8f4eb78f5bc5d1e6da6d40eadd77e3f79d4e9399e263921", "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422",
"sha256:b7c199d2cbaf892ba0f91ed36d12ff41ecd0dde46cbf64ff4bfe997a3ebc925e", "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8",
"sha256:c15d71a640fb1f8e98a1423f9c64d7f1f6a3a168f803042eaf3a5b5022fde0c1", "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485",
"sha256:c22607421f49c0cb6ff3ed593a49b6a99c6ffdeaaa6c944cdda83c2393c8864d", "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f",
"sha256:c604998ab8115db802cc55cb1b91619b2831a6128a62ca7eea577fc8ea4d3131", "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8",
"sha256:d088ea9319e49273f25b1c96a3763bf19a882cff774d1792ae6fba34bd40550a", "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc",
"sha256:db9eb8307219d7e09b33bcb43287222ef35cbcf1586ba9472b0a4b833666ada1", "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779",
"sha256:e31fef4e7b68184545c3d68baec7074532e077bd1906b040ecfba659737df188", "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989",
"sha256:e32f0fb443afcfe7f01f95172b66f279938fbc6bdaebe294b0ff6747fb6db020", "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1",
"sha256:fcbe419805c9b20db9a51d33b942feddbf6e7fb468cb20686fd7089d4164c12a" "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092",
"sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824",
"sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d",
"sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55",
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
], ],
"version": "==1.6.0" "version": "==8.1"
}, },
"zope.interface": { "zope.interface": {
"hashes": [ "hashes": [
"sha256:0103cba5ed09f27d2e3de7e48bb320338592e2fabc5ce1432cf33808eb2dfd8b", "sha256:040f833694496065147e76581c0bf32b229a8b8c5eda120a0293afb008222387",
"sha256:14415d6979356629f1c386c8c4249b4d0082f2ea7f75871ebad2e29584bd16c5", "sha256:11198b44e4a3d8c7a80cc20bbdd65522258a4d82fe467cd310c9fcce8ffe2ed2",
"sha256:1ae4693ccee94c6e0c88a4568fb3b34af8871c60f5ba30cf9f94977ed0e53ddd", "sha256:121a9dccfe0c34be9c33b2c28225f0284f9b8e090580ffdff26c38fa16c7ffe1",
"sha256:1b87ed2dc05cb835138f6a6e3595593fea3564d712cb2eb2de963a41fd35758c", "sha256:15f3082575e7e19581a80b866664f843719b647a7f7189c811ba7f9ab3309f83",
"sha256:269b27f60bcf45438e8683269f8ecd1235fa13e5411de93dae3b9ee4fe7f7bc7", "sha256:1d73d8986f948525536956ddd902e8a587a6846ebf4492117db16daba2865ddf",
"sha256:27d287e61639d692563d9dab76bafe071fbeb26818dd6a32a0022f3f7ca884b5", "sha256:208e82f73b242275b8566ac07a25158e7b21fa2f14e642a7881048430612d1a6",
"sha256:39106649c3082972106f930766ae23d1464a73b7d30b3698c986f74bf1256a34", "sha256:2557833df892558123d791d6ff80ac4a2a0351f69c7421c7d5f0c07db72c8865",
"sha256:40e4c42bd27ed3c11b2c983fecfb03356fae1209de10686d03c02c8696a1d90e", "sha256:25ea6906f9987d42546329d06f9750e69f0ee62307a2e7092955ed0758e64f09",
"sha256:461d4339b3b8f3335d7e2c90ce335eb275488c587b61aca4b305196dde2ff086", "sha256:2c867914f7608674a555ac8daf20265644ac7be709e1da7d818089eebdfe544e",
"sha256:4f98f70328bc788c86a6a1a8a14b0ea979f81ae6015dd6c72978f1feff70ecda", "sha256:2eadac20711a795d3bb7a2bfc87c04091cb5274d9c3281b43088a1227099b662",
"sha256:558a20a0845d1a5dc6ff87cd0f63d7dac982d7c3be05d2ffb6322a87c17fa286", "sha256:37999d5ebd5d7bcd32438b725ca3470df05a7de8b1e9c0395bef24296b31ca99",
"sha256:562dccd37acec149458c1791da459f130c6cf8902c94c93b8d47c6337b9fb826", "sha256:3ae8946d51789779f76e4fa326fd6676d8c19c1c3b4c4c5e9342807185264875",
"sha256:5e86c66a6dea8ab6152e83b0facc856dc4d435fe0f872f01d66ce0a2131b7f1d", "sha256:5636cd7e60583b1608044ae4405e91575399430e66a5e1812f4bf30bcc55864e",
"sha256:60a207efcd8c11d6bbeb7862e33418fba4e4ad79846d88d160d7231fcb42a5ee", "sha256:570e637cb6509998555f7e4af13006d89fad6c09cfc5c4795855385391063e4b",
"sha256:645a7092b77fdbc3f68d3cc98f9d3e71510e419f54019d6e282328c0dd140dcd", "sha256:590a40447ff3803c44050ce3c17c3958f11ca028dae3eacdd7b96775184394fa",
"sha256:6874367586c020705a44eecdad5d6b587c64b892e34305bb6ed87c9bbe22a5e9", "sha256:5aab51b9c1af1b8a84f40aa49ffe1684d41810b18d6c3e94aa50194e0a563f01",
"sha256:74bf0a4f9091131de09286f9a605db449840e313753949fe07c8d0fe7659ad1e", "sha256:5ffe4e0753393bcbcfc9a58133ed3d3a584634cc7cc2e667f8e3e6fbcbb2155d",
"sha256:7b726194f938791a6691c7592c8b9e805fc6d1b9632a833b9c0640828cd49cbc", "sha256:663982381bd428a275a841009e52983cc69c471a4979ce01344fadbf72cf353d",
"sha256:8149ded7f90154fdc1a40e0c8975df58041a6f693b8f7edcd9348484e9dc17fe", "sha256:6d06bf8e24dd6c473c4fbd8e16a83bd2e6d74add6ba25169043deb46d497b211",
"sha256:8cccf7057c7d19064a9e27660f5aec4e5c4001ffcf653a47531bde19b5aa2a8a", "sha256:6e5b9a4bf133cf1887b4a04c21c10ca9f548114f19c83957b2820d5c84254940",
"sha256:911714b08b63d155f9c948da2b5534b223a1a4fc50bb67139ab68b277c938578", "sha256:70a2aed9615645bbe9d82c0f52bc7e676d2c0f8a63933d68418e0cb307f30536",
"sha256:a5f8f85986197d1dd6444763c4a15c991bfed86d835a1f6f7d476f7198d5f56a", "sha256:7750746421c4395e3d2cc3d805919f4f57bb9f2a9a0ccd955566a9341050a1b4",
"sha256:a744132d0abaa854d1aad50ba9bc64e79c6f835b3e92521db4235a1991176813", "sha256:7fc8708bc996e50fc7a9a2ad394e1f015348e389da26789fa6916630237143d7",
"sha256:af2c14efc0bb0e91af63d00080ccc067866fb8cbbaca2b0438ab4105f5e0f08d", "sha256:91abd2f080065a7c007540f6bbd93ef7bdbbffa6df4a4cfab3892d8623b83c98",
"sha256:b054eb0a8aa712c8e9030065a59b5e6a5cf0746ecdb5f087cca5ec7685690c19", "sha256:988f8b2281f3d95c66c01bdb141cefef1cc97db0d473c25c3fe2927ef00293b9",
"sha256:b0becb75418f8a130e9d465e718316cd17c7a8acce6fe8fe07adc72762bee425", "sha256:9f56121d8a676802044584e6cc41250bbcde069d8adf725b9b817a6b0fd87f09",
"sha256:b1d2ed1cbda2ae107283befd9284e650d840f8f7568cb9060b5466d25dc48975", "sha256:a0f51536ce6e817a7aa25b0dca8b62feb210d4dc22cabfe8d1a92d47979372cd",
"sha256:ba4261c8ad00b49d48bbb3b5af388bb7576edfc0ca50a49c11dcb77caa1d897e", "sha256:a1cdd7390d7f66ddcebf545203ca3728c4890d605f9f2697bc8e31437906e8e7",
"sha256:d1fe9d7d09bb07228650903d6a9dc48ea649e3b8c69b1d263419cc722b3938e8", "sha256:b10eb4d0a77609679bf5f23708e20b1cd461a1643bd8ea42b1ca4149b1a5406c",
"sha256:d7804f6a71fc2dda888ef2de266727ec2f3915373d5a785ed4ddc603bbc91e08", "sha256:b274ac8e511b55ffb62e8292316bd2baa80c10e9fe811b1aa5ce81da6b6697d8",
"sha256:da2844fba024dd58eaa712561da47dcd1e7ad544a257482392472eae1c86d5e5", "sha256:c75b502af2c83fcfa2ee9c2257c1ba5806634a91a50db6129ff70e67c42c7e7b",
"sha256:dcefc97d1daf8d55199420e9162ab584ed0893a109f45e438b9794ced44c9fd0", "sha256:c9c8e53a5472b77f6a391b515c771105011f4b40740ce53af8428d1c8ca20004",
"sha256:dd98c436a1fc56f48c70882cc243df89ad036210d871c7427dc164b31500dc11", "sha256:d867998a56c5133b9d31992beb699892e33b72150a8bf40f86cb52b8c606c83f",
"sha256:e74671e43ed4569fbd7989e5eecc7d06dc134b571872ab1d5a88f4a123814e9f", "sha256:eb566cab630ec176b2d6115ed08b2cf4d921b47caa7f02cca1b4a9525223ee94",
"sha256:eb9b92f456ff3ec746cd4935b73c1117538d6124b8617bc0fe6fda0b3816e345", "sha256:f61e6b95b414431ffe9dc460928fe9f351095fde074e2c2f5c6dda7b67a2192d",
"sha256:ebb4e637a1fb861c34e48a00d03cffa9234f42bef923aec44e5625ffb9a8e8f9", "sha256:f718675fd071bcce4f7cbf9250cbaaf64e2e91ef1b0b32a1af596e7412647556",
"sha256:ef739fe89e7f43fb6494a43b1878a36273e5924869ba1d866f752c5812ae8d58", "sha256:f9d4bfbd015e4b80dbad11c97049975f94592a6a0440e903ee647309f6252a1f",
"sha256:f40db0e02a8157d2b90857c24d89b6310f9b6c3642369852cdc3b5ac49b92afc", "sha256:fae50fc12a5e8541f6f1cc4ed744ca8f76a9543876cf63f618fb0e6aca8f8375",
"sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6", "sha256:fcf9c8edda7f7b2fd78069e97f4197815df5e871ec47b0f22580d330c6dec561",
"sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8" "sha256:fdedce3bc5360bd29d4bb90396e8d4d3c09af49bc0383909fe84c7233c5ee675"
], ],
"version": "==5.1.0" "version": "==5.1.2"
} }
}, },
"develop": { "develop": {
@ -1250,18 +1279,17 @@
}, },
"black": { "black": {
"hashes": [ "hashes": [
"sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
"sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
], ],
"index": "pypi", "index": "pypi",
"version": "==19.10b0" "version": "==20.8b1"
}, },
"bump2version": { "bump2version": {
"hashes": [ "hashes": [
"sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0", "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410",
"sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984" "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"
], ],
"version": "==1.0.0" "version": "==1.0.1"
}, },
"bumpversion": { "bumpversion": {
"hashes": [ "hashes": [
@ -1271,20 +1299,6 @@
"index": "pypi", "index": "pypi",
"version": "==0.6.0" "version": "==0.6.0"
}, },
"certifi": {
"hashes": [
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
"version": "==2020.6.20"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": { "click": {
"hashes": [ "hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
@ -1294,11 +1308,11 @@
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.4.3" "version": "==0.4.4"
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
@ -1342,11 +1356,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:59c8125ca873ed3bdae9c12b146fbbd6ed8d0f743e4cf5f5817af50c51f1fc2f", "sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc",
"sha256:b5fbb818e751f660fa2d576d9f40c34a4c615c8b48dd383f5216e609f383371f" "sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.1.1" "version": "==3.1.2"
}, },
"django-debug-toolbar": { "django-debug-toolbar": {
"hashes": [ "hashes": [
@ -1356,14 +1370,6 @@
"index": "pypi", "index": "pypi",
"version": "==3.1.1" "version": "==3.1.1"
}, },
"docker": {
"hashes": [
"sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828",
"sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"
],
"index": "pypi",
"version": "==4.3.1"
},
"dodgy": { "dodgy": {
"hashes": [ "hashes": [
"sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a", "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a",
@ -1373,10 +1379,10 @@
}, },
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839",
"sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"
], ],
"version": "==3.8.3" "version": "==3.8.4"
}, },
"flake8-polyfill": { "flake8-polyfill": {
"hashes": [ "hashes": [
@ -1394,24 +1400,17 @@
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
"sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912", "sha256:58483ad99811321e3c0b52c8b2229ff517499229a4854752b7d128005986e409",
"sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910" "sha256:f488d43600d7299567b59fe41497d313e7c1253a9f2a8ebd2df8af2a1151c71d"
], ],
"version": "==3.1.8" "version": "==3.1.10"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"version": "==2.10"
}, },
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
], ],
"version": "==1.0.1" "version": "==1.1.1"
}, },
"isort": { "isort": {
"hashes": [ "hashes": [
@ -1453,6 +1452,13 @@
], ],
"version": "==0.6.1" "version": "==0.6.1"
}, },
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
@ -1470,10 +1476,10 @@
}, },
"pbr": { "pbr": {
"hashes": [ "hashes": [
"sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea", "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
"sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15" "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
], ],
"version": "==5.5.0" "version": "==5.5.1"
}, },
"pep8-naming": { "pep8-naming": {
"hashes": [ "hashes": [
@ -1568,19 +1574,19 @@
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:1cd09785c0a50f9af72220dd12aa78cfa49cbffc356c61eab009ca189e018a33", "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9",
"sha256:d010e24666435b39a4cf48740b039885642b6c273a3f77be3e7e03554d2806b7" "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.1.0" "version": "==6.1.1"
}, },
"pytest-django": { "pytest-django": {
"hashes": [ "hashes": [
"sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6", "sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2",
"sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4" "sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.10.0" "version": "==4.1.0"
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
@ -1608,36 +1614,35 @@
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef", "sha256:0cb23ed0e327c18fb7eac61ebbb3180ebafed5b9b86ca2e15438201e5903b5dd",
"sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c", "sha256:1a065e7a6a1b4aa851a0efa1a2579eabc765246b8b3a5fd74000aaa3134b8b4e",
"sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b", "sha256:1a511470db3aa97432ac8c1bf014fcc6c9fbfd0f4b1313024d342549cf86bcd6",
"sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c", "sha256:1c447b0d108cddc69036b1b3910fac159f2b51fdeec7f13872e059b7bc932be1",
"sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63", "sha256:2278453c6a76280b38855a263198961938108ea2333ee145c5168c36b8e2b376",
"sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc", "sha256:240509721a663836b611fa13ca1843079fc52d0b91ef3f92d9bba8da12e768a0",
"sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be", "sha256:4e21340c07090ddc8c16deebfd82eb9c9e1ec5e62f57bb86194a2595fd7b46e0",
"sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab", "sha256:570e916a44a361d4e85f355aacd90e9113319c78ce3c2d098d2ddf9631b34505",
"sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19", "sha256:59d5c6302d22c16d59611a9fd53556554010db1d47e9df5df37be05007bebe75",
"sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637", "sha256:6a46eba253cedcbe8a6469f881f014f0a98819d99d341461630885139850e281",
"sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc", "sha256:6f567df0601e9c7434958143aebea47a9c4b45434ea0ae0286a4ec19e9877169",
"sha256:9bc13e0d20b97ffb07821aa3e113f9998e84994fe4d159ffa3d3a9d1b805043b", "sha256:781906e45ef1d10a0ed9ec8ab83a09b5e0d742de70e627b20d61ccb1b1d3964d",
"sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d", "sha256:8469377a437dbc31e480993399fd1fd15fe26f382dc04c51c9cb73e42965cc06",
"sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b", "sha256:8cd0d587aaac74194ad3e68029124c06245acaeddaae14cb45844e5c9bebeea4",
"sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100", "sha256:97a023f97cddf00831ba04886d1596ef10f59b93df7f855856f037190936e868",
"sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3", "sha256:a973d5a7a324e2a5230ad7c43f5e1383cac51ef4903bf274936a5634b724b531",
"sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121", "sha256:af360e62a9790e0a96bc9ac845d87bfa0e4ee0ee68547ae8b5a9c1030517dbef",
"sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b", "sha256:b706c70070eea03411b1761fff3a2675da28d042a1ab7d0863b3efe1faa125c9",
"sha256:eda4771e0ace7f67f58bc5b560e27fb20f32a148cbc993b0c3835970935c2707", "sha256:bfd7a9fddd11d116a58b62ee6c502fd24cfe22a4792261f258f886aa41c2a899",
"sha256:f1b3afc574a3db3b25c89161059d857bd4909a1269b0b3cb3c904677c8c4a3f7", "sha256:c30d8766a055c22e39dd7e1a4f98f6266169f2de05db737efe509c2fb9c8a3c8",
"sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f" "sha256:c53dc8ee3bb7b7e28ee9feb996a0c999137be6c1d3b02cb6b3c4cba4f9e5ed09",
"sha256:c95d514093b80e5309bdca5dd99e51bcf82c44043b57c34594d9d7556bd04d05",
"sha256:d43cf21df524283daa80ecad551c306b7f52881c8d0fe4e3e76a96b626b6d8d8",
"sha256:d62205f00f461fe8b24ade07499454a3b7adf3def1225e258b994e2215fd15c5",
"sha256:e289a857dca3b35d3615c3a6a438622e20d1bf0abcb82c57d866c8d0be3f44c4",
"sha256:e5f6aa56dda92472e9d6f7b1e6331f4e2d51a67caafff4d4c5121cadac03941e",
"sha256:f4b1c65ee86bfbf7d0c3dfd90592a9e3d6e9ecd36c367c884094c050d4c35d04"
], ],
"version": "==2020.9.27" "version": "==2020.10.23"
},
"requests": {
"hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"version": "==2.24.0"
}, },
"requirements-detector": { "requirements-detector": {
"hashes": [ "hashes": [
@ -1682,10 +1687,10 @@
}, },
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
], ],
"version": "==0.3.1" "version": "==0.4.1"
}, },
"stevedore": { "stevedore": {
"hashes": [ "hashes": [
@ -1727,24 +1732,25 @@
], ],
"version": "==1.4.1" "version": "==1.4.1"
}, },
"typing-extensions": {
"hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
],
"version": "==3.7.4.3"
},
"urllib3": { "urllib3": {
"extras": [ "extras": [
"secure" "secure"
], ],
"hashes": [ "hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
], ],
"index": "pypi", "index": "pypi",
"markers": null, "markers": null,
"version": "==1.25.10" "version": "==1.25.11"
},
"websocket-client": {
"hashes": [
"sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
"sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"
],
"version": "==0.57.0"
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [

View File

@ -6,8 +6,9 @@ As passbook is currently in a pre-stable, only the latest "stable" version is su
| Version | Supported | | Version | Supported |
| -------- | ------------------ | | -------- | ------------------ |
| 0.9.x | :white_check_mark: |
| 0.10.x | :white_check_mark: | | 0.10.x | :white_check_mark: |
| 0.11.x | :white_check_mark: |
| 0.12.x | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View File

@ -89,7 +89,7 @@ stages:
versionSpec: '3.8' versionSpec: '3.8'
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
script: npm install -g pyright script: npm install -g pyright@1.1.79
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
script: | script: |
@ -169,6 +169,13 @@ stages:
dockerComposeFile: 'scripts/ci.docker-compose.yml' dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services' action: 'Run services'
buildImages: false buildImages: false
- task: CmdLine@2
displayName: Install K3d and prepare
inputs:
script: |
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
k3d cluster create
k3d kubeconfig write -o ~/.kube/config --overwrite
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
script: | script: |
@ -178,6 +185,7 @@ stages:
displayName: Run full test suite displayName: Run full test suite
inputs: inputs:
script: | script: |
export PB_TEST_K8S=true
pipenv run coverage run ./manage.py test passbook -v 3 pipenv run coverage run ./manage.py test passbook -v 3
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
@ -203,6 +211,13 @@ stages:
dockerComposeFile: 'scripts/ci.docker-compose.yml' dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services' action: 'Run services'
buildImages: false buildImages: false
- task: CmdLine@2
displayName: Install K3d and prepare
inputs:
script: |
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
k3d cluster create
k3d kubeconfig write -o ~/.kube/config --overwrite
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
script: | script: |
@ -219,11 +234,13 @@ stages:
inputs: inputs:
script: | script: |
cd passbook/static/static cd passbook/static/static
yarn npm i
npm run build
- task: CmdLine@2 - task: CmdLine@2
displayName: Run full test suite displayName: Run full test suite
inputs: inputs:
script: | script: |
export PB_TEST_K8S=true
pipenv run coverage run ./manage.py test e2e -v 3 --failfast pipenv run coverage run ./manage.py test e2e -v 3 --failfast
- task: CmdLine@2 - task: CmdLine@2
condition: always() condition: always()
@ -231,6 +248,7 @@ stages:
inputs: inputs:
script: | script: |
docker stop $(docker ps -aq) docker stop $(docker ps -aq)
docker container prune -f
- task: CmdLine@2 - task: CmdLine@2
displayName: Prepare unittests and coverage for upload displayName: Prepare unittests and coverage for upload
inputs: inputs:
@ -332,19 +350,3 @@ stages:
repository: 'beryju/passbook-static' repository: 'beryju/passbook-static'
command: 'push' command: 'push'
tags: "gh-${{ variables.branchName }}" tags: "gh-${{ variables.branchName }}"
- stage: Deploy
jobs:
- job: deploy_dev
pool:
vmImage: 'ubuntu-latest'
steps:
- task: HelmDeploy@0
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceConnection: 'k8s-beryjuorg-prd'
namespace: 'passbook-dev'
command: 'upgrade'
chartType: 'FilePath'
chartPath: 'helm/'
releaseName: 'passbook-dev'
recreate: true

View File

@ -3,7 +3,7 @@ version: '3.2'
services: services:
postgresql: postgresql:
image: postgres image: postgres:12
volumes: volumes:
- database:/var/lib/postgresql/data - database:/var/lib/postgresql/data
networks: networks:
@ -12,18 +12,14 @@ services:
- POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword} - POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword}
- POSTGRES_USER=passbook - POSTGRES_USER=passbook
- POSTGRES_DB=passbook - POSTGRES_DB=passbook
labels:
- traefik.enable=false
env_file: env_file:
- .env - .env
redis: redis:
image: redis image: redis
networks: networks:
- internal - internal
labels:
- traefik.enable=false
server: server:
image: beryju/passbook:${PASSBOOK_TAG:-0.10.8-stable} image: beryju/passbook:${PASSBOOK_TAG:-0.12.6-stable}
command: server command: server
environment: environment:
PASSBOOK_REDIS__HOST: redis PASSBOOK_REDIS__HOST: redis
@ -34,35 +30,50 @@ services:
networks: networks:
- internal - internal
labels: labels:
- traefik.port=8000 traefik.enable: 'true'
- traefik.docker.network=internal traefik.docker.network: internal
- traefik.frontend.rule=PathPrefix:/ traefik.http.routers.app-router.rule: PathPrefix(`/`)
traefik.http.routers.app-router.service: app-service
traefik.http.routers.app-router.tls: 'true'
traefik.http.services.app-service.loadbalancer.healthcheck.hostname: passbook-healthcheck-host
traefik.http.services.app-service.loadbalancer.server.port: '8000'
env_file: env_file:
- .env - .env
worker: worker:
image: beryju/passbook:${PASSBOOK_TAG:-0.10.8-stable} image: beryju/passbook:${PASSBOOK_TAG:-0.12.6-stable}
command: worker command: worker
networks: networks:
- internal - internal
labels:
- traefik.enable=false
environment: environment:
PASSBOOK_REDIS__HOST: redis PASSBOOK_REDIS__HOST: redis
PASSBOOK_POSTGRESQL__HOST: postgresql PASSBOOK_POSTGRESQL__HOST: postgresql
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS} PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./backups:/backups
- /var/run/docker.sock:/var/run/docker.sock
env_file: env_file:
- .env - .env
static: static:
image: beryju/passbook-static:${PASSBOOK_TAG:-0.10.8-stable} image: beryju/passbook-static:${PASSBOOK_TAG:-0.12.6-stable}
networks: networks:
- internal - internal
labels: labels:
- traefik.frontend.rule=PathPrefix:/static, /robots.txt, /favicon.ico traefik.enable: 'true'
- traefik.port=80 traefik.docker.network: internal
- traefik.docker.network=internal traefik.http.routers.static-router.rule: PathPrefix(`/static`, `/robots.txt`, `/favicon.ico`)
traefik.http.routers.static-router.tls: 'true'
traefik.http.routers.static-router.service: static-service
traefik.http.services.static-service.loadbalancer.healthcheck.path: /
traefik.http.services.static-service.loadbalancer.server.port: '80'
traefik: traefik:
image: traefik:1.7 image: traefik:2.3
command: --api --docker --defaultentrypoints=https --entryPoints='Name:http Address::80 Redirect.EntryPoint:https' --entryPoints='Name:https Address::443 TLS' command:
- "--accesslog=true"
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.http.address=:80"
- "--entrypoints.https.address=:443"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
ports: ports:

View File

@ -2,20 +2,25 @@
The User object has the following attributes: The User object has the following attributes:
- `username`: User's username. - `username`: User's username.
- `email` User's email. - `email` User's email.
- `name` User's display mame. - `name` User's display name.
- `is_staff` Boolean field if user is staff. - `is_staff` Boolean field if user is staff.
- `is_active` Boolean field if user is active. - `is_active` Boolean field if user is active.
- `date_joined` Date user joined/was created. - `date_joined` Date user joined/was created.
- `password_change_date` Date password was last changed. - `password_change_date` Date password was last changed.
- `attributes` Dynamic attributes. - `attributes` Dynamic attributes.
- `pb_groups` This is a queryset of all the user's groups.
You can do additional filtering like `user.pb_groups.filter(name__startswith='test')`, see [here](https://docs.djangoproject.com/en/3.1/ref/models/querysets/#id4)
To get the name of all groups, you can do `[group.name for group in user.pb_groups.all()]`
## Examples ## Examples
List all the User's group names: List all the User's group names:
```python ```python
for group in user.groups.all(): for group in user.pb_groups.all():
yield group.name yield group.name
``` ```

View File

@ -95,7 +95,8 @@
}, },
"model": "passbook_flows.flowstagebinding", "model": "passbook_flows.flowstagebinding",
"attrs": { "attrs": {
"re_evaluate_policies": false "evaluate_on_plan": false,
"re_evaluate_policies": true
} }
}, },
{ {

View File

@ -101,7 +101,7 @@
{ {
"identifiers": { "identifiers": {
"pk": "975d5502-1e22-4d10-b560-fbc5bd70ff4d", "pk": "975d5502-1e22-4d10-b560-fbc5bd70ff4d",
"name": "default-password-change-prompt" "name": "Change your password"
}, },
"model": "passbook_stages_prompt.promptstage", "model": "passbook_stages_prompt.promptstage",
"attrs": { "attrs": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 450 KiB

View File

@ -4,8 +4,8 @@ This installation method is for test-setups and small-scale productive setups.
## Prerequisites ## Prerequisites
- docker - docker
- docker-compose - docker-compose
## Install ## Install
@ -13,7 +13,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte
To optionally enable error-reporting, run `echo PASSBOOK_ERROR_REPORTING__ENABLED=true >> .env` To optionally enable error-reporting, run `echo PASSBOOK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo PASSBOOK_TAG=0.10.8-stable >> .env` To optionally deploy a different version run `echo PASSBOOK_TAG=0.12.6-stable >> .env`
If this is a fresh passbook install run the following commands to generate a password: If this is a fresh passbook install run the following commands to generate a password:

View File

@ -4,39 +4,52 @@ For a mid to high-load installation, Kubernetes is recommended. passbook is inst
This installation automatically applies database migrations on startup. After the installation is done, you can use `pbadmin` as username and password. This installation automatically applies database migrations on startup. After the installation is done, you can use `pbadmin` as username and password.
``` ```yaml
################################### ###################################
# Values directly affecting passbook # Values directly affecting passbook
################################### ###################################
image: image:
name: beryju/passbook name: beryju/passbook
name_static: beryju/passbook-static name_static: beryju/passbook-static
tag: 0.10.8-stable tag: 0.12.6-stable
nameOverride: ""
serverReplicas: 1 serverReplicas: 1
workerReplicas: 1 workerReplicas: 1
# Enable the Kubernetes integration which lets passbook deploy outposts into kubernetes
kubernetesIntegration: true
config: config:
# Optionally specify fixed secret_key, otherwise generated automatically # Optionally specify fixed secret_key, otherwise generated automatically
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o # secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
# Enable error reporting # Enable error reporting
error_reporting: errorReporting:
enabled: false enabled: false
environment: customer environment: customer
send_pii: false sendPii: false
# Log level used by web and worker # Log level used by web and worker
# Can be either debug, info, warning, error # Can be either debug, info, warning, error
log_level: warning logLevel: warning
# Enable Database Backups to S3 # Enable Database Backups to S3
# backup: # backup:
# access_key: access-key # accessKey: access-key
# secret_key: secret-key # secretKey: secret-key
# bucket: s3-bucket # bucket: s3-bucket
# region: eu-central-1
# host: s3-host # host: s3-host
ingress:
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- passbook.k8s.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - passbook.k8s.local
################################### ###################################
# Values controlling dependencies # Values controlling dependencies
################################### ###################################
@ -57,16 +70,4 @@ redis:
enabled: false enabled: false
# https://stackoverflow.com/a/59189742 # https://stackoverflow.com/a/59189742
disableCommands: [] disableCommands: []
ingress:
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- passbook.k8s.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - passbook.k8s.local
``` ```

View File

@ -0,0 +1,42 @@
# passbook behind a reverse-proxy
If you want to access passbook behind a reverse-proxy, use a config like this. It is important that Websocket is enabled, so that Outposts can connect.
```
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
# Server config
listen 80;
server_name sso.domain.tld;
# 301 to SSL
location / {
return 301 https://$host$request_uri;
}
}
server {
# Server config
listen 443 ssl http2;
server_name sso.domain.tld;
# SSL Certs
ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem;
# Proxy site
location / {
proxy_pass https://<hostname of your passbook server>;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
```

View File

@ -16,7 +16,7 @@ The following placeholders will be used:
## passbook Setup ## passbook Setup
Because Tautulli requires valid HTTP Basic credentials, you must save your HTTP Basic Credentials in passbook. The recommended way to do this, is to create a Group, called for example "Tautulli Users". For this group, add the following attributes: Because Tautulli requires valid HTTP Basic credentials, you must save your HTTP Basic Credentials in passbook. The recommended way to do this is to create a Group. Name the group "Tautulli Users", for example. For this group, add the following attributes:
```yaml ```yaml
tautulli_user: username tautulli_user: username
@ -31,13 +31,13 @@ Create an application in passbook. Create a Proxy provider with the following pa
If Tautulli is running in docker, and you're deploying the passbook proxy on the same host, set the value to `http://tautulli:3579`, where tautulli is the name of your container. If Tautulli is running in docker, and you're deploying the passbook proxy on the same host, set the value to `http://tautulli:3579`, where tautulli is the name of your container.
If Tautulli is running on a different server than where you are deploying the passbook proxy, set the value to `http://tautulli.company:3579`. If Tautulli is running on a different server to where you are deploying the passbook proxy, set the value to `http://tautulli.company:3579`.
- External host - External host
Set this to the external URL you will be accessing Tautulli from. Set this to the external URL you will be accessing Tautulli from.
Enable the `Set HTTP-Basic Authentication` option. Set and `HTTP-Basic Username` and `HTTP-Basic Password` to `tautulli_user` and `tautulli_password` respectively. These values can be chosen freely, `tautulli_` is just used a prefix for clarity. Enable the `Set HTTP-Basic Authentication` option. Set and `HTTP-Basic Username` and `HTTP-Basic Password` to `tautulli_user` and `tautulli_password` respectively. These values can be chosen freely, `tautulli_` is just used as a prefix for clarity.
## Tautulli Setup ## Tautulli Setup
@ -47,4 +47,4 @@ In Tautulli, navigate to Settings and enable the "Show Advanced" option. Navigat
Save the settings, and restart Tautulli if prompted. Save the settings, and restart Tautulli if prompted.
Afterwards, you need to deploy an Outpost in front of Tautulli, just like descried [here](../sonarr/index.md) Afterwards, you need to deploy an Outpost in front of Tautulli, as descried [here](../sonarr/index.md)

View File

@ -16,6 +16,10 @@ From https://en.wikipedia.org/wiki/VCenter
This requires VMware vCenter 7.0.0 or newer. This requires VMware vCenter 7.0.0 or newer.
!!! note
It seems that the vCenter still needs to be joined to the Active Directory Domain, otherwise group membership does not work correctly. We're working on a fix for this, for the meantime your vCenter should be part of your Domain.
## Preparation ## Preparation
The following placeholders will be used: The following placeholders will be used:
@ -60,6 +64,8 @@ Under *Providers*, create an OAuth2/OpenID Provider with these settings:
Create an application which uses this provider. Optionally apply access restrictions to the application. Create an application which uses this provider. Optionally apply access restrictions to the application.
Set the Launch URL to `https://vcenter.company/ui/login/oauth2`. This will skip vCenter's User Prompt and directly log you in.
## vCenter Setup ## vCenter Setup
Login as local Administrator account (most likely ends with vsphere.local). Using the Menu in the Navigation bar, navigate to *Administration -> Single Sing-on -> Configuration*. Login as local Administrator account (most likely ends with vsphere.local). Using the Menu in the Navigation bar, navigate to *Administration -> Single Sing-on -> Configuration*.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,55 @@
# Active Directory Integration
## Preparation
The following placeholders will be used:
- `ad.company` is the Name of the Active Directory domain.
- `passbook.company` is the FQDN of the passbook install.
## Active Directory Setup
1. Open Active Directory Users and Computers
2. Create a user in Active Directory, matching your naming scheme
![](./01_user_create.png)
3. Give the User a password, generated using for example `pwgen 64 1`.
4. Open the Delegation of Control Wizard by right-clicking the domain.
5. Select the passbook service user you've just created.
6. Ensure the "Reset user password and force password change at next logon" Option is checked.
![](./02_delegate.png)
## passbook Setup
In passbook, create a new LDAP Source in Administration -> Sources.
Use these settings:
- Server URI: `ldap://ad.company`
For passbook to be able to write passwords back to Active Directory, make sure to use `ldaps://`
- Bind CN: `<name of your service user>@ad.company`
- Bind Password: The password you've given the user above
- Base DN: The base DN which you want passbook to sync
- Property Mappings: Select all and click the right arrow
The other settings might need to be adjusted based on the setup of your domain.
- Addition User/Group DN: Additional DN which is *prepended* to your Base DN for user synchronization.
- Addition Group DN: Additional DN which is *prepended* to your Base DN for group synchronization.
- User object filter: Which objects should be considered users.
- Group object filter: Which objects should be considered groups.
- User group membership field: Which user field saves the group membership
- Object uniqueness field: A user field which contains a unique Identifier
- Sync parent group: If enabled, all synchronized groups will be given this group as a parent.
After you save the source, a synchronization will start in the background. When its done, you cen see the summary on the System Tasks page.
![](./03_pb_status.png)

View File

@ -0,0 +1,102 @@
# Backup and restore
!!! warning
Local backups are only supported for docker-compose installs. If you want to backup a Kubernetes instance locally, use an S3-compatible server such as [minio](https://min.io/)
### Backup
!!! notice
Local backups are **enabled** by default, and will be run daily at 00:00
Local backups can be created by running the following command in your passbook installation directory
```
docker-compose run --rm worker backup
```
This will dump the current database into the `./backups` folder. By defaults, the last 10 Backups are kept.
### Restore
Run this command in your passbook installation directory
```
docker-compose run --rm worker restore
```
This will prompt you to restore from your last backup. If you want to restore from a specific file, use the `-i` flag with the filename:
```
docker-compose run --rm worker restore -i default-2020-10-03-115557.psql
```
After you've restored the backup, it is recommended to restart all services with `docker-compose restart`.
### S3 Configuration
#### Preparation
passbook expects the bucket you select to already exist. The IAM User given to passbook should have the following permissions
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObjectAcl",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:PutObjectAcl"
],
"Principal": {
"AWS": "arn:aws:iam::example-AWS-account-ID:user/example-user-name"
},
"Resource": [
"arn:aws:s3:::example-bucket-name/*",
"arn:aws:s3:::example-bucket-name"
]
}
]
}
```
#### docker-compose
Set the following values in your `.env` file.
```
PASSBOOK_POSTGRESQL__S3_BACKUP__ACCESS_KEY=
PASSBOOK_POSTGRESQL__S3_BACKUP__SECRET_KEY=
PASSBOOK_POSTGRESQL__S3_BACKUP__BUCKET=
PASSBOOK_POSTGRESQL__S3_BACKUP__REGION=
```
If you want to backup to an S3-compatible server, like [minio](https://min.io/), use this setting:
```
PASSBOOK_POSTGRESQL__S3_BACKUP__HOST=http://play.min.io
```
#### Kubernetes
Simply enable these options in your values.yaml file
```yaml
# Enable Database Backups to S3
backup:
accessKey: access-key
secretKey: secret-key
bucket: s3-bucket
region: eu-central-1
host: s3-host
```
Afterwards, run a `helm upgrade` to update the ConfigMap. Backups are done automatically as above, at 00:00 every day.

View File

@ -5,7 +5,7 @@ To deploy an outpost with docker-compose, use this snippet in your docker-compo
You can also run the outpost in a separate docker-compose project, you just have to ensure that the outpost container can reach your application container. You can also run the outpost in a separate docker-compose project, you just have to ensure that the outpost container can reach your application container.
```yaml ```yaml
version: 3.5 version: '3.5'
services: services:
passbook_proxy: passbook_proxy:

View File

@ -26,7 +26,11 @@ return False
- `request.obj`: A Django Model instance. This is only set if the policy is ran against an object. - `request.obj`: A Django Model instance. This is only set if the policy is ran against an object.
- `request.context`: A dictionary with dynamic data. This depends on the origin of the execution. - `request.context`: A dictionary with dynamic data. This depends on the origin of the execution.
- `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external provider. - `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external provider.
- `pb_client_ip`: Client's IP Address or '255.255.255.255' if no IP Address could be extracted. Can be [compared](../expressions/index.md#comparing-ip-addresses) - `pb_client_ip`: Client's IP Address or 255.255.255.255 if no IP Address could be extracted. Can be [compared](../expressions/index.md#comparing-ip-addresses), for example
```python
return pb_client_ip in ip_network('10.0.0.0/24')
```
Additionally, when the policy is executed from a flow, every variable from the flow's current context is accessible under the `context` object. Additionally, when the policy is executed from a flow, every variable from the flow's current context is accessible under the `context` object.

20
docs/upgrading/to-0.11.md Normal file
View File

@ -0,0 +1,20 @@
# Upgrading to 0.11
This update brings these headline features:
- Add Backup and Restore, currently only externally schedulable, documented [here](https://passbook.beryju.org/maintenance/backups/)
- New Admin Dashboard with more metrics and Charts
Shows successful and failed logins from the last 24 hours, as well as the most used applications
- Add search to all table views
- Outpost now supports a Docker Controller, which installs the Outpost on the same host as passbook, updates and manages it
- Add Token Identifier
Tokens now have an identifier which is used to reference to them, so the Primary key is not shown in URLs
- `core/applications/list` API now shows applications the user has access to via policies
## Upgrading
This upgrade can be done as with minor upgrades, the only external change is the new docker-compose file, which enabled the Docker Integration for Outposts. To use this feature, please download the latest docker-compose from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml).
Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`.

63
docs/upgrading/to-0.12.md Normal file
View File

@ -0,0 +1,63 @@
# Upgrading to 0.12
This update brings these headline features:
- Rewrite Outpost state Logic, which now supports multiple concurrent Outpost instances.
- Add Kubernetes Integration for Outposts, which deploys and maintains Outposts with High Availability in a Kubernetes Cluster
- Add System Task Overview to see all background tasks, their status, the log output, and retry them
- Alerts now disappear automatically
- Audit Logs are now searchable
- Users can now create their own Tokens to access the API
- docker-compose deployment now uses traefik 2.3
Fixes:
- Fix high CPU Usage of the proxy when Websocket connections fail
## Upgrading
### docker-compose
Docker-compose users should download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml). This includes the new traefik 2.3.
Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`.
### Kubernetes
For Kubernetes users, there are some changes to the helm values.
The values change from
```yaml
config:
# Optionally specify fixed secret_key, otherwise generated automatically
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
# Enable error reporting
error_reporting:
enabled: false
environment: customer
send_pii: false
# Log level used by web and worker
# Can be either debug, info, warning, error
log_level: warning
```
to
```yaml
config:
# Optionally specify fixed secret_key, otherwise generated automatically
# secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
# Enable error reporting
errorReporting:
enabled: false
environment: customer
sendPii: false
# Log level used by web and worker
# Can be either debug, info, warning, error
logLevel: warning
```
in order to be consistent with the rest of the settings.
There is also a new setting called `kubernetesIntegration`, which controls the Kubernetes integration for passbook. When enabled (the default), a Service Account is created, which allows passbook to deploy and update Outposts.

View File

@ -8,7 +8,7 @@ from docker.types import Healthcheck
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
from passbook.stages.email.models import EmailStage, EmailTemplates from passbook.stages.email.models import EmailStage, EmailTemplates
from passbook.stages.identification.models import IdentificationStage from passbook.stages.identification.models import IdentificationStage
@ -34,6 +34,7 @@ class TestFlowsEnroll(SeleniumTestCase):
), ),
} }
@retry()
def test_enroll_2_step(self): def test_enroll_2_step(self):
"""Test 2-step enroll flow""" """Test 2-step enroll flow"""
# First stage fields # First stage fields
@ -104,7 +105,8 @@ class TestFlowsEnroll(SeleniumTestCase):
self.wait_for_url(self.url("passbook_core:user-settings")) self.wait_for_url(self.url("passbook_core:user-settings"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, "foo", self.driver.find_element(By.ID, "user-settings").text,
"foo",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"
@ -118,6 +120,7 @@ class TestFlowsEnroll(SeleniumTestCase):
"foo@bar.baz", "foo@bar.baz",
) )
@retry()
@override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend") @override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend")
def test_enroll_email(self): def test_enroll_email(self):
"""Test enroll with Email verification""" """Test enroll with Email verification"""
@ -208,7 +211,8 @@ class TestFlowsEnroll(SeleniumTestCase):
self.driver.find_element(By.ID, "user-settings").click() self.driver.find_element(By.ID, "user-settings").click()
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, "foo", self.driver.find_element(By.ID, "user-settings").text,
"foo",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"

View File

@ -5,13 +5,14 @@ from unittest.case import skipUnless
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsLogin(SeleniumTestCase): class TestFlowsLogin(SeleniumTestCase):
"""test default login flow""" """test default login flow"""
@retry()
def test_login(self): def test_login(self):
"""test default login flow""" """test default login flow"""
self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/") self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/")
@ -21,5 +22,6 @@ class TestFlowsLogin(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password").send_keys(USER().username) self.driver.find_element(By.ID, "id_password").send_keys(USER().username)
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, USER().username, self.driver.find_element(By.ID, "user-settings").text,
USER().username,
) )

View File

@ -12,7 +12,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.flows.models import Flow, FlowStageBinding from passbook.flows.models import Flow, FlowStageBinding
from passbook.stages.otp_validate.models import OTPValidateStage from passbook.stages.otp_validate.models import OTPValidateStage
@ -21,6 +21,7 @@ from passbook.stages.otp_validate.models import OTPValidateStage
class TestFlowsOTP(SeleniumTestCase): class TestFlowsOTP(SeleniumTestCase):
"""test flow with otp stages""" """test flow with otp stages"""
@retry()
def test_otp_validate(self): def test_otp_validate(self):
"""test flow with otp stages""" """test flow with otp stages"""
sleep(1) sleep(1)
@ -48,9 +49,11 @@ class TestFlowsOTP(SeleniumTestCase):
self.driver.find_element(By.ID, "id_code").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_code").send_keys(Keys.ENTER)
self.wait_for_url(self.url("passbook_core:overview")) self.wait_for_url(self.url("passbook_core:overview"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, USER().username, self.driver.find_element(By.ID, "user-settings").text,
USER().username,
) )
@retry()
def test_otp_totp_setup(self): def test_otp_totp_setup(self):
"""test TOTP Setup stage""" """test TOTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow") flow: Flow = Flow.objects.get(slug="default-authentication-flow")
@ -62,7 +65,8 @@ class TestFlowsOTP(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password").send_keys(USER().username) self.driver.find_element(By.ID, "id_password").send_keys(USER().username)
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, USER().username, self.driver.find_element(By.ID, "user-settings").text,
USER().username,
) )
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-page__header").click() self.driver.find_element(By.CSS_SELECTOR, ".pf-c-page__header").click()
@ -96,6 +100,7 @@ class TestFlowsOTP(SeleniumTestCase):
self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists()) self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists())
@retry()
def test_otp_static_setup(self): def test_otp_static_setup(self):
"""test Static OTP Setup stage""" """test Static OTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow") flow: Flow = Flow.objects.get(slug="default-authentication-flow")
@ -107,7 +112,8 @@ class TestFlowsOTP(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password").send_keys(USER().username) self.driver.find_element(By.ID, "id_password").send_keys(USER().username)
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, USER().username, self.driver.find_element(By.ID, "user-settings").text,
USER().username,
) )
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-page__header").click() self.driver.find_element(By.CSS_SELECTOR, ".pf-c-page__header").click()

View File

@ -5,7 +5,7 @@ from unittest.case import skipUnless
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.models import Flow, FlowDesignation from passbook.flows.models import Flow, FlowDesignation
from passbook.providers.oauth2.generators import generate_client_secret from passbook.providers.oauth2.generators import generate_client_secret
@ -16,6 +16,7 @@ from passbook.stages.password.models import PasswordStage
class TestFlowsStageSetup(SeleniumTestCase): class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows""" """test stage setup flows"""
@retry()
def test_password_change(self): def test_password_change(self):
"""test password change flow""" """test password change flow"""
# Ensure that password stage has change_flow set # Ensure that password stage has change_flow set

View File

@ -9,7 +9,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.core.models import Application from passbook.core.models import Application
from passbook.flows.models import Flow from passbook.flows.models import Flow
from passbook.policies.expression.models import ExpressionPolicy from passbook.policies.expression.models import ExpressionPolicy
@ -61,6 +61,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
}, },
} }
@retry()
def test_authorization_consent_implied(self): def test_authorization_consent_implied(self):
"""test OAuth Provider flow (default authorization flow with implied consent)""" """test OAuth Provider flow (default authorization flow with implied consent)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -77,7 +78,9 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
Application.objects.create( Application.objects.create(
name="Grafana", slug="grafana", provider=provider, name="Grafana",
slug="grafana",
provider=provider,
) )
self.driver.get("http://localhost:3000") self.driver.get("http://localhost:3000")
@ -113,6 +116,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
USER().username, USER().username,
) )
@retry()
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OAuth Provider flow (default authorization flow with explicit consent)""" """test OAuth Provider flow (default authorization flow with explicit consent)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -129,7 +133,9 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
app = Application.objects.create( app = Application.objects.create(
name="Grafana", slug="grafana", provider=provider, name="Grafana",
slug="grafana",
provider=provider,
) )
self.driver.get("http://localhost:3000") self.driver.get("http://localhost:3000")
@ -143,13 +149,17 @@ class TestProviderOAuth2Github(SeleniumTestCase):
sleep(1) sleep(1)
self.assertEqual( self.assertEqual(
app.name, self.driver.find_element(By.ID, "application-name").text, app.name,
self.driver.find_element(By.ID, "application-name").text,
) )
self.assertEqual( self.assertEqual(
"GitHub Compatibility: Access you Email addresses", "GitHub Compatibility: Access you Email addresses",
self.driver.find_element(By.ID, "scope-user:email").text, self.driver.find_element(By.ID, "scope-user:email").text,
) )
self.driver.find_element(By.CSS_SELECTOR, ("[type=submit]"),).click() self.driver.find_element(
By.CSS_SELECTOR,
("[type=submit]"),
).click()
self.wait_for_url("http://localhost:3000/?orgId=1") self.wait_for_url("http://localhost:3000/?orgId=1")
self.driver.get("http://localhost:3000/profile") self.driver.get("http://localhost:3000/profile")
@ -176,6 +186,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
USER().username, USER().username,
) )
@retry()
def test_denied(self): def test_denied(self):
"""test OAuth Provider flow (default authorization flow, denied)""" """test OAuth Provider flow (default authorization flow, denied)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -192,7 +203,9 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
app = Application.objects.create( app = Application.objects.create(
name="Grafana", slug="grafana", provider=provider, name="Grafana",
slug="grafana",
provider=provider,
) )
negative_policy = ExpressionPolicy.objects.create( negative_policy = ExpressionPolicy.objects.create(

View File

@ -10,7 +10,7 @@ from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.core.models import Application from passbook.core.models import Application
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.flows.models import Flow from passbook.flows.models import Flow
@ -80,6 +80,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
}, },
} }
@retry()
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)""" """test OpenID Provider flow (invalid redirect URI, check error message)"""
sleep(1) sleep(1)
@ -104,7 +105,9 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name="Grafana", slug=APPLICATION_SLUG, provider=provider, name="Grafana",
slug=APPLICATION_SLUG,
provider=provider,
) )
self.driver.get("http://localhost:3000") self.driver.get("http://localhost:3000")
@ -120,6 +123,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"Redirect URI Error", "Redirect URI Error",
) )
@retry()
def test_authorization_consent_implied(self): def test_authorization_consent_implied(self):
"""test OpenID Provider flow (default authorization flow with implied consent)""" """test OpenID Provider flow (default authorization flow with implied consent)"""
sleep(1) sleep(1)
@ -144,7 +148,9 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name="Grafana", slug=APPLICATION_SLUG, provider=provider, name="Grafana",
slug=APPLICATION_SLUG,
provider=provider,
) )
self.driver.get("http://localhost:3000") self.driver.get("http://localhost:3000")
@ -179,6 +185,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
USER().email, USER().email,
) )
@retry()
def test_authorization_logout(self): def test_authorization_logout(self):
"""test OpenID Provider flow with logout""" """test OpenID Provider flow with logout"""
sleep(1) sleep(1)
@ -203,7 +210,9 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name="Grafana", slug=APPLICATION_SLUG, provider=provider, name="Grafana",
slug=APPLICATION_SLUG,
provider=provider,
) )
self.driver.get("http://localhost:3000") self.driver.get("http://localhost:3000")
@ -246,6 +255,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
self.driver.find_element(By.ID, "logout").click() self.driver.find_element(By.ID, "logout").click()
@retry()
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)""" """test OpenID Provider flow (default authorization flow with explicit consent)"""
sleep(1) sleep(1)
@ -270,7 +280,9 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.save() provider.save()
app = Application.objects.create( app = Application.objects.create(
name="Grafana", slug=APPLICATION_SLUG, provider=provider, name="Grafana",
slug=APPLICATION_SLUG,
provider=provider,
) )
self.driver.get("http://localhost:3000") self.driver.get("http://localhost:3000")
@ -282,7 +294,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.assertEqual( self.assertEqual(
app.name, self.driver.find_element(By.ID, "application-name").text, app.name,
self.driver.find_element(By.ID, "application-name").text,
) )
self.wait.until( self.wait.until(
ec.presence_of_element_located((By.CSS_SELECTOR, "[type=submit]")) ec.presence_of_element_located((By.CSS_SELECTOR, "[type=submit]"))
@ -316,6 +329,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
USER().email, USER().email,
) )
@retry()
def test_authorization_denied(self): def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)""" """test OpenID Provider flow (default authorization with access deny)"""
sleep(1) sleep(1)
@ -340,7 +354,9 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
) )
provider.save() provider.save()
app = Application.objects.create( app = Application.objects.create(
name="Grafana", slug=APPLICATION_SLUG, provider=provider, name="Grafana",
slug=APPLICATION_SLUG,
provider=provider,
) )
negative_policy = ExpressionPolicy.objects.create( negative_policy = ExpressionPolicy.objects.create(

View File

@ -12,7 +12,7 @@ from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.core.models import Application from passbook.core.models import Application
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.flows.models import Flow from passbook.flows.models import Flow
@ -76,6 +76,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
LOGGER.info("Container failed healthcheck") LOGGER.info("Container failed healthcheck")
sleep(1) sleep(1)
@retry()
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)""" """test OpenID Provider flow (invalid redirect URI, check error message)"""
sleep(1) sleep(1)
@ -100,7 +101,9 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name=self.application_slug, slug=self.application_slug, provider=provider, name=self.application_slug,
slug=self.application_slug,
provider=provider,
) )
self.container = self.setup_client() self.container = self.setup_client()
@ -117,6 +120,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"Redirect URI Error", "Redirect URI Error",
) )
@retry()
def test_authorization_consent_implied(self): def test_authorization_consent_implied(self):
"""test OpenID Provider flow (default authorization flow with implied consent)""" """test OpenID Provider flow (default authorization flow with implied consent)"""
sleep(1) sleep(1)
@ -141,7 +145,9 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name=self.application_slug, slug=self.application_slug, provider=provider, name=self.application_slug,
slug=self.application_slug,
provider=provider,
) )
self.container = self.setup_client() self.container = self.setup_client()
@ -165,6 +171,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
self.assertEqual(body["IDTokenClaims"]["email"], USER().email) self.assertEqual(body["IDTokenClaims"]["email"], USER().email)
self.assertEqual(body["UserInfo"]["email"], USER().email) self.assertEqual(body["UserInfo"]["email"], USER().email)
@retry()
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)""" """test OpenID Provider flow (default authorization flow with explicit consent)"""
sleep(1) sleep(1)
@ -189,7 +196,9 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.save() provider.save()
app = Application.objects.create( app = Application.objects.create(
name=self.application_slug, slug=self.application_slug, provider=provider, name=self.application_slug,
slug=self.application_slug,
provider=provider,
) )
self.container = self.setup_client() self.container = self.setup_client()
@ -202,7 +211,8 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.assertEqual( self.assertEqual(
app.name, self.driver.find_element(By.ID, "application-name").text, app.name,
self.driver.find_element(By.ID, "application-name").text,
) )
self.wait.until( self.wait.until(
ec.presence_of_element_located((By.CSS_SELECTOR, "[type=submit]")) ec.presence_of_element_located((By.CSS_SELECTOR, "[type=submit]"))
@ -222,6 +232,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
self.assertEqual(body["IDTokenClaims"]["email"], USER().email) self.assertEqual(body["IDTokenClaims"]["email"], USER().email)
self.assertEqual(body["UserInfo"]["email"], USER().email) self.assertEqual(body["UserInfo"]["email"], USER().email)
@retry()
def test_authorization_denied(self): def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)""" """test OpenID Provider flow (default authorization with access deny)"""
sleep(1) sleep(1)
@ -246,7 +257,9 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
) )
provider.save() provider.save()
app = Application.objects.create( app = Application.objects.create(
name=self.application_slug, slug=self.application_slug, provider=provider, name=self.application_slug,
slug=self.application_slug,
provider=provider,
) )
negative_policy = ExpressionPolicy.objects.create( negative_policy = ExpressionPolicy.objects.create(

View File

@ -1,4 +1,5 @@
"""Proxy and Outpost e2e tests""" """Proxy and Outpost e2e tests"""
from dataclasses import asdict
from sys import platform from sys import platform
from time import sleep from time import sleep
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@ -10,11 +11,16 @@ from docker.models.containers import Container
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook import __version__ from passbook import __version__
from passbook.core.models import Application from passbook.core.models import Application
from passbook.flows.models import Flow from passbook.flows.models import Flow
from passbook.outposts.models import Outpost, OutpostDeploymentType, OutpostType from passbook.outposts.models import (
Outpost,
OutpostConfig,
OutpostDeploymentType,
OutpostType,
)
from passbook.providers.proxy.models import ProxyProvider from passbook.providers.proxy.models import ProxyProvider
@ -39,19 +45,19 @@ class TestProviderProxy(SeleniumTestCase):
def start_proxy(self, outpost: Outpost) -> Container: def start_proxy(self, outpost: Outpost) -> Container:
"""Start proxy container based on outpost created""" """Start proxy container based on outpost created"""
client: DockerClient = from_env() client: DockerClient = from_env()
client.images.pull("beryju/oidc-test-client")
container = client.containers.run( container = client.containers.run(
image="beryju/passbook-proxy:latest", image=f"beryju/passbook-proxy:{__version__}",
detach=True, detach=True,
network_mode="host", network_mode="host",
auto_remove=True, auto_remove=True,
environment={ environment={
"PASSBOOK_HOST": self.live_server_url, "PASSBOOK_HOST": self.live_server_url,
"PASSBOOK_TOKEN": outpost.token.token_uuid.hex, "PASSBOOK_TOKEN": outpost.token.key,
}, },
) )
return container return container
@retry()
def test_proxy_simple(self): def test_proxy_simple(self):
"""Test simple outpost setup with single provider""" """Test simple outpost setup with single provider"""
proxy: ProxyProvider = ProxyProvider.objects.create( proxy: ProxyProvider = ProxyProvider.objects.create(
@ -80,8 +86,10 @@ class TestProviderProxy(SeleniumTestCase):
# Wait until outpost healthcheck succeeds # Wait until outpost healthcheck succeeds
healthcheck_retries = 0 healthcheck_retries = 0
while healthcheck_retries < 50: while healthcheck_retries < 50:
if outpost.deployment_health: if len(outpost.state) > 0:
break state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1 healthcheck_retries += 1
sleep(0.5) sleep(0.5)
@ -103,28 +111,7 @@ class TestProviderProxy(SeleniumTestCase):
class TestProviderProxyConnect(ChannelsLiveServerTestCase): class TestProviderProxyConnect(ChannelsLiveServerTestCase):
"""Test Proxy connectivity over websockets""" """Test Proxy connectivity over websockets"""
proxy_container: Container @retry()
def tearDown(self) -> None:
self.proxy_container.kill()
super().tearDown()
def start_proxy(self, outpost: Outpost) -> Container:
"""Start proxy container based on outpost created"""
client: DockerClient = from_env()
client.images.pull("beryju/oidc-test-client")
container = client.containers.run(
image="beryju/passbook-proxy:latest",
detach=True,
network_mode="host",
auto_remove=True,
environment={
"PASSBOOK_HOST": self.live_server_url,
"PASSBOOK_TOKEN": outpost.token.token_uuid.hex,
},
)
return container
def test_proxy_connectivity(self): def test_proxy_connectivity(self):
"""Test proxy connectivity over websocket""" """Test proxy connectivity over websocket"""
SeleniumTestCase().apply_default_data() SeleniumTestCase().apply_default_data()
@ -144,20 +131,27 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
outpost: Outpost = Outpost.objects.create( outpost: Outpost = Outpost.objects.create(
name="proxy_outpost", name="proxy_outpost",
type=OutpostType.PROXY, type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.CUSTOM, deployment_type=OutpostDeploymentType.DOCKER,
_config=asdict(
OutpostConfig(passbook_host=self.live_server_url, log_level="debug")
),
) )
outpost.providers.add(proxy) outpost.providers.add(proxy)
outpost.save() outpost.save()
self.proxy_container = self.start_proxy(outpost)
# Wait until outpost healthcheck succeeds # Wait until outpost healthcheck succeeds
healthcheck_retries = 0 healthcheck_retries = 0
while healthcheck_retries < 50: while healthcheck_retries < 50:
if outpost.deployment_health: if len(outpost.state) > 0:
break state = outpost.state[0]
if state.last_seen and state.version:
break
healthcheck_retries += 1 healthcheck_retries += 1
sleep(0.5) sleep(0.5)
self.assertIsNotNone(outpost.deployment_health) state = outpost.state
self.assertEqual(outpost.deployment_version.get("version"), __version__) self.assertTrue(len(state), 1)
self.assertEqual(state[0].version, __version__)
# Make sure to delete the outpost to remove the container
outpost.delete()

View File

@ -12,7 +12,7 @@ from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase, retry
from passbook.core.models import Application from passbook.core.models import Application
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.flows.models import Flow from passbook.flows.models import Flow
@ -66,6 +66,7 @@ class TestProviderSAML(SeleniumTestCase):
LOGGER.info("Container failed healthcheck") LOGGER.info("Container failed healthcheck")
sleep(1) sleep(1)
@retry()
def test_sp_initiated_implicit(self): def test_sp_initiated_implicit(self):
"""test SAML Provider flow SP-initiated flow (implicit consent)""" """test SAML Provider flow SP-initiated flow (implicit consent)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -84,7 +85,9 @@ class TestProviderSAML(SeleniumTestCase):
provider.property_mappings.set(SAMLPropertyMapping.objects.all()) provider.property_mappings.set(SAMLPropertyMapping.objects.all())
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name="SAML", slug="passbook-saml", provider=provider, name="SAML",
slug="passbook-saml",
provider=provider,
) )
self.container = self.setup_client(provider) self.container = self.setup_client(provider)
self.driver.get("http://localhost:9009") self.driver.get("http://localhost:9009")
@ -103,6 +106,7 @@ class TestProviderSAML(SeleniumTestCase):
self.assertEqual(body["attr"]["mail"], [USER().email]) self.assertEqual(body["attr"]["mail"], [USER().email])
self.assertEqual(body["attr"]["uid"], [str(USER().pk)]) self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
@retry()
def test_sp_initiated_explicit(self): def test_sp_initiated_explicit(self):
"""test SAML Provider flow SP-initiated flow (explicit consent)""" """test SAML Provider flow SP-initiated flow (explicit consent)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -121,7 +125,9 @@ class TestProviderSAML(SeleniumTestCase):
provider.property_mappings.set(SAMLPropertyMapping.objects.all()) provider.property_mappings.set(SAMLPropertyMapping.objects.all())
provider.save() provider.save()
app = Application.objects.create( app = Application.objects.create(
name="SAML", slug="passbook-saml", provider=provider, name="SAML",
slug="passbook-saml",
provider=provider,
) )
self.container = self.setup_client(provider) self.container = self.setup_client(provider)
self.driver.get("http://localhost:9009") self.driver.get("http://localhost:9009")
@ -131,7 +137,8 @@ class TestProviderSAML(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password").send_keys(USER().username) self.driver.find_element(By.ID, "id_password").send_keys(USER().username)
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER) self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.assertEqual( self.assertEqual(
app.name, self.driver.find_element(By.ID, "application-name").text, app.name,
self.driver.find_element(By.ID, "application-name").text,
) )
sleep(1) sleep(1)
self.driver.find_element(By.CSS_SELECTOR, "[type=submit]").click() self.driver.find_element(By.CSS_SELECTOR, "[type=submit]").click()
@ -145,6 +152,7 @@ class TestProviderSAML(SeleniumTestCase):
self.assertEqual(body["attr"]["mail"], [USER().email]) self.assertEqual(body["attr"]["mail"], [USER().email])
self.assertEqual(body["attr"]["uid"], [str(USER().pk)]) self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
@retry()
def test_idp_initiated_implicit(self): def test_idp_initiated_implicit(self):
"""test SAML Provider flow IdP-initiated flow (implicit consent)""" """test SAML Provider flow IdP-initiated flow (implicit consent)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -163,7 +171,9 @@ class TestProviderSAML(SeleniumTestCase):
provider.property_mappings.set(SAMLPropertyMapping.objects.all()) provider.property_mappings.set(SAMLPropertyMapping.objects.all())
provider.save() provider.save()
Application.objects.create( Application.objects.create(
name="SAML", slug="passbook-saml", provider=provider, name="SAML",
slug="passbook-saml",
provider=provider,
) )
self.container = self.setup_client(provider) self.container = self.setup_client(provider)
self.driver.get( self.driver.get(
@ -188,6 +198,7 @@ class TestProviderSAML(SeleniumTestCase):
self.assertEqual(body["attr"]["mail"], [USER().email]) self.assertEqual(body["attr"]["mail"], [USER().email])
self.assertEqual(body["attr"]["uid"], [str(USER().pk)]) self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
@retry()
def test_sp_initiated_denied(self): def test_sp_initiated_denied(self):
"""test SAML Provider flow SP-initiated flow (Policy denies access)""" """test SAML Provider flow SP-initiated flow (Policy denies access)"""
# Bootstrap all needed objects # Bootstrap all needed objects
@ -209,7 +220,9 @@ class TestProviderSAML(SeleniumTestCase):
provider.property_mappings.set(SAMLPropertyMapping.objects.all()) provider.property_mappings.set(SAMLPropertyMapping.objects.all())
provider.save() provider.save()
app = Application.objects.create( app = Application.objects.create(
name="SAML", slug="passbook-saml", provider=provider, name="SAML",
slug="passbook-saml",
provider=provider,
) )
PolicyBinding.objects.create(target=app, policy=negative_policy, order=0) PolicyBinding.objects.create(target=app, policy=negative_policy, order=0)
self.container = self.setup_client(provider) self.container = self.setup_client(provider)

View File

@ -14,7 +14,7 @@ from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger from structlog import get_logger
from yaml import safe_dump from yaml import safe_dump
from e2e.utils import SeleniumTestCase from e2e.utils import SeleniumTestCase, retry
from passbook.flows.models import Flow from passbook.flows.models import Flow
from passbook.providers.oauth2.generators import ( from passbook.providers.oauth2.generators import (
generate_client_id, generate_client_id,
@ -106,6 +106,7 @@ class TestSourceOAuth2(SeleniumTestCase):
consumer_secret=self.client_secret, consumer_secret=self.client_secret,
) )
@retry()
def test_oauth_enroll(self): def test_oauth_enroll(self):
"""test OAuth Source With With OIDC""" """test OAuth Source With With OIDC"""
self.create_objects() self.create_objects()
@ -144,19 +145,22 @@ class TestSourceOAuth2(SeleniumTestCase):
self.driver.get(self.url("passbook_core:user-settings")) self.driver.get(self.url("passbook_core:user-settings"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, "foo", self.driver.find_element(By.ID, "user-settings").text,
"foo",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_name").get_attribute("value"), "admin", self.driver.find_element(By.ID, "id_name").get_attribute("value"),
"admin",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_email").get_attribute("value"), self.driver.find_element(By.ID, "id_email").get_attribute("value"),
"admin@example.com", "admin@example.com",
) )
@retry()
@override_settings(SESSION_COOKIE_SAMESITE="strict") @override_settings(SESSION_COOKIE_SAMESITE="strict")
def test_oauth_samesite_strict(self): def test_oauth_samesite_strict(self):
"""test OAuth Source With SameSite set to strict """test OAuth Source With SameSite set to strict
@ -193,6 +197,7 @@ class TestSourceOAuth2(SeleniumTestCase):
"Authentication Failed.", "Authentication Failed.",
) )
@retry()
def test_oauth_enroll_auth(self): def test_oauth_enroll_auth(self):
"""test OAuth Source With With OIDC (enroll and authenticate again)""" """test OAuth Source With With OIDC (enroll and authenticate again)"""
self.test_oauth_enroll() self.test_oauth_enroll()
@ -225,13 +230,15 @@ class TestSourceOAuth2(SeleniumTestCase):
self.driver.get(self.url("passbook_core:user-settings")) self.driver.get(self.url("passbook_core:user-settings"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, "foo", self.driver.find_element(By.ID, "user-settings").text,
"foo",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo" self.driver.find_element(By.ID, "id_username").get_attribute("value"), "foo"
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_name").get_attribute("value"), "admin", self.driver.find_element(By.ID, "id_name").get_attribute("value"),
"admin",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_email").get_attribute("value"), self.driver.find_element(By.ID, "id_email").get_attribute("value"),
@ -287,6 +294,7 @@ class TestSourceOAuth1(SeleniumTestCase):
consumer_secret=self.client_secret, consumer_secret=self.client_secret,
) )
@retry()
def test_oauth_enroll(self): def test_oauth_enroll(self):
"""test OAuth Source With With OIDC""" """test OAuth Source With With OIDC"""
self.create_objects() self.create_objects()
@ -313,11 +321,13 @@ class TestSourceOAuth1(SeleniumTestCase):
self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click() self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click()
# Wait until we've loaded the user info page # Wait until we've loaded the user info page
sleep(2)
self.wait.until(ec.presence_of_element_located((By.ID, "user-settings"))) self.wait.until(ec.presence_of_element_located((By.ID, "user-settings")))
self.driver.get(self.url("passbook_core:user-settings")) self.driver.get(self.url("passbook_core:user-settings"))
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "user-settings").text, "example-user", self.driver.find_element(By.ID, "user-settings").text,
"example-user",
) )
self.assertEqual( self.assertEqual(
self.driver.find_element(By.ID, "id_username").get_attribute("value"), self.driver.find_element(By.ID, "id_username").get_attribute("value"),

View File

@ -10,7 +10,7 @@ from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger from structlog import get_logger
from e2e.utils import SeleniumTestCase from e2e.utils import SeleniumTestCase, retry
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.flows.models import Flow from passbook.flows.models import Flow
from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource
@ -92,13 +92,16 @@ class TestSourceSAML(SeleniumTestCase):
}, },
} }
@retry()
def test_idp_redirect(self): def test_idp_redirect(self):
"""test SAML Source With redirect binding""" """test SAML Source With redirect binding"""
# Bootstrap all needed objects # Bootstrap all needed objects
authentication_flow = Flow.objects.get(slug="default-source-authentication") authentication_flow = Flow.objects.get(slug="default-source-authentication")
enrollment_flow = Flow.objects.get(slug="default-source-enrollment") enrollment_flow = Flow.objects.get(slug="default-source-enrollment")
keypair = CertificateKeyPair.objects.create( keypair = CertificateKeyPair.objects.create(
name="test-idp-cert", certificate_data=IDP_CERT, key_data=IDP_KEY, name="test-idp-cert",
certificate_data=IDP_CERT,
key_data=IDP_KEY,
) )
SAMLSource.objects.create( SAMLSource.objects.create(
@ -139,13 +142,16 @@ class TestSourceSAML(SeleniumTestCase):
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "" self.driver.find_element(By.ID, "id_username").get_attribute("value"), ""
) )
@retry()
def test_idp_post(self): def test_idp_post(self):
"""test SAML Source With post binding""" """test SAML Source With post binding"""
# Bootstrap all needed objects # Bootstrap all needed objects
authentication_flow = Flow.objects.get(slug="default-source-authentication") authentication_flow = Flow.objects.get(slug="default-source-authentication")
enrollment_flow = Flow.objects.get(slug="default-source-enrollment") enrollment_flow = Flow.objects.get(slug="default-source-enrollment")
keypair = CertificateKeyPair.objects.create( keypair = CertificateKeyPair.objects.create(
name="test-idp-cert", certificate_data=IDP_CERT, key_data=IDP_KEY, name="test-idp-cert",
certificate_data=IDP_CERT,
key_data=IDP_KEY,
) )
SAMLSource.objects.create( SAMLSource.objects.create(
@ -188,13 +194,16 @@ class TestSourceSAML(SeleniumTestCase):
self.driver.find_element(By.ID, "id_username").get_attribute("value"), "" self.driver.find_element(By.ID, "id_username").get_attribute("value"), ""
) )
@retry()
def test_idp_post_auto(self): def test_idp_post_auto(self):
"""test SAML Source With post binding (auto redirect)""" """test SAML Source With post binding (auto redirect)"""
# Bootstrap all needed objects # Bootstrap all needed objects
authentication_flow = Flow.objects.get(slug="default-source-authentication") authentication_flow = Flow.objects.get(slug="default-source-authentication")
enrollment_flow = Flow.objects.get(slug="default-source-enrollment") enrollment_flow = Flow.objects.get(slug="default-source-enrollment")
keypair = CertificateKeyPair.objects.create( keypair = CertificateKeyPair.objects.create(
name="test-idp-cert", certificate_data=IDP_CERT, key_data=IDP_KEY, name="test-idp-cert",
certificate_data=IDP_CERT,
key_data=IDP_KEY,
) )
SAMLSource.objects.create( SAMLSource.objects.create(

View File

@ -1,19 +1,22 @@
"""passbook e2e testing utilities""" """passbook e2e testing utilities"""
from functools import wraps
from glob import glob from glob import glob
from importlib.util import module_from_spec, spec_from_file_location from importlib.util import module_from_spec, spec_from_file_location
from inspect import getmembers, isfunction from inspect import getmembers, isfunction
from os import environ, makedirs from os import environ, makedirs
from time import sleep, time from time import sleep, time
from typing import Any, Dict, Optional from typing import Any, Callable, Dict, Optional
from django.apps import apps from django.apps import apps
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.db import connection, transaction from django.db import connection, transaction
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test.testcases import TransactionTestCase
from docker import DockerClient, from_env from docker import DockerClient, from_env
from docker.models.containers import Container from docker.models.containers import Container
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
@ -123,3 +126,41 @@ class SeleniumTestCase(StaticLiveServerTestCase):
func(apps, schema_editor) func(apps, schema_editor)
except IntegrityError: except IntegrityError:
pass pass
def retry(max_retires=3, exceptions=None):
"""Retry test multiple times. Default to catching Selenium Timeout Exception"""
if not exceptions:
exceptions = [TimeoutException]
logger = get_logger()
def retry_actual(func: Callable):
"""Retry test multiple times"""
count = 1
@wraps(func)
def wrapper(self: TransactionTestCase, *args, **kwargs):
"""Run test again if we're below max_retries, including tearDown and
setUp. Otherwise raise the error"""
nonlocal count
try:
return func(self, *args, **kwargs)
# pylint: disable=catching-non-exception
except tuple(exceptions) as exc:
count += 1
if count > max_retires:
logger.debug("Exceeded retry count", exc=exc, test=self)
# pylint: disable=raising-non-exception
raise exc
logger.debug("Retrying on error", exc=exc, test=self)
self.tearDown()
# pylint: disable=protected-access
self._post_teardown()
self.setUp()
return wrapper(self, *args, **kwargs)
return wrapper
return retry_actual

View File

@ -1,9 +1,11 @@
apiVersion: v2 apiVersion: v2
appVersion: "0.10.8-stable" description: passbook is an open-source Identity Provider focused on flexibility and versatility. You can use passbook in an existing environment to add support for new protocols. passbook is also a great solution for implementing signup/recovery/etc in your application, so you don't have to deal with it.
description: A Helm chart for passbook.
name: passbook name: passbook
version: "0.10.8-stable" home: https://passbook.beryju.org
icon: https://github.com/BeryJu/passbook/blob/master/docs/images/logo.svg sources:
- https://github.com/BeryJu/passbook
version: "0.12.6-stable"
icon: https://raw.githubusercontent.com/BeryJu/passbook/master/docs/images/logo.svg
dependencies: dependencies:
- name: postgresql - name: postgresql
version: 9.4.1 version: 9.4.1

28
helm/README.md Normal file
View File

@ -0,0 +1,28 @@
# passbook Helm Chart
| Name | Default | Description |
|-----------------------------------|-------------------------|-------------|
| image.name | beryju/passbook | Image used to run the passbook server and worker |
| image.name_static | beryju/passbook-static | Image used to run the passbook static server (CSS and JS Files) |
| image.tag | 0.12.5-stable | Image tag |
| serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment |
| kubernetesIntegration | true | Enable/disable the Kubernetes integration for passbook. This will create a service account for passbook to create and update outposts in passbook |
| config.secretKey | | Secret key used to sign session cookies, generate with `pwgen 50 1` for example. |
| config.errorReporting.enabled | false | Enable/disable error reporting |
| config.errorReporting.environment | customer | Environment sent with the error reporting |
| config.errorReporting.sendPii | false | Whether to send Personally-identifiable data with the error reporting |
| config.logLevel | warning | Log level of passbook |
| backup.accessKey | | Optionally enable S3 Backup, Access Key |
| backup.secretKey | | Optionally enable S3 Backup, Secret Key |
| backup.bucket | | Optionally enable S3 Backup, Bucket |
| backup.region | | Optionally enable S3 Backup, Region |
| backup.host | | Optionally enable S3 Backup, to custom Endpoint like minio |
| ingress.annotations | {} | Annotations for the ingress object |
| ingress.hosts | [passbook.k8s.local] | Hosts which the ingress will match |
| ingress.tls | [] | TLS Configuration, same as Ingress objects |
| install.postgresql | true | Enables/disables the packaged PostgreSQL Chart
| install.redis | true | Enables/disables the packaged Redis Chart
| postgresql.postgresqlPassword | | Password used for PostgreSQL, generated automatically.
For more info, see https://passbook.beryju.org/ and https://passbook.beryju.org/installation/kubernetes/

View File

@ -3,7 +3,7 @@
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "passbook.name" -}} {{- define "passbook.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- default .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
{{/* {{/*
@ -12,17 +12,13 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
If release name contains chart name it will be used as a full name. If release name contains chart name it will be used as a full name.
*/}} */}}
{{- define "passbook.fullname" -}} {{- define "passbook.fullname" -}}
{{- if .Values.fullnameOverride -}} {{- $name := default .Chart.Name -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}} {{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}} {{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}}
{{/* {{/*
Create chart name and version as used by the chart label. Create chart name and version as used by the chart label.

View File

@ -7,13 +7,14 @@ data:
POSTGRESQL__NAME: "{{ .Values.postgresql.postgresqlDatabase }}" POSTGRESQL__NAME: "{{ .Values.postgresql.postgresqlDatabase }}"
POSTGRESQL__USER: "{{ .Values.postgresql.postgresqlUsername }}" POSTGRESQL__USER: "{{ .Values.postgresql.postgresqlUsername }}"
{{- if .Values.backup }} {{- if .Values.backup }}
POSTGRESQL__BACKUP__ACCESS_KEY: "{{ .Values.backup.access_key }}" POSTGRESQL__S3_BACKUP__ACCESS_KEY: "{{ .Values.backup.accessKey }}"
POSTGRESQL__BACKUP__SECRET_KEY: "{{ .Values.backup.secret_key }}" POSTGRESQL__S3_BACKUP__SECRET_KEY: "{{ .Values.backup.secretKey }}"
POSTGRESQL__BACKUP__BUCKET: "{{ .Values.backup.bucket }}" POSTGRESQL__S3_BACKUP__BUCKET: "{{ .Values.backup.bucket }}"
POSTGRESQL__BACKUP__HOST: "{{ .Values.backup.host }}" POSTGRESQL__S3_BACKUP__REGION: "{{ .Values.backup.region }}"
POSTGRESQL__S3_BACKUP__HOST: "{{ .Values.backup.host }}"
{{- end}} {{- end}}
REDIS__HOST: "{{ .Release.Name }}-redis-master" REDIS__HOST: "{{ .Release.Name }}-redis-master"
ERROR_REPORTING__ENABLED: "{{ .Values.config.error_reporting.enabled }}" ERROR_REPORTING__ENABLED: "{{ .Values.config.errorReporting.enabled }}"
ERROR_REPORTING__ENVIRONMENT: "{{ .Values.config.error_reporting.environment }}" ERROR_REPORTING__ENVIRONMENT: "{{ .Values.config.errorReporting.environment }}"
ERROR_REPORTING__SEND_PII: "{{ .Values.config.error_reporting.send_pii }}" ERROR_REPORTING__SEND_PII: "{{ .Values.config.errorReporting.sendPii }}"
LOG_LEVEL: "{{ .Values.config.log_level }}" LOG_LEVEL: "{{ .Values.config.logLevel }}"

View File

@ -5,8 +5,8 @@ metadata:
name: {{ include "passbook.fullname" . }}-secret-key name: {{ include "passbook.fullname" . }}-secret-key
data: data:
monitoring_username: bW9uaXRvcg== # monitor in base64 monitoring_username: bW9uaXRvcg== # monitor in base64
{{- if .Values.config.secret_key }} {{- if .Values.config.secretKey }}
secret_key: {{ .Values.config.secret_key | b64enc | quote }} secret_key: {{ .Values.config.secretKey | b64enc | quote }}
{{- else }} {{- else }}
secret_key: {{ randAlphaNum 50 | b64enc | quote}} secret_key: {{ randAlphaNum 50 | b64enc | quote}}
{{- end }} {{- end }}

View File

@ -0,0 +1,64 @@
{{- if .Values.kubernetesIntegration }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "passbook.fullname" . }}-sa-role
rules:
- apiGroups:
- ""
resources:
- secrets
- services
verbs:
- "get"
- "create"
- "delete"
- "read"
- "patch"
- apiGroups:
- "extensions"
- "apps"
resources:
- "deployments"
verbs:
- "get"
- "create"
- "delete"
- "read"
- "patch"
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- "ingresses"
verbs:
- "get"
- "create"
- "delete"
- "read"
- "patch"
- apiGroups:
- ""
resources:
- namespaces
verbs:
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "passbook.fullname" . }}-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "passbook.fullname" . }}-sa-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "passbook.fullname" . }}-sa-role
subjects:
- kind: ServiceAccount
name: {{ include "passbook.fullname" . }}-sa
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@ -100,14 +100,14 @@ spec:
port: http port: http
httpHeaders: httpHeaders:
- name: Host - name: Host
value: kubernetes-healthcheck-host value: passbook-healthcheck-host
readinessProbe: readinessProbe:
httpGet: httpGet:
path: / path: /
port: http port: http
httpHeaders: httpHeaders:
- name: Host - name: Host
value: kubernetes-healthcheck-host value: passbook-healthcheck-host
resources: resources:
requests: requests:
cpu: 100m cpu: 100m

View File

@ -22,6 +22,9 @@ spec:
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
k8s.passbook.beryju.org/component: worker k8s.passbook.beryju.org/component: worker
spec: spec:
{{- if .Values.kubernetesIntegration }}
serviceAccountName: {{ include "passbook.fullname" . }}-sa
{{- end }}
affinity: affinity:
podAntiAffinity: podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: preferredDuringSchedulingIgnoredDuringExecution:

21
helm/values.test.yaml Normal file
View File

@ -0,0 +1,21 @@
image:
tag: gh-master
serverReplicas: 1
workerReplicas: 1
config:
# Log level used by web and worker
# Can be either debug, info, warning, error
logLevel: debug
ingress:
hosts:
- passbook.127.0.0.1.nip.io
# These values influence the bundled postgresql and redis charts, but are also used by passbook to connect
postgresql:
postgresqlPassword: EK-5jnKfjrGRm<77
redis:
password: password

View File

@ -4,32 +4,45 @@
image: image:
name: beryju/passbook name: beryju/passbook
name_static: beryju/passbook-static name_static: beryju/passbook-static
tag: 0.10.8-stable tag: 0.12.6-stable
nameOverride: ""
serverReplicas: 1 serverReplicas: 1
workerReplicas: 1 workerReplicas: 1
# Enable the Kubernetes integration which lets passbook deploy outposts into kubernetes
kubernetesIntegration: true
config: config:
# Optionally specify fixed secret_key, otherwise generated automatically # Optionally specify fixed secret_key, otherwise generated automatically
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o # secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
# Enable error reporting # Enable error reporting
error_reporting: errorReporting:
enabled: false enabled: false
environment: customer environment: customer
send_pii: false sendPii: false
# Log level used by web and worker # Log level used by web and worker
# Can be either debug, info, warning, error # Can be either debug, info, warning, error
log_level: warning logLevel: warning
# Enable Database Backups to S3 # Enable Database Backups to S3
# backup: # backup:
# access_key: access-key # accessKey: access-key
# secret_key: secret-key # secretKey: secret-key
# bucket: s3-bucket # bucket: s3-bucket
# region: eu-central-1
# host: s3-host # host: s3-host
ingress:
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- passbook.k8s.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - passbook.k8s.local
################################### ###################################
# Values controlling dependencies # Values controlling dependencies
################################### ###################################
@ -41,24 +54,3 @@ install:
# These values influence the bundled postgresql and redis charts, but are also used by passbook to connect # These values influence the bundled postgresql and redis charts, but are also used by passbook to connect
postgresql: postgresql:
postgresqlDatabase: passbook postgresqlDatabase: passbook
redis:
cluster:
enabled: false
master:
persistence:
enabled: false
# https://stackoverflow.com/a/59189742
disableCommands: []
ingress:
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- passbook.k8s.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - passbook.k8s.local

View File

@ -1,14 +1,20 @@
#!/bin/bash -e #!/bin/bash -e
python -m lifecycle.wait_for_db python -m lifecycle.wait_for_db
printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" > /dev/stderr
if [[ "$1" == "server" ]]; then if [[ "$1" == "server" ]]; then
gunicorn -c /lifecycle/gunicorn.conf.py passbook.root.asgi:application gunicorn -c /lifecycle/gunicorn.conf.py passbook.root.asgi:application
elif [[ "$1" == "worker" ]]; then elif [[ "$1" == "worker" ]]; then
celery -A passbook.root.celery worker --autoscale 10,3 -E -B -s /tmp/celerybeat-schedule -Q passbook,passbook_scheduled celery -A passbook.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q passbook,passbook_scheduled
elif [[ "$1" == "migrate" ]]; then elif [[ "$1" == "migrate" ]]; then
# Run system migrations first, run normal migrations after # Run system migrations first, run normal migrations after
python -m lifecycle.migrate python -m lifecycle.migrate
python -m manage migrate python -m manage migrate
elif [[ "$1" == "backup" ]]; then
python -m manage dbbackup --clean
elif [[ "$1" == "restore" ]]; then
python -m manage dbrestore ${@:2}
elif [[ "$1" == "bash" ]]; then
/bin/bash
else else
python -m manage "$@" python -m manage "$@"
fi fi

View File

@ -47,7 +47,9 @@ if __name__ == "__main__":
# pyright: reportGeneralTypeIssues=false # pyright: reportGeneralTypeIssues=false
spec.loader.exec_module(mod) spec.loader.exec_module(mod)
for _, sub in getmembers(mod, isclass): for name, sub in getmembers(mod, isclass):
if name != "Migration":
continue
migration = sub(curr, conn) migration = sub(curr, conn)
if migration.needs_migration(): if migration.needs_migration():
LOGGER.info("Migration needs to be applied", migration=sub) LOGGER.info("Migration needs to be applied", migration=sub)

View File

@ -25,7 +25,7 @@ delete from django_migrations where app = 'passbook_stages_password' and
name = '0002_passwordstage_change_flow';""" name = '0002_passwordstage_change_flow';"""
class To010Migration(BaseMigration): class Migration(BaseMigration):
def needs_migration(self) -> bool: def needs_migration(self) -> bool:
self.cur.execute( self.cur.execute(
"select * from information_schema.tables where table_name='oidc_provider_client'" "select * from information_schema.tables where table_name='oidc_provider_client'"

View File

@ -8,23 +8,24 @@ nav:
- Installation: - Installation:
- docker-compose: installation/docker-compose.md - docker-compose: installation/docker-compose.md
- Kubernetes: installation/kubernetes.md - Kubernetes: installation/kubernetes.md
- Reverse Proxy: installation/reverse-proxy.md
- Flows: - Flows:
Overview: flow/flows.md Overview: flow/flows.md
Examples: flow/examples/examples.md Examples: flow/examples/examples.md
- Stages: - Stages:
- Captcha Stage: flow/stages/captcha/index.md - Captcha Stage: flow/stages/captcha/index.md
- Dummy Stage: flow/stages/dummy/index.md - Dummy Stage: flow/stages/dummy/index.md
- Email Stage: flow/stages/email/index.md - Email Stage: flow/stages/email/index.md
- Identification Stage: flow/stages/identification/index.md - Identification Stage: flow/stages/identification/index.md
- Invitation Stage: flow/stages/invitation/index.md - Invitation Stage: flow/stages/invitation/index.md
- OTP Stage: flow/stages/otp/index.md - OTP Stage: flow/stages/otp/index.md
- Password Stage: flow/stages/password/index.md - Password Stage: flow/stages/password/index.md
- Prompt Stage: flow/stages/prompt/index.md - Prompt Stage: flow/stages/prompt/index.md
- Prompt Stage Validation: flow/stages/prompt/validation.md - Prompt Stage Validation: flow/stages/prompt/validation.md
- User Delete Stage: flow/stages/user_delete.md - User Delete Stage: flow/stages/user_delete.md
- User Login Stage: flow/stages/user_login.md - User Login Stage: flow/stages/user_login.md
- User Logout Stage: flow/stages/user_logout.md - User Logout Stage: flow/stages/user_logout.md
- User Write Stage: flow/stages/user_write.md - User Write Stage: flow/stages/user_write.md
- Sources: sources.md - Sources: sources.md
- Providers: - Providers:
- OAuth2: providers/oauth2.md - OAuth2: providers/oauth2.md
@ -46,20 +47,26 @@ nav:
- Overview: policies/index.md - Overview: policies/index.md
- Expression: policies/expression.md - Expression: policies/expression.md
- Integrations: - Integrations:
- as Source:
- Active Directory: integrations/sources/active-directory/index.md
- as Provider: - as Provider:
- Amazon Web Services: integrations/services/aws/index.md - Amazon Web Services: integrations/services/aws/index.md
- GitLab: integrations/services/gitlab/index.md - GitLab: integrations/services/gitlab/index.md
- Rancher: integrations/services/rancher/index.md - Rancher: integrations/services/rancher/index.md
- Harbor: integrations/services/harbor/index.md - Harbor: integrations/services/harbor/index.md
- Sentry: integrations/services/sentry/index.md - Sentry: integrations/services/sentry/index.md
- Ansible Tower/AWX: integrations/services/tower-awx/index.md - Ansible Tower/AWX: integrations/services/tower-awx/index.md
- VMware vCenter: integrations/services/vmware-vcenter/index.md - VMware vCenter: integrations/services/vmware-vcenter/index.md
- Ubuntu Landscape: integrations/services/ubuntu-landscape/index.md - Ubuntu Landscape: integrations/services/ubuntu-landscape/index.md
- Sonarr: integrations/services/sonarr/index.md - Sonarr: integrations/services/sonarr/index.md
- Tautulli: integrations/services/tautulli/index.md - Tautulli: integrations/services/tautulli/index.md
- Maintenance:
- Backups: maintenance/backups/index.md
- Upgrading: - Upgrading:
- to 0.9: upgrading/to-0.9.md - to 0.9: upgrading/to-0.9.md
- to 0.10: upgrading/to-0.10.md - to 0.10: upgrading/to-0.10.md
- to 0.11: upgrading/to-0.11.md
- to 0.12: upgrading/to-0.12.md
- Troubleshooting: - Troubleshooting:
- Access problems: troubleshooting/access.md - Access problems: troubleshooting/access.md

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = "0.10.8-stable" __version__ = "0.12.6-stable"

View File

View File

@ -0,0 +1,80 @@
"""passbook administration overview"""
from django.core.cache import cache
from django.http import response
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from passbook import __version__
from passbook.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from passbook.core.models import Provider
from passbook.policies.models import Policy
from passbook.root.celery import CELERY_APP
class AdministrationOverviewSerializer(Serializer):
"""Overview View"""
version = SerializerMethodField()
version_latest = SerializerMethodField()
worker_count = SerializerMethodField()
providers_without_application = SerializerMethodField()
policies_without_binding = SerializerMethodField()
cached_policies = SerializerMethodField()
cached_flows = SerializerMethodField()
def get_version(self, _) -> str:
"""Get current version"""
return __version__
def get_version_latest(self, _) -> str:
"""Get latest version from cache"""
version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache:
update_latest_version.delay()
return __version__
return version_in_cache
def get_worker_count(self, _) -> int:
"""Ping workers"""
return len(CELERY_APP.control.ping(timeout=0.5))
def get_providers_without_application(self, _) -> int:
"""Count of providers without application"""
return len(Provider.objects.filter(application=None))
def get_policies_without_binding(self, _) -> int:
"""Count of policies not bound or use in prompt stages"""
return len(
Policy.objects.filter(bindings__isnull=True, promptstage__isnull=True)
)
def get_cached_policies(self, _) -> int:
"""Get cached policy count"""
return len(cache.keys("policy_*"))
def get_cached_flows(self, _) -> int:
"""Get cached flow count"""
return len(cache.keys("flow_*"))
def create(self, request: Request) -> response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class AdministrationOverviewViewSet(ViewSet):
"""Return single instance of AdministrationOverviewSerializer"""
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AdministrationOverviewSerializer(many=True)})
def list(self, request: Request) -> Response:
"""Return single instance of AdministrationOverviewSerializer"""
serializer = AdministrationOverviewSerializer(True)
return Response(serializer.data)

View File

@ -0,0 +1,80 @@
"""passbook administration overview"""
import time
from collections import Counter
from datetime import timedelta
from typing import Dict, List
from django.db.models import Count, ExpressionWrapper, F
from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour
from django.http import response
from django.utils.timezone import now
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from passbook.audit.models import Event, EventAction
class AdministrationMetricsSerializer(Serializer):
"""Overview View"""
logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField()
def get_events_per_1h(self, action: str) -> List[Dict[str, int]]:
"""Get event count by hour in the last day, fill with zeros"""
date_from = now() - timedelta(days=1)
result = (
Event.objects.filter(action=action, created__gte=date_from)
.annotate(
age=ExpressionWrapper(
now() - F("created"), output_field=DurationField()
)
)
.annotate(age_hours=ExtractHour("age"))
.values("age_hours")
.annotate(count=Count("pk"))
.order_by("age_hours")
)
data = Counter({d["age_hours"]: d["count"] for d in result})
results = []
_now = now()
for hour in range(0, -24, -1):
results.append(
{
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
"y": data[hour * -1],
}
)
return results
def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours"""
return self.get_events_per_1h(EventAction.LOGIN)
def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours"""
return self.get_events_per_1h(EventAction.LOGIN_FAILED)
def create(self, request: Request) -> response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class AdministrationMetricsViewSet(ViewSet):
"""Return single instance of AdministrationMetricsSerializer"""
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
def list(self, request: Request) -> Response:
"""Return single instance of AdministrationMetricsSerializer"""
serializer = AdministrationMetricsSerializer(True)
return Response(serializer.data)

View File

@ -0,0 +1,72 @@
"""Tasks API"""
from importlib import import_module
from django.contrib import messages
from django.http.response import Http404
from django.utils.translation import gettext_lazy as _
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from passbook.lib.tasks import TaskInfo
class TaskSerializer(Serializer):
"""Serialize TaskInfo and TaskResult"""
task_name = CharField()
task_description = CharField()
task_finish_timestamp = DateTimeField(source="finish_timestamp")
status = IntegerField(source="result.status.value")
messages = ListField(source="result.messages")
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class TaskViewSet(ViewSet):
"""Read-only view set that returns all background tasks"""
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: TaskSerializer(many=True)})
def list(self, request: Request) -> Response:
"""List current messages and pass into Serializer"""
return Response(TaskSerializer(TaskInfo.all().values(), many=True).data)
@action(detail=True, methods=["post"])
# pylint: disable=invalid-name
def retry(self, request: Request, pk=None) -> Response:
"""Retry task"""
task = TaskInfo.by_name(pk)
if not task:
raise Http404
try:
task_module = import_module(task.task_call_module)
task_func = getattr(task_module, task.task_call_func)
task_func.delay(*task.task_call_args, **task.task_call_kwargs)
messages.success(
self.request,
_(
"Successfully re-scheduled Task %(name)s!"
% {"name": task.task_name}
),
)
return Response(
{
"successful": True,
}
)
except ImportError:
# if we get an import error, the module path has probably changed
task.delete()
return Response({"successful": False})

View File

@ -1,9 +1,35 @@
"""YAML fields""" """Additional fields"""
import yaml import yaml
from django import forms from django import forms
from django.utils.datastructures import MultiValueDict
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class ArrayFieldSelectMultiple(forms.SelectMultiple):
"""This is a Form Widget for use with a Postgres ArrayField. It implements
a multi-select interface that can be given a set of `choices`.
You can provide a `delimiter` keyword argument to specify the delimeter used.
https://gist.github.com/stephane/00e73c0002de52b1c601"""
def __init__(self, *args, **kwargs):
# Accept a `delimiter` argument, and grab it (defaulting to a comma)
self.delimiter = kwargs.pop("delimiter", ",")
super().__init__(*args, **kwargs)
def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
# Normally, we'd want a list here, which is what we get from the
# SelectMultiple superclass, but the SimpleArrayField expects to
# get a delimited string, so we're doing a little extra work.
return self.delimiter.join(data.getlist(name))
return data.get(name)
def get_context(self, name, value, attrs):
return super().get_context(name, value.split(self.delimiter), attrs)
class CodeMirrorWidget(forms.Textarea): class CodeMirrorWidget(forms.Textarea):
"""Custom Textarea-based Widget that triggers a CodeMirror editor""" """Custom Textarea-based Widget that triggers a CodeMirror editor"""
@ -49,7 +75,9 @@ class YAMLField(forms.JSONField):
converted = yaml.safe_load(value) converted = yaml.safe_load(value)
except yaml.YAMLError: except yaml.YAMLError:
raise forms.ValidationError( raise forms.ValidationError(
self.error_messages["invalid"], code="invalid", params={"value": value}, self.error_messages["invalid"],
code="invalid",
params={"value": value},
) )
if isinstance(converted, str): if isinstance(converted, str):
return YAMLString(converted) return YAMLString(converted)

View File

@ -3,6 +3,7 @@ from django.core.cache import cache
from requests import RequestException, get from requests import RequestException, get
from structlog import get_logger from structlog import get_logger
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from passbook.root.celery import CELERY_APP from passbook.root.celery import CELERY_APP
LOGGER = get_logger() LOGGER = get_logger()
@ -10,8 +11,8 @@ VERSION_CACHE_KEY = "passbook_latest_version"
VERSION_CACHE_TIMEOUT = 2 * 60 * 60 # 2 hours VERSION_CACHE_TIMEOUT = 2 * 60 * 60 # 2 hours
@CELERY_APP.task() @CELERY_APP.task(bind=True, base=MonitoredTask)
def update_latest_version(): def update_latest_version(self: MonitoredTask):
"""Update latest version info""" """Update latest version info"""
try: try:
data = get( data = get(
@ -19,5 +20,11 @@ def update_latest_version():
).json() ).json()
tag_name = data.get("tag_name") tag_name = data.get("tag_name")
cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], VERSION_CACHE_TIMEOUT) cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], VERSION_CACHE_TIMEOUT)
except (RequestException, IndexError): self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL, ["Successfully updated latest Version"]
)
)
except (RequestException, IndexError) as exc:
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))

View File

@ -18,6 +18,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>
@ -62,18 +63,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-applications pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Applications.' %} {% trans 'No Applications.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any application." %}
{% else %}
{% trans 'Currently no applications exist. Click the button below to create one.' %} {% trans 'Currently no applications exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -25,7 +25,7 @@
<li class="pf-c-nav__item"> <li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:overview' %}" <a href="{% url 'passbook_admin:overview' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:overview' %}"> class="pf-c-nav__link {% is_active 'passbook_admin:overview' %}">
{% trans 'System Status' %} {% trans 'Overview' %}
</a> </a>
</li> </li>
<li class="pf-c-nav__item"> <li class="pf-c-nav__item">
@ -146,6 +146,12 @@
{% trans 'Groups' %} {% trans 'Groups' %}
</a> </a>
</li> </li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:tasks' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:tasks' %}">
{% trans 'System Tasks' %}
</a>
</li>
</ul> </ul>
</nav> </nav>
</div> </div>

View File

@ -18,6 +18,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>
@ -64,18 +65,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-key pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Certificates.' %} {% trans 'No Certificates.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any certificates." %}
{% else %}
{% trans 'Currently no certificates exist. Click the button below to create one.' %} {% trans 'Currently no certificates exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -18,6 +18,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-secondary" type="button">{% trans 'Import' %}</a> <a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-secondary" type="button">{% trans 'Import' %}</a>
@ -69,18 +70,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-process-automation pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Flows.' %} {% trans 'No Flows.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any flows." %}
{% else %}
{% trans 'Currently no flows exist. Click the button below to create one.' %} {% trans 'Currently no flows exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Import' %}</a> <a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Import' %}</a>

View File

@ -19,6 +19,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}" <a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}"
class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
@ -61,18 +62,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-users pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Groups.' %} {% trans 'No Groups.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any groups." %}
{% else %}
{% trans 'Currently no group exist. Click the button below to create one.' %} {% trans 'Currently no group exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -20,6 +20,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:outpost-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:outpost-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>
@ -47,30 +48,41 @@
{{ outpost.providers.all.select_subclasses|join:", " }} {{ outpost.providers.all.select_subclasses|join:", " }}
</span> </span>
</td> </td>
<td role="cell"> {% with states=outpost.state %}
{% with health=outpost.deployment_health %} {% if states|length > 0 %}
{% if health %} <td role="cell">
<i class="fas fa-check pf-m-success"></i> {{ health|naturaltime }} {% for state in states %}
{% else %} <div>
<i class="fas fa-times pf-m-danger"></i> Unhealthy {% if state.last_seen %}
{% endif %} <i class="fas fa-check pf-m-success"></i> {{ state.last_seen|naturaltime }}
{% endwith %}
</td>
<td role="cell">
<span>
{% with ver=outpost.deployment_version %}
{% if ver.outdated %}
{% if ver.version == "" %}
<i class="fas fa-times pf-m-danger"></i> -
{% else %} {% else %}
<i class="fas fa-times pf-m-danger"></i> {% blocktrans with is=ver.version should=ver.should %}{{ is }}, should be {{ should }}{% endblocktrans %} <i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
{% endif %} {% endif %}
{% else %} </div>
<i class="fas fa-check pf-m-success"></i> {{ ver.version }} {% endfor %}
{% endif %} </td>
{% endwith %} <td role="cell">
</span> {% for state in states %}
</td> <div>
{% if not state.version %}
<i class="fas fa-question-circle"></i>
{% elif state.version_outdated %}
<i class="fas fa-times pf-m-danger"></i> {% blocktrans with is=state.version should=state.version_should %}{{ is }}, should be {{ should }}{% endblocktrans %}
{% else %}
<i class="fas fa-check pf-m-success"></i> {{ state.version }}
{% endif %}
</div>
{% endfor %}
</td>
{% else %}
<td role="cell">
<i class="fas fa-question-circle"></i>
</td>
<td role="cell">
<i class="fas fa-question-circle"></i>
</td>
{% endif %}
{% endwith %}
<td> <td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-update' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> <a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-update' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-delete' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> <a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-delete' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
@ -83,18 +95,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Outposts.' %} {% trans 'No Outposts.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any outposts." %}
{% else %}
{% trans 'Currently no outposts exist. Click the button below to create one.' %} {% trans 'Currently no outposts exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:outpost-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:outpost-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -1,6 +1,7 @@
{% extends "administration/base.html" %} {% extends "administration/base.html" %}
{% load i18n %} {% load i18n %}
{% load static %}
{% block content %} {% block content %}
<section class="pf-c-page__main-section pf-m-light"> <section class="pf-c-page__main-section pf-m-light">
@ -10,139 +11,123 @@
</section> </section>
<section class="pf-c-page__main-section"> <section class="pf-c-page__main-section">
<div class="pf-l-gallery pf-m-gutter"> <div class="pf-l-gallery pf-m-gutter">
<a href="{% url 'passbook_admin:applications' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 3;grid-row-end: span 2;">
<div class="pf-c-card__header"> <div class="pf-c-card__header">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-applications"></i> {% trans 'Applications' %} <i class="pf-icon pf-icon-server"></i> {% trans 'Logins over the last 24 hours' %}
</div>
</div>
<div class="pf-c-card__body" style="position: relative; height:100%; width:100%">
<canvas id="logins-last-metrics"></canvas>
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 2;grid-row-end: span 3;">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Apps with most usage' %}
</div> </div>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<p class="aggregate-status"> <table class="pf-c-table pf-m-compact" role="grid">
<i class="fa fa-check-circle"></i> {{ application_count }} <thead>
</p> <tr role="row">
<th role="columnheader" scope="col">{% trans 'Application' %}</th>
<th role="columnheader" scope="col">{% trans 'Logins' %}</th>
<th role="columnheader" scope="col"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for app in most_used_applications %}
<tr role="row">
<td role="cell">
{{ app.application.name }}
</td>
<td role="cell">
{{ app.total_logins }}
</td>
<td role="cell">
<progress value="{{ app.total_logins }}" max="{{ most_used_applications.0.total_logins }}"></progress>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</a> </div>
<a href="{% url 'passbook_admin:sources' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header"> <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-middleware"></i> {% trans 'Sources' %}
</div>
</div>
<div class="pf-c-card__body">
<p class="aggregate-status">
<i class="fa fa-check-circle"></i> {{ source_count }}
</p>
</div>
</a>
<a href="{% url 'passbook_admin:providers' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %} <i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
</div> </div>
<a href="{% url 'passbook_admin:providers' %}">
<i class="fa fa-external-link-alt"> </i>
</a>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
{% if providers_without_application.exists %} {% if providers_without_application.exists %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> {{ provider_count }} <i class="fa fa-exclamation-triangle"></i> {{ provider_count }}
</p> </p>
<p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p> <p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p>
{% else %} {% else %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-check-circle"></i> {{ provider_count }} <i class="fa fa-check-circle"></i> {{ provider_count }}
</p> </p>
{% endif %} {% endif %}
</div> </div>
</a> </div>
<a href="{% url 'passbook_admin:stages' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header"> <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Stages' %}
</div>
</div>
<div class="pf-c-card__body">
{% if stage_count < 1 %}
<p class="aggregate-status">
<i class="pficon-error-circle-o"></i> {{ stage_count }}
</p>
<p>{% trans 'No Stages configured. No Users will be able to login.' %}"></p>
{% else %}
<p class="aggregate-status">
<i class="fa fa-check-circle"></i> {{ stage_count }}
</p>
{% endif %}
</div>
</a>
<a href="{% url 'passbook_admin:stages' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-topology"></i> {% trans 'Flows' %}
</div>
</div>
<div class="pf-c-card__body">
<p class="aggregate-status">
<i class="fa fa-check-circle"></i> {{ flow_count }}
</p>
</div>
</a>
<a href="{% url 'passbook_admin:policies' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %} <i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
</div> </div>
<a href="{% url 'passbook_admin:policies' %}">
<i class="fa fa-external-link-alt"> </i>
</a>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
{% if policies_without_binding %} {% if policies_without_binding %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> {{ policy_count }} <i class="fa fa-exclamation-triangle"></i> {{ policy_count }}
</p> </p>
<p>{% trans 'Policies without binding exist.' %}</p> <p>{% trans 'Policies without binding exist.' %}</p>
{% else %} {% else %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-check-circle"></i> {{ policy_count }} <i class="fa fa-check-circle"></i> {{ policy_count }}
</p> </p>
{% endif %} {% endif %}
</div> </div>
</a> </div>
<a href="{% url 'passbook_admin:stage-invitations' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header"> <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-migration"></i> {% trans 'Invitation' %}
</div>
</div>
<div class="pf-c-card__body">
<p class="aggregate-status">
<i class="fa fa-check-circle"></i> {{ invitation_count }}
</p>
</div>
</a>
<a href="{% url 'passbook_admin:users' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %} <i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
</div> </div>
<a href="{% url 'passbook_admin:users' %}">
<i class="fa fa-external-link-alt"> </i>
</a>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-check-circle"></i> {{ user_count }} <i class="fa fa-check-circle"></i> {{ user_count }}
</p> </p>
</div> </div>
</a> </div>
<div class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header"> <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %} <i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
</div> </div>
<a href="https://github.com/BeryJu/passbook/releases" target="_blank">
<i class="fa fa-external-link-alt"> </i>
</a>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<p class="aggregate-status"> <p class="pb-aggregate-card">
{% if version >= version_latest %} {% if version >= version_latest %}
<i class="fa fa-check-circle"></i> {{ version }} <i class="fa fa-check-circle"></i> {{ version }}
{% else %} {% else %}
@ -161,97 +146,192 @@
</div> </div>
</div> </div>
<div class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header"> <div class="pf-c-card__header">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %} <i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %}
</div> </div>
</div> </div>
<fetch-fill-slot class="pf-c-card__body" url="{% url 'passbook_api:admin_overview-list' %}" key="worker_count">
<div slot="value < 1">
<p class="pb-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> <span data-value></span>
</p>
<p>{% trans 'No workers connected.' %}</p>
</div>
<div slot="value >= 1">
<p class="pb-aggregate-card">
<i class="fa fa-check-circle"></i> <span data-value></span>
</p>
</div>
<div>
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</fetch-fill-slot>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
</div>
<a data-target="modal" data-modal="clearPolicyCache">
<i class="fa fa-trash"> </i>
</a>
</div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
{% if worker_count < 1 %} {% if cached_policies < 1 %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> {{ worker_count }} <i class="fa fa-exclamation-triangle"></i> {{ cached_policies }}
</p> </p>
<p>{% trans 'No workers connected.' %}</p> <p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
{% else %} {% else %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-check-circle"></i> {{ worker_count }} <i class="fa fa-check-circle"></i> {{ cached_policies }}
</p> </p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<a class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact" data-target="modal" data-modal="clearCacheModalRoot"> <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header"> <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
</div>
</div>
<div class="pf-c-card__body">
{% if cached_policies < 1 %}
<p class="aggregate-status">
<i class="fa fa-exclamation-triangle"></i> {{ cached_policies }}
</p>
<p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
{% else %}
<p class="aggregate-status">
<i class="fa fa-check-circle"></i> {{ cached_policies }}
</p>
{% endif %}
</div>
</a>
<div class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %} <i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %}
</div> </div>
<a data-target="modal" data-modal="clearFlowCache">
<i class="fa fa-trash"> </i>
</a>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
{% if cached_flows < 1 %} {% if cached_flows < 1 %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<span class="fa fa-exclamation-triangle"></span> {{ cached_flows }} <span class="fa fa-exclamation-triangle"></span> {{ cached_flows }}
</p> </p>
<p>{% trans 'No flows cached.' %}</p> <p>{% trans 'No flows cached.' %}</p>
{% else %} {% else %}
<p class="aggregate-status"> <p class="pb-aggregate-card">
<i class="fa fa-check-circle"></i> {{ cached_flows }} <i class="fa fa-check-circle"></i> {{ cached_flows }}
</p> </p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</section> </div>
</div> </section>
<div class="pf-c-backdrop" id="clearCacheModalRoot" hidden>
<div class="pf-c-backdrop" id="clearPolicyCache" hidden>
<div class="pf-l-bullseye"> <div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-sm" role="dialog"> <div class="pf-c-modal-box pf-m-sm" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog"> <button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i> <i class="fas fa-times" aria-hidden="true"></i>
</button> </button>
<div class="pf-c-modal-box__header"> <div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Cache' %}?</h1> <h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Policy Cache' %}?</h1>
</div> </div>
<div class="pf-c-modal-box__body" id="modal-description"> <div class="pf-c-modal-box__body" id="modal-description">
<form method="post" id="clearForm"> <form method="post" id="clear_policies">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="clear"> <input type="hidden" name="clear_policies">
<p> <p>
{% blocktrans %} {% blocktrans %}
Are you sure you want to clear the cache? This includes all user sessions and all cached Policy results. Are you sure you want to clear the policy cache? This will cause all policies to be re-evaluated on their next usage.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<h3>
{% blocktrans %}
This will also log you out.
{% endblocktrans %}
</h3>
</form> </form>
</div> </div>
<footer class="pf-c-modal-box__footer pf-m-align-left"> <footer class="pf-c-modal-box__footer pf-m-align-left">
<button form="clearForm" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button> <button form="clear_policies" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button> <button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
</footer> </footer>
</div> </div>
</div> </div>
</div> </div>
<div class="pf-c-backdrop" id="clearFlowCache" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-sm" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Flow Cache' %}?</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<form method="post" id="clear_flows">
{% csrf_token %}
<input type="hidden" name="clear_flows">
<p>
{% blocktrans %}
Are you sure you want to clear the flow cache? This will cause all flows to be re-evaluated on their next usage.
{% endblocktrans %}
</p>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button form="clear_flows" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
</footer>
</div>
</div>
</div>
<script src="{% static 'node_modules/chart.js/dist/Chart.bundle.min.js' %}"></script>
<script>
var ctx = document.getElementById('logins-last-metrics').getContext('2d');
fetch("{% url 'passbook_api:admin_metrics-list' %}").then(r => r.json()).then(r => {
var myChart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [
{
label: 'Failed Logins',
backgroundColor: "rgba(201, 25, 11, .5)",
spanGaps: true,
data: r.logins_failed_per_1h,
},
{
label: 'Successful Logins',
backgroundColor: "rgba(189, 229, 184, .5)",
spanGaps: true,
data: r.logins_per_1h,
},
]
},
options: {
maintainAspectRatio: false,
spanGaps: true,
scales: {
xAxes: [{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
type: 'time',
offset: true,
ticks: {
callback: function (value, index, values) {
const date = new Date();
const delta = (date - values[index].value);
const ago = Math.round(delta / 1000 / 3600);
return `${ago} Hours ago`;
},
autoSkip: true,
maxTicksLimit: 8
}
}],
yAxes: [{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
}
}]
}
}
});
});
</script>
{% endblock %} {% endblock %}

View File

@ -18,6 +18,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">
@ -78,18 +79,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-infrastructure pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Policies.' %} {% trans 'No Policies.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any policies." %}
{% else %}
{% trans 'Currently no policies exist. Click the button below to create one.' %} {% trans 'Currently no policies exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">

View File

@ -75,7 +75,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}

View File

@ -19,6 +19,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">
@ -72,18 +73,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-blueprint pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Property Mappings.' %} {% trans 'No Property Mappings.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any property mappings." %}
{% else %}
{% trans 'Currently no property mappings exist. Click the button below to create one.' %} {% trans 'Currently no property mappings exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">

View File

@ -20,6 +20,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">
@ -91,18 +92,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon-integration pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Providers.' %} {% trans 'No Providers.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any providers." %}
{% else %}
{% trans 'Currently no providers exist. Click the button below to create one.' %} {% trans 'Currently no providers exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">

View File

@ -20,6 +20,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">
@ -85,18 +86,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-middleware pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Sources.' %} {% trans 'No Sources.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any sources." %}
{% else %}
{% trans 'Currently no sources exist. Click the button below to create one.' %} {% trans 'Currently no sources exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">

View File

@ -11,8 +11,7 @@
<i class="pf-icon pf-icon-plugged"></i> <i class="pf-icon pf-icon-plugged"></i>
{% trans 'Stages' %} {% trans 'Stages' %}
</h1> </h1>
<p>{% trans "Stages are single steps of a Flow that a user is guided through." %} <p>{% trans "Stages are single steps of a Flow that a user is guided through." %}</p>
</p>
</div> </div>
</section> </section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile"> <section class="pf-c-page__main-section pf-m-no-padding-mobile">
@ -20,6 +19,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">
@ -81,18 +81,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Stages.' %} {% trans 'No Stages.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any stages." %}
{% else %}
{% trans 'Currently no stages exist. Click the button below to create one.' %} {% trans 'Currently no stages exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<div class="pf-c-dropdown"> <div class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">

View File

@ -81,7 +81,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}

View File

@ -19,6 +19,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:stage-invitation-create' %}?back={{ request.get_full_path }}" <a href="{% url 'passbook_admin:stage-invitation-create' %}?back={{ request.get_full_path }}"
class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
@ -54,18 +55,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-migration pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Invitations.' %} {% trans 'No Invitations.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any invitations." %}
{% else %}
{% trans 'Currently no invitations exist. Click the button below to create one.' %} {% trans 'Currently no invitations exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:stage-invitation-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:stage-invitation-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -19,6 +19,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>
@ -80,18 +81,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Stage Prompts.' %} {% trans 'No Stage Prompts.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any stage prompts." %}
{% else %}
{% trans 'Currently no stage prompts exist. Click the button below to create one.' %} {% trans 'Currently no stage prompts exist. Click the button below to create one.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -0,0 +1,77 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load humanize %}
{% load passbook_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-automation"></i>
{% trans 'System Tasks' %}
</h1>
<p>{% trans "Long-running operations which passbook executes in the background." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'Description' %}</th>
<th role="columnheader" scope="col">{% trans 'Last Run' %}</th>
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
<th role="columnheader" scope="col">{% trans 'Messages' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for task in object_list %}
<tr role="row">
<th role="columnheader">
<pre>{{ task.task_name }}</pre>
</th>
<td role="cell">
<span>
{{ task.task_description }}
</span>
</td>
<td role="cell">
<span>
{{ task.finish_timestamp|naturaltime }}
</span>
</td>
<td role="cell">
<span>
{% if task.result.status == task_successful %}
<i class="fas fa-check pf-m-success"></i> {% trans 'Successful' %}
{% elif task.result.status == task_warning %}
<i class="fas fa-exclamation-triangle pf-m-warning"></i> {% trans 'Warning' %}
{% elif task.result.status == task_error %}
<i class="fas fa-times pf-m-danger"></i> {% trans 'Error' %}
{% else %}
<i class="fas fa-question-circle"></i> {% trans 'Unknown' %}
{% endif %}
</span>
</td>
<td>
{% for message in task.result.messages %}
<div>
{{ message }}
</div>
{% endfor %}
</td>
<td>
<button is="action-button" class="pf-c-button pf-m-primary" url="{% url 'passbook_api:admin_system_tasks-retry' pk=task.task_name %}">
{% trans 'Retry Task' %}
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@ -18,13 +18,14 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
</div> </div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead> <thead>
<tr role="row"> <tr role="row">
<th role="columnheader" scope="col">{% trans 'Token' %}</th> <th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'User' %}</th> <th role="columnheader" scope="col">{% trans 'User' %}</th>
<th role="columnheader" scope="col">{% trans 'Expires?' %}</th> <th role="columnheader" scope="col">{% trans 'Expires?' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th> <th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th>
@ -35,9 +36,7 @@
{% for token in object_list %} {% for token in object_list %}
<tr role="row"> <tr role="row">
<th role="columnheader"> <th role="columnheader">
<div> <div>{{ token.identifier }}</div>
<div>{{ token.pk.hex }}</div>
</div>
</th> </th>
<td role="cell"> <td role="cell">
<span> <span>
@ -65,18 +64,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Tokens.' %} {% trans 'No Tokens.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any token." %}
{% else %}
{% trans 'Currently no tokens exist.' %} {% trans 'Currently no tokens exist.' %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,42 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load passbook_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
{% block above_form %}
<h1>
{% blocktrans with object_type=object|verbose_name %}
Disable {{ object_type }}
{% endblocktrans %}
</h1>
{% endblock %}
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-l-stack">
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form action="" method="post" class="pf-c-form">
{% csrf_token %}
<p>
{% blocktrans with object_type=object|verbose_name name=object %}
Are you sure you want to disable {{ object_type }} "{{ object }}"?
{% endblocktrans %}
</p>
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__actions">
<input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Disable' %}" />
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -17,6 +17,7 @@
{% if object_list %} {% if object_list %}
<div class="pf-c-toolbar"> <div class="pf-c-toolbar">
<div class="pf-c-toolbar__content"> <div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select"> <div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>
@ -53,7 +54,11 @@
</td> </td>
<td> <td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> <a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> {% if user.is_active %}
<a class="pf-c-button pf-m-warning" href="{% url 'passbook_admin:user-disable' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Disable' %}</a>
{% else %}
<a class="pf-c-button pf-m-primary" href="{% url 'passbook_admin:user-enable' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Enable' %}</a>
{% endif %}
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a> <a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a> <a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
</td> </td>
@ -61,18 +66,27 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom"> <div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% else %} {% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state"> <div class="pf-c-empty-state">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> <i class="pf-icon pf-icon-user pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg"> <h1 class="pf-c-title pf-m-lg">
{% trans 'No Users.' %} {% trans 'No Users.' %}
</h1> </h1>
<div class="pf-c-empty-state__body"> <div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any users." %}
{% else %}
{% trans 'Currently no users exist. How did you even get here.' %} {% trans 'Currently no users exist. How did you even get here.' %}
{% endif %}
</div> </div>
<a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> <a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div> </div>

View File

@ -1,4 +1,4 @@
{% extends "administration/base.html" %} {% extends container_template|default:"administration/base.html" %}
{% load i18n %} {% load i18n %}
{% load passbook_utils %} {% load passbook_utils %}

View File

@ -17,6 +17,7 @@ from passbook.admin.views import (
stages_bindings, stages_bindings,
stages_invitations, stages_invitations,
stages_prompts, stages_prompts,
tasks,
tokens, tokens,
users, users,
) )
@ -191,10 +192,20 @@ urlpatterns = [
), ),
# Flows # Flows
path("flows/", flows.FlowListView.as_view(), name="flows"), path("flows/", flows.FlowListView.as_view(), name="flows"),
path("flows/create/", flows.FlowCreateView.as_view(), name="flow-create",),
path("flows/import/", flows.FlowImportView.as_view(), name="flow-import",),
path( path(
"flows/<uuid:pk>/update/", flows.FlowUpdateView.as_view(), name="flow-update", "flows/create/",
flows.FlowCreateView.as_view(),
name="flow-create",
),
path(
"flows/import/",
flows.FlowImportView.as_view(),
name="flow-import",
),
path(
"flows/<uuid:pk>/update/",
flows.FlowUpdateView.as_view(),
name="flow-update",
), ),
path( path(
"flows/<uuid:pk>/execute/", "flows/<uuid:pk>/execute/",
@ -202,10 +213,14 @@ urlpatterns = [
name="flow-execute", name="flow-execute",
), ),
path( path(
"flows/<uuid:pk>/export/", flows.FlowExportView.as_view(), name="flow-export", "flows/<uuid:pk>/export/",
flows.FlowExportView.as_view(),
name="flow-export",
), ),
path( path(
"flows/<uuid:pk>/delete/", flows.FlowDeleteView.as_view(), name="flow-delete", "flows/<uuid:pk>/delete/",
flows.FlowDeleteView.as_view(),
name="flow-delete",
), ),
# Property Mappings # Property Mappings
path( path(
@ -233,6 +248,10 @@ urlpatterns = [
path("users/create/", users.UserCreateView.as_view(), name="user-create"), path("users/create/", users.UserCreateView.as_view(), name="user-create"),
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"), path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"), path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
path(
"users/<int:pk>/disable/", users.UserDisableView.as_view(), name="user-disable"
),
path("users/<int:pk>/enable/", users.UserEnableView.as_view(), name="user-enable"),
path( path(
"users/<int:pk>/reset/", "users/<int:pk>/reset/",
users.UserPasswordResetView.as_view(), users.UserPasswordResetView.as_view(),
@ -273,9 +292,15 @@ urlpatterns = [
name="certificatekeypair-delete", name="certificatekeypair-delete",
), ),
# Outposts # Outposts
path("outposts/", outposts.OutpostListView.as_view(), name="outposts",),
path( path(
"outposts/create/", outposts.OutpostCreateView.as_view(), name="outpost-create", "outposts/",
outposts.OutpostListView.as_view(),
name="outposts",
),
path(
"outposts/create/",
outposts.OutpostCreateView.as_view(),
name="outpost-create",
), ),
path( path(
"outposts/<uuid:pk>/update/", "outposts/<uuid:pk>/update/",
@ -287,4 +312,10 @@ urlpatterns = [
outposts.OutpostDeleteView.as_view(), outposts.OutpostDeleteView.as_view(),
name="outpost-delete", name="outpost-delete",
), ),
# Tasks
path(
"tasks/",
tasks.TaskListView.as_view(),
name="tasks",
),
] ]

View File

@ -12,6 +12,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.core.forms.applications import ApplicationForm from passbook.core.forms.applications import ApplicationForm
@ -20,7 +21,11 @@ from passbook.lib.views import CreateAssignPermView
class ApplicationListView( class ApplicationListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all applications""" """Show list of all applications"""
@ -29,6 +34,15 @@ class ApplicationListView(
ordering = "name" ordering = "name"
template_name = "administration/application/list.html" template_name = "administration/application/list.html"
search_fields = [
"name",
"slug",
"meta_launch_url",
"meta_icon_url",
"meta_description",
"meta_publisher",
]
class ApplicationCreateView( class ApplicationCreateView(
SuccessMessageMixin, SuccessMessageMixin,

View File

@ -12,6 +12,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.crypto.forms import CertificateKeyPairForm from passbook.crypto.forms import CertificateKeyPairForm
@ -20,7 +21,11 @@ from passbook.lib.views import CreateAssignPermView
class CertificateKeyPairListView( class CertificateKeyPairListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all keypairs""" """Show list of all keypairs"""
@ -29,6 +34,8 @@ class CertificateKeyPairListView(
ordering = "name" ordering = "name"
template_name = "administration/certificatekeypair/list.html" template_name = "administration/certificatekeypair/list.html"
search_fields = ["name"]
class CertificateKeyPairCreateView( class CertificateKeyPairCreateView(
SuccessMessageMixin, SuccessMessageMixin,

View File

@ -14,6 +14,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.flows.forms import FlowForm, FlowImportForm from passbook.flows.forms import FlowForm, FlowImportForm
@ -28,7 +29,11 @@ from passbook.lib.views import CreateAssignPermView
class FlowListView( class FlowListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all flows""" """Show list of all flows"""
@ -36,6 +41,7 @@ class FlowListView(
permission_required = "passbook_flows.view_flow" permission_required = "passbook_flows.view_flow"
ordering = "name" ordering = "name"
template_name = "administration/flow/list.html" template_name = "administration/flow/list.html"
search_fields = ["name", "slug", "designation", "title"]
class FlowCreateView( class FlowCreateView(
@ -100,7 +106,9 @@ class FlowDebugExecuteView(LoginRequiredMixin, PermissionRequiredMixin, DetailVi
plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user}) plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user})
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(
"passbook_flows:flow-executor-shell", self.request.GET, flow_slug=flow.slug, "passbook_flows:flow-executor-shell",
self.request.GET,
flow_slug=flow.slug,
) )

View File

@ -12,6 +12,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.core.forms.groups import GroupForm from passbook.core.forms.groups import GroupForm
@ -20,7 +21,11 @@ from passbook.lib.views import CreateAssignPermView
class GroupListView( class GroupListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all groups""" """Show list of all groups"""
@ -28,6 +33,7 @@ class GroupListView(
permission_required = "passbook_core.view_group" permission_required = "passbook_core.view_group"
ordering = "name" ordering = "name"
template_name = "administration/group/list.html" template_name = "administration/group/list.html"
search_fields = ["name", "attributes"]
class GroupCreateView( class GroupCreateView(

View File

@ -15,6 +15,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.lib.views import CreateAssignPermView from passbook.lib.views import CreateAssignPermView
@ -23,7 +24,11 @@ from passbook.outposts.models import Outpost, OutpostConfig
class OutpostListView( class OutpostListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all outposts""" """Show list of all outposts"""
@ -31,6 +36,7 @@ class OutpostListView(
permission_required = "passbook_outposts.view_outpost" permission_required = "passbook_outposts.view_outpost"
ordering = "name" ordering = "name"
template_name = "administration/outpost/list.html" template_name = "administration/outpost/list.html"
search_fields = ["name", "_config"]
class OutpostCreateView( class OutpostCreateView(

View File

@ -1,19 +1,22 @@
"""passbook administration overview""" """passbook administration overview"""
from typing import Union from typing import Union
from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.shortcuts import redirect, reverse from django.db.models import Count
from django.db.models.fields.json import KeyTextTransform
from django.views.generic import TemplateView from django.views.generic import TemplateView
from packaging.version import LegacyVersion, Version, parse from packaging.version import LegacyVersion, Version, parse
from structlog import get_logger
from passbook import __version__ from passbook import __version__
from passbook.admin.mixins import AdminRequiredMixin from passbook.admin.mixins import AdminRequiredMixin
from passbook.admin.tasks import VERSION_CACHE_KEY, update_latest_version from passbook.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from passbook.core.models import Application, Provider, Source, User from passbook.audit.models import Event, EventAction
from passbook.flows.models import Flow, Stage from passbook.core.models import Provider, User
from passbook.policies.models import Policy from passbook.policies.models import Policy
from passbook.root.celery import CELERY_APP
from passbook.stages.invitation.models import Invitation LOGGER = get_logger()
class AdministrationOverviewView(AdminRequiredMixin, TemplateView): class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
@ -23,31 +26,46 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
"""Handle post (clear cache from modal)""" """Handle post (clear cache from modal)"""
if "clear" in self.request.POST: if "clear_policies" in self.request.POST:
cache.clear() keys = cache.keys("policy_*")
return redirect(reverse("passbook_flows:default-authentication")) cache.delete_many(keys)
LOGGER.debug("Cleared Policy cache", keys=len(keys))
if "clear_flows" in self.request.POST:
keys = cache.keys("flow_*")
cache.delete_many(keys)
LOGGER.debug("Cleared flow cache", keys=len(keys))
return self.get(*args, **kwargs) return self.get(*args, **kwargs)
def get_latest_version(self) -> Union[LegacyVersion, Version]: def get_latest_version(self) -> Union[LegacyVersion, Version]:
"""Get latest version from cache""" """Get latest version from cache"""
version_in_cache = cache.get(VERSION_CACHE_KEY) version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache: if not version_in_cache:
update_latest_version.delay() if not settings.DEBUG:
update_latest_version.delay()
return parse(__version__) return parse(__version__)
return parse(version_in_cache) return parse(version_in_cache)
def get_most_used_applications(self):
"""Get Most used applications, total login counts and unique users that have used them."""
return (
Event.objects.filter(action=EventAction.AUTHORIZE_APPLICATION)
.exclude(context__authorized_application=None)
.annotate(application=KeyTextTransform("authorized_application", "context"))
.annotate(user_pk=KeyTextTransform("pk", "user"))
.values("application")
.annotate(total_logins=Count("application"))
.annotate(unique_users=Count("user_pk", distinct=True))
.values("unique_users", "application", "total_logins")
.order_by("-total_logins")[:15]
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs["application_count"] = len(Application.objects.all())
kwargs["policy_count"] = len(Policy.objects.all()) kwargs["policy_count"] = len(Policy.objects.all())
kwargs["user_count"] = len(User.objects.all()) - 1 # Remove anonymous user kwargs["user_count"] = len(User.objects.all()) - 1 # Remove anonymous user
kwargs["provider_count"] = len(Provider.objects.all()) kwargs["provider_count"] = len(Provider.objects.all())
kwargs["source_count"] = len(Source.objects.all())
kwargs["stage_count"] = len(Stage.objects.all())
kwargs["flow_count"] = len(Flow.objects.all())
kwargs["invitation_count"] = len(Invitation.objects.all())
kwargs["version"] = parse(__version__) kwargs["version"] = parse(__version__)
kwargs["version_latest"] = self.get_latest_version() kwargs["version_latest"] = self.get_latest_version()
kwargs["worker_count"] = len(CELERY_APP.control.ping(timeout=0.5)) kwargs["most_used_applications"] = self.get_most_used_applications()
kwargs["providers_without_application"] = Provider.objects.filter( kwargs["providers_without_application"] = Provider.objects.filter(
application=None application=None
) )

View File

@ -22,6 +22,7 @@ from passbook.admin.views.utils import (
InheritanceCreateView, InheritanceCreateView,
InheritanceListView, InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.policies.models import Policy, PolicyBinding from passbook.policies.models import Policy, PolicyBinding
@ -29,7 +30,11 @@ from passbook.policies.process import PolicyProcess, PolicyRequest
class PolicyListView( class PolicyListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, InheritanceListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
): ):
"""Show list of all policies""" """Show list of all policies"""
@ -37,6 +42,7 @@ class PolicyListView(
permission_required = "passbook_policies.view_policy" permission_required = "passbook_policies.view_policy"
ordering = "name" ordering = "name"
template_name = "administration/policy/list.html" template_name = "administration/policy/list.html"
search_fields = ["name"]
class PolicyCreateView( class PolicyCreateView(

View File

@ -14,13 +14,18 @@ from passbook.admin.views.utils import (
InheritanceCreateView, InheritanceCreateView,
InheritanceListView, InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.core.models import PropertyMapping from passbook.core.models import PropertyMapping
class PropertyMappingListView( class PropertyMappingListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, InheritanceListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
): ):
"""Show list of all property_mappings""" """Show list of all property_mappings"""
@ -28,6 +33,7 @@ class PropertyMappingListView(
permission_required = "passbook_core.view_propertymapping" permission_required = "passbook_core.view_propertymapping"
template_name = "administration/property_mapping/list.html" template_name = "administration/property_mapping/list.html"
ordering = "name" ordering = "name"
search_fields = ["name", "expression"]
class PropertyMappingCreateView( class PropertyMappingCreateView(

View File

@ -14,13 +14,18 @@ from passbook.admin.views.utils import (
InheritanceCreateView, InheritanceCreateView,
InheritanceListView, InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.core.models import Provider from passbook.core.models import Provider
class ProviderListView( class ProviderListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, InheritanceListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
): ):
"""Show list of all providers""" """Show list of all providers"""
@ -28,6 +33,7 @@ class ProviderListView(
permission_required = "passbook_core.add_provider" permission_required = "passbook_core.add_provider"
template_name = "administration/provider/list.html" template_name = "administration/provider/list.html"
ordering = "id" ordering = "id"
search_fields = ["id", "name"]
class ProviderCreateView( class ProviderCreateView(

View File

@ -14,13 +14,18 @@ from passbook.admin.views.utils import (
InheritanceCreateView, InheritanceCreateView,
InheritanceListView, InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.core.models import Source from passbook.core.models import Source
class SourceListView( class SourceListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, InheritanceListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
): ):
"""Show list of all sources""" """Show list of all sources"""
@ -28,6 +33,7 @@ class SourceListView(
permission_required = "passbook_core.view_source" permission_required = "passbook_core.view_source"
ordering = "name" ordering = "name"
template_name = "administration/source/list.html" template_name = "administration/source/list.html"
search_fields = ["name", "slug"]
class SourceCreateView( class SourceCreateView(

View File

@ -14,13 +14,18 @@ from passbook.admin.views.utils import (
InheritanceCreateView, InheritanceCreateView,
InheritanceListView, InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.flows.models import Stage from passbook.flows.models import Stage
class StageListView( class StageListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, InheritanceListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
): ):
"""Show list of all stages""" """Show list of all stages"""
@ -28,6 +33,7 @@ class StageListView(
template_name = "administration/stage/list.html" template_name = "administration/stage/list.html"
permission_required = "passbook_flows.view_stage" permission_required = "passbook_flows.view_stage"
ordering = "name" ordering = "name"
search_fields = ["name"]
class StageCreateView( class StageCreateView(

View File

@ -13,6 +13,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.lib.views import CreateAssignPermView from passbook.lib.views import CreateAssignPermView
@ -22,7 +23,11 @@ from passbook.stages.invitation.signals import invitation_created
class InvitationListView( class InvitationListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all invitations""" """Show list of all invitations"""
@ -30,6 +35,7 @@ class InvitationListView(
permission_required = "passbook_stages_invitation.view_invitation" permission_required = "passbook_stages_invitation.view_invitation"
template_name = "administration/stage_invitation/list.html" template_name = "administration/stage_invitation/list.html"
ordering = "-expires" ordering = "-expires"
search_fields = ["created_by__username", "expires", "fixed_data"]
class InvitationCreateView( class InvitationCreateView(

View File

@ -12,6 +12,7 @@ from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
SearchListMixin,
UserPaginateListMixin, UserPaginateListMixin,
) )
from passbook.lib.views import CreateAssignPermView from passbook.lib.views import CreateAssignPermView
@ -20,7 +21,11 @@ from passbook.stages.prompt.models import Prompt
class PromptListView( class PromptListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all prompts""" """Show list of all prompts"""
@ -28,6 +33,12 @@ class PromptListView(
permission_required = "passbook_stages_prompt.view_prompt" permission_required = "passbook_stages_prompt.view_prompt"
ordering = "order" ordering = "order"
template_name = "administration/stage_prompt/list.html" template_name = "administration/stage_prompt/list.html"
search_fields = [
"field_key",
"label",
"type",
"placeholder",
]
class PromptCreateView( class PromptCreateView(

View File

@ -0,0 +1,23 @@
"""passbook Tasks List"""
from typing import Any, Dict
from django.views.generic.base import TemplateView
from passbook.admin.mixins import AdminRequiredMixin
from passbook.lib.tasks import TaskInfo, TaskResultStatus
class TaskListView(AdminRequiredMixin, TemplateView):
"""Show list of all background tasks"""
template_name = "administration/task/list.html"
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
kwargs["object_list"] = sorted(
TaskInfo.all().values(), key=lambda x: x.task_name
)
kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL
kwargs["task_warning"] = TaskResultStatus.WARNING
kwargs["task_error"] = TaskResultStatus.ERROR
return kwargs

View File

@ -5,12 +5,20 @@ from django.utils.translation import gettext as _
from django.views.generic import ListView from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import DeleteMessageView, UserPaginateListMixin from passbook.admin.views.utils import (
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from passbook.core.models import Token from passbook.core.models import Token
class TokenListView( class TokenListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
): ):
"""Show list of all tokens""" """Show list of all tokens"""
@ -18,6 +26,12 @@ class TokenListView(
permission_required = "passbook_core.view_token" permission_required = "passbook_core.view_token"
ordering = "expires" ordering = "expires"
template_name = "administration/token/list.html" template_name = "administration/token/list.html"
search_fields = [
"identifier",
"intent",
"user__username",
"description",
]
class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):

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