Compare commits

..

70 Commits

Author SHA1 Message Date
e6b515e3f7 release: 2021.7.1 2021-07-27 10:35:45 +02:00
36eaecfdec build(deps): bump drf-spectacular from 0.17.2 to 0.17.3 (#1188)
Bumps [drf-spectacular](https://github.com/tfranzel/drf-spectacular) from 0.17.2 to 0.17.3.
- [Release notes](https://github.com/tfranzel/drf-spectacular/releases)
- [Changelog](https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tfranzel/drf-spectacular/compare/0.17.2...0.17.3)

---
updated-dependencies:
- dependency-name: drf-spectacular
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 09:27:06 +02:00
3973efae19 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1185)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.4 to 4.28.5.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.5/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 09:26:58 +02:00
d8492e0df5 build(deps): bump @typescript-eslint/parser in /web (#1186) 2021-07-27 08:47:31 +02:00
b64da0dd28 build(deps): bump boto3 from 1.18.6 to 1.18.7 (#1187) 2021-07-27 08:46:56 +02:00
c3ae3e02f3 website/docs: add go requirement
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 22:52:58 +02:00
7c6a96394b root: add code of conduct and PR template
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 22:49:17 +02:00
0fe43f8319 root: add contributing file
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 22:42:00 +02:00
7e32723748 website/docs: update terminology for dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 21:50:49 +02:00
577aa7ba79 web/admin: add status card for https and timedrift
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 19:58:26 +02:00
b752540800 core: fix pagination not working correctly with applications API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 19:12:23 +02:00
64c8ca9b5d web/admin: default to authentication flow for LDAP provider
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 18:47:59 +02:00
5552e0ffa7 web/admin: add notice for event_retention
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 18:47:46 +02:00
e7b7bfddd6 providers/oauth2: fix blank redirect_uri not working with TokenView
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 11:29:16 +02:00
28f970c795 build(deps): bump boto3 from 1.18.5 to 1.18.6 (#1183) 2021-07-26 08:40:05 +02:00
d1dbdfa9fe build(deps): bump chart.js from 3.4.1 to 3.5.0 in /web (#1182) 2021-07-26 08:39:57 +02:00
c4f4e3eac7 build(deps): bump rollup from 2.53.3 to 2.54.0 in /web (#1181) 2021-07-26 08:39:49 +02:00
f21ebf5488 core: add tests for flow_manager
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 23:20:38 +02:00
5615613ed1 core: fix CheckApplication's for_user flag not being checked correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 22:29:15 +02:00
669329e49c tenants: set tenant uuid in sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 22:28:09 +02:00
0587ab26e8 web/admin: fix ApplicationView's CheckAccess not sending UserID correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 21:03:32 +02:00
3c9cc9d421 Merge branch 'version-2021.7' 2021-07-24 20:07:42 +02:00
1972464a20 tenants: make event retention configurable on tenant level
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-24 20:07:12 +02:00
3041a30193 release: 2021.7.1-rc2 2021-07-24 18:32:05 +02:00
1e28a1e311 ci: fix relative path for sourcemaps
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-24 17:25:37 +02:00
5a1b912b76 web: fix lint error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 22:42:45 +02:00
464c27ef17 web: improve UI for event actions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 22:27:51 +02:00
a745022f06 website/docs: prepare 2021.7.1-rc2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 21:30:15 +02:00
0b34f70205 web/admin: fix missing dark theme for notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 21:27:56 +02:00
a4b051fcc1 web: fix icon flashing in header, fix notification header icon in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 20:57:06 +02:00
5ff3e9b418 outposts/ldap: add support for member query
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 20:00:23 +02:00
8ae7403abc core: add group filter by member username and pk
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 19:35:41 +02:00
f6e1bfdfc8 outpost: fix 100% CPU Usage when not connected to websocket
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 18:57:26 +02:00
aca3a5c458 outpost: add tracing for http client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 17:37:06 +02:00
d16c24fd53 website/docs: clear up outpost uuids
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 16:07:47 +02:00
6a8be0dc71 outposts/ldap: improve parsing of LDAP filters
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 15:41:09 +02:00
81b9b37e5e build(deps): bump @sentry/tracing from 6.9.0 to 6.10.0 in /web (#1174)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.9.0...6.10.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-23 10:11:16 +02:00
22b01962fb build(deps): bump @sentry/tracing from 6.9.0 to 6.10.0 in /website (#1175)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.9.0...6.10.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-23 10:11:05 +02:00
86cc99be35 build(deps): bump @sentry/react from 6.9.0 to 6.10.0 in /website (#1176) 2021-07-23 09:05:26 +02:00
416f917c4a build(deps): bump @sentry/browser from 6.9.0 to 6.10.0 in /web (#1177) 2021-07-23 09:05:04 +02:00
f77bece790 build(deps): bump boto3 from 1.18.4 to 1.18.5 (#1178) 2021-07-23 09:04:50 +02:00
a8dd846437 Revert "root: fix root dir for coverage"
This reverts commit 4c50769040.
2021-07-22 23:52:58 +02:00
4c50769040 root: fix root dir for coverage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 23:15:52 +02:00
34189fcc06 outposts/ldap: search users and group in parallel
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 22:55:23 +02:00
fb5c8f3d7f ci: attempt to load variable group
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 22:18:58 +02:00
049a55a761 ci: add zeus
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 21:34:17 +02:00
4cd53f3d11 ci: remove unused variables
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 21:05:55 +02:00
0d0dcf8de0 outposts/ldap: optimise backend Search API requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 20:38:30 +02:00
8cd1223081 core: add email filter for user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 20:10:42 +02:00
1b4654bb1d outposts/ldap: add tracing for LDAP bind and search
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 19:23:56 +02:00
0a3fade1fd providers/proxy: remove deprecated field
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 16:20:26 +02:00
ff64814f40 web/admin: improve UI for notification toggle
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 14:17:56 +02:00
cbeb6e58ac web: separate websocket connection from messages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 13:47:27 +02:00
285a9b8b1d website/docs: remove duplicate proxy docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:48:10 +02:00
66bfa6879d outposts/proxy: add X-Auth-Groups header to pass groups
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:47:58 +02:00
c05240afbf lib: fix outpost fake-ip not working, add tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:10:25 +02:00
7370dd5f3f outposts: ensure outpost SAs always have permissions to fake IP
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:02:20 +02:00
477c8b099e build(deps-dev): bump pylint from 2.9.4 to 2.9.5 (#1173) 2021-07-22 09:32:24 +02:00
2c761da883 build(deps): bump boto3 from 1.18.3 to 1.18.4 (#1172) 2021-07-22 09:32:16 +02:00
75070232b1 build(deps): bump codemirror from 5.62.1 to 5.62.2 in /web (#1170) 2021-07-22 09:32:08 +02:00
690b35e1a3 build(deps): bump postcss from 8.3.5 to 8.3.6 in /website (#1169) 2021-07-22 09:31:59 +02:00
bd67f2362f build(deps): bump rollup from 2.53.2 to 2.53.3 in /web (#1171) 2021-07-22 09:31:43 +02:00
896e5adce2 sources/ldap: fix lint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 00:40:55 +02:00
7f25b6311d web/admin: fix negative count for policies when more cached than total policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 00:01:28 +02:00
253f345fc4 outposts: save certificate fingerprint and check before re-fetching to cleanup logs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 23:53:43 +02:00
a3abbcec6a sources/ldap: improve error handling for property mappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 23:49:09 +02:00
70e000d327 providers/saml: improve error handling for property mappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 23:14:03 +02:00
a7467e6740 providers/oauth2: handler PropertyMapping exceptions and create event
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 22:51:39 +02:00
b3da94bbb8 core: broaden error catching for propertymappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 22:50:39 +02:00
e62f5a75e4 outposts: fix git hash not being set in outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 21:31:25 +02:00
97 changed files with 2370 additions and 651 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.7.1-rc1
current_version = 2021.7.1
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)

19
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,19 @@
<!--
👋 Hello there! Welcome.
Please check the [Contributing guidelines](https://github.com/goauthentik/authentik/blob/master/CONTRIBUTING.md#how-can-i-contribute).
-->
# Details
* **Does this resolve an issue?**
Resolves #
## Changes
### New Features
* Adds feature which does x, y, and z.
### Breaking Changes
* Adds breaking change which causes \<issue\>.
## Additional
Any further notes or comments you want to make.

View File

@ -33,14 +33,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.7.1-rc1,
beryju/authentik:2021.7.1,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.7.1-rc1,
ghcr.io/goauthentik/server:2021.7.1,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.7.1-rc1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.1', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +75,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.7.1-rc1,
beryju/authentik-proxy:2021.7.1,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.7.1-rc1,
ghcr.io/goauthentik/proxy:2021.7.1,
ghcr.io/goauthentik/proxy:latest
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.7.1-rc1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.1', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +117,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.7.1-rc1,
beryju/authentik-ldap:2021.7.1,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.7.1-rc1,
ghcr.io/goauthentik/ldap:2021.7.1,
ghcr.io/goauthentik/ldap:latest
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.7.1-rc1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.1', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -176,6 +176,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.7.1-rc1
version: authentik@2021.7.1
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: /static/dist

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
hello@beryju.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

180
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,180 @@
# Contributing to authentik
:+1::tada: Thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to authentik and its components, which are hosted in the [goauthentik Organization](https://github.com/goauthentik) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table Of Contents
[Code of Conduct](#code-of-conduct)
[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [Atom and Packages](#atom-and-packages)
* [Atom Design Decisions](#design-decisions)
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Your First Code Contribution](#your-first-code-contribution)
* [Pull Requests](#pull-requests)
[Styleguides](#styleguides)
* [Git Commit Messages](#git-commit-messages)
* [JavaScript Styleguide](#javascript-styleguide)
* [CoffeeScript Styleguide](#coffeescript-styleguide)
* [Specs Styleguide](#specs-styleguide)
* [Documentation Styleguide](#documentation-styleguide)
[Additional Notes](#additional-notes)
* [Issue and Pull Request Labels](#issue-and-pull-request-labels)
## Code of Conduct
Basically, don't be a dickhead. This is an open-source non-profit project, that is made in the free time of Volunteers. If there's something you dislike or think can be done better, tell us! We'd love to hear any suggestions for improvement.
## I don't want to read this whole thing I just have a question!!!
Either [create a question on GitHub](https://github.com/goauthentik/authentik/issues/new?assignees=&labels=question&template=question.md&title=) or join [the Discord server](https://discord.gg/jg33eMhnj6)
## What should I know before I get started?
### The components
authentik consists of a few larger components:
- *authentik* the actual application server, is described below.
- *outpost-proxy* is a Go application based on a forked version of oauth2_proxy, which does identity-aware reverse proxying.
- *outpost-ldap* is a Go LDAP server that uses the *authentik* application server as its backend
- *web* is the web frontend, both for administrating and using authentik. It is written in TypeScript using lit-html and the PatternFly CSS Library.
- *website* is the Website/documentation, which uses docusaurus.
### authentik's structure
authentik is at it's very core a Django project. It consists of many individual django applications. These applications are intended to separate concerns, and they may share code between each other.
These are the current packages:
<a id="authentik-packages"/>
```
authentik
├── admin - Administrative tasks and APIs, no models (Version updates, Metrics, system tasks)
├── api - General API Configuration (Routes, Schema and general API utilities)
├── core - Core authentik functionality, central routes, core Models
├── crypto - Cryptography, currently used to generate and hold Certificates and Private Keys
├── events - Event Log, middleware and signals to generate signals
├── flows - Flows, the FlowPlanner and the FlowExecutor, used for all flows for authentication, authorization, etc
├── lib - Generic library of functions, few dependencies on other packages.
├── managed - Handle managed models and their state.
├── outposts - Configure and deploy outposts on kubernetes and docker.
├── policies - General PolicyEngine
│   ├── dummy - A Dummy policy used for testing
│   ├── event_matcher - Match events based on different criteria
│   ├── expiry - Check when a user's password was last set
│   ├── expression - Execute any arbitrary python code
│   ├── hibp - Check a password against HaveIBeenPwned
│   ├── password - Check a password against several rules
│   └── reputation - Check the user's/client's reputation
├── providers
│   ├── ldap - Provide LDAP access to authentik users/groups using an outpost
│   ├── oauth2 - OIDC-compliant OAuth2 provider
│   ├── proxy - Provides an identity-aware proxy using an outpost
│   └── saml - SAML2 Provider
├── recovery - Generate keys to use in case you lock yourself out
├── root - Root django application, contains global settings and routes
├── sources
│   ├── ldap - Sync LDAP users from OpenLDAP or Active Directory into authentik
│   ├── oauth - OAuth1 and OAuth2 Source
│   ├── plex - Plex source
│   └── saml - SAML2 Source
├── stages
│   ├── authenticator_duo - Configure a DUO authenticator
│   ├── authenticator_static - Configure TOTP backup keys
│   ├── authenticator_totp - Configure a TOTP authenticator
│   ├── authenticator_validate - Validate any authenticator
│   ├── authenticator_webauthn - Configure a WebAuthn authenticator
│   ├── captcha - Make the user pass a captcha
│   ├── consent - Let the user decide if they want to consent to an action
│   ├── deny - Static deny, can be used with policies
│   ├── dummy - Dummy stage to test
│   ├── email - Send the user an email and block execution until they click the link
│   ├── identification - Identify a user with any combination of fields
│   ├── invitation - Invitation system to limit flows to certain users
│   ├── password - Password authentication
│   ├── prompt - Arbitrary prompts
│   ├── user_delete - Delete the currently pending user
│   ├── user_login - Login the currently pending user
│   ├── user_logout - Logout the currently pending user
│   └── user_write - Write any currenetly pending data to the user.
└── tenants - Soft tennancy, configure defaults and branding per domain
```
This django project is running in gunicorn, which spawns multiple workers and threads. Gunicorn is run from a lightweight Go application which reverse-proxies it, handles static files and will eventually gain more functionality as more code is migrated to go.
There are also several background tasks which run in Celery, the root celery application is defined in `authentik.root.celery`.
## How Can I Contribute?
### Reporting Bugs
This section guides you through submitting a bug report for authentik. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Whenever authentik encounters an error, it will be logged as an Event with the type `system_exception`. This event type has a button to directly open a pre-filled GitHub issue form.
This form will have the full stack trace of the error that ocurred and shouldn't contain any sensitive data.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for authentik, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
When you are creating an enhancement suggestion, please fill in [the template](https://github.com/goauthentik/authentik/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=), including the steps that you imagine you would take if the feature you're requesting existed.
### Your First Code Contribution
#### Local development
authentik can be run locally, all though depending on which part you want to work on, different pre-requisites are required.
This is documented in the [developer docs](https://goauthentik.io/developer-docs/)
### Pull Requests
The process described here has several goals:
- Maintain authentik's quality
- Fix problems that are important to users
- Engage the community in working toward the best possible authentik
- Enable a sustainable system for authentik's maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers:
1. Follow the [styleguides](#styleguides)
2. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing <details><summary>What if the status checks are failing?</summary>If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.</details>
3. Ensure your Code has tests. While it is not always possible to test every single case, the majority of the code should be tested.
While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
## Styleguides
### Git Commit Messages
* Use the format of `<package>: <verb> <description>`
- See [here](#authentik-packages) for `package`
- Example: `providers/saml2: fix parsing of requests`
* Reference issues and pull requests liberally after the first line
### Python Styleguide
All Python code is linted with [black](https://black.readthedocs.io/en/stable/), [PyLint](https://www.pylint.org/) and [isort](https://pycqa.github.io/isort/).
authentik runs on Python 3.9 at the time of writing this.
* Use native type-annotations wherever possible.
* Add meaningful docstrings when possible.
* Ensure any database migrations work properly from the last stable version (this is checked via CI)
* If your code changes central functions, make sure nothing else is broken.
### Documentation Styleguide
* Use [MDX](https://mdxjs.com/) whenever appropriate.

66
Pipfile.lock generated
View File

@ -122,19 +122,19 @@
},
"boto3": {
"hashes": [
"sha256:13e60f88d13161df951d6e52bd483cdbe1a36a31f818746289d8ba0879465710",
"sha256:3be2f259b279d69495433e3288db3670817fdb1813cfde92abf867bba3ad8148"
"sha256:a012570d3535ec6c4db97e60ef51c2f39f38246429e1455cecc26c633ed81c10",
"sha256:c7f45b0417395d3020c98cdc10f942939883018210e29dbfe6fbfc0a74e503ec"
],
"index": "pypi",
"version": "==1.18.3"
"version": "==1.18.7"
},
"botocore": {
"hashes": [
"sha256:0b6f378c9efbc72eee61aba1e16cab90bde53a37bd2d861f6435552fd7030adf",
"sha256:285ab9459cdd49d4a9322692c6e13772b97af723a03c0eed519b589446491a5b"
"sha256:34c8b151a25616ed7791218f6d7780c3a97725fe3ceeaa28085b345a8513af6e",
"sha256:dcf399d21170bb899e00d2a693bddcc79e61471fbfead8500a65578700a3190a"
],
"markers": "python_version >= '3.6'",
"version": "==1.21.3"
"version": "==1.21.7"
},
"cachetools": {
"hashes": [
@ -146,22 +146,22 @@
},
"cbor2": {
"hashes": [
"sha256:059363ae716c60f6ba29aa61b3d9c57896189c351c4119095f0542aec169e4dc",
"sha256:0b80a4a4fca830af3d3cf36b725c31f0a98106e9c2b02004ab73b0ec7f139446",
"sha256:0d22b47fb24b384200277fcfb0582c3a3551c413ad51f3bd3ee334caaf79a483",
"sha256:3c586a6e328ba5020802346f5e0304f81b982dcafeb51ee4109c9be9cccbc4a0",
"sha256:4dd142764607b1a8b5e3e3b474d2b84099e9cbb323596a15ee8db0d78901d95f",
"sha256:6f8a7911c2307ee8f8d4940bdcfb8bd21608f14203a83b651fcd7868bce377a5",
"sha256:7ecc4e9c548282a5d296d4535244efa69c7f67cda959f28e14929cf1d6af8a97",
"sha256:8bc9f5054650d05e6d3e90f6490dcd6ef6c01ad9c1568958a48dde2702824cb1",
"sha256:98410520482796a547af2d5ffe11a8a2dc3b9f2124834fa7c12db8264935ed61",
"sha256:a7926f7244b08c413f1a4fa71a81aa256771c75bdf1a4fd77308547a2d63dd48",
"sha256:ae31d3b5966807fdff6c9e6f894b0aa10474295d9ff8467a8b978a569c8fec47",
"sha256:ce6219986385778b1ab7f9b542f160bb4d3558f52975e914a27b774e47016fb7",
"sha256:d562b2773e14ee1d65ea5b85351a83a64d4f3fd011bc2b4c70a6e813e78203ce"
"sha256:0144ba1f44e4e36f7a8e8408eea72e1af6fc3ee42a704dacd4446307024e5231",
"sha256:15102b45dd8b1879b8743159af4538cbf4b3240fe3ebc4e747f6842cd7775888",
"sha256:40aa7c9dc9f69c38a2f9954e0adec266b04c55ed3188dc7a0213a92a2054220e",
"sha256:4b62aa7a95960d1c382e858c2c4cb24375cde3cae137d11875bb9a4667731011",
"sha256:6288b22cd3c0c842db2a4896473512fe83d24fa8ef4bc592d970635a2bb42e0e",
"sha256:81676dc7802029299dc168a1240cf1058c1fe5303fbc64598fe14bdb1f8bc076",
"sha256:8684c6ffbd35258cb9790ef2722559f585fb971288d6f55ee5efd9ba75dcc81b",
"sha256:8ce511337cbac10ccb97093649d6597aacb648ce3198e6afe8b4931fd1cabc61",
"sha256:986a8a9a4d3598008ece7241b746261118ef8d7c0efe7e6e9ce8b275f0421646",
"sha256:a8bf432f6cb595f50aeb8fed2a4aa3b3f7caa7f135fb57e4378eaa39242feac9",
"sha256:ba5e8065ca901ebec7ae390a183f3c13560454b6bd7dd81bf72c320e252b6461",
"sha256:d66350d1323460e1e9dcb2f9caa591b60833623f909173b840a0891a245cad83",
"sha256:e921d445575fbbe62ae68dc8ff3c6e05b341077fd24c6310c917b96fabe5e64a"
],
"markers": "python_version >= '3.6'",
"version": "==5.4.0"
"version": "==5.4.1"
},
"celery": {
"hashes": [
@ -446,11 +446,11 @@
},
"drf-spectacular": {
"hashes": [
"sha256:6ffbfde7d96a4a2febd19182cc405217e1e86a50280fc739402291c93d1a32b7",
"sha256:77593024bb899f69227abedcf87def7851a11c9978f781aa4b385a10f67a38b7"
"sha256:f080128c42183fcaed6b9e8e5afcd2e5cd68426b1f80bfc85938f25e62db7fe5",
"sha256:fb19aa69fcfcd37b0c9dfb9989c0671e1bb47af332ca2171378c7f840263788c"
],
"index": "pypi",
"version": "==0.17.2"
"version": "==0.17.3"
},
"duo-client": {
"hashes": [
@ -995,10 +995,10 @@
},
"python-dotenv": {
"hashes": [
"sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d",
"sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d"
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
],
"version": "==0.18.0"
"version": "==0.19.0"
},
"pytz": {
"hashes": [
@ -1436,11 +1436,11 @@
},
"astroid": {
"hashes": [
"sha256:6021561b2e87ed6b3c93c2682ac50079c65ab08f1e4e0277ba38f97e0e492185",
"sha256:a670dd7af3fe603f51aa7117462588b7c3bdcd58007edfaee752bf82eceecd28"
"sha256:7b963d1c590d490f60d2973e57437115978d3a2529843f160b5003b721e1e925",
"sha256:83e494b02d75d07d4e347b27c066fd791c0c74fc96c613d1ea3de0c82c48168f"
],
"markers": "python_version ~= '3.6'",
"version": "==2.6.4"
"version": "==2.6.5"
},
"attrs": {
"hashes": [
@ -1598,7 +1598,7 @@
"sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813",
"sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e"
],
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"markers": "python_version < '4' and python_full_version >= '3.6.1'",
"version": "==5.9.2"
},
"lazy-object-proxy": {
@ -1684,11 +1684,11 @@
},
"pylint": {
"hashes": [
"sha256:2a971129fb2d594068913a7e531d4b6d2785b2a68c6857e2baa40d3214da30f4",
"sha256:a622c4c4c79dc8fe5e784efccacec3afe9d5e5ffab5fda2264fb5afa7c9b5797"
"sha256:1f333dc72ef7f5ea166b3230936ebcfb1f3b722e76c980cb9fe6b9f95e8d3172",
"sha256:748f81e5776d6273a6619506e08f1b48ff9bcb8198366a56821cf11aac14fc87"
],
"index": "pypi",
"version": "==2.9.4"
"version": "==2.9.5"
},
"pylint-django": {
"hashes": [

View File

@ -2,10 +2,13 @@
## Supported Versions
(.x being the latest patch release for each version)
| Version | Supported |
| ---------- | ------------------ |
| 2021.4.x | :white_check_mark: |
| 2021.5.x | :white_check_mark: |
| 2021.6.x | :white_check_mark: |
| 2021.7.x | :white_check_mark: |
## Reporting a Vulnerability

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.7.1-rc1"
__version__ = "2021.7.1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -114,23 +114,23 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
},
)
@action(detail=True, methods=["GET"])
# pylint: disable=unused-argument
def check_access(self, request: Request, slug: str) -> Response:
"""Check access to a single application by slug"""
# Don't use self.get_object as that checks for view_application permission
# which the user might not have, even if they have access
application = get_object_or_404(Application, slug=slug)
# If the current user is superuser, they can set `for_user`
for_user = self.request.user
if self.request.user.is_superuser and "for_user" in request.data:
for_user = get_object_or_404(User, pk=request.data.get("for_user"))
engine = PolicyEngine(application, for_user, self.request)
for_user = request.user
if request.user.is_superuser and "for_user" in request.query_params:
for_user = get_object_or_404(User, pk=request.query_params.get("for_user"))
engine = PolicyEngine(application, for_user, request)
engine.use_cache = False
engine.build()
result = engine.result
response = PolicyTestResultSerializer(PolicyResult(False))
if result.passing:
response = PolicyTestResultSerializer(PolicyResult(True))
if self.request.user.is_superuser:
if request.user.is_superuser:
response = PolicyTestResultSerializer(result)
return Response(response.data)
@ -145,18 +145,20 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
)
def list(self, request: Request) -> Response:
"""Custom list method that checks Policy based access instead of guardian"""
self.request.session.pop(USER_LOGIN_AUTHENTICATED, None)
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)
should_cache = request.GET.get("search", "") == ""
superuser_full_list = (
str(request.GET.get("superuser_full_list", "false")).lower() == "true"
)
if superuser_full_list and request.user.is_superuser:
serializer = self.get_serializer(queryset, many=True)
return self.get_paginated_response(serializer.data)
return super().list(request)
# To prevent the user from having to double login when prompt is set to login
# and the user has just signed it. This session variable is set in the UserLoginStage
# and is (quite hackily) removed from the session in applications's API's List method
self.request.session.pop(USER_LOGIN_AUTHENTICATED, None)
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)
allowed_applications = []
if not should_cache:

View File

@ -1,5 +1,7 @@
"""Groups API Viewset"""
from django.db.models.query import QuerySet
from django_filters.filters import ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
from rest_framework.fields import BooleanField, CharField, JSONField
from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework.viewsets import ModelViewSet
@ -57,13 +59,32 @@ class GroupSerializer(ModelSerializer):
]
class GroupFilter(FilterSet):
"""Filter for groups"""
members_by_username = ModelMultipleChoiceFilter(
field_name="users__username",
to_field_name="username",
queryset=User.objects.all(),
)
members_by_pk = ModelMultipleChoiceFilter(
field_name="users",
queryset=User.objects.all(),
)
class Meta:
model = Group
fields = ["name", "is_superuser", "members_by_pk", "members_by_username"]
class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
filterset_fields = ["name", "is_superuser"]
filterset_class = GroupFilter
ordering = ["name"]
def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet:

View File

@ -128,7 +128,14 @@ class UsersFilter(FilterSet):
class Meta:
model = User
fields = ["username", "name", "is_active", "is_superuser", "attributes"]
fields = [
"username",
"email",
"name",
"is_active",
"is_superuser",
"attributes",
]
class UserViewSet(UsedByMixin, ModelViewSet):
@ -136,7 +143,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
queryset = User.objects.none()
serializer_class = UserSerializer
search_fields = ["username", "name", "is_active"]
search_fields = ["username", "name", "is_active", "email"]
filterset_class = UsersFilter
def get_queryset(self): # pragma: no cover
@ -206,6 +213,6 @@ class UserViewSet(UsedByMixin, ModelViewSet):
return queryset
def filter_queryset(self, queryset):
if self.request.user.has_perm("authentik_core.view_group"):
if self.request.user.has_perm("authentik_core.view_user"):
return self._filter_queryset_for_list(queryset)
return super().filter_queryset(queryset)

View File

@ -491,8 +491,8 @@ class PropertyMapping(SerializerModel, ManagedModel):
evaluator.set_context(user, request, self, **kwargs)
try:
return evaluator.evaluate(self.expression)
except (ValueError, SyntaxError) as exc:
raise PropertyMappingExpressionException from exc
except Exception as exc:
raise PropertyMappingExpressionException(str(exc)) from exc
def __str__(self):
return f"Property Mapping {self.name}"

View File

@ -141,11 +141,11 @@ class SourceFlowManager:
self._logger.info("denying source because user exists", user=user)
return Action.DENY, None
# Should never get here as default enroll case is returned above.
return Action.DENY, None
return Action.DENY, None # pragma: no cover
def update_connection(
self, connection: UserSourceConnection, **kwargs
) -> UserSourceConnection:
) -> UserSourceConnection: # pragma: no cover
"""Optionally make changes to the connection after it is looked up/created."""
return connection
@ -178,7 +178,7 @@ class SourceFlowManager:
% {"source": self.source.name}
),
)
return redirect("/")
return redirect(reverse("authentik_core:root-redirect"))
# pylint: disable=unused-argument
def get_stages_to_append(self, flow: Flow) -> list[Stage]:

View File

@ -31,7 +31,7 @@ class TestPropertyMappings(TestCase):
"""Test expression error"""
expr = "return aaa"
mapping = PropertyMapping.objects.create(name="test", expression=expr)
with self.assertRaises(NameError):
with self.assertRaises(PropertyMappingExpressionException):
mapping.evaluate(None, None)
events = Event.objects.filter(
action=EventAction.PROPERTY_MAPPING_EXCEPTION, context__expression=expr
@ -44,7 +44,7 @@ class TestPropertyMappings(TestCase):
expr = "return aaa"
request = self.factory.get("/")
mapping = PropertyMapping.objects.create(name="test", expression=expr)
with self.assertRaises(NameError):
with self.assertRaises(PropertyMappingExpressionException):
mapping.evaluate(get_anonymous_user(), request)
events = Event.objects.filter(
action=EventAction.PROPERTY_MAPPING_EXCEPTION, context__expression=expr

View File

@ -0,0 +1,160 @@
"""Test Source flow_manager"""
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import HttpRequest
from django.test import TestCase
from django.test.client import RequestFactory
from guardian.utils import get_anonymous_user
from authentik.core.models import SourceUserMatchingModes, User
from authentik.core.sources.flow_manager import Action
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.providers.oauth2.generators import generate_client_id
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.views.callback import OAuthSourceFlowManager
class TestSourceFlowManager(TestCase):
"""Test Source flow_manager"""
def setUp(self) -> None:
super().setUp()
self.source = OAuthSource.objects.create(name="test")
self.factory = RequestFactory()
self.identifier = generate_client_id()
def get_request(self, user: User) -> HttpRequest:
"""Helper to create a get request with session and message middleware"""
request = self.factory.get("/")
request.user = user
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
middleware = MessageMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
return request
def test_unauthenticated_enroll(self):
"""Test un-authenticated user enrolling"""
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()
def test_unauthenticated_auth(self):
"""Test un-authenticated user authenticating"""
UserOAuthSourceConnection.objects.create(
user=get_anonymous_user(), source=self.source, identifier=self.identifier
)
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.AUTH)
flow_manager.get_flow()
def test_authenticated_link(self):
"""Test authenticated user linking"""
UserOAuthSourceConnection.objects.create(
user=get_anonymous_user(), source=self.source, identifier=self.identifier
)
user = User.objects.create(username="foo", email="foo@bar.baz")
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(user), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
flow_manager.get_flow()
def test_unauthenticated_enroll_email(self):
"""Test un-authenticated user enrolling (link on email)"""
User.objects.create(username="foo", email="foo@bar.baz")
self.source.user_matching_mode = SourceUserMatchingModes.EMAIL_LINK
# Without email, deny
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
flow_manager.get_flow()
# With email
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"email": "foo@bar.baz"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
flow_manager.get_flow()
def test_unauthenticated_enroll_username(self):
"""Test un-authenticated user enrolling (link on username)"""
User.objects.create(username="foo", email="foo@bar.baz")
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_LINK
# Without username, deny
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
flow_manager.get_flow()
# With username
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
flow_manager.get_flow()
def test_unauthenticated_enroll_username_deny(self):
"""Test un-authenticated user enrolling (deny on username)"""
User.objects.create(username="foo", email="foo@bar.baz")
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_DENY
# With non-existent username, enroll
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{
"username": "bar",
},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()
# With username
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
flow_manager.get_flow()
def test_unauthenticated_enroll_link_non_existent(self):
"""Test un-authenticated user enrolling (link on username), username doesn't exist"""
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_LINK
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()

View File

@ -24,8 +24,10 @@ from authentik.events.geo import GEOIP_READER
from authentik.events.utils import cleanse_dict, get_user, model_to_dict, sanitize_dict
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import get_client_ip
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
from authentik.stages.email.utils import TemplateEmailMessage
from authentik.tenants.models import Tenant
from authentik.tenants.utils import DEFAULT_TENANT
LOGGER = get_logger("authentik.events")
@ -37,7 +39,8 @@ GAUGE_EVENTS = Gauge(
def default_event_duration():
"""Default duration an Event is saved"""
"""Default duration an Event is saved.
This is used as a fallback when no tenant is available"""
return now() + timedelta(days=365)
@ -147,7 +150,12 @@ class Event(ExpiringModel):
"method": request.method,
}
if hasattr(request, "tenant"):
self.tenant = sanitize_dict(model_to_dict(request.tenant))
tenant: Tenant = request.tenant
# Because self.created only gets set on save, we can't use it's value here
# hence we set self.created to now and then use it
self.created = now()
self.expires = self.created + timedelta_from_string(tenant.event_retention)
self.tenant = sanitize_dict(model_to_dict(tenant))
if hasattr(request, "user"):
original_user = None
if hasattr(request, "session"):

View File

@ -0,0 +1,67 @@
"""Test HTTP Helpers"""
from django.test import RequestFactory, TestCase
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
Token,
TokenIntents,
User,
)
from authentik.lib.utils.http import (
OUTPOST_REMOTE_IP_HEADER,
OUTPOST_TOKEN_HEADER,
get_client_ip,
)
class TestHTTP(TestCase):
"""Test HTTP Helpers"""
def setUp(self) -> None:
self.user = User.objects.get(username="akadmin")
self.factory = RequestFactory()
def test_normal(self):
"""Test normal request"""
request = self.factory.get("/")
self.assertEqual(get_client_ip(request), "127.0.0.1")
def test_forward_for(self):
"""Test x-forwarded-for request"""
request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2")
self.assertEqual(get_client_ip(request), "127.0.0.2")
def test_fake_outpost(self):
"""Test faked IP which is overridden by an outpost"""
token = Token.objects.create(
identifier="test", user=self.user, intent=TokenIntents.INTENT_API
)
# Invalid, non-existant token
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: "abc",
},
)
self.assertEqual(get_client_ip(request), "127.0.0.1")
# Invalid, user doesn't have permisions
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key,
},
)
self.assertEqual(get_client_ip(request), "127.0.0.1")
# Valid
self.user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
self.user.save()
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key,
},
)
self.assertEqual(get_client_ip(request), "1.2.3.4")

View File

@ -40,24 +40,30 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
or OUTPOST_TOKEN_HEADER not in request.META
):
return None
fake_ip = request.META[OUTPOST_REMOTE_IP_HEADER]
tokens = Token.filter_not_expired(
key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API
)
if not tokens.exists():
LOGGER.warning("Attempted remote-ip override without token")
LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip)
return None
user = tokens.first().user
if user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
if not user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
LOGGER.warning(
"Remote-IP override: user doesn't have permission",
user=user,
fake_ip=fake_ip,
)
return None
return request.META[OUTPOST_REMOTE_IP_HEADER]
return fake_ip
def get_client_ip(request: Optional[HttpRequest]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found"""
if request:
override = _get_outpost_override_ip(request)
if override:
return override
return _get_client_ip_from_meta(request.META)
return DEFAULT_IP
if not request:
return DEFAULT_IP
override = _get_outpost_override_ip(request)
if override:
return override
return _get_client_ip_from_meta(request.META)

View File

@ -5,12 +5,12 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
ALLOWED_KEYS = (
"days",
"seconds",
"microseconds",
"milliseconds",
"seconds",
"minutes",
"hours",
"days",
"weeks",
)

View File

@ -344,12 +344,13 @@ class Outpost(ManagedModel):
users = User.objects.filter(username=self.user_identifier)
if not users.exists():
user: User = User.objects.create(username=self.user_identifier)
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.set_unusable_password()
user.save()
else:
user = users.first()
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.save()
# To ensure the user only has the correct permissions, we delete all of them and re-add
# the ones the user needs
with transaction.atomic():

View File

@ -0,0 +1,111 @@
"""Test userinfo view"""
import json
from dataclasses import asdict
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import (
generate_client_id,
generate_client_secret,
)
from authentik.providers.oauth2.models import (
IDToken,
OAuth2Provider,
RefreshToken,
ScopeMapping,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestUserinfo(OAuthTestCase):
"""Test token view"""
def setUp(self) -> None:
super().setUp()
ObjectManager().run()
self.app = Application.objects.create(name="test", slug="test")
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="",
rsa_key=CertificateKeyPair.objects.first(),
)
self.provider.property_mappings.set(ScopeMapping.objects.all())
# Needs to be assigned to an application for iss to be set
self.app.provider = self.provider
self.app.save()
self.user = User.objects.get(username="akadmin")
self.token: RefreshToken = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
access_token=generate_client_id(),
refresh_token=generate_client_id(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
def test_userinfo_normal(self):
"""test user info with all normal scopes"""
res = self.client.get(
reverse("authentik_providers_oauth2:userinfo"),
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
{
"name": "authentik Default Admin",
"given_name": "authentik Default Admin",
"family_name": "",
"preferred_username": "akadmin",
"nickname": "akadmin",
"groups": ["authentik Admins"],
"sub": "bar",
},
)
self.assertEqual(res.status_code, 200)
def test_userinfo_invalid_scope(self):
"""test user info with a broken scope"""
scope = ScopeMapping.objects.create(
name="test", scope_name="openid", expression="q"
)
self.provider.property_mappings.add(scope)
res = self.client.get(
reverse("authentik_providers_oauth2:userinfo"),
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
{
"name": "authentik Default Admin",
"given_name": "authentik Default Admin",
"family_name": "",
"preferred_username": "akadmin",
"nickname": "akadmin",
"groups": ["authentik Admins"],
"sub": "bar",
},
)
self.assertEqual(res.status_code, 200)
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
)
self.assertTrue(events.exists())
self.assertEqual(
events.first().context["message"],
"Failed to evaluate property-mapping: name 'q' is not defined",
)

View File

@ -126,7 +126,15 @@ class TokenParams:
LOGGER.warning("Missing authorization code")
raise TokenError("invalid_grant")
if self.redirect_uri not in self.provider.redirect_uris.split():
allowed_redirect_urls = self.provider.redirect_uris.split()
if len(allowed_redirect_urls) < 1:
LOGGER.warning(
"Provider has no allowed redirect_uri set, allowing all.",
allow=self.redirect_uri.lower(),
)
elif self.redirect_uri.lower() not in [
x.lower() for x in allowed_redirect_urls
]:
LOGGER.warning(
"Invalid redirect uri",
uri=self.redirect_uri,

View File

@ -7,6 +7,8 @@ from django.http.response import HttpResponseBadRequest
from django.views import View
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.constants import (
SCOPE_GITHUB_ORG_READ,
SCOPE_GITHUB_USER,
@ -63,12 +65,20 @@ class UserInfoView(View):
for scope in ScopeMapping.objects.filter(
provider=token.provider, scope_name__in=scopes_from_client
).order_by("scope_name"):
value = scope.evaluate(
user=token.user,
request=self.request,
provider=token.provider,
token=token,
)
value = None
try:
value = scope.evaluate(
user=token.user,
request=self.request,
provider=token.provider,
token=token,
)
except PropertyMappingExpressionException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=scope,
).from_http(self.request)
if value is None:
continue
if not isinstance(value, dict):

View File

@ -90,12 +90,6 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"""Proxy provider serializer for outposts"""
oidc_configuration = SerializerMethodField()
forward_auth_mode = SerializerMethodField()
def get_forward_auth_mode(self, instance: ProxyProvider) -> bool:
"""Legacy field for 2021.5 outposts"""
# TODO: remove in 2021.7
return instance.mode in [ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN]
class Meta:
@ -117,8 +111,6 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"basic_auth_user_attribute",
"mode",
"cookie_domain",
# Legacy field, remove in 2021.7
"forward_auth_mode",
]
@extend_schema_field(OpenIDConnectConfigurationSerializer)

View File

@ -113,6 +113,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
authResponseHeaders=[
"Set-Cookie",
"X-Auth-Username",
"X-Auth-Groups",
"X-Forwarded-Email",
"X-Forwarded-Preferred-Username",
"X-Forwarded-User",

View File

@ -9,6 +9,7 @@ from lxml.etree import Element, SubElement # nosec
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.request_parser import AuthNRequest
@ -99,7 +100,11 @@ class AssertionProcessor:
attribute_statement.append(attribute)
except PropertyMappingExpressionException as exc:
LOGGER.warning(str(exc))
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=mapping,
).from_http(self.http_request)
continue
return attribute_statement
@ -161,7 +166,11 @@ class AssertionProcessor:
name_id.text = value
return name_id
except PropertyMappingExpressionException as exc:
LOGGER.warning(str(exc))
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=self.provider.name_id_mapping,
).from_http(self.http_request)
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL:
name_id.text = self.http_request.user.email

View File

@ -6,9 +6,12 @@ from django.http.request import QueryDict
from django.test import RequestFactory, TestCase
from guardian.utils import get_anonymous_user
from authentik.core.models import User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
@ -79,6 +82,7 @@ class TestAuthNRequest(TestCase):
"""Test AuthN Request generator and parser"""
def setUp(self):
ObjectManager().run()
cert = CertificateKeyPair.objects.first()
self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=Flow.objects.get(
@ -243,3 +247,60 @@ class TestAuthNRequest(TestCase):
parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST)
self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)
def test_request_attributes(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
self.assertIn("akadmin", response_proc.build_response())
def test_request_attributes_invalid(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# Create invalid PropertyMapping
scope = SAMLPropertyMapping.objects.create(
name="test", saml_name="test", expression="q"
)
self.provider.property_mappings.add(scope)
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
self.assertIn("akadmin", response_proc.build_response())
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
)
self.assertTrue(events.exists())
self.assertEqual(
events.first().context["message"],
"Failed to evaluate property-mapping: name 'q' is not defined",
)

View File

@ -27,6 +27,7 @@ class ChannelsStorage(BaseStorage):
uid,
{
"type": "event.update",
"message_type": "message",
"level": message.level_tag,
"tags": message.tags,
"message": message.message,

View File

@ -5,6 +5,7 @@ from django.db.models.query import QuerySet
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -83,6 +84,11 @@ class BaseLDAPSynchronizer:
else:
properties[object_field] = self._flatten(value)
except PropertyMappingExpressionException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=mapping,
).save()
self._logger.warning(
"Mapping failed to evaluate", exc=exc, mapping=mapping
)

View File

@ -5,6 +5,7 @@ from django.db.models import Q
from django.test import TestCase
from authentik.core.models import Group, User
from authentik.events.models import Event, EventAction
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -31,6 +32,33 @@ class LDAPSyncTests(TestCase):
additional_group_dn="ou=groups",
)
def test_sync_error(self):
"""Test user sync"""
self.source.property_mappings.set(
LDAPPropertyMapping.objects.filter(
Q(managed__startswith="goauthentik.io/sources/ldap/default")
| Q(managed__startswith="goauthentik.io/sources/ldap/ms")
)
)
mapping = LDAPPropertyMapping.objects.create(
name="name",
object_field="name",
expression="q",
)
self.source.property_mappings.set([mapping])
self.source.save()
connection = PropertyMock(return_value=mock_ad_connection(LDAP_PASSWORD))
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
user_sync = UserLDAPSynchronizer(self.source)
user_sync.sync()
self.assertFalse(User.objects.filter(username="user0_sn").exists())
self.assertFalse(User.objects.filter(username="user1_sn").exists())
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
context__message="Failed to evaluate property-mapping: name 'q' is not defined",
)
self.assertTrue(events.exists())
def test_sync_users_ad(self):
"""Test user sync"""
self.source.property_mappings.set(

View File

@ -38,6 +38,7 @@ class TenantSerializer(ModelSerializer):
"flow_invalidation",
"flow_recovery",
"flow_unenrollment",
"event_retention",
]

View File

@ -3,6 +3,7 @@ from typing import Callable
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from sentry_sdk.api import set_tag
from authentik.tenants.utils import get_tenant_for_request
@ -19,4 +20,6 @@ class TenantMiddleware:
if not hasattr(request, "tenant"):
tenant = get_tenant_for_request(request)
setattr(request, "tenant", tenant)
set_tag("authentik.tenant_uuid", tenant.tenant_uuid.hex)
set_tag("authentik.tenant_domain", tenant.domain)
return self.get_response(request)

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.5 on 2021-07-24 17:06
from django.db import migrations, models
import authentik.lib.utils.time
class Migration(migrations.Migration):
dependencies = [
("authentik_tenants", "0003_tenant_branding_favicon"),
]
operations = [
migrations.AddField(
model_name="tenant",
name="event_retention",
field=models.TextField(
default="days=365",
help_text="Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
]

View File

@ -5,6 +5,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from authentik.flows.models import Flow
from authentik.lib.utils.time import timedelta_string_validator
class Tenant(models.Model):
@ -22,6 +23,7 @@ class Tenant(models.Model):
)
branding_title = models.TextField(default="authentik")
branding_logo = models.TextField(
default="/static/dist/assets/icons/icon_left_brand.svg"
)
@ -40,6 +42,17 @@ class Tenant(models.Model):
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_unenrollment"
)
event_retention = models.TextField(
default="days=365",
validators=[timedelta_string_validator],
help_text=_(
(
"Events will be deleted after this duration."
"(Format: weeks=3;days=2;hours=3,seconds=2)."
)
),
)
def __str__(self) -> str:
if self.default:
return "Default tenant"

View File

@ -1,9 +1,12 @@
"""Test tenants"""
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.tenants.models import Tenant
@ -57,3 +60,28 @@ class TestTenants(TestCase):
"ui_footer_links": CONFIG.y("footer_links"),
},
)
def test_event_retention(self):
"""Test tenant's event retention"""
tenant = Tenant.objects.create(
domain="foo",
default=True,
branding_title="custom",
event_retention="weeks=3",
)
factory = RequestFactory()
request = factory.get("/")
request.tenant = tenant
event = Event.new(
action=EventAction.SYSTEM_EXCEPTION, message="test"
).from_http(request)
self.assertEqual(
event.expires.day, (event.created + timedelta_from_string("weeks=3")).day
)
self.assertEqual(
event.expires.month,
(event.created + timedelta_from_string("weeks=3")).month,
)
self.assertEqual(
event.expires.year, (event.created + timedelta_from_string("weeks=3")).year
)

View File

@ -6,12 +6,6 @@ trigger:
- next
- version-*
variables:
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:
branchName: ${{ replace(variables['System.PullRequest.SourceBranch'], '/', '-') }}
${{ if startsWith(variables['Build.SourceBranch'], 'refs/heads/') }}:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
stages:
- stage: generate
jobs:

View File

@ -14,13 +14,13 @@ resources:
- repo: self
variables:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:
branchName: ${{ replace(variables['System.PullRequest.SourceBranch'], '/', '-') }}
${{ if startsWith(variables['Build.SourceBranch'], 'refs/heads/') }}:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
- name: POSTGRES_DB
value: authentik
- name: POSTGRES_USER
value: authentik
- name: POSTGRES_PASSWORD
value: "EK-5jnKfjrGRm<77"
- group: coverage
stages:
- stage: Lint_and_test
@ -397,6 +397,16 @@ stages:
- task: CmdLine@2
inputs:
script: bash <(curl -s https://codecov.io/bash)
- task: CmdLine@2
inputs:
script: |
npm install -g @zeus-ci/cli
npx zeus job update -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -r $BUILD_SOURCEVERSION
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-cobertura+xml" coverage.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-e2e/unittest.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-integration/unittest.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-unittest/unittest.xml
npx zeus job update --status=passed -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -r $BUILD_SOURCEVERSION
- stage: Build
jobs:
- job: build_server

View File

@ -21,7 +21,7 @@ services:
networks:
- internal
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1-rc1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1}
restart: unless-stopped
command: server
environment:
@ -44,7 +44,7 @@ services:
- "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443"
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1-rc1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1}
restart: unless-stopped
command: worker
networks:

View File

@ -17,4 +17,4 @@ func OutpostUserAgent() string {
return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD())
}
const VERSION = "2021.7.1-rc1"
const VERSION = "2021.7.1"

View File

@ -44,7 +44,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
config.Host = akURL.Host
config.Scheme = akURL.Scheme
config.HTTPClient = &http.Client{
Transport: GetTLSTransport(),
Transport: NewTracingTransport(GetTLSTransport()),
}
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))

View File

@ -71,14 +71,12 @@ func (ac *APIController) Shutdown() {
func (ac *APIController) startWSHandler() {
logger := ac.logger.WithField("loop", "ws-handler")
for {
if !ac.wsConn.IsConnected() {
continue
}
var wsMsg websocketMessage
err := ac.wsConn.ReadJSON(&wsMsg)
if err != nil {
logger.WithError(err).Warning("ws write error, reconnecting")
ac.wsConn.CloseAndReconnect()
time.Sleep(time.Second * 5)
continue
}
if wsMsg.Instruction == WebsocketInstructionTriggerUpdate {

View File

@ -0,0 +1,85 @@
package ak
import (
"context"
"crypto/tls"
log "github.com/sirupsen/logrus"
"goauthentik.io/api"
)
type CryptoStore struct {
api *api.CryptoApiService
log *log.Entry
fingerprints map[string]string
certificates map[string]*tls.Certificate
}
func NewCryptoStore(cryptoApi *api.CryptoApiService) *CryptoStore {
return &CryptoStore{
api: cryptoApi,
log: log.WithField("logger", "authentik.outpost.cryptostore"),
fingerprints: make(map[string]string),
certificates: make(map[string]*tls.Certificate),
}
}
func (cs *CryptoStore) AddKeypair(uuid string) error {
// If they keypair was already added, don't
// do it again
if _, ok := cs.fingerprints[uuid]; ok {
return nil
}
// reset fingerprint to force update
cs.fingerprints[uuid] = ""
err := cs.Fetch(uuid)
if err != nil {
return err
}
cs.fingerprints[uuid] = cs.getFingerprint(uuid)
return nil
}
func (cs *CryptoStore) getFingerprint(uuid string) string {
kp, _, err := cs.api.CryptoCertificatekeypairsRetrieve(context.Background(), uuid).Execute()
if err != nil {
cs.log.WithField("uuid", uuid).WithError(err).Warning("Failed to fetch certificate's fingerprint")
return ""
}
return kp.FingerprintSha256
}
func (cs *CryptoStore) Fetch(uuid string) error {
cfp := cs.getFingerprint(uuid)
if cfp == cs.fingerprints[uuid] {
cs.log.WithField("uuid", uuid).Info("Fingerprint hasn't changed, not fetching cert")
return nil
}
cs.log.WithField("uuid", uuid).Info("Fetching certificate and private key")
cert, _, err := cs.api.CryptoCertificatekeypairsViewCertificateRetrieve(context.Background(), uuid).Execute()
if err != nil {
return err
}
key, _, err := cs.api.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), uuid).Execute()
if err != nil {
return err
}
x509cert, err := tls.X509KeyPair([]byte(cert.Data), []byte(key.Data))
if err != nil {
return err
}
cs.certificates[uuid] = &x509cert
return nil
}
func (cs *CryptoStore) Get(uuid string) *tls.Certificate {
err := cs.Fetch(uuid)
if err != nil {
cs.log.WithError(err).Warning("failed to fetch certificate")
}
return cs.certificates[uuid]
}

View File

@ -1,8 +1,6 @@
package ak
import (
"context"
"crypto/tls"
"net/http"
"os"
"strings"
@ -11,7 +9,6 @@ import (
"github.com/getsentry/sentry-go"
httptransport "github.com/go-openapi/runtime/client"
log "github.com/sirupsen/logrus"
"goauthentik.io/api"
"goauthentik.io/internal/constants"
)
@ -38,15 +35,17 @@ func doGlobalSetup(config map[string]interface{}) {
}
log.WithField("buildHash", constants.BUILD()).WithField("version", constants.VERSION).Info("Starting authentik outpost")
env := config[ConfigErrorReportingEnvironment].(string)
var dsn string
if config[ConfigErrorReportingEnabled].(bool) {
dsn = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
log.Debug("Error reporting enabled")
log.WithField("env", env).Debug("Error reporting enabled")
}
err := sentry.Init(sentry.ClientOptions{
Dsn: dsn,
Environment: config[ConfigErrorReportingEnvironment].(string),
Dsn: dsn,
Environment: env,
TracesSampleRate: 1,
})
if err != nil {
log.Fatalf("sentry.Init: %s", err)
@ -69,21 +68,3 @@ func GetTLSTransport() http.RoundTripper {
}
return tlsTransport
}
// ParseCertificate Load certificate from Keyepair UUID and parse it into a go Certificate
func ParseCertificate(kpUuid string, cryptoApi *api.CryptoApiService) (*tls.Certificate, error) {
cert, _, err := cryptoApi.CryptoCertificatekeypairsViewCertificateRetrieve(context.Background(), kpUuid).Execute()
if err != nil {
return nil, err
}
key, _, err := cryptoApi.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), kpUuid).Execute()
if err != nil {
return nil, err
}
x509cert, err := tls.X509KeyPair([]byte(cert.Data), []byte(key.Data))
if err != nil {
return nil, err
}
return &x509cert, nil
}

View File

@ -0,0 +1,23 @@
package ak
import (
"net/http"
"github.com/getsentry/sentry-go"
)
type tracingTransport struct {
inner http.RoundTripper
}
func NewTracingTransport(inner http.RoundTripper) *tracingTransport {
return &tracingTransport{inner}
}
func (tt *tracingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
span := sentry.StartSpan(r.Context(), "authentik.go.http_request")
span.SetTag("url", r.URL.String())
span.SetTag("method", r.Method)
defer span.Finish()
return tt.inner.RoundTrip(r.WithContext(span.Context()))
}

View File

@ -11,6 +11,7 @@ import (
"strconv"
"strings"
"github.com/getsentry/sentry-go"
log "github.com/sirupsen/logrus"
"goauthentik.io/api"
"goauthentik.io/internal/constants"
@ -34,14 +35,19 @@ const (
type FlowExecutor struct {
Params url.Values
Answers map[StageComponent]string
Context context.Context
api *api.APIClient
flowSlug string
log *log.Entry
token string
sp *sentry.Span
}
func NewFlowExecutor(flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor {
func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor {
rsp := sentry.StartSpan(ctx, "authentik.outposts.flow_executor")
l := log.WithField("flow", flowSlug).WithFields(logFields)
jar, err := cookiejar.New(nil)
if err != nil {
@ -55,16 +61,18 @@ func NewFlowExecutor(flowSlug string, refConfig *api.Configuration, logFields lo
config.UserAgent = constants.OutpostUserAgent()
config.HTTPClient = &http.Client{
Jar: jar,
Transport: ak.GetTLSTransport(),
Transport: ak.NewTracingTransport(ak.GetTLSTransport()),
}
apiClient := api.NewAPIClient(config)
return &FlowExecutor{
Params: url.Values{},
Answers: make(map[StageComponent]string),
Context: rsp.Context(),
api: apiClient,
flowSlug: flowSlug,
log: l,
token: strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1],
sp: rsp,
}
}
@ -89,7 +97,9 @@ func (fe *FlowExecutor) DelegateClientIP(a net.Addr) {
}
func (fe *FlowExecutor) CheckApplicationAccess(appSlug string) (bool, error) {
p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), appSlug).Execute()
acsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.check_access")
defer acsp.Finish()
p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(acsp.Context(), appSlug).Execute()
if !p.Passing {
fe.log.Info("Access denied for user")
return false, nil
@ -113,14 +123,24 @@ func (fe *FlowExecutor) Execute() (bool, error) {
}
func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
req := fe.api.FlowsApi.FlowsExecutorGet(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
defer fe.sp.Finish()
// Get challenge
gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge")
req := fe.api.FlowsApi.FlowsExecutorGet(gcsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
challenge, _, err := req.Execute()
if err != nil {
return false, errors.New("failed to get challenge")
}
ch := challenge.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge")
responseReq := fe.api.FlowsApi.FlowsExecutorSolve(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
gcsp.SetTag("ak_challenge", string(ch.GetType()))
gcsp.SetTag("ak_component", ch.GetComponent())
gcsp.Finish()
// Resole challenge
scsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.solve_challenge")
responseReq := fe.api.FlowsApi.FlowsExecutorSolve(scsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
switch ch.GetComponent() {
case string(StageIdentification):
responseReq = responseReq.FlowChallengeResponseRequest(api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))))
@ -150,9 +170,14 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
default:
return false, fmt.Errorf("unsupported challenge type %s", ch.GetComponent())
}
response, _, err := responseReq.Execute()
ch = response.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response")
scsp.SetTag("ak_challenge", string(ch.GetType()))
scsp.SetTag("ak_component", ch.GetComponent())
scsp.Finish()
switch ch.GetComponent() {
case string(StageAccessDenied):
return false, errors.New("got ak-stage-access-denied")

View File

@ -13,7 +13,6 @@ import (
"github.com/go-openapi/strfmt"
"github.com/pires/go-proxyproto"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ak"
)
func (ls *LDAPServer) Refresh() error {
@ -45,14 +44,12 @@ func (ls *LDAPServer) Refresh() error {
gidStartNumber: *provider.GidStartNumber,
}
if provider.Certificate.Get() != nil {
logger.WithField("provider", provider.Name).Debug("Enabling TLS")
cert, err := ak.ParseCertificate(*provider.Certificate.Get(), ls.ac.Client.CryptoApi)
kp := provider.Certificate.Get()
err := ls.cs.AddKeypair(*kp)
if err != nil {
logger.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
} else {
providers[idx].cert = cert
logger.WithField("provider", provider.Name).Debug("Loaded certificates")
ls.log.WithError(err).Warning("Failed to initially fetch certificate")
}
providers[idx].cert = ls.cs.Get(*kp)
}
}
ls.providers = providers

View File

@ -1,9 +1,11 @@
package ldap
import (
"context"
"net"
"strings"
"github.com/getsentry/sentry-go"
"github.com/google/uuid"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
@ -15,9 +17,15 @@ type BindRequest struct {
id string
conn net.Conn
log *log.Entry
ctx context.Context
}
func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.bind",
sentry.TransactionName("authentik.providers.ldap.bind"))
span.SetTag("user.username", bindDN)
defer span.Finish()
bindDN = strings.ToLower(bindDN)
rid := uuid.New().String()
req := BindRequest{
@ -26,6 +34,7 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()),
id: rid,
ctx: span.Context(),
}
req.log.Info("Bind request")
for _, instance := range ls.providers {

View File

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
@ -34,7 +35,7 @@ func (pi *ProviderInstance) getUsername(dn string) (string, error) {
}
func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPResultCode, error) {
fe := outpost.NewFlowExecutor(pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
fe := outpost.NewFlowExecutor(req.ctx, pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
"bindDN": req.BindDN,
"client": req.conn.RemoteAddr().String(),
"requestId": req.id,
@ -53,6 +54,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
req.log.WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultOperationsError, nil
}
access, err := fe.CheckApplicationAccess(pi.appSlug)
if !access {
req.log.Info("Access denied for user")
@ -63,6 +65,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
return ldap.LDAPResultOperationsError, nil
}
req.log.Info("User has access")
uisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.bind.user_info")
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
@ -78,7 +81,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
if pi.boundUsers[req.BindDN].CanSearch {
req.log.WithField("group", cs).Info("Allowed access to search")
}
uisp.Finish()
defer pi.boundUsersMutex.Unlock()
pi.delayDeleteUserInfo(username)
return ldap.LDAPResultSuccess, nil

View File

@ -1,11 +1,12 @@
package ldap
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"goauthentik.io/api"
)
@ -17,6 +18,7 @@ func (pi *ProviderInstance) SearchMe(user api.User) (ldap.ServerSearchResult, er
}
func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult, error) {
accsp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.check_access")
baseDN := strings.ToLower("," + pi.BaseDN)
entries := []*ldap.Entry{}
@ -32,8 +34,8 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}
pi.boundUsersMutex.RLock()
defer pi.boundUsersMutex.RUnlock()
flags, ok := pi.boundUsers[req.BindDN]
pi.boundUsersMutex.RUnlock()
if !ok {
pi.log.Debug("User info not cached")
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
@ -42,31 +44,60 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
pi.log.Debug("User can't search, showing info about user")
return pi.SearchMe(flags.UserInfo)
}
accsp.Finish()
parsedFilter, err := ldap.CompileFilter(req.Filter)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter)
}
switch filterEntity {
default:
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter)
case GroupObjectClass:
groups, _, err := pi.s.ac.Client.CoreApi.CoreGroupsList(context.Background()).Execute()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
pi.log.WithField("count", len(groups.Results)).Trace("Got results from API")
wg := sync.WaitGroup{}
wg.Add(2)
for _, g := range groups.Results {
entries = append(entries, pi.GroupEntry(pi.APIGroupToLDAPGroup(g)))
}
gEntries := make([]*ldap.Entry, 0)
uEntries := make([]*ldap.Entry, 0)
users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
go func() {
defer wg.Done()
gapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_group")
groups, _, err := parseFilterForGroup(pi.s.ac.Client.CoreApi.CoreGroupsList(gapisp.Context()), parsedFilter).Execute()
gapisp.Finish()
if err != nil {
req.log.WithError(err).Warning("failed to get groups")
return
}
pi.log.WithField("count", len(groups.Results)).Trace("Got results from API")
for _, u := range users.Results {
entries = append(entries, pi.GroupEntry(pi.APIUserToLDAPGroup(u)))
}
for _, g := range groups.Results {
gEntries = append(gEntries, pi.GroupEntry(pi.APIGroupToLDAPGroup(g)))
}
}()
go func() {
defer wg.Done()
uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
users, _, err := parseFilterForUser(pi.s.ac.Client.CoreApi.CoreUsersList(uapisp.Context()), parsedFilter).Execute()
uapisp.Finish()
if err != nil {
req.log.WithError(err).Warning("failed to get groups")
return
}
for _, u := range users.Results {
uEntries = append(uEntries, pi.GroupEntry(pi.APIUserToLDAPGroup(u)))
}
}()
wg.Wait()
entries = append(gEntries, uEntries...)
case UserObjectClass, "":
users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute()
uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
users, _, err := parseFilterForUser(pi.s.ac.Client.CoreApi.CoreUsersList(uapisp.Context()), parsedFilter).Execute()
uapisp.Finish()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
@ -74,7 +105,6 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
entries = append(entries, pi.UserEntry(u))
}
}
pi.log.WithField("filter", req.Filter).Debug("Search OK")
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}

View File

@ -0,0 +1,57 @@
package ldap
import (
goldap "github.com/go-ldap/ldap/v3"
ber "github.com/nmcclain/asn1-ber"
"github.com/nmcclain/ldap"
"goauthentik.io/api"
)
func parseFilterForGroup(req api.ApiCoreGroupsListRequest, f *ber.Packet) api.ApiCoreGroupsListRequest {
switch f.Tag {
case ldap.FilterEqualityMatch:
return parseFilterForGroupSingle(req, f)
case ldap.FilterAnd:
for _, child := range f.Children {
req = parseFilterForGroup(req, child)
}
return req
}
return req
}
func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet) api.ApiCoreGroupsListRequest {
// We can only handle key = value pairs here
if len(f.Children) < 2 {
return req
}
k := f.Children[0].Value
// Ensure key is string
if _, ok := k.(string); !ok {
return req
}
v := f.Children[1].Value
// Null values are ignored
if v == nil {
return req
}
// Switch on type of the value, then check the key
switch vv := v.(type) {
case string:
switch k {
case "cn":
return req.Name(vv)
case "member":
userDN, err := goldap.ParseDN(vv)
if err != nil {
return req
}
username := userDN.RDNs[0].Attributes[0].Value
return req.MembersByUsername([]string{username})
}
// TODO: Support int
default:
return req
}
return req
}

View File

@ -0,0 +1,54 @@
package ldap
import (
ber "github.com/nmcclain/asn1-ber"
"github.com/nmcclain/ldap"
"goauthentik.io/api"
)
func parseFilterForUser(req api.ApiCoreUsersListRequest, f *ber.Packet) api.ApiCoreUsersListRequest {
switch f.Tag {
case ldap.FilterEqualityMatch:
return parseFilterForUserSingle(req, f)
case ldap.FilterAnd:
for _, child := range f.Children {
req = parseFilterForUser(req, child)
}
return req
}
return req
}
func parseFilterForUserSingle(req api.ApiCoreUsersListRequest, f *ber.Packet) api.ApiCoreUsersListRequest {
// We can only handle key = value pairs here
if len(f.Children) < 2 {
return req
}
k := f.Children[0].Value
// Ensure key is string
if _, ok := k.(string); !ok {
return req
}
v := f.Children[1].Value
// Null values are ignored
if v == nil {
return req
}
// Switch on type of the value, then check the key
switch vv := v.(type) {
case string:
switch k {
case "cn":
return req.Username(vv)
case "name":
case "displayName":
return req.Name(vv)
case "mail":
return req.Email(vv)
}
// TODO: Support int
default:
return req
}
return req
}

View File

@ -47,6 +47,7 @@ type LDAPServer struct {
s *ldap.Server
log *log.Entry
ac *ak.APIController
cs *ak.CryptoStore
defaultCert *tls.Certificate
providers []*ProviderInstance
}
@ -69,6 +70,7 @@ func NewServer(ac *ak.APIController) *LDAPServer {
s: s,
log: log.WithField("logger", "authentik.outpost.ldap"),
ac: ac,
cs: ak.NewCryptoStore(ac.Client.CryptoApi),
providers: []*ProviderInstance{},
}
defaultCert, err := crypto.GenerateSelfSignedCert()

View File

@ -1,10 +1,12 @@
package ldap
import (
"context"
"errors"
"net"
"strings"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3"
"github.com/google/uuid"
"github.com/nmcclain/ldap"
@ -17,9 +19,15 @@ type SearchRequest struct {
id string
conn net.Conn
log *log.Entry
ctx context.Context
}
func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search"))
span.SetTag("user.username", bindDN)
span.SetTag("ak_filter", searchReq.Filter)
span.SetTag("ak_base_dn", searchReq.BaseDN)
bindDN = strings.ToLower(bindDN)
rid := uuid.New().String()
req := SearchRequest{
@ -28,8 +36,22 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
id: rid,
ctx: span.Context(),
}
req.log.Info("Search request")
defer func() {
span.Finish()
req.log.WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Search request")
}()
defer func() {
err := recover()
if err == nil {
return
}
log.WithError(err.(error)).Error("recover in serach request")
sentry.CaptureException(err.(error))
}()
if searchReq.BaseDN == "" {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil

View File

@ -15,7 +15,6 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
log "github.com/sirupsen/logrus"
"goauthentik.io/api"
"goauthentik.io/internal/outpost/ak"
)
type providerBundle struct {
@ -89,14 +88,12 @@ func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options.
}
if provider.Certificate.Get() != nil {
pb.log.WithField("provider", provider.Name).Debug("Enabling TLS")
cert, err := ak.ParseCertificate(*provider.Certificate.Get(), pb.s.ak.Client.CryptoApi)
kp := provider.Certificate.Get()
err := pb.s.cs.AddKeypair(*kp)
if err != nil {
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
return providerOpts
pb.log.WithError(err).Warning("Failed to initially fetch certificate")
}
pb.cert = cert
pb.log.WithField("provider", provider.Name).Debug("Loaded certificates")
pb.cert = pb.s.cs.Get(*kp)
}
return providerOpts
}

View File

@ -10,6 +10,7 @@ type Claims struct {
Proxy struct {
UserAttributes map[string]interface{} `json:"user_attributes"`
} `json:"ak_proxy"`
Groups []string `json:"groups"`
}
func (c *Claims) FromIDToken(idToken string) error {

View File

@ -428,6 +428,10 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
if err != nil {
log.WithError(err).Warning("Failed to parse IDToken")
}
// Set groups in header
groups := strings.Join(claims.Groups, "|")
req.Header["X-Auth-Groups"] = []string{groups}
userAttributes := claims.Proxy.UserAttributes
// Attempt to set basic auth based on user's attributes
if p.SetBasicAuth {
@ -461,6 +465,7 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
if p.PassUserHeaders {
req.Header.Del("X-Forwarded-User")
req.Header.Del("X-Auth-Groups")
req.Header.Del("X-Forwarded-Email")
req.Header.Del("X-Forwarded-Preferred-Username")
}

View File

@ -21,6 +21,7 @@ type Server struct {
stop chan struct{} // channel for waiting shutdown
logger *log.Entry
ak *ak.APIController
cs *ak.CryptoStore
defaultCert tls.Certificate
}
@ -35,6 +36,7 @@ func NewServer(ac *ak.APIController) *Server {
logger: log.WithField("logger", "authentik.outpost.proxy-http-server"),
defaultCert: defaultCert,
ak: ac,
cs: ak.NewCryptoStore(ac.Client.CryptoApi),
}
}

View File

@ -15,8 +15,6 @@ RUN docker-entrypoint.sh generate \
# Stage 2: Build
FROM golang:1.16.6 AS builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
WORKDIR /go/src/goauthentik.io
@ -28,6 +26,9 @@ RUN go build -o /go/ldap ./cmd/ldap
# Stage 3: Run
FROM gcr.io/distroless/base-debian10:debug
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
COPY --from=builder /go/ldap /
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:4180/akprox/ping" ]

View File

@ -15,8 +15,6 @@ RUN docker-entrypoint.sh generate \
# Stage 2: Build
FROM golang:1.16.6 AS builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
WORKDIR /go/src/goauthentik.io
@ -28,6 +26,9 @@ RUN go build -o /go/proxy ./cmd/proxy
# Stage 3: Run
FROM gcr.io/distroless/base-debian10:debug
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
COPY --from=builder /go/proxy /
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:4180/akprox/ping" ]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2021.7.1-rc1
version: 2021.7.1
description: Making authentication simple.
contact:
email: hello@beryju.org
@ -1932,6 +1932,24 @@ paths:
name: is_superuser
schema:
type: boolean
- in: query
name: members_by_pk
schema:
type: array
items:
type: integer
explode: true
style: form
- in: query
name: members_by_username
schema:
type: array
items:
type: string
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
explode: true
style: form
- in: query
name: name
schema:
@ -2843,6 +2861,10 @@ paths:
schema:
type: string
description: Attributes
- in: query
name: email
schema:
type: string
- in: query
name: is_active
schema:
@ -25880,6 +25902,9 @@ components:
type: string
format: uuid
nullable: true
event_retention:
type: string
description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
PatchedTokenRequest:
type: object
description: Token Serializer
@ -26694,13 +26719,8 @@ components:
Exclusive with internal_host.
cookie_domain:
type: string
forward_auth_mode:
type: boolean
readOnly: true
deprecated: true
required:
- external_host
- forward_auth_mode
- name
- oidc_configuration
- pk
@ -27960,6 +27980,9 @@ components:
type: string
format: uuid
nullable: true
event_retention:
type: string
description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
required:
- domain
- tenant_uuid
@ -27995,6 +28018,9 @@ components:
type: string
format: uuid
nullable: true
event_retention:
type: string
description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
required:
- domain
Token:

334
web/package-lock.json generated
View File

@ -24,20 +24,20 @@
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.3",
"@sentry/browser": "^6.9.0",
"@sentry/tracing": "^6.9.0",
"@sentry/browser": "^6.10.0",
"@sentry/tracing": "^6.10.0",
"@types/chart.js": "^2.9.34",
"@types/codemirror": "5.60.2",
"@types/grecaptcha": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
"@webcomponents/webcomponentsjs": "^2.5.0",
"authentik-api": "file:api",
"babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1",
"chart.js": "^3.4.1",
"chart.js": "^3.5.0",
"chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.62.1",
"codemirror": "^5.62.2",
"construct-style-sheets-polyfill": "^2.4.16",
"eslint": "^7.31.0",
"eslint-config-google": "^0.14.0",
@ -48,7 +48,7 @@
"lit-html": "^1.4.1",
"moment": "^2.29.1",
"rapidoc": "^9.0.0",
"rollup": "^2.53.2",
"rollup": "^2.54.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2",
@ -2341,13 +2341,13 @@
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
},
"node_modules/@sentry/browser": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.9.0.tgz",
"integrity": "sha512-4JnEPcwoNs6JqeEd4wscBq+hxpotEJ0DJ4eOIsaNZIMyqEHXBHTXCk/gfrSsiZFrkHM4PgvUHOxaC0HcZ92oBA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.10.0.tgz",
"integrity": "sha512-H0Blgp8f8bomebkkGWIgxHVjabtQAlsKJDiFXBg7gIc75YcarRxwH0R3hMog1/h8mmv4CGGUsy5ljYW6jsNnvA==",
"dependencies": {
"@sentry/core": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/core": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2360,14 +2360,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/core": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.9.0.tgz",
"integrity": "sha512-oFX2qQcMLujCeIuCQGlhpTUIOXiU5n6V2lqDnvMXUV8gKpplBPalwdlR9bgbSi+VO8u7LjHR1IKM0RAPWgNHWw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.10.0.tgz",
"integrity": "sha512-5KlxHJlbD7AMo+b9pMGkjxUOfMILtsqCtGgI7DMvZNfEkdohO8QgUY+hPqr540kmwArFS91ipQYWhqzGaOhM3Q==",
"dependencies": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2380,12 +2380,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/hub": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.9.0.tgz",
"integrity": "sha512-5mors7ojbo7G85ZmoVPQBgFBMONAJwyZfV0LNLy14GenoaVNuxTPyvAQiJb1FYq+x6YZ3CvqGX6r74KRKQU87w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.10.0.tgz",
"integrity": "sha512-MV8wjhWiFAXZAhmj7Ef5QdBr2IF93u8xXiIo2J+dRZ7eVa4/ZszoUiDbhUcl/TPxczaw4oW2a6tINBNFLzXiig==",
"dependencies": {
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2398,12 +2398,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/minimal": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.9.0.tgz",
"integrity": "sha512-GBZ6wG2Rc1wInYEl2BZTZc/t57O1Da876ifLsSPpEQAEnGWbqZWb8RLjZskH09ZIL/K4XCIDDi5ySzN8kFUWJw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.10.0.tgz",
"integrity": "sha512-yarm046UgUFIBoxqnBan2+BEgaO9KZCrLzsIsmALiQvpfW92K1lHurSawl5W6SR7wCYBnNn7CPvPE/BHFdy4YA==",
"dependencies": {
"@sentry/hub": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2416,14 +2416,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/tracing": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.9.0.tgz",
"integrity": "sha512-gogVTypolhPazXr3Lue8HgzBg5Sy1cQpEp5Iq9LtECs+TlOlxJ+S+P+EIjEZ0f1AHVu706jr5cY2G2Shluli9g==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.10.0.tgz",
"integrity": "sha512-jZj6Aaf8kU5wgyNXbAJHosHn8OOFdK14lgwYPb/AIDsY35g9a9ncTOqIOBp8X3KkmSR8lcBzAEyiUzCxAis2jA==",
"dependencies": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2436,19 +2436,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/types": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.9.0.tgz",
"integrity": "sha512-v52HJqLoLapEnqS2NdVtUXPvT+aezQgNXQkp8hiQ3RUdTm5cffwBVG7wlbpE6OsOOIZxd6p1zKylFkwCypiIIA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.10.0.tgz",
"integrity": "sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw==",
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/utils": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.9.0.tgz",
"integrity": "sha512-PimDr6KAi4cCp5hQZ8Az2/pDcdfhTu7WAU30Dd9MZwknpHSTmD4G6QvkdrB5er6kMMnNQOC7rMo6w/Do3m6X3w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.10.0.tgz",
"integrity": "sha512-F9OczOcZMFtazYVZ6LfRIe65/eOfQbiAedIKS0li4npuMz0jKYRbxrjd/U7oLiNQkPAp4/BujU4m1ZIwq6a+tg==",
"dependencies": {
"@sentry/types": "6.9.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2606,12 +2606,12 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.4.tgz",
"integrity": "sha512-s1oY4RmYDlWMlcV0kKPBaADn46JirZzvvH7c2CtAqxCY96S538JRBAzt83RrfkDheV/+G/vWNK0zek+8TB3Gmw==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.5.tgz",
"integrity": "sha512-m31cPEnbuCqXtEZQJOXAHsHvtoDi9OVaeL5wZnO2KZTnkvELk+u6J6jHg+NzvWQxk+87Zjbc4lJS4NHmgImz6Q==",
"dependencies": {
"@typescript-eslint/experimental-utils": "4.28.4",
"@typescript-eslint/scope-manager": "4.28.4",
"@typescript-eslint/experimental-utils": "4.28.5",
"@typescript-eslint/scope-manager": "4.28.5",
"debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.1.0",
@ -2636,14 +2636,14 @@
}
},
"node_modules/@typescript-eslint/experimental-utils": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.4.tgz",
"integrity": "sha512-OglKWOQRWTCoqMSy6pm/kpinEIgdcXYceIcH3EKWUl4S8xhFtN34GQRaAvTIZB9DD94rW7d/U7tUg3SYeDFNHA==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.5.tgz",
"integrity": "sha512-bGPLCOJAa+j49hsynTaAtQIWg6uZd8VLiPcyDe4QPULsvQwLHGLSGKKcBN8/lBxIX14F74UEMK2zNDI8r0okwA==",
"dependencies": {
"@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.28.4",
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/typescript-estree": "4.28.4",
"@typescript-eslint/scope-manager": "4.28.5",
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/typescript-estree": "4.28.5",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
@ -2676,13 +2676,13 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.4.tgz",
"integrity": "sha512-4i0jq3C6n+og7/uCHiE6q5ssw87zVdpUj1k6VlVYMonE3ILdFApEzTWgppSRG4kVNB/5jxnH+gTeKLMNfUelQA==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.5.tgz",
"integrity": "sha512-NPCOGhTnkXGMqTznqgVbA5LqVsnw+i3+XA1UKLnAb+MG1Y1rP4ZSK9GX0kJBmAZTMIktf+dTwXToT6kFwyimbw==",
"dependencies": {
"@typescript-eslint/scope-manager": "4.28.4",
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/typescript-estree": "4.28.4",
"@typescript-eslint/scope-manager": "4.28.5",
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/typescript-estree": "4.28.5",
"debug": "^4.3.1"
},
"engines": {
@ -2702,12 +2702,12 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.4.tgz",
"integrity": "sha512-ZJBNs4usViOmlyFMt9X9l+X0WAFcDH7EdSArGqpldXu7aeZxDAuAzHiMAeI+JpSefY2INHrXeqnha39FVqXb8w==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz",
"integrity": "sha512-PHLq6n9nTMrLYcVcIZ7v0VY1X7dK309NM8ya9oL/yG8syFINIMHxyr2GzGoBYUdv3NUfCOqtuqps0ZmcgnZTfQ==",
"dependencies": {
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/visitor-keys": "4.28.4"
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/visitor-keys": "4.28.5"
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
@ -2718,9 +2718,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.4.tgz",
"integrity": "sha512-3eap4QWxGqkYuEmVebUGULMskR6Cuoc/Wii0oSOddleP4EGx1tjLnZQ0ZP33YRoMDCs5O3j56RBV4g14T4jvww==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.5.tgz",
"integrity": "sha512-MruOu4ZaDOLOhw4f/6iudyks/obuvvZUAHBDSW80Trnc5+ovmViLT2ZMDXhUV66ozcl6z0LJfKs1Usldgi/WCA==",
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
},
@ -2730,12 +2730,12 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.4.tgz",
"integrity": "sha512-z7d8HK8XvCRyN2SNp+OXC2iZaF+O2BTquGhEYLKLx5k6p0r05ureUtgEfo5f6anLkhCxdHtCf6rPM1p4efHYDQ==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.5.tgz",
"integrity": "sha512-FzJUKsBX8poCCdve7iV7ShirP8V+ys2t1fvamVeD1rWpiAnIm550a+BX/fmTHrjEpQJ7ZAn+Z7ZZwJjytk9rZw==",
"dependencies": {
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/visitor-keys": "4.28.4",
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/visitor-keys": "4.28.5",
"debug": "^4.3.1",
"globby": "^11.0.3",
"is-glob": "^4.0.1",
@ -2775,11 +2775,11 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.4.tgz",
"integrity": "sha512-NIAXAdbz1XdOuzqkJHjNKXKj8QQ4cv5cxR/g0uQhCYf/6//XrmfpaYsM7PnBcNbfvTDLUkqQ5TPNm1sozDdTWg==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.5.tgz",
"integrity": "sha512-dva/7Rr+EkxNWdJWau26xU/0slnFlkh88v3TsyTgRS/IIYFi5iIfpCFM4ikw0vQTFUR9FYSSyqgK4w64gsgxhg==",
"dependencies": {
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/types": "4.28.5",
"eslint-visitor-keys": "^2.0.0"
},
"engines": {
@ -3343,9 +3343,9 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"node_modules/chart.js": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.1.tgz",
"integrity": "sha512-0R4mL7WiBcYoazIhrzSYnWcOw6RmrRn7Q4nKZNsBQZCBrlkZKodQbfeojCCo8eETPRCs1ZNTsAcZhIfyhyP61g=="
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz",
"integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA=="
},
"node_modules/chartjs-adapter-moment": {
"version": "1.0.0",
@ -3502,9 +3502,9 @@
}
},
"node_modules/codemirror": {
"version": "5.62.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.1.tgz",
"integrity": "sha512-39ce8tHh/M9J+Epa90R5zMGg06pxVXc1+Y0SRR6eKaUjjzuj5iYkk7rHc2uU+FzvfsWYGEYKPFf0pBVBLmYXNQ=="
"version": "5.62.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.2.tgz",
"integrity": "sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw=="
},
"node_modules/collection-visit": {
"version": "1.0.0",
@ -6790,9 +6790,9 @@
}
},
"node_modules/rollup": {
"version": "2.53.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz",
"integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==",
"version": "2.54.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.54.0.tgz",
"integrity": "sha512-RHzvstAVwm9A751NxWIbGPFXs3zL4qe/eYg+N7WwGtIXVLy1cK64MiU37+hXeFm1jqipK6DGgMi6Z2hhPuCC3A==",
"bin": {
"rollup": "dist/bin/rollup"
},
@ -9716,13 +9716,13 @@
}
},
"@sentry/browser": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.9.0.tgz",
"integrity": "sha512-4JnEPcwoNs6JqeEd4wscBq+hxpotEJ0DJ4eOIsaNZIMyqEHXBHTXCk/gfrSsiZFrkHM4PgvUHOxaC0HcZ92oBA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.10.0.tgz",
"integrity": "sha512-H0Blgp8f8bomebkkGWIgxHVjabtQAlsKJDiFXBg7gIc75YcarRxwH0R3hMog1/h8mmv4CGGUsy5ljYW6jsNnvA==",
"requires": {
"@sentry/core": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/core": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -9734,14 +9734,14 @@
}
},
"@sentry/core": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.9.0.tgz",
"integrity": "sha512-oFX2qQcMLujCeIuCQGlhpTUIOXiU5n6V2lqDnvMXUV8gKpplBPalwdlR9bgbSi+VO8u7LjHR1IKM0RAPWgNHWw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.10.0.tgz",
"integrity": "sha512-5KlxHJlbD7AMo+b9pMGkjxUOfMILtsqCtGgI7DMvZNfEkdohO8QgUY+hPqr540kmwArFS91ipQYWhqzGaOhM3Q==",
"requires": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -9753,12 +9753,12 @@
}
},
"@sentry/hub": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.9.0.tgz",
"integrity": "sha512-5mors7ojbo7G85ZmoVPQBgFBMONAJwyZfV0LNLy14GenoaVNuxTPyvAQiJb1FYq+x6YZ3CvqGX6r74KRKQU87w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.10.0.tgz",
"integrity": "sha512-MV8wjhWiFAXZAhmj7Ef5QdBr2IF93u8xXiIo2J+dRZ7eVa4/ZszoUiDbhUcl/TPxczaw4oW2a6tINBNFLzXiig==",
"requires": {
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -9770,12 +9770,12 @@
}
},
"@sentry/minimal": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.9.0.tgz",
"integrity": "sha512-GBZ6wG2Rc1wInYEl2BZTZc/t57O1Da876ifLsSPpEQAEnGWbqZWb8RLjZskH09ZIL/K4XCIDDi5ySzN8kFUWJw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.10.0.tgz",
"integrity": "sha512-yarm046UgUFIBoxqnBan2+BEgaO9KZCrLzsIsmALiQvpfW92K1lHurSawl5W6SR7wCYBnNn7CPvPE/BHFdy4YA==",
"requires": {
"@sentry/hub": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -9787,14 +9787,14 @@
}
},
"@sentry/tracing": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.9.0.tgz",
"integrity": "sha512-gogVTypolhPazXr3Lue8HgzBg5Sy1cQpEp5Iq9LtECs+TlOlxJ+S+P+EIjEZ0f1AHVu706jr5cY2G2Shluli9g==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.10.0.tgz",
"integrity": "sha512-jZj6Aaf8kU5wgyNXbAJHosHn8OOFdK14lgwYPb/AIDsY35g9a9ncTOqIOBp8X3KkmSR8lcBzAEyiUzCxAis2jA==",
"requires": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -9806,16 +9806,16 @@
}
},
"@sentry/types": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.9.0.tgz",
"integrity": "sha512-v52HJqLoLapEnqS2NdVtUXPvT+aezQgNXQkp8hiQ3RUdTm5cffwBVG7wlbpE6OsOOIZxd6p1zKylFkwCypiIIA=="
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.10.0.tgz",
"integrity": "sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw=="
},
"@sentry/utils": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.9.0.tgz",
"integrity": "sha512-PimDr6KAi4cCp5hQZ8Az2/pDcdfhTu7WAU30Dd9MZwknpHSTmD4G6QvkdrB5er6kMMnNQOC7rMo6w/Do3m6X3w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.10.0.tgz",
"integrity": "sha512-F9OczOcZMFtazYVZ6LfRIe65/eOfQbiAedIKS0li4npuMz0jKYRbxrjd/U7oLiNQkPAp4/BujU4m1ZIwq6a+tg==",
"requires": {
"@sentry/types": "6.9.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -9972,12 +9972,12 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
},
"@typescript-eslint/eslint-plugin": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.4.tgz",
"integrity": "sha512-s1oY4RmYDlWMlcV0kKPBaADn46JirZzvvH7c2CtAqxCY96S538JRBAzt83RrfkDheV/+G/vWNK0zek+8TB3Gmw==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.5.tgz",
"integrity": "sha512-m31cPEnbuCqXtEZQJOXAHsHvtoDi9OVaeL5wZnO2KZTnkvELk+u6J6jHg+NzvWQxk+87Zjbc4lJS4NHmgImz6Q==",
"requires": {
"@typescript-eslint/experimental-utils": "4.28.4",
"@typescript-eslint/scope-manager": "4.28.4",
"@typescript-eslint/experimental-utils": "4.28.5",
"@typescript-eslint/scope-manager": "4.28.5",
"debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.1.0",
@ -9986,14 +9986,14 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.4.tgz",
"integrity": "sha512-OglKWOQRWTCoqMSy6pm/kpinEIgdcXYceIcH3EKWUl4S8xhFtN34GQRaAvTIZB9DD94rW7d/U7tUg3SYeDFNHA==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.5.tgz",
"integrity": "sha512-bGPLCOJAa+j49hsynTaAtQIWg6uZd8VLiPcyDe4QPULsvQwLHGLSGKKcBN8/lBxIX14F74UEMK2zNDI8r0okwA==",
"requires": {
"@types/json-schema": "^7.0.7",
"@typescript-eslint/scope-manager": "4.28.4",
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/typescript-estree": "4.28.4",
"@typescript-eslint/scope-manager": "4.28.5",
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/typescript-estree": "4.28.5",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
@ -10009,37 +10009,37 @@
}
},
"@typescript-eslint/parser": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.4.tgz",
"integrity": "sha512-4i0jq3C6n+og7/uCHiE6q5ssw87zVdpUj1k6VlVYMonE3ILdFApEzTWgppSRG4kVNB/5jxnH+gTeKLMNfUelQA==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.5.tgz",
"integrity": "sha512-NPCOGhTnkXGMqTznqgVbA5LqVsnw+i3+XA1UKLnAb+MG1Y1rP4ZSK9GX0kJBmAZTMIktf+dTwXToT6kFwyimbw==",
"requires": {
"@typescript-eslint/scope-manager": "4.28.4",
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/typescript-estree": "4.28.4",
"@typescript-eslint/scope-manager": "4.28.5",
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/typescript-estree": "4.28.5",
"debug": "^4.3.1"
}
},
"@typescript-eslint/scope-manager": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.4.tgz",
"integrity": "sha512-ZJBNs4usViOmlyFMt9X9l+X0WAFcDH7EdSArGqpldXu7aeZxDAuAzHiMAeI+JpSefY2INHrXeqnha39FVqXb8w==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.5.tgz",
"integrity": "sha512-PHLq6n9nTMrLYcVcIZ7v0VY1X7dK309NM8ya9oL/yG8syFINIMHxyr2GzGoBYUdv3NUfCOqtuqps0ZmcgnZTfQ==",
"requires": {
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/visitor-keys": "4.28.4"
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/visitor-keys": "4.28.5"
}
},
"@typescript-eslint/types": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.4.tgz",
"integrity": "sha512-3eap4QWxGqkYuEmVebUGULMskR6Cuoc/Wii0oSOddleP4EGx1tjLnZQ0ZP33YRoMDCs5O3j56RBV4g14T4jvww=="
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.5.tgz",
"integrity": "sha512-MruOu4ZaDOLOhw4f/6iudyks/obuvvZUAHBDSW80Trnc5+ovmViLT2ZMDXhUV66ozcl6z0LJfKs1Usldgi/WCA=="
},
"@typescript-eslint/typescript-estree": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.4.tgz",
"integrity": "sha512-z7d8HK8XvCRyN2SNp+OXC2iZaF+O2BTquGhEYLKLx5k6p0r05ureUtgEfo5f6anLkhCxdHtCf6rPM1p4efHYDQ==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.5.tgz",
"integrity": "sha512-FzJUKsBX8poCCdve7iV7ShirP8V+ys2t1fvamVeD1rWpiAnIm550a+BX/fmTHrjEpQJ7ZAn+Z7ZZwJjytk9rZw==",
"requires": {
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/visitor-keys": "4.28.4",
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/visitor-keys": "4.28.5",
"debug": "^4.3.1",
"globby": "^11.0.3",
"is-glob": "^4.0.1",
@ -10063,11 +10063,11 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.4.tgz",
"integrity": "sha512-NIAXAdbz1XdOuzqkJHjNKXKj8QQ4cv5cxR/g0uQhCYf/6//XrmfpaYsM7PnBcNbfvTDLUkqQ5TPNm1sozDdTWg==",
"version": "4.28.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.5.tgz",
"integrity": "sha512-dva/7Rr+EkxNWdJWau26xU/0slnFlkh88v3TsyTgRS/IIYFi5iIfpCFM4ikw0vQTFUR9FYSSyqgK4w64gsgxhg==",
"requires": {
"@typescript-eslint/types": "4.28.4",
"@typescript-eslint/types": "4.28.5",
"eslint-visitor-keys": "^2.0.0"
}
},
@ -10508,9 +10508,9 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"chart.js": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.1.tgz",
"integrity": "sha512-0R4mL7WiBcYoazIhrzSYnWcOw6RmrRn7Q4nKZNsBQZCBrlkZKodQbfeojCCo8eETPRCs1ZNTsAcZhIfyhyP61g=="
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz",
"integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA=="
},
"chartjs-adapter-moment": {
"version": "1.0.0",
@ -10636,9 +10636,9 @@
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
},
"codemirror": {
"version": "5.62.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.1.tgz",
"integrity": "sha512-39ce8tHh/M9J+Epa90R5zMGg06pxVXc1+Y0SRR6eKaUjjzuj5iYkk7rHc2uU+FzvfsWYGEYKPFf0pBVBLmYXNQ=="
"version": "5.62.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.2.tgz",
"integrity": "sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw=="
},
"collection-visit": {
"version": "1.0.0",
@ -13241,9 +13241,9 @@
}
},
"rollup": {
"version": "2.53.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz",
"integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==",
"version": "2.54.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.54.0.tgz",
"integrity": "sha512-RHzvstAVwm9A751NxWIbGPFXs3zL4qe/eYg+N7WwGtIXVLy1cK64MiU37+hXeFm1jqipK6DGgMi6Z2hhPuCC3A==",
"requires": {
"fsevents": "~2.3.2"
}

View File

@ -53,20 +53,20 @@
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.3",
"@sentry/browser": "^6.9.0",
"@sentry/tracing": "^6.9.0",
"@sentry/browser": "^6.10.0",
"@sentry/tracing": "^6.10.0",
"@types/chart.js": "^2.9.34",
"@types/codemirror": "5.60.2",
"@types/grecaptcha": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
"@webcomponents/webcomponentsjs": "^2.5.0",
"authentik-api": "file:api",
"babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1",
"chart.js": "^3.4.1",
"chart.js": "^3.5.0",
"chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.62.1",
"codemirror": "^5.62.2",
"construct-style-sheets-polyfill": "^2.4.16",
"eslint": "^7.31.0",
"eslint-config-google": "^0.14.0",
@ -77,7 +77,7 @@
"lit-html": "^1.4.1",
"moment": "^2.29.1",
"rapidoc": "^9.0.0",
"rollup": "^2.53.2",
"rollup": "^2.54.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2",

View File

@ -7,6 +7,7 @@
--ak-dark-background: #18191a;
--ak-dark-background-darker: #000000;
--ak-dark-background-light: #1c1e21;
--ak-dark-background-light-ish: #212427;
--ak-dark-background-lighter: #2b2e33;
}
@ -127,7 +128,8 @@ body {
.pf-c-page__main-section {
--pf-c-page__main-section--BackgroundColor: var(--ak-dark-background);
}
.sidebar-trigger {
.sidebar-trigger,
.notification-trigger {
background-color: var(--ak-dark-background-light) !important;
}
.pf-c-page__main-section.pf-m-light {
@ -302,6 +304,9 @@ body {
color: var(--ak-dark-foreground);
}
/* notifications */
.pf-c-drawer__panel {
background-color: var(--ak-dark-background);
}
.pf-c-notification-drawer {
--pf-c-notification-drawer--BackgroundColor: var(--ak-dark-background);
}
@ -309,6 +314,10 @@ body {
background-color: var(--ak-dark-background-lighter);
color: var(--ak-dark-foreground);
}
.pf-c-notification-drawer__list-item {
background-color: var(--ak-dark-background-light-ish);
color: var(--ak-dark-foreground);
}
/* data list */
.pf-c-data-list__item {
--pf-c-data-list__item--BackgroundColor: var(--ak-dark-background-light);

61
web/src/common/ws.ts Normal file
View File

@ -0,0 +1,61 @@
import { t } from "@lingui/macro";
import { EVENT_WS_MESSAGE } from "../constants";
import { MessageLevel } from "../elements/messages/Message";
import { showMessage } from "../elements/messages/MessageContainer";
export interface WSMessage {
message_type: string;
}
export class WebsocketClient {
messageSocket?: WebSocket;
retryDelay = 200;
constructor() {
try {
this.connect();
} catch (error) {
console.warn(`authentik/ws: failed to connect to ws ${error}`);
}
}
connect(): void {
if (navigator.webdriver) return;
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${window.location.host
}/ws/client/`;
this.messageSocket = new WebSocket(wsUrl);
this.messageSocket.addEventListener("open", () => {
console.debug(`authentik/ws: connected to ${wsUrl}`);
this.retryDelay = 200;
});
this.messageSocket.addEventListener("close", (e) => {
console.debug(`authentik/ws: closed ws connection: ${e}`);
if (this.retryDelay > 3000) {
showMessage({
level: MessageLevel.error,
message: t`Connection error, reconnecting...`
});
}
setTimeout(() => {
console.debug(`authentik/ws: reconnecting ws in ${this.retryDelay}ms`);
this.connect();
}, this.retryDelay);
this.retryDelay = this.retryDelay * 2;
});
this.messageSocket.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
window.dispatchEvent(
new CustomEvent(EVENT_WS_MESSAGE, {
bubbles: true,
composed: true,
detail: data as WSMessage,
})
);
});
this.messageSocket.addEventListener("error", () => {
this.retryDelay = this.retryDelay * 2;
});
}
}

View File

@ -3,11 +3,16 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2021.7.1-rc1";
export const VERSION = "2021.7.1";
export const PAGE_SIZE = 20;
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";
export const EVENT_REFRESH = "ak-refresh";
export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle";
export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle";
export const EVENT_API_DRAWER_REFRESH = "ak-api-drawer-refresh";
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";
export const EVENT_WS_MESSAGE = "ak-ws-message";
export const WS_MSG_TYPE_MESSAGE = "message";
export const WS_MSG_TYPE_REFRESH = "refresh";

View File

@ -4,8 +4,9 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css";
import AKGlobal from "../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import { EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants";
import { tenant } from "../api/Config";
import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants";
import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { EventsApi } from "../../api/dist";
@customElement("ak-page-header")
export class PageHeader extends LitElement {
@ -16,6 +17,9 @@ export class PageHeader extends LitElement {
@property({type: Boolean})
iconImage = false
@property({type: Boolean})
hasNotifications = false;
@property()
set header(value: string) {
tenant().then(tenant => {
@ -44,12 +48,12 @@ export class PageHeader extends LitElement {
flex-direction: row;
min-height: 114px;
}
button.pf-c-button.pf-m-plain.sidebar-trigger {
.pf-c-button.pf-m-plain {
background-color: var(--pf-c-page__main-section--m-light--BackgroundColor);
border-radius: 0px;
}
.pf-c-page__main-section {
width: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
@ -57,6 +61,13 @@ export class PageHeader extends LitElement {
img.pf-icon {
max-height: 24px;
}
.sidebar-trigger,
.notification-trigger {
font-size: 24px;
}
.notification-trigger.has-notifications {
color: #2B9AF3;
}
`];
}
@ -70,6 +81,16 @@ export class PageHeader extends LitElement {
return html``;
}
firstUpdated(): void {
new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
seen: false,
ordering: "-created",
pageSize: 1,
}).then(r => {
this.hasNotifications = r.pagination.count > 0;
});
}
render(): TemplateResult {
return html`<button
class="sidebar-trigger pf-c-button pf-m-plain"
@ -92,7 +113,19 @@ export class PageHeader extends LitElement {
${this.description ?
html`<p>${this.description}</p>` : html``}
</div>
</section>`;
</section>
<button
class="notification-trigger pf-c-button pf-m-plain ${this.hasNotifications ? "has-notifications" : ""}"
@click=${() => {
this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_TOGGLE, {
bubbles: true,
composed: true,
})
);
}}>
<i class="fas fa-bell"></i>
</button>`;
}
}

View File

@ -1,9 +1,10 @@
import { t } from "@lingui/macro";
import { LitElement, html, customElement, TemplateResult, property, CSSResult, css } from "lit-element";
import "./Message";
import { APIMessage, MessageLevel } from "./Message";
import { APIMessage } from "./Message";
import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { EVENT_WS_MESSAGE, WS_MSG_TYPE_MESSAGE } from "../../constants";
import { WSMessage } from "../../common/ws";
export function showMessage(message: APIMessage): void {
const container = document.querySelector<MessageContainer>("ak-message-container");
@ -20,9 +21,6 @@ export class MessageContainer extends LitElement {
@property({attribute: false})
messages: APIMessage[] = [];
messageSocket?: WebSocket;
retryDelay = 200;
static get styles(): CSSResult[] {
return [PFBase, PFAlertGroup, css`
/* Fix spacing between messages */
@ -34,11 +32,10 @@ export class MessageContainer extends LitElement {
constructor() {
super();
try {
this.connect();
} catch (error) {
console.warn(`authentik/messages: failed to connect to ws ${error}`);
}
this.addEventListener(EVENT_WS_MESSAGE, ((e: CustomEvent<WSMessage>) => {
if (e.detail.message_type !== WS_MSG_TYPE_MESSAGE) return;
this.addMessage(e.detail as unknown as APIMessage);
}) as EventListener);
}
// add a new message, but only if the message isn't currently shown.
@ -49,40 +46,6 @@ export class MessageContainer extends LitElement {
}
}
connect(): void {
if (navigator.webdriver) return;
const wsUrl = `${window.location.protocol.replace("http", "ws")}//${
window.location.host
}/ws/client/`;
this.messageSocket = new WebSocket(wsUrl);
this.messageSocket.addEventListener("open", () => {
console.debug(`authentik/messages: connected to ${wsUrl}`);
this.retryDelay = 200;
});
this.messageSocket.addEventListener("close", (e) => {
console.debug(`authentik/messages: closed ws connection: ${e}`);
if (this.retryDelay > 3000) {
showMessage({
level: MessageLevel.error,
message: t`Connection error, reconnecting...`
});
}
setTimeout(() => {
console.debug(`authentik/messages: reconnecting ws in ${this.retryDelay}ms`);
this.connect();
}, this.retryDelay);
this.retryDelay = this.retryDelay * 2;
});
this.messageSocket.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
this.addMessage(data);
this.requestUpdate();
});
this.messageSocket.addEventListener("error", () => {
this.retryDelay = this.retryDelay * 2;
});
}
render(): TemplateResult {
return html`<ul class="pf-c-alert-group pf-m-toast">
${this.messages.map((m) => {

View File

@ -10,6 +10,7 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css";
import AKGlobal from "../../authentik.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import { EVENT_NOTIFICATION_TOGGLE } from "../../constants";
import { ActionToLabel } from "../../pages/events/utils";
@customElement("ak-notification-drawer")
export class NotificationDrawer extends LitElement {
@ -23,6 +24,15 @@ export class NotificationDrawer extends LitElement {
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFNotificationDrawer, PFContent, PFDropdown, AKGlobal].concat(
css`
.pf-c-notification-drawer__header {
height: 114px;
align-items: center;
}
.pf-c-notification-drawer__header-action,
.pf-c-notification-drawer__header-action-close,
.pf-c-notification-drawer__header-action-close > .pf-c-button.pf-m-plain {
height: 100%;
}
.pf-c-notification-drawer__list-item-description {
white-space: pre-wrap;
}
@ -55,13 +65,13 @@ export class NotificationDrawer extends LitElement {
default:
break;
}
return html`<li class="pf-c-notification-drawer__list-item pf-m-read">
return html`<li class="pf-c-notification-drawer__list-item">
<div class="pf-c-notification-drawer__list-item-header">
<span class="pf-c-notification-drawer__list-item-header-icon ${level}">
<i class="fas fa-info-circle" aria-hidden="true"></i>
</span>
<h2 class="pf-c-notification-drawer__list-item-header-title">
${item.event?.action}
${ActionToLabel(item.event?.action)}
</h2>
</div>
<div class="pf-c-notification-drawer__list-item-action">
@ -95,12 +105,14 @@ export class NotificationDrawer extends LitElement {
return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer">
<div class="pf-c-notification-drawer__header">
<h1 class="pf-c-notification-drawer__header-title">
${t`Notifications`}
</h1>
<span class="pf-c-notification-drawer__header-status">
${t`${this.unread} unread`}
</span>
<div class="text">
<h1 class="pf-c-notification-drawer__header-title">
${t`Notifications`}
</h1>
<span>
${t`${this.unread} unread`}
</span>
</div>
<div class="pf-c-notification-drawer__header-action">
<div class="pf-c-notification-drawer__header-action-close">
<button

View File

@ -1,31 +0,0 @@
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
import FA from "@fortawesome/fontawesome-free/css/fontawesome.css";
import { EVENT_NOTIFICATION_TOGGLE } from "../../constants";
@customElement("ak-notification-trigger")
export class NotificationRule extends LitElement {
static get styles(): CSSResult[] {
return [PFBase, PFDropdown, FA];
}
constructor() {
super();
this.addEventListener("click", () => {
this.dispatchEvent(
new CustomEvent(EVENT_NOTIFICATION_TOGGLE, {
bubbles: true,
composed: true,
})
);
});
}
render(): TemplateResult {
// TODO: Show icon with red dot when unread notifications exist
return html`<i class="fas fa-bell pf-c-dropdown__toggle-icon" aria-hidden="true"></i>`;
}
}

View File

@ -5,7 +5,6 @@ import { me } from "../../api/Users";
import { until } from "lit-html/directives/until";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import "../notifications/NotificationTrigger";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-sidebar-user")
@ -39,8 +38,6 @@ export class SidebarUser extends LitElement {
return html`<img class="pf-c-avatar" src="${ifDefined(u.user.avatar)}" alt="" />`;
}), html``)}
</a>
<ak-notification-trigger class="pf-c-nav__link user-notifications">
</ak-notification-trigger>
<a href="/flows/-/default/invalidation/" class="pf-c-nav__link user-logout" id="logout">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
</a>

View File

@ -33,6 +33,7 @@ import { until } from "lit-html/directives/until";
import { PFSize } from "../elements/Spinner";
import { TITLE_DEFAULT } from "../constants";
import { configureSentry } from "../api/Sentry";
import { WebsocketClient } from "../common/ws";
@customElement("ak-flow-executor")
export class FlowExecutor extends LitElement implements StageHost {
@ -48,6 +49,8 @@ export class FlowExecutor extends LitElement implements StageHost {
@property({ attribute: false })
tenant?: CurrentTenant;
ws: WebsocketClient;
static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal].concat(css`
.ak-loading {
@ -75,6 +78,8 @@ export class FlowExecutor extends LitElement implements StageHost {
constructor() {
super();
this.ws = new WebsocketClient();
this.ws.connect();
this.flowSlug = window.location.pathname.split("/")[3];
}

View File

@ -9,6 +9,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
import AKGlobal from "../authentik.css";
import "../elements/router/RouterOutlet";
import "../elements/messages/MessageContainer";
@ -18,6 +19,7 @@ import { until } from "lit-html/directives/until";
import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, VERSION } from "../constants";
import { AdminApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../api/Config";
import { WebsocketClient } from "../common/ws";
@customElement("ak-interface-admin")
@ -29,8 +31,10 @@ export class AdminInterface extends LitElement {
@property({type: Boolean})
notificationOpen = false;
ws: WebsocketClient;
static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFDrawer, css`
return [PFBase, PFPage, PFButton, PFDrawer, AKGlobal, css`
.pf-c-page__main, .pf-c-drawer__content, .pf-c-page__drawer {
z-index: auto !important;
}
@ -39,6 +43,8 @@ export class AdminInterface extends LitElement {
constructor() {
super();
this.ws = new WebsocketClient();
this.ws.connect();
this.sidebarOpen = window.innerWidth >= 1280;
window.addEventListener("resize", () => {
this.sidebarOpen = window.innerWidth >= 1280;

View File

@ -208,6 +208,10 @@ msgstr "Application"
msgid "Application Icon"
msgstr "Application Icon"
#: src/pages/events/utils.ts
msgid "Application authorized"
msgstr "Application authorized"
#: src/flows/stages/consent/ConsentStage.ts
msgid "Application requires following permissions:"
msgstr "Application requires following permissions:"
@ -676,6 +680,10 @@ msgstr "Confidential clients are capable of maintaining the confidentiality of t
msgid "Configuration"
msgstr "Configuration"
#: src/pages/events/utils.ts
msgid "Configuration error"
msgstr "Configuration error"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
@ -735,7 +743,7 @@ msgstr "Connect"
msgid "Connected."
msgstr "Connected."
#: src/elements/messages/MessageContainer.ts
#: src/common/ws.ts
msgid "Connection error, reconnecting..."
msgstr "Connection error, reconnecting..."
@ -1222,6 +1230,10 @@ msgstr "Duo activation"
msgid "Duo push-notifications"
msgstr "Duo push-notifications"
#: src/pages/tenants/TenantForm.ts
msgid "Duration after which events will be deleted from the database."
msgstr "Duration after which events will be deleted from the database."
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Each provider has a different issuer, based on the application slug."
msgstr "Each provider has a different issuer, based on the application slug."
@ -1299,6 +1311,10 @@ msgstr "Email address"
msgid "Email info:"
msgstr "Email info:"
#: src/pages/events/utils.ts
msgid "Email sent"
msgstr "Email sent"
#: src/pages/stages/prompt/PromptForm.ts
msgid "Email: Text field with Email type."
msgstr "Email: Text field with Email type."
@ -1396,6 +1412,10 @@ msgstr "Event Log"
msgid "Event info"
msgstr "Event info"
#: src/pages/tenants/TenantForm.ts
msgid "Event retention"
msgstr "Event retention"
#: src/pages/events/EventInfoPage.ts
msgid "Event {0}"
msgstr "Event {0}"
@ -1404,6 +1424,10 @@ msgstr "Event {0}"
msgid "Events"
msgstr "Events"
#: src/pages/admin-overview/cards/SystemStatusCard.ts
msgid "Everything is ok."
msgstr "Everything is ok."
#: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts
@ -1505,6 +1529,10 @@ msgstr "External host"
msgid "Failed attempts before cancel"
msgstr "Failed attempts before cancel"
#: src/pages/events/utils.ts
msgid "Failed login"
msgstr "Failed login"
#: src/pages/admin-overview/charts/LDAPSyncStatusChart.ts
msgid "Failed sources"
msgstr "Failed sources"
@ -1650,6 +1678,10 @@ msgstr "Forgot username or password?"
msgid "Form didn't return a promise for submitting"
msgstr "Form didn't return a promise for submitting"
#: src/pages/tenants/TenantForm.ts
msgid "Format: \"weeks=3;days=2;hours=3,seconds=2\"."
msgstr "Format: \"weeks=3;days=2;hours=3,seconds=2\"."
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "Forward auth (domain level)"
msgstr "Forward auth (domain level)"
@ -1674,6 +1706,10 @@ msgstr "From address"
msgid "GID start number"
msgstr "GID start number"
#: src/pages/events/utils.ts
msgid "General system exception"
msgstr "General system exception"
#: src/pages/admin-overview/AdminOverviewPage.ts
msgid "General system status"
msgstr "General system status"
@ -1741,6 +1777,10 @@ msgstr "HTTP-Basic Password Key"
msgid "HTTP-Basic Username Key"
msgstr "HTTP-Basic Username Key"
#: src/pages/admin-overview/cards/SystemStatusCard.ts
msgid "HTTPS is not detected correctly"
msgstr "HTTPS is not detected correctly"
#: src/pages/outposts/OutpostListPage.ts
msgid "Health and Version"
msgstr "Health and Version"
@ -1857,6 +1897,14 @@ msgstr "If your authentik Instance is using a self-signed certificate, set this
msgid "Impersonate"
msgstr "Impersonate"
#: src/pages/events/utils.ts
msgid "Impersonation ended"
msgstr "Impersonation ended"
#: src/pages/events/utils.ts
msgid "Impersonation started"
msgstr "Impersonation started"
#: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowListPage.ts
msgid "Import"
@ -1918,6 +1966,10 @@ msgstr "Invalidation flow"
msgid "Invitation"
msgstr "Invitation"
#: src/pages/events/utils.ts
msgid "Invitation used"
msgstr "Invitation used"
#: src/interfaces/AdminInterface.ts
#: src/pages/stages/invitation/InvitationListPage.ts
msgid "Invitations"
@ -2136,6 +2188,10 @@ msgstr "Local"
msgid "Log the currently pending user in."
msgstr "Log the currently pending user in."
#: src/pages/events/utils.ts
msgid "Login"
msgstr "Login"
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP."
msgstr "Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP."
@ -2157,6 +2213,10 @@ msgstr "Logins over the last 24 hours"
msgid "Logo"
msgstr "Logo"
#: src/pages/events/utils.ts
msgid "Logout"
msgstr "Logout"
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
msgid "Logout URL"
msgstr "Logout URL"
@ -2247,6 +2307,18 @@ msgstr "Mode"
msgid "Model Name"
msgstr "Model Name"
#: src/pages/events/utils.ts
msgid "Model created"
msgstr "Model created"
#: src/pages/events/utils.ts
msgid "Model deleted"
msgstr "Model deleted"
#: src/pages/events/utils.ts
msgid "Model updated"
msgstr "Model updated"
#: src/interfaces/AdminInterface.ts
msgid "Monitor"
msgstr "Monitor"
@ -2591,6 +2663,10 @@ msgstr "Optionally set this to your parent domain, if you want authentication an
msgid "Order"
msgstr "Order"
#: src/pages/tenants/TenantForm.ts
msgid "Other global settings"
msgstr "Other global settings"
#: src/pages/admin-overview/charts/OutpostStatusChart.ts
msgid "Outdated outposts"
msgstr "Outdated outposts"
@ -2665,6 +2741,10 @@ msgstr "Password"
msgid "Password field"
msgstr "Password field"
#: src/pages/events/utils.ts
msgid "Password set"
msgstr "Password set"
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "Password stage"
msgstr "Password stage"
@ -2730,6 +2810,14 @@ msgstr "Policy binding"
msgid "Policy engine mode"
msgstr "Policy engine mode"
#: src/pages/events/utils.ts
msgid "Policy exception"
msgstr "Policy exception"
#: src/pages/events/utils.ts
msgid "Policy execution"
msgstr "Policy execution"
#: src/pages/policies/BoundPoliciesList.ts
msgid "Policy {0}"
msgstr "Policy {0}"
@ -2798,6 +2886,10 @@ msgstr "Prompts"
msgid "Property Mapping"
msgstr "Property Mapping"
#: src/pages/events/utils.ts
msgid "Property Mapping exception"
msgstr "Property Mapping exception"
#: src/interfaces/AdminInterface.ts
#: src/pages/property-mappings/PropertyMappingListPage.ts
msgid "Property Mappings"
@ -3144,6 +3236,14 @@ msgstr "Search..."
msgid "Secret key"
msgstr "Secret key"
#: src/pages/events/utils.ts
msgid "Secret was rotation"
msgstr "Secret was rotation"
#: src/pages/events/utils.ts
msgid "Secret was viewed"
msgstr "Secret was viewed"
#: src/pages/events/EventInfo.ts
msgid "Secret:"
msgstr "Secret:"
@ -3228,6 +3328,10 @@ msgstr "Separator: Static Separator Line"
msgid "Server URI"
msgstr "Server URI"
#: src/pages/admin-overview/cards/SystemStatusCard.ts
msgid "Server and client are further than 5 seconds apart."
msgstr "Server and client are further than 5 seconds apart."
#: src/pages/providers/ldap/LDAPProviderForm.ts
msgid "Server name for which this provider's certificate is valid for."
msgstr "Server name for which this provider's certificate is valid for."
@ -3349,6 +3453,10 @@ msgstr "Something went wrong! Please try again later."
msgid "Source"
msgstr "Source"
#: src/pages/events/utils.ts
msgid "Source linked"
msgstr "Source linked"
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
msgid "Source {0}"
msgstr "Source {0}"
@ -3768,6 +3876,10 @@ msgstr "Superuser-groups"
msgid "Superusers"
msgstr "Superusers"
#: src/pages/events/utils.ts
msgid "Suspicious request"
msgstr "Suspicious request"
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Symbol charset"
msgstr "Symbol charset"
@ -3797,6 +3909,18 @@ msgstr "System Overview"
msgid "System Tasks"
msgstr "System Tasks"
#: src/pages/admin-overview/AdminOverviewPage.ts
msgid "System status"
msgstr "System status"
#: src/pages/events/utils.ts
msgid "System task exception"
msgstr "System task exception"
#: src/pages/events/utils.ts
msgid "System task execution"
msgstr "System task execution"
#: src/pages/outposts/ServiceConnectionDockerForm.ts
msgid "TLS Authentication Certificate"
msgstr "TLS Authentication Certificate"
@ -3933,6 +4057,10 @@ msgstr "These policies control which users can access this application."
msgid "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well."
msgstr "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well."
#: src/pages/tenants/TenantForm.ts
msgid "This setting only affects new Events, as the expiration is saved per-event."
msgstr "This setting only affects new Events, as the expiration is saved per-event."
#: src/pages/stages/invitation/InvitationStageForm.ts
msgid "This stage can be included in enrollment flows to accept invitations."
msgstr "This stage can be included in enrollment flows to accept invitations."
@ -4255,6 +4383,10 @@ msgstr "Update Token"
msgid "Update User"
msgstr "Update User"
#: src/pages/events/utils.ts
msgid "Update available"
msgstr "Update available"
#: src/pages/user-settings/UserDetailsPage.ts
msgid "Update details"
msgstr "Update details"
@ -4375,6 +4507,10 @@ msgstr "User object filter"
msgid "User password writeback"
msgstr "User password writeback"
#: src/pages/events/utils.ts
msgid "User was written to"
msgstr "User was written to"
#: src/pages/policies/BoundPoliciesList.ts
#: src/pages/users/UserViewPage.ts
msgid "User {0}"
@ -4492,6 +4628,8 @@ msgstr "Wait (max)"
msgid "Wait (min)"
msgstr "Wait (min)"
#: src/pages/admin-overview/cards/SystemStatusCard.ts
#: src/pages/admin-overview/cards/SystemStatusCard.ts
#: src/pages/events/RuleForm.ts
#: src/pages/system-tasks/SystemTaskListPage.ts
msgid "Warning"
@ -4559,6 +4697,10 @@ msgstr "When selected, incoming assertion's Signatures will be validated against
msgid "When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged."
msgstr "When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged."
#: src/pages/tenants/TenantForm.ts
msgid "When using an external logging solution for archiving, this can be set to \"minutes=5\"."
msgstr "When using an external logging solution for archiving, this can be set to \"minutes=5\"."
#: src/flows/FlowExecutor.ts
msgid "Whoops!"
msgstr "Whoops!"

View File

@ -208,6 +208,10 @@ msgstr ""
msgid "Application Icon"
msgstr ""
#: src/pages/events/utils.ts
msgid "Application authorized"
msgstr ""
#: src/flows/stages/consent/ConsentStage.ts
msgid "Application requires following permissions:"
msgstr ""
@ -670,6 +674,10 @@ msgstr ""
msgid "Configuration"
msgstr ""
#: src/pages/events/utils.ts
msgid "Configuration error"
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
@ -729,7 +737,7 @@ msgstr ""
msgid "Connected."
msgstr ""
#: src/elements/messages/MessageContainer.ts
#: src/common/ws.ts
msgid "Connection error, reconnecting..."
msgstr ""
@ -1214,6 +1222,10 @@ msgstr ""
msgid "Duo push-notifications"
msgstr ""
#: src/pages/tenants/TenantForm.ts
msgid "Duration after which events will be deleted from the database."
msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Each provider has a different issuer, based on the application slug."
msgstr ""
@ -1291,6 +1303,10 @@ msgstr ""
msgid "Email info:"
msgstr ""
#: src/pages/events/utils.ts
msgid "Email sent"
msgstr ""
#: src/pages/stages/prompt/PromptForm.ts
msgid "Email: Text field with Email type."
msgstr ""
@ -1388,6 +1404,10 @@ msgstr ""
msgid "Event info"
msgstr ""
#: src/pages/tenants/TenantForm.ts
msgid "Event retention"
msgstr ""
#: src/pages/events/EventInfoPage.ts
msgid "Event {0}"
msgstr ""
@ -1396,6 +1416,10 @@ msgstr ""
msgid "Events"
msgstr ""
#: src/pages/admin-overview/cards/SystemStatusCard.ts
msgid "Everything is ok."
msgstr ""
#: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts
@ -1497,6 +1521,10 @@ msgstr ""
msgid "Failed attempts before cancel"
msgstr ""
#: src/pages/events/utils.ts
msgid "Failed login"
msgstr ""
#: src/pages/admin-overview/charts/LDAPSyncStatusChart.ts
msgid "Failed sources"
msgstr ""
@ -1642,6 +1670,10 @@ msgstr ""
msgid "Form didn't return a promise for submitting"
msgstr ""
#: src/pages/tenants/TenantForm.ts
msgid "Format: \"weeks=3;days=2;hours=3,seconds=2\"."
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "Forward auth (domain level)"
msgstr ""
@ -1666,6 +1698,10 @@ msgstr ""
msgid "GID start number"
msgstr ""
#: src/pages/events/utils.ts
msgid "General system exception"
msgstr ""
#: src/pages/admin-overview/AdminOverviewPage.ts
msgid "General system status"
msgstr ""
@ -1733,6 +1769,10 @@ msgstr ""
msgid "HTTP-Basic Username Key"
msgstr ""
#: src/pages/admin-overview/cards/SystemStatusCard.ts
msgid "HTTPS is not detected correctly"
msgstr ""
#: src/pages/outposts/OutpostListPage.ts
msgid "Health and Version"
msgstr ""
@ -1849,6 +1889,14 @@ msgstr ""
msgid "Impersonate"
msgstr ""
#: src/pages/events/utils.ts
msgid "Impersonation ended"
msgstr ""
#: src/pages/events/utils.ts
msgid "Impersonation started"
msgstr ""
#: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowListPage.ts
msgid "Import"
@ -1910,6 +1958,10 @@ msgstr ""
msgid "Invitation"
msgstr ""
#: src/pages/events/utils.ts
msgid "Invitation used"
msgstr ""
#: src/interfaces/AdminInterface.ts
#: src/pages/stages/invitation/InvitationListPage.ts
msgid "Invitations"
@ -2128,6 +2180,10 @@ msgstr ""
msgid "Log the currently pending user in."
msgstr ""
#: src/pages/events/utils.ts
msgid "Login"
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Login password is synced from LDAP into authentik automatically. Enable this option only to write password changes in authentik back to LDAP."
msgstr ""
@ -2149,6 +2205,10 @@ msgstr ""
msgid "Logo"
msgstr ""
#: src/pages/events/utils.ts
msgid "Logout"
msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
msgid "Logout URL"
msgstr ""
@ -2239,6 +2299,18 @@ msgstr ""
msgid "Model Name"
msgstr ""
#: src/pages/events/utils.ts
msgid "Model created"
msgstr ""
#: src/pages/events/utils.ts
msgid "Model deleted"
msgstr ""
#: src/pages/events/utils.ts
msgid "Model updated"
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "Monitor"
msgstr ""
@ -2583,6 +2655,10 @@ msgstr ""
msgid "Order"
msgstr ""
#: src/pages/tenants/TenantForm.ts
msgid "Other global settings"
msgstr ""
#: src/pages/admin-overview/charts/OutpostStatusChart.ts
msgid "Outdated outposts"
msgstr ""
@ -2657,6 +2733,10 @@ msgstr ""
msgid "Password field"
msgstr ""
#: src/pages/events/utils.ts
msgid "Password set"
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "Password stage"
msgstr ""
@ -2722,6 +2802,14 @@ msgstr ""
msgid "Policy engine mode"
msgstr ""
#: src/pages/events/utils.ts
msgid "Policy exception"
msgstr ""
#: src/pages/events/utils.ts
msgid "Policy execution"
msgstr ""
#: src/pages/policies/BoundPoliciesList.ts
msgid "Policy {0}"
msgstr ""
@ -2790,6 +2878,10 @@ msgstr ""
msgid "Property Mapping"
msgstr ""
#: src/pages/events/utils.ts
msgid "Property Mapping exception"
msgstr ""
#: src/interfaces/AdminInterface.ts
#: src/pages/property-mappings/PropertyMappingListPage.ts
msgid "Property Mappings"
@ -3136,6 +3228,14 @@ msgstr ""
msgid "Secret key"
msgstr ""
#: src/pages/events/utils.ts
msgid "Secret was rotation"
msgstr ""
#: src/pages/events/utils.ts
msgid "Secret was viewed"
msgstr ""
#: src/pages/events/EventInfo.ts
msgid "Secret:"
msgstr ""
@ -3220,6 +3320,10 @@ msgstr ""
msgid "Server URI"
msgstr ""
#: src/pages/admin-overview/cards/SystemStatusCard.ts
msgid "Server and client are further than 5 seconds apart."
msgstr ""
#: src/pages/providers/ldap/LDAPProviderForm.ts
msgid "Server name for which this provider's certificate is valid for."
msgstr ""
@ -3341,6 +3445,10 @@ msgstr ""
msgid "Source"
msgstr ""
#: src/pages/events/utils.ts
msgid "Source linked"
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
msgid "Source {0}"
msgstr ""
@ -3760,6 +3868,10 @@ msgstr ""
msgid "Superusers"
msgstr ""
#: src/pages/events/utils.ts
msgid "Suspicious request"
msgstr ""
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Symbol charset"
msgstr ""
@ -3789,6 +3901,18 @@ msgstr ""
msgid "System Tasks"
msgstr ""
#: src/pages/admin-overview/AdminOverviewPage.ts
msgid "System status"
msgstr ""
#: src/pages/events/utils.ts
msgid "System task exception"
msgstr ""
#: src/pages/events/utils.ts
msgid "System task execution"
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
msgid "TLS Authentication Certificate"
msgstr ""
@ -3918,6 +4042,10 @@ msgstr ""
msgid "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well."
msgstr ""
#: src/pages/tenants/TenantForm.ts
msgid "This setting only affects new Events, as the expiration is saved per-event."
msgstr ""
#: src/pages/stages/invitation/InvitationStageForm.ts
msgid "This stage can be included in enrollment flows to accept invitations."
msgstr ""
@ -4240,6 +4368,10 @@ msgstr ""
msgid "Update User"
msgstr ""
#: src/pages/events/utils.ts
msgid "Update available"
msgstr ""
#: src/pages/user-settings/UserDetailsPage.ts
msgid "Update details"
msgstr ""
@ -4360,6 +4492,10 @@ msgstr ""
msgid "User password writeback"
msgstr ""
#: src/pages/events/utils.ts
msgid "User was written to"
msgstr ""
#: src/pages/policies/BoundPoliciesList.ts
#: src/pages/users/UserViewPage.ts
msgid "User {0}"
@ -4477,6 +4613,8 @@ msgstr ""
msgid "Wait (min)"
msgstr ""
#: src/pages/admin-overview/cards/SystemStatusCard.ts
#: src/pages/admin-overview/cards/SystemStatusCard.ts
#: src/pages/events/RuleForm.ts
#: src/pages/system-tasks/SystemTaskListPage.ts
msgid "Warning"
@ -4544,6 +4682,10 @@ msgstr ""
msgid "When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged."
msgstr ""
#: src/pages/tenants/TenantForm.ts
msgid "When using an external logging solution for archiving, this can be set to \"minutes=5\"."
msgstr ""
#: src/flows/FlowExecutor.ts
msgid "Whoops!"
msgstr ""

View File

@ -9,6 +9,7 @@ import "./cards/AdminStatusCard";
import "./cards/BackupStatusCard";
import "./cards/VersionStatusCard";
import "./cards/WorkerStatusCard";
import "./cards/SystemStatusCard";
import "./charts/FlowStatusChart";
import "./charts/LDAPSyncStatusChart";
@ -92,14 +93,18 @@ export class AdminOverviewPage extends LitElement {
<ak-admin-status-version icon="pf-icon pf-icon-bundle" header=${t`Version`} headerLink="https://github.com/goauthentik/authentik/releases">
</ak-admin-status-version>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container">
<div class="pf-l-grid__item pf-m-6-col pf-m-2-col-on-md pf-m-2-col-on-xl card-container">
<ak-admin-status-card-backup icon="fa fa-database" header=${t`Backup status`} headerLink="#/administration/system-tasks">
</ak-admin-status-card-backup>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container">
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container">
<ak-admin-status-card-workers icon="pf-icon pf-icon-server" header=${t`Workers`}>
</ak-admin-status-card-workers>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl card-container">
<ak-admin-status-system icon="pf-icon pf-icon-server" header=${t`System status`}>
</ak-admin-status-system>
</div>
<div class="pf-l-grid__item pf-m-12-col row-divider">
<hr>
</div>

View File

@ -0,0 +1,46 @@
import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { AdminApi, System } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-system")
export class SystemStatusCard extends AdminStatusCard<System> {
now?: Date;
header = "OK";
getPrimaryValue(): Promise<System> {
this.now = new Date();
return new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
}
getStatus(value: System): Promise<AdminStatus> {
if (!value.httpIsSecure && document.location.protocol === "https:") {
this.header = t`Warning`;
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: t`HTTPS is not detected correctly`,
});
}
const timeDiff = value.serverTime.getTime() - (this.now || new Date()).getTime();
console.log(`authentik/: timediff ${timeDiff}`);
if (timeDiff > 5000 || timeDiff < -5000) {
this.header = t`Warning`;
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: t`Server and client are further than 5 seconds apart.`,
});
}
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success",
message: t`Everything is ok.`
});
}
renderValue(): TemplateResult {
return html`${this.header}`;
}
}

View File

@ -42,7 +42,9 @@ export class PolicyStatusChart extends AKChart<PolicyMetrics> {
})).pagination.count;
this.centerText = count.toString();
return {
count: count - cached - unbound,
// If we have more cache than total policies, only show that
// otherwise show count without unbound
count: cached >= count ? cached : count - unbound,
cached: cached,
unbound: unbound,
};

View File

@ -8,7 +8,7 @@ import { until } from "lit-html/directives/until";
import "../../elements/forms/HorizontalFormElement";
@customElement("ak-application-check-access-form")
export class ApplicationCheckAccessForm extends Form<number> {
export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
@property({attribute: false})
application!: Application;
@ -23,14 +23,19 @@ export class ApplicationCheckAccessForm extends Form<number> {
return t`Successfully sent test-request.`;
}
send = (data: number): Promise<PolicyTestResult> => {
this.request = data;
send = (data: { forUser: number }): Promise<PolicyTestResult> => {
this.request = data.forUser;
return new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data,
forUser: data.forUser,
}).then(result => this.result = result);
};
resetForm(): void {
super.resetForm();
this.result = undefined;
}
renderResult(): TemplateResult {
return html`
<ak-form-element-horizontal

View File

@ -8,6 +8,7 @@ import { PAGE_SIZE } from "../../constants";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import "./EventInfo";
import { ActionToLabel } from "./utils";
@customElement("ak-event-list")
export class EventListPage extends TablePage<Event> {
@ -51,7 +52,7 @@ export class EventListPage extends TablePage<Event> {
row(item: EventWithContext): TemplateResult[] {
return [
html`<div>${item.action}</div>
html`<div>${ActionToLabel(item.action)}</div>
<small>${item.app}</small>`,
item.user?.username ?
html`<a href="#/identity/users/${item.user.pk}">

View File

@ -0,0 +1,60 @@
import { t } from "@lingui/macro";
import { EventActions } from "authentik-api";
export function ActionToLabel(action?: EventActions): string {
if (!action) return "";
switch (action) {
case EventActions.Login:
return t`Login`;
case EventActions.LoginFailed:
return t`Failed login`;
case EventActions.Logout:
return t`Logout`;
case EventActions.UserWrite:
return t`User was written to`;
case EventActions.SuspiciousRequest:
return t`Suspicious request`;
case EventActions.PasswordSet:
return t`Password set`;
case EventActions.SecretView:
return t`Secret was viewed`;
case EventActions.SecretRotate:
return t`Secret was rotation`;
case EventActions.InvitationUsed:
return t`Invitation used`;
case EventActions.AuthorizeApplication:
return t`Application authorized`;
case EventActions.SourceLinked:
return t`Source linked`;
case EventActions.ImpersonationStarted:
return t`Impersonation started`;
case EventActions.ImpersonationEnded:
return t`Impersonation ended`;
case EventActions.PolicyExecution:
return t`Policy execution`;
case EventActions.PolicyException:
return t`Policy exception`;
case EventActions.PropertyMappingException:
return t`Property Mapping exception`;
case EventActions.SystemTaskExecution:
return t`System task execution`;
case EventActions.SystemTaskException:
return t`System task exception`;
case EventActions.SystemException:
return t`General system exception`;
case EventActions.ConfigurationError:
return t`Configuration error`;
case EventActions.ModelCreated:
return t`Model created`;
case EventActions.ModelUpdated:
return t`Model updated`;
case EventActions.ModelDeleted:
return t`Model deleted`;
case EventActions.EmailSent:
return t`Email sent`;
case EventActions.UpdateAvailable:
return t`Update available`;
default:
return action;
}
}

View File

@ -2,7 +2,7 @@ import { FlowsApi, ProvidersApi, LDAPProvider, CoreApi, FlowsInstancesListDesign
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { DEFAULT_CONFIG, tenant } from "../../../api/Config";
import { ModelForm } from "../../../elements/forms/ModelForm";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
@ -53,12 +53,18 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
?required=${true}
name="authorizationFlow">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${ifDefined(flow.pk)} ?selected=${this.instance?.authorizationFlow === flow.pk}>${flow.name} (${flow.slug})</option>`;
${until(tenant().then(t => {
new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
let selected = flow.pk === t.flowAuthentication;
if (this.instance?.authorizationFlow === flow.pk) {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
});
}), html`<option>${t`Loading...`}</option>`)}
</select>

View File

@ -162,6 +162,29 @@ export class TenantForm extends ModelForm<Tenant, string> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">
${t`Other global settings`}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Event retention`}
?required=${true}
name="eventRetention">
<input type="text" value="${first(this.instance?.eventRetention, "days=365")}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">
${t`Duration after which events will be deleted from the database.`}
</p>
<p class="pf-c-form__helper-text">
${t`When using an external logging solution for archiving, this can be set to "minutes=5".`}
</p>
<p class="pf-c-form__helper-text">
${t`This setting only affects new Events, as the expiration is saved per-event.`}
</p>
<p class="pf-c-form__helper-text">${t`Format: "weeks=3;days=2;hours=3,seconds=2".`}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}

View File

@ -8,6 +8,7 @@ To create a local development setup for authentik, you need the following:
- Python 3.9
- pipenv, which is used to manage dependencies, and can be installed with `pip install pipenv`
- Go 1.16
- PostgreSQL (any recent version will do)
- Redis (any recent version will do)
@ -24,11 +25,13 @@ log_level: debug
secret_key: "A long key you can generate with `pwgen 40 1` for example"
```
Afterwards, you can start authentik by running `./manage.py runserver`. Generally speaking, authentik is a Django application.
Afterwards, you can start authentik by running `make run`.
Generally speaking, authentik is a Django application, ran by gunicorn, proxied by a Go application. The Go application serves static files.
Most functions and classes have type-hints and docstrings, so it is recommended to install a Python Type-checking Extension in your IDE to navigate around the code.
Before committing code, run `make lint` to ensure your code is formatted well. This also requires `pyright`, which can be installed with npm.
Before committing code, run `make lint` to ensure your code is formatted well. This also requires `pyright@1.1.136`, which can be installed with npm.
Run `make gen` to generate an updated OpenAPI document for any changes you made.

View File

@ -12,11 +12,11 @@ This installation method is for test-setups and small-scale productive setups.
## Preparation
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.7.1-rc1/docker-compose.yml). Place it in a directory of your choice.
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.7.1/docker-compose.yml). Place it in a directory of your choice.
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.7.1-rc1 >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.7.1 >> .env`
If this is a fresh authentik install run the following commands to generate a password:

View File

@ -11,7 +11,7 @@ version: "3.5"
services:
authentik_proxy:
image: ghcr.io/goauthentik/proxy:2021.7.1-rc1
image: ghcr.io/goauthentik/proxy:2021.7.1
ports:
- 4180:4180
- 4443:4443
@ -21,7 +21,7 @@ services:
AUTHENTIK_TOKEN: token-generated-by-authentik
# Or, for the LDAP Outpost
authentik_proxy:
image: ghcr.io/goauthentik/ldap:2021.7.1-rc1
image: ghcr.io/goauthentik/ldap:2021.7.1
ports:
- 389:3389
environment:

View File

@ -14,7 +14,7 @@ metadata:
app.kubernetes.io/instance: __OUTPOST_NAME__
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.7.1-rc1
app.kubernetes.io/version: 2021.7.1
name: authentik-outpost-api
stringData:
authentik_host: "__AUTHENTIK_URL__"
@ -29,7 +29,7 @@ metadata:
app.kubernetes.io/instance: __OUTPOST_NAME__
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.7.1-rc1
app.kubernetes.io/version: 2021.7.1
name: authentik-outpost
spec:
ports:
@ -54,7 +54,7 @@ metadata:
app.kubernetes.io/instance: __OUTPOST_NAME__
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.7.1-rc1
app.kubernetes.io/version: 2021.7.1
name: authentik-outpost
spec:
selector:
@ -62,14 +62,14 @@ spec:
app.kubernetes.io/instance: __OUTPOST_NAME__
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.7.1-rc1
app.kubernetes.io/version: 2021.7.1
template:
metadata:
labels:
app.kubernetes.io/instance: __OUTPOST_NAME__
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.7.1-rc1
app.kubernetes.io/version: 2021.7.1
spec:
containers:
- env:
@ -88,7 +88,7 @@ spec:
secretKeyRef:
key: authentik_host_insecure
name: authentik-outpost-api
image: ghcr.io/goauthentik/proxy:2021.7.1-rc1
image: ghcr.io/goauthentik/proxy:2021.7.1
name: proxy
ports:
- containerPort: 4180
@ -110,7 +110,7 @@ metadata:
app.kubernetes.io/instance: __OUTPOST_NAME__
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.7.1-rc1
app.kubernetes.io/version: 2021.7.1
name: authentik-outpost
spec:
rules:

View File

@ -1,5 +1,5 @@
---
title: LDAP Outpost
title: LDAP Provider
---
:::info
@ -55,7 +55,7 @@ The following fields are current set for groups:
- "group"
- "goauthentik.io/ldap/group"
A virtual group is also created for each user, they have the same fields as groups but have an additional objectClass: `goauthentik.io/ldap/virtual-group`.
A virtual group is also created for each user, they have the same fields as groups but have an additional objectClass: `goauthentik.io/ldap/virtual-group`.
The virtual groups gidNumber is equal to the uidNumber of the user.
**Additionally**, for both users and (non-virtual) groups, any attributes you set are also present as LDAP Attributes.

View File

@ -1,27 +0,0 @@
---
title: Proxy Provider
---
:::info
This provider is to be used in conjunction with [Outposts](../outposts/outposts.md)
:::
This provider protects applications, which have no built-in support for OAuth2 or SAML. This is done by running a lightweight Reverse Proxy in front of the application, which authenticates the requests.
authentik Proxy is based on [oauth2_proxy](https://github.com/oauth2-proxy/oauth2-proxy), but has been integrated more tightly with authentik.
The Proxy these extra headers to the application:
| Header Name | Value |
| ------------------------------ | --------------------------------------------------- |
| X-Forwarded-User | The user's unique identifier (**not the username**) |
| X-Forwarded-Email | The user's email address |
| X-Forwarded-Preferred-Username | The user's username |
| X-Auth-Username | The user's username |
Additionally, you can add more custom headers using `additionalHeaders` in the User or Group Properties, for example
```yaml
additionalHeaders:
X-additional-header: bar
```

View File

@ -32,6 +32,10 @@ is redirect to the outpost.
For domain level, you'd use the same domain as authentik.
:::info
*example-outpost* is used as a placeholder for the outpost name.
:::
## Nginx
import Tabs from '@theme/Tabs';
@ -103,7 +107,7 @@ spec:
http:
paths:
- backend:
serviceName: authentik-outpost-*uuid of the service generated by authentik*
serviceName: authentik-outpost-example-outpost
servicePort: 4180
path: /akprox
```
@ -117,7 +121,7 @@ metadata:
annotations:
nginx.ingress.kubernetes.io/auth-url: https://*external host that you configured in authentik*/akprox/auth?nginx
nginx.ingress.kubernetes.io/auth-signin: https://*external host that you configured in authentik*/akprox/start?rd=$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Username,X-Forwarded-Email,X-Forwarded-Preferred-Username,X-Forwarded-User
nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Username,X-Forwarded-Email,X-Forwarded-Preferred-Username,X-Forwarded-User,X-Auth-Groups
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
```
@ -140,11 +144,12 @@ http:
middlewares:
authentik:
forwardAuth:
address: http://authentik-outpost-*uuid of the service generated by authentik*:4180/akprox/auth?traefik
address: http://authentik-outpost-example-outpost:4180/akprox/auth?traefik
trustForwardHeader: true
authResponseHeaders:
- Set-Cookie
- X-Auth-Username
- X-Auth-Groups
- X-Forwarded-Email
- X-Forwarded-Preferred-Username
- X-Forwarded-User
@ -211,7 +216,7 @@ services:
traefik.http.routers.authentik.tls: true
traefik.http.middlewares.authentik.forwardauth.address: http://authentik_proxy:4180/akprox/auth?traefik
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: Set-Cookie,X-Auth-Username,X-Forwarded-Email,X-Forwarded-Preferred-Username,X-Forwarded-User
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: Set-Cookie,X-Auth-Username,X-Auth-Groups,X-Forwarded-Email,X-Forwarded-Preferred-Username,X-Forwarded-User
restart: unless-stopped
whoami:
@ -236,11 +241,12 @@ metadata:
name: authentik
spec:
forwardAuth:
address: http://authentik-outpost-*uuid of the service generated by authentik*:4180/akprox/auth?traefik
address: http://authentik-outpost-example-outpost:4180/akprox/auth?traefik
trustForwardHeader: true
authResponseHeaders:
- Set-Cookie
- X-Auth-Username
- X-Auth-Groups
- X-Forwarded-Email
- X-Forwarded-Preferred-Username
- X-Forwarded-User
@ -262,7 +268,7 @@ spec:
priority: 15
services:
- kind: Service
name: authentik-outpost-*uuid of the service generated by authentik*
name: authentik-outpost-example-outpost
port: 4180
```
</TabItem>

View File

@ -1,11 +1,12 @@
---
title: Proxy Outpost
title: Proxy provider
---
The proxy outpost sets the following headers:
```
X-Auth-Username: akadmin # The username of the currently logged in user
X-Auth-Groups: foo|bar|baz # The groups the user is member of, separated by a pipe
X-Forwarded-Email: root@localhost # The email address of the currently logged in user
X-Forwarded-Preferred-Username: akadmin # The username of the currently logged in user
X-Forwarded-User: 900347b8a29876b45ca6f75722635ecfedf0e931c6022e3a29a8aa13fb5516fb # The hashed identifier of the currently logged in user.

View File

@ -13,7 +13,7 @@ This update brings a lot of big features, such as:
Due to this new OAuth2 Provider, the Application Gateway Provider, now simply called "Proxy Provider" has been revamped as well. The new authentik Proxy integrates more tightly with authentik via the new Outposts system. The new proxy also supports multiple applications per proxy instance, can configure TLS based on authentik Keypairs, and more.
See [Proxy](../providers/proxy.md)
See [Proxy](../providers/proxy/proxy.md)
- Outpost System

View File

@ -20,7 +20,7 @@ This feature is still in technical preview, so please report any Bugs you run in
- Compatibility with forwardAuth/auth_request
The authentik proxy is now compatible with forwardAuth (traefik) / auth_request (nginx). All that is required is the latest version of the outpost,
and the correct config from [here](../outposts/proxy/forward_auth.mdx).
and the correct config from [here](../providers/proxy/forward_auth.mdx).
- Docker images for ARM

View File

@ -32,6 +32,31 @@ slug: "2021.7"
- root: subclass SessionMiddleware to set Secure and SameSite flag depending on context
- web: fix error when showing error message of request
## Fixed in 2021.7.1-rc2
- core: add email filter for user
- core: add group filter by member username and pk
- core: broaden error catching for propertymappings
- lib: fix outpost fake-ip not working, add tests
- outpost: fix 100% CPU Usage when not connected to websocket
- outposts: ensure outpost SAs always have permissions to fake IP
- outposts: fix git hash not being set in outposts
- outposts: save certificate fingerprint and check before re-fetching to cleanup logs
- outposts/ldap: add tracing for LDAP bind and search
- outposts/ldap: improve parsing of LDAP filters
- outposts/ldap: optimise backend Search API requests
- outposts/proxy: add X-Auth-Groups header to pass groups
- providers/oauth2: handler PropertyMapping exceptions and create event
- providers/saml: improve error handling for property mappings
- sources/ldap: improve error handling for property mappings
- web: fix icon flashing in header, fix notification header icon in dark mode
- web: separate websocket connection from messages
- web/admin: fix missing dark theme for notifications
- web/admin: fix negative count for policies when more cached than total policies
- web/admin: improve UI for notification toggle
- website/docs: clear up outpost uuids
- website/docs: remove duplicate proxy docs
## Upgrading
This release does not introduce any new requirements.

View File

@ -11,10 +11,10 @@
"@docusaurus/plugin-client-redirects": "2.0.0-beta.3",
"@docusaurus/preset-classic": "2.0.0-beta.3",
"@mdx-js/react": "^1.6.22",
"@sentry/react": "^6.9.0",
"@sentry/tracing": "^6.9.0",
"@sentry/react": "^6.10.0",
"@sentry/tracing": "^6.10.0",
"clsx": "^1.1.1",
"postcss": "^8.3.5",
"postcss": "^8.3.6",
"rapidoc": "^9.0.0",
"react": "^17.0.2",
"react-before-after-slider-component": "^0.7.4",
@ -2606,13 +2606,13 @@
"integrity": "sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA=="
},
"node_modules/@sentry/browser": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.9.0.tgz",
"integrity": "sha512-4JnEPcwoNs6JqeEd4wscBq+hxpotEJ0DJ4eOIsaNZIMyqEHXBHTXCk/gfrSsiZFrkHM4PgvUHOxaC0HcZ92oBA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.10.0.tgz",
"integrity": "sha512-H0Blgp8f8bomebkkGWIgxHVjabtQAlsKJDiFXBg7gIc75YcarRxwH0R3hMog1/h8mmv4CGGUsy5ljYW6jsNnvA==",
"dependencies": {
"@sentry/core": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/core": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2625,14 +2625,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/core": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.9.0.tgz",
"integrity": "sha512-oFX2qQcMLujCeIuCQGlhpTUIOXiU5n6V2lqDnvMXUV8gKpplBPalwdlR9bgbSi+VO8u7LjHR1IKM0RAPWgNHWw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.10.0.tgz",
"integrity": "sha512-5KlxHJlbD7AMo+b9pMGkjxUOfMILtsqCtGgI7DMvZNfEkdohO8QgUY+hPqr540kmwArFS91ipQYWhqzGaOhM3Q==",
"dependencies": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2645,12 +2645,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/hub": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.9.0.tgz",
"integrity": "sha512-5mors7ojbo7G85ZmoVPQBgFBMONAJwyZfV0LNLy14GenoaVNuxTPyvAQiJb1FYq+x6YZ3CvqGX6r74KRKQU87w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.10.0.tgz",
"integrity": "sha512-MV8wjhWiFAXZAhmj7Ef5QdBr2IF93u8xXiIo2J+dRZ7eVa4/ZszoUiDbhUcl/TPxczaw4oW2a6tINBNFLzXiig==",
"dependencies": {
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2663,12 +2663,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/minimal": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.9.0.tgz",
"integrity": "sha512-GBZ6wG2Rc1wInYEl2BZTZc/t57O1Da876ifLsSPpEQAEnGWbqZWb8RLjZskH09ZIL/K4XCIDDi5ySzN8kFUWJw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.10.0.tgz",
"integrity": "sha512-yarm046UgUFIBoxqnBan2+BEgaO9KZCrLzsIsmALiQvpfW92K1lHurSawl5W6SR7wCYBnNn7CPvPE/BHFdy4YA==",
"dependencies": {
"@sentry/hub": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2681,14 +2681,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/react": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.9.0.tgz",
"integrity": "sha512-ccMhpL+YHcq171EhSHU02IYh476mBjPfK1zq+vW2fJkaigg+mEqbOHnQV0Uu3zFYHGqVg4CZKZc6v92cvbBwEg==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.10.0.tgz",
"integrity": "sha512-ICHAxMKaQ+3MimzsKQWKivjqJWCbc9ZJ071XoTkRgaOIBLFk8VAVWOldaxrLaWLQdPNT2OwVWnsZI7IvzQNW6w==",
"dependencies": {
"@sentry/browser": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/browser": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^1.9.3"
},
@ -2705,14 +2705,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/tracing": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.9.0.tgz",
"integrity": "sha512-gogVTypolhPazXr3Lue8HgzBg5Sy1cQpEp5Iq9LtECs+TlOlxJ+S+P+EIjEZ0f1AHVu706jr5cY2G2Shluli9g==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.10.0.tgz",
"integrity": "sha512-jZj6Aaf8kU5wgyNXbAJHosHn8OOFdK14lgwYPb/AIDsY35g9a9ncTOqIOBp8X3KkmSR8lcBzAEyiUzCxAis2jA==",
"dependencies": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -2725,19 +2725,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/types": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.9.0.tgz",
"integrity": "sha512-v52HJqLoLapEnqS2NdVtUXPvT+aezQgNXQkp8hiQ3RUdTm5cffwBVG7wlbpE6OsOOIZxd6p1zKylFkwCypiIIA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.10.0.tgz",
"integrity": "sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw==",
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/utils": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.9.0.tgz",
"integrity": "sha512-PimDr6KAi4cCp5hQZ8Az2/pDcdfhTu7WAU30Dd9MZwknpHSTmD4G6QvkdrB5er6kMMnNQOC7rMo6w/Do3m6X3w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.10.0.tgz",
"integrity": "sha512-F9OczOcZMFtazYVZ6LfRIe65/eOfQbiAedIKS0li4npuMz0jKYRbxrjd/U7oLiNQkPAp4/BujU4m1ZIwq6a+tg==",
"dependencies": {
"@sentry/types": "6.9.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"engines": {
@ -10143,9 +10143,9 @@
}
},
"node_modules/postcss": {
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz",
"integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==",
"dependencies": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",
@ -17460,13 +17460,13 @@
"integrity": "sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA=="
},
"@sentry/browser": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.9.0.tgz",
"integrity": "sha512-4JnEPcwoNs6JqeEd4wscBq+hxpotEJ0DJ4eOIsaNZIMyqEHXBHTXCk/gfrSsiZFrkHM4PgvUHOxaC0HcZ92oBA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.10.0.tgz",
"integrity": "sha512-H0Blgp8f8bomebkkGWIgxHVjabtQAlsKJDiFXBg7gIc75YcarRxwH0R3hMog1/h8mmv4CGGUsy5ljYW6jsNnvA==",
"requires": {
"@sentry/core": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/core": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -17478,14 +17478,14 @@
}
},
"@sentry/core": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.9.0.tgz",
"integrity": "sha512-oFX2qQcMLujCeIuCQGlhpTUIOXiU5n6V2lqDnvMXUV8gKpplBPalwdlR9bgbSi+VO8u7LjHR1IKM0RAPWgNHWw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.10.0.tgz",
"integrity": "sha512-5KlxHJlbD7AMo+b9pMGkjxUOfMILtsqCtGgI7DMvZNfEkdohO8QgUY+hPqr540kmwArFS91ipQYWhqzGaOhM3Q==",
"requires": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -17497,12 +17497,12 @@
}
},
"@sentry/hub": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.9.0.tgz",
"integrity": "sha512-5mors7ojbo7G85ZmoVPQBgFBMONAJwyZfV0LNLy14GenoaVNuxTPyvAQiJb1FYq+x6YZ3CvqGX6r74KRKQU87w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.10.0.tgz",
"integrity": "sha512-MV8wjhWiFAXZAhmj7Ef5QdBr2IF93u8xXiIo2J+dRZ7eVa4/ZszoUiDbhUcl/TPxczaw4oW2a6tINBNFLzXiig==",
"requires": {
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -17514,12 +17514,12 @@
}
},
"@sentry/minimal": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.9.0.tgz",
"integrity": "sha512-GBZ6wG2Rc1wInYEl2BZTZc/t57O1Da876ifLsSPpEQAEnGWbqZWb8RLjZskH09ZIL/K4XCIDDi5ySzN8kFUWJw==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.10.0.tgz",
"integrity": "sha512-yarm046UgUFIBoxqnBan2+BEgaO9KZCrLzsIsmALiQvpfW92K1lHurSawl5W6SR7wCYBnNn7CPvPE/BHFdy4YA==",
"requires": {
"@sentry/hub": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -17531,14 +17531,14 @@
}
},
"@sentry/react": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.9.0.tgz",
"integrity": "sha512-ccMhpL+YHcq171EhSHU02IYh476mBjPfK1zq+vW2fJkaigg+mEqbOHnQV0Uu3zFYHGqVg4CZKZc6v92cvbBwEg==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.10.0.tgz",
"integrity": "sha512-ICHAxMKaQ+3MimzsKQWKivjqJWCbc9ZJ071XoTkRgaOIBLFk8VAVWOldaxrLaWLQdPNT2OwVWnsZI7IvzQNW6w==",
"requires": {
"@sentry/browser": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/browser": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^1.9.3"
},
@ -17551,14 +17551,14 @@
}
},
"@sentry/tracing": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.9.0.tgz",
"integrity": "sha512-gogVTypolhPazXr3Lue8HgzBg5Sy1cQpEp5Iq9LtECs+TlOlxJ+S+P+EIjEZ0f1AHVu706jr5cY2G2Shluli9g==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.10.0.tgz",
"integrity": "sha512-jZj6Aaf8kU5wgyNXbAJHosHn8OOFdK14lgwYPb/AIDsY35g9a9ncTOqIOBp8X3KkmSR8lcBzAEyiUzCxAis2jA==",
"requires": {
"@sentry/hub": "6.9.0",
"@sentry/minimal": "6.9.0",
"@sentry/types": "6.9.0",
"@sentry/utils": "6.9.0",
"@sentry/hub": "6.10.0",
"@sentry/minimal": "6.10.0",
"@sentry/types": "6.10.0",
"@sentry/utils": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -17570,16 +17570,16 @@
}
},
"@sentry/types": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.9.0.tgz",
"integrity": "sha512-v52HJqLoLapEnqS2NdVtUXPvT+aezQgNXQkp8hiQ3RUdTm5cffwBVG7wlbpE6OsOOIZxd6p1zKylFkwCypiIIA=="
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.10.0.tgz",
"integrity": "sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw=="
},
"@sentry/utils": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.9.0.tgz",
"integrity": "sha512-PimDr6KAi4cCp5hQZ8Az2/pDcdfhTu7WAU30Dd9MZwknpHSTmD4G6QvkdrB5er6kMMnNQOC7rMo6w/Do3m6X3w==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.10.0.tgz",
"integrity": "sha512-F9OczOcZMFtazYVZ6LfRIe65/eOfQbiAedIKS0li4npuMz0jKYRbxrjd/U7oLiNQkPAp4/BujU4m1ZIwq6a+tg==",
"requires": {
"@sentry/types": "6.9.0",
"@sentry/types": "6.10.0",
"tslib": "^1.9.3"
},
"dependencies": {
@ -23150,9 +23150,9 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"postcss": {
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz",
"integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==",
"requires": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",

View File

@ -15,10 +15,10 @@
"@docusaurus/plugin-client-redirects": "2.0.0-beta.3",
"@docusaurus/preset-classic": "2.0.0-beta.3",
"@mdx-js/react": "^1.6.22",
"@sentry/react": "^6.9.0",
"@sentry/tracing": "^6.9.0",
"@sentry/react": "^6.10.0",
"@sentry/tracing": "^6.10.0",
"clsx": "^1.1.1",
"postcss": "^8.3.5",
"postcss": "^8.3.6",
"rapidoc": "^9.0.0",
"react": "^17.0.2",
"react-before-after-slider-component": "^0.7.4",

View File

@ -23,7 +23,19 @@ module.exports = {
{
type: "category",
label: "Providers",
items: ["providers/oauth2", "providers/saml", "providers/proxy"],
items: [
"providers/oauth2",
"providers/saml",
{
type: "category",
label: "Proxy",
items: [
"providers/proxy/proxy",
"providers/proxy/forward_auth",
],
},
"providers/ldap",
],
},
{
type: "category",
@ -39,21 +51,6 @@ module.exports = {
"outposts/manual-deploy-kubernetes",
],
},
{
type: "category",
label: "Proxy",
items: [
"outposts/proxy/proxy",
"outposts/proxy/forward_auth",
],
},
{
type: "category",
label: "LDAP",
items: [
"outposts/ldap/ldap",
],
},
],
},
{

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB