Compare commits

..

57 Commits

Author SHA1 Message Date
5a7508d2e0 core: fix token expiration not being updated upon key rotation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-12 17:24:19 +02:00
9c31ea1aa6 core: fix expired tokens not being returned by API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-12 17:24:19 +02:00
18211a2033 release: 2021.7.3 2021-08-05 19:23:03 +02:00
b4cfc56e5e web/admin: fix source form's userMatchingMode being swapped
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/pages/sources/oauth/OAuthSourceForm.ts
#	web/src/pages/sources/plex/PlexSourceForm.ts
2021-08-05 18:48:02 +02:00
8e797fa76b outpost/ldap: fix errors with new UserSelf serializer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-05 18:16:06 +02:00
1b91543add core: add UserSelfSerializer and separate method for users to update themselves with limited fields
rework user settings page to better use form
closes #1227

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	authentik/core/api/users.py
#	web/src/elements/forms/ModelForm.ts
#	web/src/pages/user-settings/UserDetailsPage.ts
#	web/src/pages/user-settings/UserSettingsPage.ts
2021-08-05 17:47:45 +02:00
1cd59be8dc web/admin: fix email being required
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/pages/user-settings/UserDetailsPage.ts
#	web/src/pages/users/UserForm.ts
2021-08-05 17:46:28 +02:00
66bb68a747 lifecycle: decrease default worker count on compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-05 09:44:58 +02:00
aa4f7fb2b6 providers/saml: fix error when PropertyMapping return value isn't string
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-04 00:22:07 +02:00
4f1c11c5ef providers/saml: add WantAssertionsSigned
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	authentik/providers/saml/processors/metadata_parser.py
2021-08-04 00:21:54 +02:00
add7a80fdc release: 2021.7.2 2021-08-01 19:11:50 +02:00
aac91c2e9d stages/email: handle OSError
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 18:25:53 +02:00
85e86351cd flows: fix flows not redirecting correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 18:25:53 +02:00
d767504474 flows: don't check redirect URL when set from flow plan (set from authentik or policy)
closes #1203

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
f84cd6208c flows: fix unhandled error in stage execution not being logged as SYSTEM_EXCEPTION event
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
1ec540ea9a providers/saml: fix metadata being inaccessible without authentication
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
29fe731bbf providers/saml: fix Error when getting metadata for invalid ID
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 14:09:22 +02:00
26e66969c9 stages/invitation: delete invite only after full enrollment flow is completed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 13:22:02 +02:00
fe629f8b51 web/admin: fix empty column when no invitation expiry was set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 13:22:02 +02:00
72b7642c5a outposts: catch invalid ServiceConnection error in outpost controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:21 +02:00
a97f842112 sources/plex: add background task to monitor validity of plex token
closes #1205

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:21 +02:00
16e6e4c3b7 web/admin: add re-authenticate button for plex
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1205
2021-08-01 12:33:21 +02:00
dc0d715885 web/admin: add UI to copy invitation link
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:20 +02:00
7ecd57ecff outpost: bump timer for periodic config reloads
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:20 +02:00
0cb4d64b57 stages/email: fix error when re-requesting email after token has expired
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-30 09:39:42 +02:00
a4fd58a0db events: ensure fallback result is set for on_failure
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-30 09:39:42 +02:00
fb6e8ca1eb events: remove default result for MonitoredTasks, only save when result was set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 22:43:29 +02:00
4c41948e75 e2e: fix broken selenium by locking images
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:53:09 +02:00
a5c8caf909 providers/oauth2: fix error when requesting jwks keys with no rs256 aet
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:22:59 +02:00
970655ab21 ci: fix sentry sourcemap path
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:22:52 +02:00
c8c7202c61 web/admin: fix LDAP Provider bind flow list being empty
closes #1192

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:54 +02:00
a3981dd3cd providers/proxy: fix hosts for ingress not being compared correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:50 +02:00
affafc31cf sources/ldap: improve ms-ad password complexity checking
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:47 +02:00
602aed674b web/admin: fully remove response cloning due to errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:44 +02:00
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
78 changed files with 1704 additions and 438 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.7.1-rc2
current_version = 2021.7.3
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-rc2,
beryju/authentik:2021.7.3,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.7.1-rc2,
ghcr.io/goauthentik/server:2021.7.3,
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-rc2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.3', '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-rc2,
beryju/authentik-proxy:2021.7.3,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.7.1-rc2,
ghcr.io/goauthentik/proxy:2021.7.3,
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-rc2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.3', '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-rc2,
beryju/authentik-ldap:2021.7.3,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.7.1-rc2,
ghcr.io/goauthentik/ldap:2021.7.3,
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-rc2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -176,7 +176,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.7.1-rc2
version: authentik@2021.7.3
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: /static/dist
url_prefix: '~/static/dist'

2
.gitignore vendored
View File

@ -200,4 +200,4 @@ media/
*mmdb
.idea/
api/
/api/

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.

68
Pipfile.lock generated
View File

@ -122,19 +122,19 @@
},
"boto3": {
"hashes": [
"sha256:0ee91710160166be01acdb7f57cf4f32876cc2e51a54d17e42a3a818a20f9e93",
"sha256:468100af69b090893bc790a21423fc74f5221b92bba1e49fb83aadcc2774c7b5"
"sha256:a012570d3535ec6c4db97e60ef51c2f39f38246429e1455cecc26c633ed81c10",
"sha256:c7f45b0417395d3020c98cdc10f942939883018210e29dbfe6fbfc0a74e503ec"
],
"index": "pypi",
"version": "==1.18.5"
"version": "==1.18.7"
},
"botocore": {
"hashes": [
"sha256:0070c5e02b581db40ff5fd1b5e02db90ed88e7e861901894bd78fd998656da68",
"sha256:bed34fe7a007180f4208b65515bab1755cdd9fcf2c6720f74ae7ecd2e707f4b7"
"sha256:34c8b151a25616ed7791218f6d7780c3a97725fe3ceeaa28085b345a8513af6e",
"sha256:dcf399d21170bb899e00d2a693bddcc79e61471fbfead8500a65578700a3190a"
],
"markers": "python_version >= '3.6'",
"version": "==1.21.5"
"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": [
@ -1573,11 +1573,11 @@
},
"gitpython": {
"hashes": [
"sha256:17690588e36bd0cee21921ce621fad1e8be45a37afa486ff846fb8efcf53c34c",
"sha256:18f4039b96b5567bc4745eb851737ce456a2d499cecd71e84f5c0950e92d0e53"
"sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b",
"sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"
],
"markers": "python_version >= '3.6'",
"version": "==3.1.19"
"version": "==3.1.18"
},
"idna": {
"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": {
@ -1866,14 +1866,6 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"typing-extensions": {
"hashes": [
"sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
"sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
"sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
],
"version": "==3.10.0.0"
},
"urllib3": {
"extras": [
"secure"

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-rc2"
__version__ = "2021.7.3"
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

@ -48,7 +48,7 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
"""Token Viewset"""
lookup_field = "identifier"
queryset = Token.filter_not_expired()
queryset = Token.objects.all()
serializer_class = TokenSerializer
search_fields = [
"identifier",

View File

@ -10,6 +10,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.utils import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
@ -62,12 +63,40 @@ class UserSerializer(ModelSerializer):
]
class UserSelfSerializer(ModelSerializer):
"""User Serializer for information a user can retrieve about themselves and
update about themselves"""
is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True)
groups = ListSerializer(child=GroupSerializer(), read_only=True, source="ak_groups")
uid = CharField(read_only=True)
class Meta:
model = User
fields = [
"pk",
"username",
"name",
"is_active",
"is_superuser",
"groups",
"email",
"avatar",
"uid",
]
extra_kwargs = {
"is_active": {"read_only": True},
}
class SessionUserSerializer(PassiveSerializer):
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
and, if this user is being impersonated, the original user in the `original` property."""
user = UserSerializer()
original = UserSerializer(required=False)
user = UserSelfSerializer()
original = UserSelfSerializer(required=False)
class UserMetricsSerializer(PassiveSerializer):
@ -158,12 +187,36 @@ class UserViewSet(UsedByMixin, ModelViewSet):
data={"user": UserSerializer(request.user).data}
)
if SESSION_IMPERSONATE_USER in request._request.session:
serializer.initial_data["original"] = UserSerializer(
serializer.initial_data["original"] = UserSelfSerializer(
request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
).data
serializer.is_valid()
return Response(serializer.data)
@extend_schema(
request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)}
)
@action(
methods=["PUT"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[IsAuthenticated],
)
def update_self(self, request: Request) -> Response:
"""Allow users to change information on their own profile"""
data = UserSelfSerializer(
instance=User.objects.get(pk=request.user.pk), data=request.data
)
if not data.is_valid():
return Response(data.errors)
new_user = data.save()
# If we're impersonating, we need to update that user object
# since it caches the full object
if SESSION_IMPERSONATE_USER in request.session:
request.session[SESSION_IMPERSONATE_USER] = new_user
return self.me(request)
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
@extend_schema(responses={200: UserMetricsSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[])

View File

@ -438,6 +438,7 @@ class Token(ManagedModel, ExpiringModel):
from authentik.events.models import Event, EventAction
self.key = default_token_key()
self.expires = default_token_duration()
self.save(*args, **kwargs)
Event.new(
action=EventAction.SECRET_ROTATE,

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

@ -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

@ -114,7 +114,7 @@ class MonitoredTask(Task):
# For tasks that should only be listed if they failed, set this to False
save_on_success: bool
_result: TaskResult
_result: Optional[TaskResult]
_uid: Optional[str]
@ -122,7 +122,7 @@ class MonitoredTask(Task):
super().__init__(*args, **kwargs)
self.save_on_success = True
self._uid = None
self._result = TaskResult(status=TaskResultStatus.ERROR, messages=[])
self._result = None
self.result_timeout_hours = 6
self.start = default_timer()
@ -138,25 +138,30 @@ class MonitoredTask(Task):
def after_return(
self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo
):
if not self._result.uid:
self._result.uid = self._uid
if self.save_on_success:
TaskInfo(
task_name=self.__name__,
task_description=self.__doc__,
start_timestamp=self.start,
finish_timestamp=default_timer(),
finish_time=datetime.now(),
result=self._result,
task_call_module=self.__module__,
task_call_func=self.__name__,
task_call_args=args,
task_call_kwargs=kwargs,
).save(self.result_timeout_hours)
if self._result:
if not self._result.uid:
self._result.uid = self._uid
if self.save_on_success:
TaskInfo(
task_name=self.__name__,
task_description=self.__doc__,
start_timestamp=self.start,
finish_timestamp=default_timer(),
finish_time=datetime.now(),
result=self._result,
task_call_module=self.__module__,
task_call_func=self.__name__,
task_call_args=args,
task_call_kwargs=kwargs,
).save(self.result_timeout_hours)
return super().after_return(status, retval, task_id, args, kwargs, einfo=einfo)
# pylint: disable=too-many-arguments
def on_failure(self, exc, task_id, args, kwargs, einfo):
if not self._result:
self._result = TaskResult(
status=TaskResultStatus.ERROR, messages=[str(exc)]
)
if not self._result.uid:
self._result.uid = self._uid
TaskInfo(

View File

@ -26,7 +26,7 @@ from sentry_sdk import capture_exception
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import cleanse_dict
from authentik.events.models import Event, EventAction, cleanse_dict
from authentik.flows.challenge import (
AccessDeniedChallenge,
Challenge,
@ -52,6 +52,7 @@ from authentik.flows.planner import (
FlowPlanner,
)
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.tenants.models import Tenant
@ -203,6 +204,18 @@ class FlowExecutorView(APIView):
except InvalidStageError as exc:
return self.stage_invalid(str(exc))
def handle_exception(self, exc: Exception) -> HttpResponse:
"""Handle exception in stage execution"""
if settings.DEBUG or settings.TEST:
raise exc
capture_exception(exc)
self._logger.warning(exc)
Event.new(
action=EventAction.SYSTEM_EXCEPTION,
message=exception_to_string(exc),
).from_http(self.request)
return to_stage_response(self.request, FlowErrorResponse(self.request, exc))
@extend_schema(
responses={
200: PolymorphicProxySerializer(
@ -237,11 +250,7 @@ class FlowExecutorView(APIView):
stage_response = self.current_stage_view.get(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except
if settings.DEBUG or settings.TEST:
raise exc
capture_exception(exc)
self._logger.warning(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
return self.handle_exception(exc)
@extend_schema(
responses={
@ -278,11 +287,7 @@ class FlowExecutorView(APIView):
stage_response = self.current_stage_view.post(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except
if settings.DEBUG or settings.TEST:
raise exc
capture_exception(exc)
self._logger.warning(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
return self.handle_exception(exc)
def _initiate_plan(self) -> FlowPlan:
planner = FlowPlanner(self.flow)
@ -317,13 +322,15 @@ class FlowExecutorView(APIView):
"""User Successfully passed all stages"""
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
# extract the next param before cancel as that cleans it
next_param = None
if self.plan:
next_param = self.plan.context.get(PLAN_CONTEXT_REDIRECT)
if not next_param:
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:root-redirect"
)
if self.plan and PLAN_CONTEXT_REDIRECT in self.plan.context:
# The context `redirect` variable can only be set by
# an expression policy or authentik itself, so we don't
# check if its an absolute URL or a relative one
self.cancel()
return redirect(self.plan.context.get(PLAN_CONTEXT_REDIRECT))
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:root-redirect"
)
self.cancel()
return to_stage_response(self.request, redirect_with_qs(next_param))

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

@ -22,7 +22,7 @@ def redirect_with_qs(view: str, get_query_set=None, **kwargs) -> HttpResponse:
except NoReverseMatch:
if not is_url_absolute(view):
return redirect(view)
LOGGER.debug("redirect target is not a valid view", view=view)
LOGGER.warning("redirect target is not a valid view", view=view)
raise
else:
if get_query_set:

View File

@ -28,6 +28,7 @@ from authentik.outposts.models import (
OutpostServiceConnection,
OutpostState,
OutpostType,
ServiceConnectionInvalid,
)
from authentik.providers.ldap.controllers.docker import LDAPDockerController
from authentik.providers.ldap.controllers.kubernetes import LDAPKubernetesController
@ -114,7 +115,7 @@ def outpost_controller(
for log in logs:
LOGGER.debug(log)
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
except ControllerException as exc:
except (ControllerException, ServiceConnectionInvalid) as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
else:
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))

View File

@ -0,0 +1,54 @@
"""JWKS tests"""
import json
from django.test import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.providers.oauth2.models import OAuth2Provider
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestJWKS(OAuthTestCase):
"""Test JWKS view"""
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
def test_rs256(self):
"""Test JWKS request with RS256"""
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
)
app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get(
reverse(
"authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}
)
)
body = json.loads(force_str(response.content))
self.assertEqual(len(body["keys"]), 1)
def test_hs256(self):
"""Test JWKS request with HS256"""
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get(
reverse(
"authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}
)
)
self.assertJSONEqual(force_str(response.content), {})

View File

@ -30,7 +30,7 @@ class JWKSView(View):
response_data = {}
if provider.jwt_alg == JWTAlgorithms.RS256:
if provider.jwt_alg == JWTAlgorithms.RS256 and provider.rsa_key:
public_key: RSAPublicKey = provider.rsa_key.private_key.public_key()
public_numbers = public_key.public_numbers()
response_data["keys"] = [

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

@ -60,12 +60,12 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
expected_hosts.sort()
expected_hosts_tls.sort()
have_hosts = [rule.host for rule in reference.spec.rules]
have_hosts = [rule.host for rule in current.spec.rules]
have_hosts.sort()
have_hosts_tls = []
for tls_config in reference.spec.tls:
if tls_config:
for tls_config in current.spec.tls:
if tls_config and tls_config.hosts:
have_hosts_tls += tls_config.hosts
have_hosts_tls.sort()

View File

@ -2,7 +2,7 @@
from xml.etree.ElementTree import ParseError # nosec
from defusedxml.ElementTree import fromstring
from django.http.response import HttpResponse
from django.http.response import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@ -116,7 +116,10 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
def metadata(self, request: Request, pk: int) -> Response:
"""Return metadata as XML string"""
# We don't use self.get_object() on purpose as this view is un-authenticated
provider = get_object_or_404(SAMLProvider, pk=pk)
try:
provider = get_object_or_404(SAMLProvider, pk=pk)
except ValueError:
raise Http404
try:
metadata = MetadataProcessor(provider, request).build_entity_descriptor()
if "download" in request._request.GET:

View File

@ -163,7 +163,7 @@ class AssertionProcessor:
provider=self.provider,
)
if value is not None:
name_id.text = value
name_id.text = str(value)
return name_id
except PropertyMappingExpressionException as exc:
Event.new(

View File

@ -134,10 +134,18 @@ class ServiceProviderMetadataParser:
# For now we'll only look at the first descriptor.
# Even if multiple descriptors exist, we can only configure one
descriptor = sp_sso_descriptors[0]
auth_n_request_signed = (
descriptor.attrib["AuthnRequestsSigned"].lower() == "true"
)
assertion_signed = descriptor.attrib["WantAssertionsSigned"].lower() == "true"
auth_n_request_signed = False
if "AuthnRequestsSigned" in descriptor.attrib:
auth_n_request_signed = (
descriptor.attrib["AuthnRequestsSigned"].lower() == "true"
)
assertion_signed = False
if "WantAssertionsSigned" in descriptor.attrib:
assertion_signed = (
descriptor.attrib["WantAssertionsSigned"].lower() == "true"
)
acs_services = descriptor.findall(
f"{{{NS_SAML_METADATA}}}AssertionConsumerService"

View File

@ -20,6 +20,7 @@ class TestSAMLProviderAPI(APITestCase):
def test_metadata(self):
"""Test metadata export (normal)"""
self.client.logout()
provider = SAMLProvider.objects.create(
name="test",
authorization_flow=Flow.objects.get(
@ -34,6 +35,7 @@ class TestSAMLProviderAPI(APITestCase):
def test_metadata_download(self):
"""Test metadata export (download)"""
self.client.logout()
provider = SAMLProvider.objects.create(
name="test",
authorization_flow=Flow.objects.get(
@ -50,6 +52,7 @@ class TestSAMLProviderAPI(APITestCase):
def test_metadata_invalid(self):
"""Test metadata export (invalid)"""
self.client.logout()
# Provider without application
provider = SAMLProvider.objects.create(
name="test",
@ -61,6 +64,10 @@ class TestSAMLProviderAPI(APITestCase):
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}),
)
self.assertEqual(200, response.status_code)
response = self.client.get(
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": "abc"}),
)
self.assertEqual(404, response.status_code)
def test_import_success(self):
"""Test metadata import (success case)"""

View File

@ -105,15 +105,17 @@ class LDAPPasswordChanger:
if len(user_attributes["sAMAccountName"]) >= 3:
if password.lower() in user_attributes["sAMAccountName"].lower():
return False
display_name_tokens = split(
RE_DISPLAYNAME_SEPARATORS, user_attributes["displayName"]
)
for token in display_name_tokens:
# Ignore tokens under 3 chars
if len(token) < 3:
continue
if token.lower() in password.lower():
return False
# No display name set, can't check any further
if len(user_attributes["displayName"]) < 1:
return True
for display_name in user_attributes["displayName"]:
display_name_tokens = split(RE_DISPLAYNAME_SEPARATORS, display_name)
for token in display_name_tokens:
# Ignore tokens under 3 chars
if len(token) < 3:
continue
if token.lower() in password.lower():
return False
return True
def ad_password_complexity(

View File

@ -0,0 +1,10 @@
"""Plex source settings"""
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"check_plex_token": {
"task": "authentik.sources.plex.tasks.check_plex_token_all",
"schedule": crontab(minute="31", hour="*/3"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -0,0 +1,43 @@
"""Plex tasks"""
from requests import RequestException
from authentik.events.models import Event, EventAction
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.root.celery import CELERY_APP
from authentik.sources.plex.models import PlexSource
from authentik.sources.plex.plex import PlexAuth
@CELERY_APP.task()
def check_plex_token_all():
"""Check plex token for all plex sources"""
for source in PlexSource.objects.all():
check_plex_token.delay(source.slug)
@CELERY_APP.task(bind=True, base=MonitoredTask)
def check_plex_token(self: MonitoredTask, source_slug: int):
"""Check the validity of a Plex source."""
sources = PlexSource.objects.filter(slug=source_slug)
if not sources.exists():
return
source: PlexSource = sources.first()
self.set_uid(source.slug)
auth = PlexAuth(source, source.plex_token)
try:
auth.get_user_info()
self.set_status(
TaskResult(TaskResultStatus.SUCCESSFUL, ["Plex token is valid."])
)
except RequestException as exc:
self.set_status(
TaskResult(
TaskResultStatus.ERROR,
["Plex token is invalid/an error occurred:", str(exc)],
)
)
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Plex token invalid, please re-authenticate source.\n{str(exc)}",
source=source,
).save()

View File

@ -1,10 +1,13 @@
"""plex Source tests"""
from django.test import TestCase
from requests.exceptions import RequestException
from requests_mock import Mocker
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.sources.plex.models import PlexSource
from authentik.sources.plex.plex import PlexAuth
from authentik.sources.plex.tasks import check_plex_token_all
USER_INFO_RESPONSE = {
"id": 1234123419,
@ -62,3 +65,18 @@ class TestPlexSource(TestCase):
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/resources", json=RESOURCES_RESPONSE)
self.assertTrue(api.check_server_overlap())
def test_check_task(self):
"""Test token check task"""
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/user", json=USER_INFO_RESPONSE)
check_plex_token_all()
self.assertFalse(
Event.objects.filter(action=EventAction.CONFIGURATION_ERROR).exists()
)
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/user", exc=RequestException())
check_plex_token_all()
self.assertTrue(
Event.objects.filter(action=EventAction.CONFIGURATION_ERROR).exists()
)

View File

@ -67,10 +67,15 @@ class EmailStageView(ChallengeStageView):
"user": pending_user,
"identifier": f"ak-email-stage-{current_stage.name}-{pending_user}",
}
tokens = Token.filter_not_expired(**token_filters)
# Don't check for validity here, we only care if the token exists
tokens = Token.objects.filter(**token_filters)
if not tokens.exists():
return Token.objects.create(expires=now() + valid_delta, **token_filters)
return tokens.first()
token = tokens.first()
# Check if token is expired and rotate key if so
if token.is_expired:
token.expire_action()
return token
def send_email(self):
"""Helper function that sends the actual email. Implies that you've

View File

@ -91,7 +91,7 @@ def send_mail(
messages=["Successfully sent Mail."],
)
)
except (SMTPException, ConnectionError) as exc:
except (SMTPException, ConnectionError, OSError) as exc:
LOGGER.debug("Error sending email, retrying...", exc=exc)
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
raise exc

View File

@ -1,18 +1,23 @@
"""invitation stage logic"""
from copy import deepcopy
from typing import Optional
from deepmerge import always_merger
from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from structlog.stdlib import get_logger
from authentik.flows.models import in_memory_stage
from authentik.flows.stage import StageView
from authentik.flows.views import SESSION_KEY_GET
from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.signals import invitation_used
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
INVITATION_TOKEN_KEY = "token" # nosec
INVITATION_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation"
class InvitationStageView(StageView):
@ -39,9 +44,37 @@ class InvitationStageView(StageView):
return self.executor.stage_invalid()
invite: Invitation = get_object_or_404(Invitation, pk=token)
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = deepcopy(invite.fixed_data)
self.executor.plan.context[INVITATION_IN_EFFECT] = True
self.executor.plan.context[INVITATION] = invite
context = {}
always_merger.merge(
context, self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {})
)
always_merger.merge(context, invite.fixed_data)
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = context
invitation_used.send(sender=self, request=request, invitation=invite)
if invite.single_use:
invite.delete()
self.executor.plan.append_stage(in_memory_stage(InvitationFinalStageView))
return self.executor.stage_ok()
class InvitationFinalStageView(StageView):
"""Final stage which is injected by invitation stage. Deletes
the used invitation."""
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Call get as this request may be called with post"""
return self.get(request, *args, **kwargs)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Delete invitation if single_use is active"""
invitation: Invitation = self.executor.plan.context.get(INVITATION, None)
if not invitation:
LOGGER.warning("InvitationFinalStageView stage called without invitation")
return HttpResponseBadRequest
if not invitation.single_use:
return self.executor.stage_ok()
invitation.delete()
return self.executor.stage_ok()

View File

@ -156,11 +156,13 @@ class TestUserLoginStage(TestCase):
base_url = reverse(
"authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}
)
response = self.client.get(base_url)
response = self.client.get(base_url, follow=True)
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
self.assertEqual(
plan.context[PLAN_CONTEXT_PROMPT], data | plan.context[PLAN_CONTEXT_PROMPT]
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(

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

@ -21,7 +21,7 @@ services:
networks:
- internal
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1-rc2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.3}
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-rc2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.3}
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-rc2"
const VERSION = "2021.7.3"

View File

@ -116,7 +116,7 @@ func (ac *APIController) startWSHealth() {
func (ac *APIController) startIntervalUpdater() {
logger := ac.logger.WithField("loop", "interval-updater")
ticker := time.NewTicker(time.Second * 150)
ticker := time.NewTicker(5 * time.Minute)
for ; true; <-ticker.C {
err := ac.Server.Refresh()
if err != nil {

View File

@ -75,7 +75,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
pi.boundUsersMutex.Lock()
cs := pi.SearchAccessCheck(userInfo.User)
pi.boundUsers[req.BindDN] = UserFlags{
UserInfo: userInfo.User,
UserPk: userInfo.User.Pk,
CanSearch: cs != nil,
}
if pi.boundUsers[req.BindDN].CanSearch {
@ -88,7 +88,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
}
// SearchAccessCheck Check if the current user is allowed to search
func (pi *ProviderInstance) SearchAccessCheck(user api.User) *string {
func (pi *ProviderInstance) SearchAccessCheck(user api.UserSelf) *string {
for _, group := range user.Groups {
for _, allowedGroup := range pi.searchAllowedGroups {
pi.log.WithField("userGroup", group.Pk).WithField("allowedGroup", allowedGroup).Trace("Checking search access")

View File

@ -11,9 +11,17 @@ import (
"goauthentik.io/api"
)
func (pi *ProviderInstance) SearchMe(user api.User) (ldap.ServerSearchResult, error) {
func (pi *ProviderInstance) SearchMe(req SearchRequest, f UserFlags) (ldap.ServerSearchResult, error) {
if f.UserInfo == nil {
u, _, err := pi.s.ac.Client.CoreApi.CoreUsersRetrieve(req.ctx, f.UserInfo.Pk).Execute()
if err != nil {
req.log.WithError(err).Warning("Failed to get user info")
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Failed to get userinfo")
}
f.UserInfo = &u
}
entries := make([]*ldap.Entry, 1)
entries[0] = pi.UserEntry(user)
entries[0] = pi.UserEntry(*f.UserInfo)
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
@ -42,7 +50,7 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}
if !flags.CanSearch {
pi.log.Debug("User can't search, showing info about user")
return pi.SearchMe(flags.UserInfo)
return pi.SearchMe(req, flags)
}
accsp.Finish()

View File

@ -39,7 +39,8 @@ type ProviderInstance struct {
}
type UserFlags struct {
UserInfo api.User
UserInfo *api.User
UserPk int32
CanSearch bool
}

View File

@ -51,7 +51,8 @@ logconfig_dict = {
if SERVICE_HOST_ENV_NAME in os.environ:
workers = 2
else:
workers = int(os.environ.get("WORKERS", cpu_count() * 2 + 1))
default_workers = max(cpu_count() * 0.25, 1) + 1 # Minimum of 2 workers
workers = int(os.environ.get("WORKERS", default_workers))
threads = 4
warnings.simplefilter("once")

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2021.7.1-rc2
version: 2021.7.3
description: Making authentication simple.
contact:
email: hello@beryju.org
@ -3185,6 +3185,38 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/core/users/update_self/:
put:
operationId: core_users_update_self_update
description: Allow users to change information on their own profile
tags:
- core
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserSelfRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/UserSelfRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/UserSelfRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SessionUser'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/crypto/certificatekeypairs/:
get:
operationId: crypto_certificatekeypairs_list
@ -25902,6 +25934,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
@ -27574,9 +27609,9 @@ components:
and, if this user is being impersonated, the original user in the `original` property.
properties:
user:
$ref: '#/components/schemas/User'
$ref: '#/components/schemas/UserSelf'
original:
$ref: '#/components/schemas/User'
$ref: '#/components/schemas/UserSelf'
required:
- user
SetIconRequest:
@ -27977,6 +28012,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
@ -28012,6 +28050,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:
@ -28469,6 +28510,82 @@ components:
required:
- name
- username
UserSelf:
type: object
description: |-
User Serializer for information a user can retrieve about themselves and
update about themselves
properties:
pk:
type: integer
readOnly: true
title: ID
username:
type: string
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
pattern: ^[\w.@+-]+$
maxLength: 150
name:
type: string
description: User's display name.
is_active:
type: boolean
readOnly: true
title: Active
description: Designates whether this user should be treated as active. Unselect
this instead of deleting accounts.
is_superuser:
type: boolean
readOnly: true
groups:
type: array
items:
$ref: '#/components/schemas/Group'
readOnly: true
email:
type: string
format: email
title: Email address
maxLength: 254
avatar:
type: string
readOnly: true
uid:
type: string
readOnly: true
required:
- avatar
- groups
- is_active
- is_superuser
- name
- pk
- uid
- username
UserSelfRequest:
type: object
description: |-
User Serializer for information a user can retrieve about themselves and
update about themselves
properties:
username:
type: string
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
pattern: ^[\w.@+-]+$
maxLength: 150
name:
type: string
description: User's display name.
email:
type: string
format: email
title: Email address
maxLength: 254
required:
- name
- username
UserSetting:
type: object
description: Serializer for User settings for stages and sources

View File

@ -2,7 +2,7 @@ version: '3.7'
services:
chrome:
image: selenium/standalone-chrome:3.141
image: selenium/standalone-chrome:3.141.59-20210713
volumes:
- /dev/shm:/dev/shm
network_mode: host

View File

@ -2,7 +2,7 @@ version: '3.7'
services:
chrome:
image: selenium/standalone-chrome-debug:3.141
image: selenium/standalone-chrome-debug:3.141.59-20210713
volumes:
- /dev/shm:/dev/shm
network_mode: host

View File

@ -1,20 +1,36 @@
"""Test Controllers"""
from typing import Optional
import yaml
from django.test import TestCase
from structlog.stdlib import get_logger
from authentik.flows.models import Flow
from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
from authentik.outposts.tasks import outpost_local_connection
from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler
from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController
from authentik.providers.proxy.models import ProxyProvider
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
LOGGER = get_logger()
class TestProxyKubernetes(TestCase):
"""Test Controllers"""
controller: Optional[KubernetesController]
def setUp(self):
# Ensure that local connection have been created
outpost_local_connection()
self.controller = None
def tearDown(self) -> None:
if self.controller:
for log in self.controller.down_with_logs():
LOGGER.info(log)
return super().tearDown()
def test_kubernetes_controller_static(self):
"""Test Kubernetes Controller"""
@ -33,18 +49,26 @@ class TestProxyKubernetes(TestCase):
outpost.providers.add(provider)
outpost.save()
controller = ProxyKubernetesController(outpost, service_connection)
manifest = controller.get_static_deployment()
self.controller = ProxyKubernetesController(outpost, service_connection)
manifest = self.controller.get_static_deployment()
self.assertEqual(len(list(yaml.load_all(manifest, Loader=yaml.SafeLoader))), 4)
def test_kubernetes_controller_deploy(self):
"""Test Kubernetes Controller"""
def test_kubernetes_controller_ingress(self):
"""Test Kubernetes Controller's Ingress"""
provider: ProxyProvider = ProxyProvider.objects.create(
name="test",
internal_host="http://localhost",
external_host="http://localhost",
external_host="https://localhost",
authorization_flow=Flow.objects.first(),
)
provider2: ProxyProvider = ProxyProvider.objects.create(
name="test2",
internal_host="http://otherhost",
external_host="https://otherhost",
mode=ProxyMode.FORWARD_SINGLE,
authorization_flow=Flow.objects.first(),
)
service_connection = KubernetesServiceConnection.objects.first()
outpost: Outpost = Outpost.objects.create(
name="test",
@ -52,8 +76,19 @@ class TestProxyKubernetes(TestCase):
service_connection=service_connection,
)
outpost.providers.add(provider)
outpost.save()
controller = ProxyKubernetesController(outpost, service_connection)
controller.up()
controller.down()
self.controller = ProxyKubernetesController(outpost, service_connection)
ingress_rec = IngressReconciler(self.controller)
ingress = ingress_rec.retrieve()
self.assertEqual(len(ingress.spec.rules), 1)
self.assertEqual(ingress.spec.rules[0].host, "localhost")
# add provider, check again
outpost.providers.add(provider2)
ingress = ingress_rec.retrieve()
self.assertEqual(len(ingress.spec.rules), 2)
self.assertEqual(ingress.spec.rules[0].host, "localhost")
self.assertEqual(ingress.spec.rules[1].host, "otherhost")

168
web/package-lock.json generated
View File

@ -29,13 +29,13 @@
"@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.2",
"construct-style-sheets-polyfill": "^2.4.16",
@ -48,7 +48,7 @@
"lit-html": "^1.4.1",
"moment": "^2.29.1",
"rapidoc": "^9.0.0",
"rollup": "^2.53.3",
"rollup": "^2.54.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2",
@ -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",
@ -6790,9 +6790,9 @@
}
},
"node_modules/rollup": {
"version": "2.53.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.3.tgz",
"integrity": "sha512-79QIGP5DXz5ZHYnCPi3tLz+elOQi6gudp9YINdaJdjG0Yddubo6JRFUM//qCZ0Bap/GJrsUoEBVdSOc4AkMlRA==",
"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"
},
@ -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",
@ -13241,9 +13241,9 @@
}
},
"rollup": {
"version": "2.53.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.3.tgz",
"integrity": "sha512-79QIGP5DXz5ZHYnCPi3tLz+elOQi6gudp9YINdaJdjG0Yddubo6JRFUM//qCZ0Bap/GJrsUoEBVdSOc4AkMlRA==",
"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

@ -58,13 +58,13 @@
"@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.2",
"construct-style-sheets-polyfill": "^2.4.16",
@ -77,7 +77,7 @@
"lit-html": "^1.4.1",
"moment": "^2.29.1",
"rapidoc": "^9.0.0",
"rollup": "^2.53.3",
"rollup": "^2.54.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2",

View File

@ -33,15 +33,7 @@ export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
}
}
if (hint.originalException instanceof Response) {
const response = hint.originalException as Response;
// We only care about server errors
if (response.status < 500) {
return null;
}
// Need to clone the response, otherwise the .text() and .json() can't be re-used
const resCopy = response.clone();
const body = await resCopy.json();
event.message = `${response.status} ${response.url}: ${JSON.stringify(body)}`
return null;
}
if (event.exception) {
me().then(user => {

View File

@ -3,7 +3,7 @@ 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-rc2";
export const VERSION = "2021.7.3";
export const PAGE_SIZE = 20;
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";

View File

@ -3,18 +3,20 @@ import { EVENT_REFRESH } from "../../constants";
import { Form } from "./Form";
export abstract class ModelForm<T, PKT extends string | number> extends Form<T> {
viewportCheck = true;
abstract loadInstance(pk: PKT): Promise<T>;
@property({attribute: false})
set instancePk(value: PKT) {
this._instancePk = value;
if (this.isInViewport) {
this.loadInstance(value).then(instance => {
this.instance = instance;
this.requestUpdate();
});
if (this.viewportCheck && !this.isInViewport) {
return;
}
this.loadInstance(value).then((instance) => {
this.instance = instance;
this.requestUpdate();
});
}
private _instancePk?: PKT;

View File

@ -1077,7 +1077,7 @@ msgstr "Delete Refresh Code"
msgid "Delete Session"
msgstr "Delete Session"
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
msgid "Delete account"
msgstr "Delete account"
@ -1230,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."
@ -1293,7 +1297,7 @@ msgstr "Either no applications are defined, or you don't have access to any."
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/events/TransportForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
msgid "Email"
@ -1408,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}"
@ -1416,13 +1424,16 @@ 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
msgid "Exception"
msgstr "Exception"
#: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowViewPage.ts
msgid "Execute"
msgstr "Execute"
@ -1475,7 +1486,6 @@ msgstr "Expiry date"
msgid "Explicit Consent"
msgstr "Explicit Consent"
#: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowViewPage.ts
msgid "Export"
msgstr "Export"
@ -1666,6 +1676,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)"
@ -1761,6 +1775,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"
@ -2060,6 +2078,10 @@ msgstr "Link to a user with identical email address. Can have security implicati
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
msgstr "Link to a user with identical username address. Can have security implications when a username is used with another source."
#: src/pages/stages/invitation/InvitationListLink.ts
msgid "Link to use the invitation."
msgstr "Link to use the invitation."
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
msgid "Link users on unique identifier"
@ -2089,7 +2111,7 @@ msgstr "Load servers"
#: src/flows/stages/prompt/PromptStage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/utils.ts
msgid "Loading"
msgstr "Loading"
@ -2148,6 +2170,7 @@ msgstr "Loading"
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/invitation/InvitationListLink.ts
#: src/pages/stages/password/PasswordStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts
@ -2377,7 +2400,7 @@ msgstr "My Applications"
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
#: src/pages/stages/user_write/UserWriteStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
@ -2643,6 +2666,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"
@ -2986,6 +3013,10 @@ msgstr "RSA-SHA384"
msgid "RSA-SHA512"
msgstr "RSA-SHA512"
#: src/pages/sources/plex/PlexSourceForm.ts
msgid "Re-authenticate with plex"
msgstr "Re-authenticate with plex"
#: src/pages/flows/StageBindingForm.ts
msgid "Re-evaluate policies"
msgstr "Re-evaluate policies"
@ -3085,7 +3116,7 @@ msgstr "Request token URL"
msgid "Required"
msgstr "Required"
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
@ -3239,6 +3270,10 @@ msgstr "Select a provider that this application should use. Alternatively, creat
msgid "Select all rows"
msgstr "Select all rows"
#: src/pages/stages/invitation/InvitationListLink.ts
msgid "Select an enrollment flow"
msgstr "Select an enrollment flow"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Select an identification method."
msgstr "Select an identification method."
@ -3304,6 +3339,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."
@ -3724,7 +3763,7 @@ msgstr "Successfully updated binding."
msgid "Successfully updated certificate-key pair."
msgstr "Successfully updated certificate-key pair."
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
msgid "Successfully updated details."
msgstr "Successfully updated details."
@ -3881,6 +3920,10 @@ 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"
@ -4025,6 +4068,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."
@ -4248,7 +4295,7 @@ msgstr "Up-to-date!"
#: src/pages/stages/StageListPage.ts
#: src/pages/stages/prompt/PromptListPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
@ -4351,7 +4398,7 @@ msgstr "Update User"
msgid "Update available"
msgstr "Update available"
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSettingsPage.ts
msgid "Update details"
msgstr "Update details"
@ -4484,7 +4531,7 @@ msgstr "User {0}"
msgid "User's avatar"
msgstr "User's avatar"
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
msgid "User's display name."
msgstr "User's display name."
@ -4504,7 +4551,7 @@ msgstr "Userinfo URL"
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/policies/reputation/UserReputationListPage.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
msgid "Username"
@ -4592,6 +4639,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"
@ -4659,6 +4708,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

@ -1071,7 +1071,7 @@ msgstr ""
msgid "Delete Session"
msgstr ""
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
msgid "Delete account"
msgstr ""
@ -1222,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 ""
@ -1285,7 +1289,7 @@ msgstr ""
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/events/TransportForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
msgid "Email"
@ -1400,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 ""
@ -1408,13 +1416,16 @@ 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
msgid "Exception"
msgstr ""
#: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowViewPage.ts
msgid "Execute"
msgstr ""
@ -1467,7 +1478,6 @@ msgstr ""
msgid "Explicit Consent"
msgstr ""
#: src/pages/flows/FlowListPage.ts
#: src/pages/flows/FlowViewPage.ts
msgid "Export"
msgstr ""
@ -1658,6 +1668,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 ""
@ -1753,6 +1767,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 ""
@ -2052,6 +2070,10 @@ msgstr ""
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
msgstr ""
#: src/pages/stages/invitation/InvitationListLink.ts
msgid "Link to use the invitation."
msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
msgid "Link users on unique identifier"
@ -2081,7 +2103,7 @@ msgstr ""
#: src/flows/stages/prompt/PromptStage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/utils.ts
msgid "Loading"
msgstr ""
@ -2140,6 +2162,7 @@ msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/stages/invitation/InvitationListLink.ts
#: src/pages/stages/password/PasswordStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts
#: src/pages/stages/prompt/PromptStageForm.ts
@ -2369,7 +2392,7 @@ msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
#: src/pages/stages/user_write/UserWriteStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
@ -2635,6 +2658,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 ""
@ -2978,6 +3005,10 @@ msgstr ""
msgid "RSA-SHA512"
msgstr ""
#: src/pages/sources/plex/PlexSourceForm.ts
msgid "Re-authenticate with plex"
msgstr ""
#: src/pages/flows/StageBindingForm.ts
msgid "Re-evaluate policies"
msgstr ""
@ -3077,7 +3108,7 @@ msgstr ""
msgid "Required"
msgstr ""
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
@ -3231,6 +3262,10 @@ msgstr ""
msgid "Select all rows"
msgstr ""
#: src/pages/stages/invitation/InvitationListLink.ts
msgid "Select an enrollment flow"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Select an identification method."
msgstr ""
@ -3296,6 +3331,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 ""
@ -3716,7 +3755,7 @@ msgstr ""
msgid "Successfully updated certificate-key pair."
msgstr ""
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
msgid "Successfully updated details."
msgstr ""
@ -3873,6 +3912,10 @@ 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 ""
@ -4010,6 +4053,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 ""
@ -4233,7 +4280,7 @@ msgstr ""
#: src/pages/stages/StageListPage.ts
#: src/pages/stages/prompt/PromptListPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
@ -4336,7 +4383,7 @@ msgstr ""
msgid "Update available"
msgstr ""
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSettingsPage.ts
msgid "Update details"
msgstr ""
@ -4469,7 +4516,7 @@ msgstr ""
msgid "User's avatar"
msgstr ""
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
msgid "User's display name."
msgstr ""
@ -4489,7 +4536,7 @@ msgstr ""
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/policies/reputation/UserReputationListPage.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
msgid "Username"
@ -4577,6 +4624,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"
@ -4644,6 +4693,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

@ -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

@ -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 => {
return 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

@ -124,16 +124,16 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
${t`Link users on unique identifier`}
</option>
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
</option>
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
</option>
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
</option>
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
${t`Use the user's username, but deny enrollment when the username already exists.`}
</option>
</select>

View File

@ -85,7 +85,13 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
${t`Load servers`}
</button>`;
}
return html`<ak-form-element-horizontal name="allowFriends">
return html`
<button class="pf-c-button pf-m-secondary" type="button" @click=${() => {
this.doAuth();
}}>
${t`Re-authenticate with plex`}
</button>
<ak-form-element-horizontal name="allowFriends">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.allowFriends, true)}>
<label class="pf-c-check__label">
@ -140,16 +146,16 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
${t`Link users on unique identifier`}
</option>
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
</option>
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
</option>
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
</option>
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
${t`Use the user's username, but deny enrollment when the username already exists.`}
</option>
</select>

View File

@ -0,0 +1,67 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import AKGlobal from "../../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
import { FlowsApi, FlowsInstancesListDesignationEnum } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
@customElement("ak-stage-invitation-list-link")
export class InvitationListLink extends LitElement {
@property()
invitation?: string
@property()
selectedFlow?: string;
static get styles(): CSSResult[] {
return [PFBase, PFForm, PFFormControl, PFFlex, PFDescriptionList, AKGlobal];
}
renderLink(): string {
return `${window.location.protocol}//${window.location.host}/if/flow/${this.selectedFlow}/?token=${this.invitation}`;
}
render(): TemplateResult {
return html`<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Select an enrollment flow`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.selectedFlow = current;
}}>
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowsInstancesListDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
return html`<option value=${flow.slug} ?selected=${flow.slug === this.selectedFlow}>${flow.slug}</option>`;
});
}), html`<option>${t`Loading...`}</option>`)}
</select>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Link to use the invitation.`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<input class="pf-c-form-control" readonly type="text" value=${this.renderLink()} />
</div>
</dd>
</div>
</dl>`;
}
}

View File

@ -8,6 +8,7 @@ import "../../../elements/buttons/SpinnerButton";
import "../../../elements/forms/DeleteForm";
import "../../../elements/forms/ModalForm";
import "./InvitationForm";
import "./InvitationListLink";
import { TableColumn } from "../../../elements/table/Table";
import { PAGE_SIZE } from "../../../constants";
import { Invitation, StagesApi } from "authentik-api";
@ -15,6 +16,8 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
@customElement("ak-stage-invitation-list")
export class InvitationListPage extends TablePage<Invitation> {
expandable = true;
searchEnabled(): boolean {
return true;
}
@ -53,7 +56,7 @@ export class InvitationListPage extends TablePage<Invitation> {
return [
html`${item.pk}`,
html`${item.createdBy?.username}`,
html`${item.expires?.toLocaleString()}`,
html`${item.expires?.toLocaleString() || "-"}`,
html`
<ak-forms-delete
.obj=${item}
@ -75,6 +78,18 @@ export class InvitationListPage extends TablePage<Invitation> {
];
}
renderExpanded(item: Invitation): TemplateResult {
return html`
<td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<ak-stage-invitation-list-link invitation=${item.pk}></ak-stage-invitation-list-link>
</div>
</td>
<td></td>
<td></td>
<td></td>`;
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>

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

@ -1,101 +0,0 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import AKGlobal from "../../authentik.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import { CoreApi, User } from "authentik-api";
import { me } from "../../api/Users";
import { ifDefined } from "lit-html/directives/if-defined";
import { DEFAULT_CONFIG, tenant } from "../../api/Config";
import "../../elements/forms/FormElement";
import "../../elements/EmptyState";
import "../../elements/forms/Form";
import "../../elements/forms/HorizontalFormElement";
import { until } from "lit-html/directives/until";
@customElement("ak-user-details")
export class UserDetailsPage extends LitElement {
static get styles(): CSSResult[] {
return [PFBase, PFCard, PFForm, PFFormControl, PFButton, AKGlobal];
}
@property({attribute: false})
user?: User;
firstUpdated(): void {
me().then((user) => {
this.user = user.user;
});
}
render(): TemplateResult {
if (!this.user) {
return html`<ak-empty-state
?loading="${true}"
header=${t`Loading`}>
</ak-empty-state>`;
}
return html`<div class="pf-c-card">
<div class="pf-c-card__title">
${t`Update details`}
</div>
<div class="pf-c-card__body">
<ak-form
successMessage=${t`Successfully updated details.`}
.send=${(data: unknown) => {
return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({
id: this.user?.pk || 0,
userRequest: data as User
});
}}>
<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Username`}
?required=${true}
name="username">
<input type="text" value="${ifDefined(this.user?.username)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Name`}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.user?.name)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${t`User's display name.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Email`}
?required=${true}
name="email">
<input type="email" value="${ifDefined(this.user?.email)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<button class="pf-c-button pf-m-primary">
${t`Update`}
</button>
${until(tenant().then(tenant => {
if (tenant.flowUnenrollment) {
return html`<a class="pf-c-button pf-m-danger"
href="/if/flow/${tenant.flowUnenrollment}">
${t`Delete account`}
</a>`;
}
return html``;
}))}
</div>
</div>
</div>
</form>
</ak-form>
</div>
</div>`;
}
}

View File

@ -0,0 +1,100 @@
import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { CoreApi, UserSelf } from "authentik-api";
import { ifDefined } from "lit-html/directives/if-defined";
import { DEFAULT_CONFIG, tenant } from "../../api/Config";
import "../../elements/forms/FormElement";
import "../../elements/EmptyState";
import "../../elements/forms/Form";
import "../../elements/forms/HorizontalFormElement";
import { until } from "lit-html/directives/until";
import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-user-self-form")
export class UserSelfForm extends ModelForm<UserSelf, number> {
viewportCheck = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadInstance(pk: number): Promise<UserSelf> {
return new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().then((su) => {
return su.user;
});
}
getSuccessMessage(): string {
return t`Successfully updated details.`;
}
send = (data: UserSelf): Promise<UserSelf> => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersUpdateSelfUpdate({
userSelfRequest: data,
})
.then((su) => {
return su.user;
});
};
renderForm(): TemplateResult {
if (!this.instance) {
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
}
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Username`} ?required=${true} name="username">
<input
type="text"
value="${ifDefined(this.instance?.username)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${t`User's display name.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Email`} name="email">
<input
type="email"
value="${ifDefined(this.instance?.email)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<button
@click=${(ev: Event) => {
return this.submit(ev);
}}
class="pf-c-button pf-m-primary"
>
${t`Update`}
</button>
${until(
tenant().then((tenant) => {
if (tenant.flowUnenrollment) {
return html`<a
class="pf-c-button pf-m-danger"
href="/if/flow/${tenant.flowUnenrollment}"
>
${t`Delete account`}
</a>`;
}
return html``;
}),
)}
</div>
</div>
</div>
</form>`;
}
}

View File

@ -20,7 +20,7 @@ import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/Tabs";
import "../../elements/PageHeader";
import "./tokens/UserTokenList";
import "./UserDetailsPage";
import "./UserSelfForm";
import "./settings/UserSettingsAuthenticatorDuo";
import "./settings/UserSettingsAuthenticatorStatic";
import "./settings/UserSettingsAuthenticatorTOTP";
@ -95,8 +95,17 @@ export class UserSettingsPage extends LitElement {
description=${t`Configure settings relevant to your user profile.`}>
</ak-page-header>
<ak-tabs ?vertical="${true}" style="height: 100%;">
<section slot="page-details" data-tab-title="${t`User details`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<ak-user-details></ak-user-details>
<section
slot="page-details"
data-tab-title="${t`User details`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${t`Update details`}</div>
<div class="pf-c-card__body">
<ak-user-self-form .instancePk=${1}></ak-user-self-form>
</div>
</div>
</section>
<section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<ak-user-token-list></ak-user-token-list>

View File

@ -58,9 +58,8 @@ export class UserForm extends ModelForm<User, number> {
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Email`}
?required=${true}
name="email">
<input type="email" autocomplete="off" value="${ifDefined(this.instance?.email)}" class="pf-c-form-control" required>
<input type="email" autocomplete="off" value="${ifDefined(this.instance?.email)}" class="pf-c-form-control">
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="isActive">

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-rc2/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.3/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-rc2 >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.7.3 >> .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-rc2
image: ghcr.io/goauthentik/proxy:2021.7.3
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-rc2
image: ghcr.io/goauthentik/ldap:2021.7.3
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-rc2
app.kubernetes.io/version: 2021.7.3
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-rc2
app.kubernetes.io/version: 2021.7.3
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-rc2
app.kubernetes.io/version: 2021.7.3
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-rc2
app.kubernetes.io/version: 2021.7.3
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-rc2
app.kubernetes.io/version: 2021.7.3
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-rc2
image: ghcr.io/goauthentik/proxy:2021.7.3
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-rc2
app.kubernetes.io/version: 2021.7.3
name: authentik-outpost
spec:
rules:

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB