Compare commits

..

3 Commits

Author SHA1 Message Date
10b1bd5bf4 release: 2022.9.1 2022-11-02 21:54:21 +01:00
55cc1812c0 core: bump golang from 1.19.2-bullseye to 1.19.3-bullseye (#3925)
Bumps golang from 1.19.2-bullseye to 1.19.3-bullseye.

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-02 10:09:00 +01:00
6962d8c59c core: bump golang from 1.19.1-bullseye to 1.19.2-bullseye (#3728) 2022-11-02 10:08:54 +01:00
518 changed files with 12803 additions and 32778 deletions

View File

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

View File

@ -32,27 +32,32 @@ runs:
shell: python
run: |
"""Helper script to get the actual branch name, docker safe"""
import configparser
import os
from time import time
parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg")
env_pr_branch = "GITHUB_HEAD_REF"
default_branch = "GITHUB_REF"
sha = "GITHUB_SHA"
branch_name = os.environ["GITHUB_REF"]
if os.environ.get("GITHUB_HEAD_REF", "") != "":
branch_name = os.environ["GITHUB_HEAD_REF"]
branch_name = os.environ[default_branch]
if os.environ.get(env_pr_branch, "") != "":
branch_name = os.environ[env_pr_branch]
should_build = str(os.environ.get("DOCKER_USERNAME", "") != "").lower()
print("##[set-output name=branchName]%s" % branch_name)
print(
"##[set-output name=branchNameContainer]%s"
% branch_name.replace("refs/heads/", "").replace("/", "-")
)
print("##[set-output name=timestamp]%s" % int(time()))
print("##[set-output name=sha]%s" % os.environ[sha])
print("##[set-output name=shouldBuild]%s" % should_build)
import configparser
parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg")
version = parser.get("bumpversion", "current_version")
version_family = ".".join(version.split(".")[:-1])
safe_branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print("branchName=%s" % branch_name, file=_output)
print("branchNameContainer=%s" % safe_branch_name, file=_output)
print("timestamp=%s" % int(time()), file=_output)
print("sha=%s" % os.environ["GITHUB_SHA"], file=_output)
print("shouldBuild=%s" % should_build, file=_output)
print("version=%s" % version, file=_output)
print("versionFamily=%s" % version_family, file=_output)
print("##[set-output name=version]%s" % version)
print("##[set-output name=versionFamily]%s" % version_family)

View File

@ -1,5 +1,5 @@
name: 'Setup authentik testing environment'
description: 'Setup authentik testing environment'
name: 'Setup authentik testing environemnt'
description: 'Setup authentik testing environemnt'
runs:
using: "composite"

16
.github/transifex.yml vendored
View File

@ -1,16 +0,0 @@
git:
filters:
- filter_type: file
# all supported i18n types: https://docs.transifex.com/formats
file_format: PO
source_language: en
source_file: web/src/locales/en.po
# path expression to translation files, must contain <lang> placeholder
translation_files_expression: 'web/src/locales/<lang>.po'
- filter_type: file
# all supported i18n types: https://docs.transifex.com/formats
file_format: PO
source_language: en
source_file: locale/en/LC_MESSAGES/django.po
# path expression to translation files, must contain <lang> placeholder
translation_files_expression: 'locale/<lang>/LC_MESSAGES/django.po'

View File

@ -108,7 +108,7 @@ jobs:
with:
domain: ${{github.repository_owner}}
- name: Create k8s Kind Cluster
uses: helm/kind-action@v1.4.0
uses: helm/kind-action@v1.3.0
- name: run integration
run: |
poetry run make test-integration
@ -130,7 +130,7 @@ jobs:
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- name: Setup e2e env (chrome, etc)
- name: Setup authentik env
run: |
docker-compose -f tests/e2e/docker-compose.yml up -d
- id: cache-web
@ -166,7 +166,7 @@ jobs:
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- name: Setup e2e env (chrome, etc)
- name: Setup authentik env
run: |
docker-compose -f tests/e2e/docker-compose.yml up -d
- id: cache-web
@ -217,7 +217,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables

View File

@ -63,7 +63,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
@ -111,7 +111,7 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
@ -47,7 +47,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
@ -54,7 +54,7 @@ jobs:
with:
go-version: "^1.17"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
@ -100,7 +100,7 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
registry-url: 'https://registry.npmjs.org'

View File

@ -17,24 +17,24 @@ diverse, inclusive, and healthy community.
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
* 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
* 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
@ -106,7 +106,7 @@ 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
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

View File

@ -11,22 +11,19 @@ The following is a set of guidelines for contributing to authentik and its compo
[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)
- [The components](#the-components)
- [authentik's structure](#authentiks-structure)
* [The components](#the-components)
* [authentik's structure](#authentiks-structure)
[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)
* [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)
- [Python Styleguide](#python-styleguide)
- [Documentation Styleguide](#documentation-styleguide)
* [Git Commit Messages](#git-commit-messages)
* [Python Styleguide](#python-styleguide)
* [Documentation Styleguide](#documentation-styleguide)
## Code of Conduct
@ -42,11 +39,11 @@ Either [create a question on GitHub](https://github.com/goauthentik/authentik/is
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* 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
@ -140,10 +137,10 @@ This is documented in the [developer docs](https://goauthentik.io/developer-docs
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
- 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:
@ -157,10 +154,10 @@ While the prerequisites above must be satisfied prior to having your pull reques
### 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
* 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
@ -168,11 +165,11 @@ All Python code is linted with [black](https://black.readthedocs.io/en/stable/),
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.
* 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.
* Use [MDX](https://mdxjs.com/) whenever appropriate.

View File

@ -3,7 +3,6 @@ FROM --platform=${BUILDPLATFORM} docker.io/node:18 as website-builder
COPY ./website /work/website/
COPY ./blueprints /work/blueprints/
COPY ./SECURITY.md /work/
ENV NODE_ENV=production
WORKDIR /work/website
@ -31,7 +30,7 @@ RUN pip install --no-cache-dir poetry && \
poetry export -f requirements.txt --dev --output requirements-dev.txt
# Stage 4: Build go proxy
FROM docker.io/golang:1.19.2-bullseye AS go-builder
FROM docker.io/golang:1.19.3-bullseye AS go-builder
WORKDIR /work
@ -44,7 +43,7 @@ COPY ./internal /work/internal
COPY ./go.mod /work/go.mod
COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
FROM docker.io/python:3.10.7-slim-bullseye AS final-image

View File

@ -53,7 +53,7 @@ migrate:
python -m lifecycle.migrate
run:
go run -v ./cmd/server/
go run -v cmd/server/main.go
i18n-extract: i18n-extract-core web-extract
@ -73,7 +73,7 @@ gen-diff:
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-diff:2.1.0-beta.3 \
docker.io/openapitools/openapi-diff:2.0.1 \
--markdown /local/diff.md \
/local/old_schema.yml /local/schema.yml
rm old_schema.yml
@ -90,11 +90,11 @@ gen-client-ts:
-i /local/schema.yml \
-g typescript-fetch \
-o /local/gen-ts-api \
-c /local/scripts/api-ts-config.yaml \
--additional-properties=npmVersion=${NPM_VERSION} \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=@goauthentik/api,npmVersion=${NPM_VERSION} \
--git-repo-id authentik \
--git-user-id goauthentik
mkdir -p web/node_modules/@goauthentik/api
\cp -fv scripts/web_api_readme.md gen-ts-api/README.md
cd gen-ts-api && npm i
\cp -rfv gen-ts-api/* web/node_modules/@goauthentik/api
@ -151,7 +151,7 @@ web-extract:
## Website
#########################
website: website-lint-fix website-build
website: website-lint-fix
website-install:
cd website && npm ci
@ -159,9 +159,6 @@ website-install:
website-lint-fix:
cd website && npm run prettier
website-build:
cd website && npm run build
website-watch:
cd website && npm run watch

View File

@ -26,10 +26,10 @@ For bigger setups, there is a Helm Chart [here](https://github.com/goauthentik/h
## Screenshots
| Light | Dark |
| ------------------------------------------------------ | ----------------------------------------------------- |
| ![](https://goauthentik.io/img/screen_apps_light.jpg) | ![](https://goauthentik.io/img/screen_apps_dark.jpg) |
| ![](https://goauthentik.io/img/screen_admin_light.jpg) | ![](https://goauthentik.io/img/screen_admin_dark.jpg) |
Light | Dark
--- | ---
![](https://goauthentik.io/img/screen_apps_light.jpg) | ![](https://goauthentik.io/img/screen_apps_dark.jpg)
![](https://goauthentik.io/img/screen_admin_light.jpg) | ![](https://goauthentik.io/img/screen_admin_dark.jpg)
## Development

View File

@ -1,44 +1,15 @@
Authentik takes security very seriously. We follow the rules of [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure), and we urge our community to do so as well, instead of reporting vulnerabilities publicly. This allows us to patch the issue quickly, announce it's existence and release the fixed version.
# Security Policy
## Supported Versions
(.x being the latest patch release for each version)
| Version | Supported |
| --------- | ------------------ |
| 2022.10.x | :white_check_mark: |
| 2022.11.x | :white_check_mark: |
| Version | Supported |
| ---------- | ------------------ |
| 2022.6.x | :white_check_mark: |
| 2022.7.x | :white_check_mark: |
| 2022.8.x | :white_check_mark: |
## Reporting a Vulnerability
To report a vulnerability, send an email to [security@goauthentik.io](mailto:security@goauthentik.io). Be sure to include relevant information like which version you've found the issue in, instructions on how to reproduce the issue, and anything else that might make it easier for us to find the bug.
## Criticality levels
### High
- Authorization bypass
- Circumvention of policies
### Moderate
- Denial-of-Service attacks
### Low
- Unvalidated redirects
- Issues requiring uncommon setups
## Disclosure process
1. Issue is reported via Email as listed above.
2. The authentik Security team will try to reproduce the issue and ask for more information if required.
3. A criticality level is assigned.
4. A fix is created, and if possible tested by the issue reporter.
5. The fix is backported to other supported versions, and if possible a workaround for other versions is created.
6. An announcement is sent out with a fixed release date and criticality level of the issue. The announcement will be sent at least 24 hours before the release of the fix
7. The fixed version is released for the supported versions.
## Getting security notifications
To get security notifications, join the [discord](https://goauthentik.io/discord) server. In the future there will be a mailing list too.
To report a vulnerability, send an email to [security@goauthentik.io](mailto:security@goauthentik.io)

View File

@ -2,7 +2,7 @@
from os import environ
from typing import Optional
__version__ = "2022.10.2"
__version__ = "2022.9.1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -23,7 +23,6 @@ class LoginMetricsSerializer(PassiveSerializer):
logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField()
authorizations_per_1h = SerializerMethodField()
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins_per_1h(self, _):
@ -45,16 +44,6 @@ class LoginMetricsSerializer(PassiveSerializer):
.get_events_per_hour()
)
@extend_schema_field(CoordinateSerializer(many=True))
def get_authorizations_per_1h(self, _):
"""Get successful authorizations per hour for the last 24 hours"""
user = self.context["user"]
return (
get_objects_for_user(user, "authentik_events.view_event")
.filter(action=EventAction.AUTHORIZE_APPLICATION)
.get_events_per_hour()
)
class AdministrationMetricsViewSet(APIView):
"""Login Metrics per 1h"""

View File

@ -28,7 +28,6 @@ class Capabilities(models.TextChoices):
CAN_SAVE_MEDIA = "can_save_media"
CAN_GEO_IP = "can_geo_ip"
CAN_IMPERSONATE = "can_impersonate"
CAN_DEBUG = "can_debug"
class ErrorReportingConfigSerializer(PassiveSerializer):
@ -67,8 +66,6 @@ class ConfigView(APIView):
caps.append(Capabilities.CAN_GEO_IP)
if CONFIG.y_bool("impersonation"):
caps.append(Capabilities.CAN_IMPERSONATE)
if settings.DEBUG:
caps.append(Capabilities.CAN_DEBUG)
return caps
def get_config(self) -> ConfigSerializer:

View File

@ -59,8 +59,7 @@ from authentik.sources.oauth.api.source import OAuthSourceViewSet
from authentik.sources.oauth.api.source_connection import UserOAuthSourceConnectionViewSet
from authentik.sources.plex.api.source import PlexSourceViewSet
from authentik.sources.plex.api.source_connection import PlexSourceConnectionViewSet
from authentik.sources.saml.api.source import SAMLSourceViewSet
from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionViewSet
from authentik.sources.saml.api import SAMLSourceViewSet
from authentik.stages.authenticator_duo.api import (
AuthenticatorDuoStageViewSet,
DuoAdminDeviceViewSet,
@ -139,7 +138,6 @@ router.register("sources/all", SourceViewSet)
router.register("sources/user_connections/all", UserSourceConnectionViewSet)
router.register("sources/user_connections/oauth", UserOAuthSourceConnectionViewSet)
router.register("sources/user_connections/plex", PlexSourceConnectionViewSet)
router.register("sources/user_connections/saml", UserSAMLSourceConnectionViewSet)
router.register("sources/ldap", LDAPSourceViewSet)
router.register("sources/saml", SAMLSourceViewSet)
router.register("sources/oauth", OAuthSourceViewSet)

View File

@ -63,7 +63,7 @@ class BlueprintEntry:
all_attrs = get_attrs(model)
for extra_identifier_name in extra_identifier_names:
identifiers[extra_identifier_name] = all_attrs.pop(extra_identifier_name, None)
identifiers[extra_identifier_name] = all_attrs.pop(extra_identifier_name)
return BlueprintEntry(
identifiers=identifiers,
model=f"{model._meta.app_label}.{model._meta.model_name}",
@ -139,7 +139,7 @@ class KeyOf(YAMLTag):
):
return _entry._state.instance.pbm_uuid
return _entry._state.instance.pk
raise EntryInvalidError(
raise ValueError(
f"KeyOf: failed to find entry with `id` of `{self.id_from}` and a model instance"
)
@ -227,7 +227,6 @@ class BlueprintDumper(SafeDumper):
self.add_representer(UUID, lambda self, data: self.represent_str(str(data)))
self.add_representer(OrderedDict, lambda self, data: self.represent_dict(dict(data)))
self.add_representer(Enum, lambda self, data: self.represent_str(data.value))
self.add_representer(None, lambda self, data: self.represent_str(str(data)))
def represent(self, data) -> None:
if is_dataclass(data):

View File

@ -187,10 +187,7 @@ class Importer:
if "pk" in updated_identifiers:
model_instance.pk = updated_identifiers["pk"]
serializer_kwargs["instance"] = model_instance
try:
full_data = self.__update_pks_for_attrs(entry.get_attrs(self.__import))
except ValueError as exc:
raise EntryInvalidError(exc) from exc
full_data = self.__update_pks_for_attrs(entry.get_attrs(self.__import))
full_data.update(updated_identifiers)
serializer_kwargs["data"] = full_data

View File

@ -232,11 +232,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
return Response({})
if icon:
app.meta_icon = icon
try:
app.save()
except PermissionError as exc:
LOGGER.warning("Failed to save icon", exc=exc)
return HttpResponseBadRequest()
app.save()
return Response({})
return HttpResponseBadRequest()

View File

@ -470,7 +470,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
# pylint: disable=invalid-name, unused-argument
def recovery_email(self, request: Request, pk: int) -> Response:
"""Create a temporary link that a user can use to recover their accounts"""
for_user: User = self.get_object()
for_user = self.get_object()
if for_user.email == "":
LOGGER.debug("User doesn't have an email address")
return Response(status=404)
@ -488,9 +488,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
email_stage: EmailStage = stages.first()
message = TemplateEmailMessage(
subject=_(email_stage.subject),
to=[for_user.email],
template_name=email_stage.template,
language=for_user.locale(request),
to=[for_user.email],
template_context={
"url": link,
"user": for_user,

View File

@ -4,6 +4,7 @@ from typing import Optional
from django.db.models import Model
from django.http import HttpRequest
from guardian.utils import get_anonymous_user
from authentik.core.models import User
from authentik.events.models import Event, EventAction
@ -26,7 +27,7 @@ class PropertyMappingEvaluator(BaseEvaluator):
else:
_filename = str(model)
super().__init__(filename=_filename)
req = PolicyRequest(user=User())
req = PolicyRequest(user=get_anonymous_user())
req.obj = model
if user:
req.user = user

View File

@ -4,7 +4,6 @@ from typing import Callable, Optional
from uuid import uuid4
from django.http import HttpRequest, HttpResponse
from django.utils.translation import activate
from sentry_sdk.api import set_tag
from structlog.contextvars import STRUCTLOG_KEY_PREFIX
@ -30,10 +29,6 @@ class ImpersonateMiddleware:
def __call__(self, request: HttpRequest) -> HttpResponse:
# No permission checks are done here, they need to be checked before
# SESSION_KEY_IMPERSONATE_USER is set.
if request.user.is_authenticated:
locale = request.user.locale(request)
if locale != "":
activate(locale)
if SESSION_KEY_IMPERSONATE_USER in request.session:
request.user = request.session[SESSION_KEY_IMPERSONATE_USER]

View File

@ -0,0 +1,55 @@
# Generated by Django 3.0.6 on 2020-05-23 11:33
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0003_auto_20200523_1133"),
("authentik_core", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name="application",
name="skip_authorization",
),
migrations.AddField(
model_name="source",
name="authentication_flow",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Flow to use when authenticating existing users.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="source_authentication",
to="authentik_flows.Flow",
),
),
migrations.AddField(
model_name="source",
name="enrollment_flow",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Flow to use when enrolling new users.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="source_enrollment",
to="authentik_flows.Flow",
),
),
migrations.AddField(
model_name="provider",
name="authorization_flow",
field=models.ForeignKey(
help_text="Flow used when authorizing this provider.",
on_delete=django.db.models.deletion.CASCADE,
related_name="provider_authorization",
to="authentik_flows.Flow",
),
),
]

View File

@ -0,0 +1,57 @@
# Generated by Django 3.0.6 on 2020-05-23 16:40
from os import environ
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from django.contrib.auth.hashers import make_password
User = apps.get_model("authentik_core", "User")
db_alias = schema_editor.connection.alias
akadmin, _ = User.objects.using(db_alias).get_or_create(
username="akadmin", email="root@localhost", name="authentik Default Admin"
)
password = None
if "TF_BUILD" in environ or settings.TEST:
password = "akadmin" # noqa # nosec
if "AK_ADMIN_PASS" in environ:
password = environ["AK_ADMIN_PASS"]
if "AUTHENTIK_BOOTSTRAP_PASSWORD" in environ:
password = environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]
if password:
akadmin.password = make_password(password)
else:
akadmin.password = make_password(None)
akadmin.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0002_auto_20200523_1133"),
]
operations = [
migrations.RemoveField(
model_name="user",
name="is_superuser",
),
migrations.RemoveField(
model_name="user",
name="is_staff",
),
migrations.RunPython(create_default_user),
migrations.AddField(
model_name="user",
name="is_superuser",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user", name="is_staff", field=models.BooleanField(default=False)
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.0.7 on 2020-07-03 22:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0003_default_user"),
]
operations = [
migrations.AlterModelOptions(
name="application",
options={
"verbose_name": "Application",
"verbose_name_plural": "Applications",
},
),
migrations.AlterModelOptions(
name="user",
options={
"permissions": (("reset_user_password", "Reset Password"),),
"verbose_name": "User",
"verbose_name_plural": "Users",
},
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.0.7 on 2020-07-05 21:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0004_auto_20200703_2213"),
]
operations = [
migrations.AddField(
model_name="token",
name="intent",
field=models.TextField(
choices=[
("verification", "Intent Verification"),
("api", "Intent Api"),
],
default="verification",
),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.8 on 2020-07-09 16:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0005_token_intent"),
]
operations = [
migrations.AlterField(
model_name="source",
name="slug",
field=models.SlugField(help_text="Internal source name, used in URLs.", unique=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-08-15 18:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0006_auto_20200709_1608"),
]
operations = [
migrations.AlterField(
model_name="user",
name="first_name",
field=models.CharField(blank=True, max_length=150, verbose_name="first name"),
),
]

View File

@ -0,0 +1,36 @@
# Generated by Django 3.1 on 2020-08-24 15:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("authentik_core", "0007_auto_20200815_1841"),
]
operations = [
migrations.RemoveField(
model_name="user",
name="groups",
field=models.ManyToManyField(to="authentik_core.Group"),
),
migrations.AddField(
model_name="user",
name="groups",
field=models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.Group",
verbose_name="groups",
),
),
migrations.AddField(
model_name="user",
name="pb_groups",
field=models.ManyToManyField(to="authentik_core.Group"),
),
]

View File

@ -0,0 +1,59 @@
# Generated by Django 3.1.1 on 2020-09-15 19:53
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
def create_default_admin_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
User = apps.get_model("authentik_core", "User")
# Creates a default admin group
group, _ = Group.objects.using(db_alias).get_or_create(
is_superuser=True,
defaults={
"name": "authentik Admins",
},
)
group.users.set(User.objects.filter(username="akadmin"))
group.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0008_auto_20200824_1532"),
]
operations = [
migrations.RemoveField(
model_name="user",
name="is_superuser",
),
migrations.RemoveField(
model_name="user",
name="is_staff",
),
migrations.AlterField(
model_name="user",
name="pb_groups",
field=models.ManyToManyField(related_name="users", to="authentik_core.Group"),
),
migrations.AddField(
model_name="group",
name="is_superuser",
field=models.BooleanField(
default=False, help_text="Users added to this group will be superusers."
),
),
migrations.RunPython(create_default_admin_group),
migrations.AlterModelManagers(
name="user",
managers=[
("objects", authentik.core.models.UserManager()),
],
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.1 on 2020-09-17 10:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0009_group_is_superuser"),
]
operations = [
migrations.AlterModelOptions(
name="user",
options={
"permissions": (
("reset_user_password", "Reset Password"),
("impersonate", "Can impersonate other users"),
),
"verbose_name": "User",
"verbose_name_plural": "Users",
},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.2 on 2020-10-03 17:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0010_auto_20200917_1021"),
]
operations = [
migrations.AddField(
model_name="provider",
name="name_temp",
field=models.TextField(default=""),
preserve_default=False,
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.1.2 on 2020-10-03 17:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0011_provider_name_temp"),
("authentik_providers_oauth2", "0006_remove_oauth2provider_name"),
("authentik_providers_saml", "0006_remove_samlprovider_name"),
]
operations = [
migrations.RenameField(
model_name="provider",
old_name="name_temp",
new_name="name",
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 3.1.2 on 2020-10-03 21:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0012_auto_20201003_1737"),
]
operations = [
migrations.AddField(
model_name="token",
name="identifier",
field=models.TextField(default=""),
preserve_default=False,
),
migrations.AlterField(
model_name="token",
name="intent",
field=models.TextField(
choices=[
("verification", "Intent Verification"),
("api", "Intent Api"),
("recovery", "Intent Recovery"),
],
default="verification",
),
),
migrations.AlterUniqueTogether(
name="token",
unique_together={("identifier", "user")},
),
]

View File

@ -0,0 +1,48 @@
# Generated by Django 3.1.2 on 2020-10-18 11:58
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Token = apps.get_model("authentik_core", "Token")
for token in Token.objects.using(db_alias).all():
token.key = token.pk.hex
token.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0013_auto_20201003_2132"),
]
operations = [
migrations.AddField(
model_name="token",
name="key",
field=models.TextField(default=authentik.core.models.default_token_key),
),
migrations.AlterUniqueTogether(
name="token",
unique_together=set(),
),
migrations.AlterField(
model_name="token",
name="identifier",
field=models.SlugField(max_length=255),
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["key"], name="authentik_co_key_e45007_idx"),
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["identifier"], name="authentik_co_identif_1a34a8_idx"),
),
migrations.RunPython(set_default_token_key),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.1.3 on 2020-11-23 17:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0014_auto_20201018_1158"),
]
operations = [
migrations.RemoveField(
model_name="application",
name="meta_icon_url",
),
migrations.AddField(
model_name="application",
name="meta_icon",
field=models.FileField(blank=True, default="", upload_to="application-icons/"),
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 3.1.3 on 2020-12-02 22:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0015_application_icon"),
]
operations = [
migrations.RemoveIndex(
model_name="token",
name="authentik_co_key_e45007_idx",
),
migrations.RemoveIndex(
model_name="token",
name="authentik_co_identif_1a34a8_idx",
),
migrations.RenameField(
model_name="user",
old_name="pb_groups",
new_name="ak_groups",
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["identifier"], name="authentik_c_identif_d9d032_idx"),
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["key"], name="authentik_c_key_f71355_idx"),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.1.7 on 2021-03-30 13:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0017_managed"),
]
operations = [
migrations.AlterModelOptions(
name="token",
options={
"permissions": (("view_token_key", "View token's key"),),
"verbose_name": "Token",
"verbose_name_plural": "Tokens",
},
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2 on 2021-04-09 14:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0018_auto_20210330_1345"),
]
operations = [
migrations.AddField(
model_name="source",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.2 on 2021-05-03 17:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0019_source_managed"),
]
operations = [
migrations.AddField(
model_name="source",
name="user_matching_mode",
field=models.TextField(
choices=[
("identifier", "Use the source-specific identifier"),
(
"email_link",
"Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses.",
),
(
"email_deny",
"Use the user's email address, but deny enrollment when the email address already exists.",
),
(
"username_link",
"Link to a user with identical username. Can have security implications when a username is used with another source.",
),
(
"username_deny",
"Use the user's username, but deny enrollment when the username already exists.",
),
],
default="identifier",
help_text="How the source determines if an existing user should be authenticated or a new user enrolled.",
),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.3 on 2021-05-14 08:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0020_source_user_matching_mode"),
]
operations = [
migrations.AlterField(
model_name="application",
name="slug",
field=models.SlugField(
help_text="Internal application name, used in URLs.", unique=True
),
),
]

View File

@ -0,0 +1,58 @@
# Generated by Django 3.2.3 on 2021-05-29 22:14
import uuid
import django.db.models.deletion
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
session_keys = cache.keys(KEY_PREFIX + "*")
cache.delete_many(session_keys)
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0021_alter_application_slug"),
]
operations = [
migrations.CreateModel(
name="AuthenticatedSession",
fields=[
(
"expires",
models.DateTimeField(default=authentik.core.models.default_token_duration),
),
("expiring", models.BooleanField(default=True)),
(
"uuid",
models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
),
("session_key", models.CharField(max_length=40)),
("last_ip", models.TextField()),
("last_user_agent", models.TextField(blank=True)),
("last_used", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.RunPython(migrate_sessions),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.3 on 2021-06-02 21:51
from django.db import migrations, models
import authentik.lib.models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0022_authenticatedsession"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_launch_url",
field=models.TextField(
blank=True,
default="",
validators=[authentik.lib.models.DomainlessURLValidator()],
),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 4.1.2 on 2022-10-19 18:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0022_alter_group_parent"),
]
operations = [
migrations.AddIndex(
model_name="source",
index=models.Index(fields=["slug"], name="authentik_c_slug_ccb2e5_idx"),
),
migrations.AddIndex(
model_name="source",
index=models.Index(fields=["name"], name="authentik_c_name_affae6_idx"),
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.3 on 2021-06-03 09:33
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.models import Count
def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Token = apps.get_model("authentik_core", "token")
identifiers = (
Token.objects.using(db_alias)
.values("identifier")
.annotate(identifier_count=Count("identifier"))
.filter(identifier_count__gt=1)
)
for ident in identifiers:
Token.objects.using(db_alias).filter(identifier=ident["identifier"]).delete()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0023_alter_application_meta_launch_url"),
]
operations = [
migrations.RunPython(fix_duplicates),
migrations.AlterField(
model_name="token",
name="identifier",
field=models.SlugField(max_length=255, unique=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.3 on 2021-06-05 19:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0024_alter_token_identifier"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_icon",
field=models.FileField(default=None, null=True, upload_to="application-icons/"),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.2.5 on 2021-07-09 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0025_alter_application_meta_icon"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_icon",
field=models.FileField(
default=None, max_length=500, null=True, upload_to="application-icons/"
),
),
migrations.AlterModelOptions(
name="authenticatedsession",
options={
"verbose_name": "Authenticated Session",
"verbose_name_plural": "Authenticated Sessions",
},
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.2.5 on 2021-08-11 19:40
from os import environ
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.core.models import TokenIntents
User = apps.get_model("authentik_core", "User")
Token = apps.get_model("authentik_core", "Token")
db_alias = schema_editor.connection.alias
akadmin = User.objects.using(db_alias).filter(username="akadmin")
if not akadmin.exists():
return
key = None
if "AK_ADMIN_TOKEN" in environ:
key = environ["AK_ADMIN_TOKEN"]
if "AUTHENTIK_BOOTSTRAP_TOKEN" in environ:
key = environ["AUTHENTIK_BOOTSTRAP_TOKEN"]
if not key:
return
Token.objects.using(db_alias).create(
identifier="authentik-bootstrap-token",
user=akadmin.first(),
intent=TokenIntents.INTENT_API,
expiring=False,
key=key,
)
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0026_alter_application_meta_icon"),
]
operations = [
migrations.RunPython(create_default_user_token),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.6 on 2021-08-23 14:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0027_bootstrap_token"),
]
operations = [
migrations.AlterField(
model_name="token",
name="intent",
field=models.TextField(
choices=[
("verification", "Intent Verification"),
("api", "Intent Api"),
("recovery", "Intent Recovery"),
("app_password", "Intent App Password"),
],
default="verification",
),
),
]

View File

@ -220,17 +220,6 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser):
"""Generate a globally unique UID, based on the user ID and the hashed secret key"""
return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest()
def locale(self, request: Optional[HttpRequest] = None) -> str:
"""Get the locale the user has configured"""
try:
return self.attributes.get("settings", {}).get("locale", "")
# pylint: disable=broad-except
except Exception as exc:
LOGGER.warning("Failed to get default locale", exc=exc)
if request:
return request.tenant.locale
return ""
@property
def avatar(self) -> str:
"""Get avatar, depending on authentik.avatar setting"""
@ -483,21 +472,6 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
def __str__(self):
return self.name
class Meta:
indexes = [
models.Index(
fields=[
"slug",
]
),
models.Index(
fields=[
"name",
]
),
]
class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
"""Connection between User and Source."""

View File

@ -5,7 +5,7 @@ from typing import Any, Optional
from django.contrib import messages
from django.db import IntegrityError
from django.db.models.query_utils import Q
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext as _
@ -23,10 +23,8 @@ from authentik.flows.planner import (
PLAN_CONTEXT_SSO,
FlowPlanner,
)
from authentik.flows.stage import StageView
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_INBUILT
@ -45,26 +43,6 @@ class Action(Enum):
DENY = "deny"
class MessageStage(StageView):
"""Show a pre-configured message after the flow is done"""
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Show a pre-configured message after the flow is done"""
message = getattr(self.executor.current_stage, "message", "")
level = getattr(self.executor.current_stage, "level", messages.SUCCESS)
messages.add_message(
self.request,
level,
message,
)
return self.executor.stage_ok()
def post(self, request: HttpRequest) -> HttpResponse:
"""Wrapper for post requests"""
return self.get(request)
class SourceFlowManager:
"""Help sources decide what they should do after authorization. Based on source settings and
previous connections, authenticate the user, enroll a new user, link to an existing user
@ -172,16 +150,16 @@ class SourceFlowManager:
action, connection = self.get_action(**kwargs)
except IntegrityError as exc:
self._logger.warning("failed to get action", exc=exc)
return redirect(reverse("authentik_core:root-redirect"))
return redirect("/")
self._logger.debug("get_action", action=action, connection=connection)
try:
if connection:
if action == Action.LINK:
self._logger.debug("Linking existing user")
return self.handle_existing_link(connection)
return self.handle_existing_user_link(connection)
if action == Action.AUTH:
self._logger.debug("Handling auth user")
return self.handle_auth(connection)
return self.handle_auth_user(connection)
if action == Action.ENROLL:
self._logger.debug("Handling enrollment of new user")
return self.handle_enroll(connection)
@ -220,12 +198,8 @@ class SourceFlowManager:
]
return []
def _prepare_flow(
self,
flow: Flow,
connection: UserSourceConnection,
stages: Optional[list[StageView]] = None,
**kwargs,
def _handle_login_flow(
self, flow: Flow, connection: UserSourceConnection, **kwargs
) -> HttpResponse:
"""Prepare Authentication Plan, redirect user FlowExecutor"""
# Ensure redirect is carried through when user was trying to
@ -245,18 +219,12 @@ class SourceFlowManager:
)
kwargs.update(self.policy_context)
if not flow:
return bad_request_message(
self.request,
_("Configured flow does not exist."),
)
return HttpResponseBadRequest()
# We run the Flow planner here so we can pass the Pending user in the context
planner = FlowPlanner(flow)
plan = planner.plan(self.request, kwargs)
for stage in self.get_stages_to_append(flow):
plan.append_stage(stage)
if stages:
for stage in stages:
plan.append_stage(stage)
plan.append_stage(stage=stage)
self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
@ -265,35 +233,24 @@ class SourceFlowManager:
)
# pylint: disable=unused-argument
def handle_auth(
def handle_auth_user(
self,
connection: UserSourceConnection,
) -> HttpResponse:
"""Login user and redirect."""
flow_kwargs = {PLAN_CONTEXT_PENDING_USER: connection.user}
return self._prepare_flow(
self.source.authentication_flow,
connection,
stages=[
in_memory_stage(
MessageStage,
message=_(
"Successfully authenticated with %(source)s!" % {"source": self.source.name}
),
)
],
**flow_kwargs,
messages.success(
self.request,
_("Successfully authenticated with %(source)s!" % {"source": self.source.name}),
)
flow_kwargs = {PLAN_CONTEXT_PENDING_USER: connection.user}
return self._handle_login_flow(self.source.authentication_flow, connection, **flow_kwargs)
def handle_existing_link(
def handle_existing_user_link(
self,
connection: UserSourceConnection,
) -> HttpResponse:
"""Handler when the user was already authenticated and linked an external source
to their account."""
# When request isn't authenticated we jump straight to auth
if not self.request.user.is_authenticated:
return self.handle_auth(connection)
# Connection has already been saved
Event.new(
EventAction.SOURCE_LINKED,
@ -304,6 +261,9 @@ class SourceFlowManager:
self.request,
_("Successfully linked %(source)s!" % {"source": self.source.name}),
)
# When request isn't authenticated we jump straight to auth
if not self.request.user.is_authenticated:
return self.handle_auth_user(connection)
return redirect(
reverse(
"authentik_core:if-user",
@ -316,24 +276,18 @@ class SourceFlowManager:
connection: UserSourceConnection,
) -> HttpResponse:
"""User was not authenticated and previous request was not authenticated."""
messages.success(
self.request,
_("Successfully authenticated with %(source)s!" % {"source": self.source.name}),
)
# We run the Flow planner here so we can pass the Pending user in the context
if not self.source.enrollment_flow:
self._logger.warning("source has no enrollment flow")
return bad_request_message(
self.request,
_("Source is not configured for enrollment."),
)
return self._prepare_flow(
return HttpResponseBadRequest()
return self._handle_login_flow(
self.source.enrollment_flow,
connection,
stages=[
in_memory_stage(
MessageStage,
message=_(
"Successfully authenticated with %(source)s!" % {"source": self.source.name}
),
)
],
**{
PLAN_CONTEXT_PROMPT: delete_none_keys(self.enroll_info),
PLAN_CONTEXT_USER_PATH: self.source.get_user_path(),

View File

@ -1,23 +0,0 @@
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
<script>
window.authentik = {};
window.authentik.locale = "{{ LANGUAGE_CODE }}";
window.authentik.config = JSON.parse('{{ config_json|escapejs }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|escapejs }}');
window.addEventListener("DOMContentLoaded", () => {
{% for message in messages %}
window.dispatchEvent(
new CustomEvent("ak-message", {
bubbles: true,
composed: true,
detail: {
level: "{{ message.tags|escapejs }}",
message: "{{ message.message|escapejs }}",
},
}),
);
{% endfor %}
});
</script>

View File

@ -9,12 +9,17 @@
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon }}">
{% include "base/header_js.html" %}
<script>
window.authentik = {};
window.authentik.locale = "{{ tenant.default_locale }}";
window.authentik.config = JSON.parse('{{ config_json|escapejs }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|escapejs }}');
</script>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-admin>
<ak-message-container data-refresh-on-locale="true"></ak-message-container>
<ak-interface-admin data-refresh-on-locale="true">
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">

View File

@ -4,7 +4,7 @@
{% load i18n %}
{% block title %}
{{ tenant.branding_title }}
{% trans 'End session' %} - {{ tenant.branding_title }}
{% endblock %}
{% block card_title %}

View File

@ -11,8 +11,11 @@
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
{% include "base/header_js.html" %}
<script>
window.authentik = {};
window.authentik.locale = "{{ tenant.default_locale }}";
window.authentik.config = JSON.parse('{{ config_json|escapejs }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|escapejs }}');
window.authentik.flow = {
"layout": "{{ flow.layout }}",
};
@ -29,8 +32,8 @@ window.authentik.flow = {
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-flow-executor>
<ak-message-container data-refresh-on-locale="true"></ak-message-container>
<ak-flow-executor data-refresh-on-locale="true">
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">

View File

@ -9,12 +9,17 @@
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
<link rel="icon" href="{{ tenant.branding_favicon }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon }}">
{% include "base/header_js.html" %}
<script>
window.authentik = {};
window.authentik.locale = "{{ tenant.default_locale }}";
window.authentik.config = JSON.parse('{{ config_json|escapejs }}');
window.authentik.tenant = JSON.parse('{{ tenant_json|escapejs }}');
</script>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-user>
<ak-message-container data-refresh-on-locale="true"></ak-message-container>
<ak-interface-user data-refresh-on-locale="true">
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">

View File

@ -6,7 +6,6 @@
{% block head_before %}
<link rel="prefetch" href="/static/dist/assets/images/flow_background.jpg" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
{% include "base/header_js.html" %}
{% endblock %}
{% block head %}

View File

@ -44,11 +44,9 @@ def create_test_tenant() -> Tenant:
return Tenant.objects.create(domain=uid, default=True)
def create_test_cert(use_ec_private_key=False) -> CertificateKeyPair:
def create_test_cert() -> CertificateKeyPair:
"""Generate a certificate for testing"""
builder = CertificateBuilder(
use_ec_private_key=use_ec_private_key,
)
builder = CertificateBuilder()
builder.common_name = "goauthentik.io"
builder.build(
subject_alt_names=["goauthentik.io"],

View File

@ -1,5 +1,4 @@
"""Crypto API Views"""
from datetime import datetime
from typing import Optional
from cryptography.hazmat.backends import default_backend
@ -35,10 +34,7 @@ LOGGER = get_logger()
class CertificateKeyPairSerializer(ModelSerializer):
"""CertificateKeyPair Serializer"""
fingerprint_sha256 = SerializerMethodField()
fingerprint_sha1 = SerializerMethodField()
cert_expiry = SerializerMethodField()
cert_expiry = DateTimeField(source="certificate.not_valid_after", read_only=True)
cert_subject = SerializerMethodField()
private_key_available = SerializerMethodField()
private_key_type = SerializerMethodField()
@ -46,35 +42,8 @@ class CertificateKeyPairSerializer(ModelSerializer):
certificate_download_url = SerializerMethodField()
private_key_download_url = SerializerMethodField()
@property
def _should_include_details(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_details", "true")).lower() == "true"
def get_fingerprint_sha256(self, instance: CertificateKeyPair) -> Optional[str]:
"Get certificate Hash (SHA256)"
if not self._should_include_details:
return None
return instance.fingerprint_sha256
def get_fingerprint_sha1(self, instance: CertificateKeyPair) -> Optional[str]:
"Get certificate Hash (SHA1)"
if not self._should_include_details:
return None
return instance.fingerprint_sha1
def get_cert_expiry(self, instance: CertificateKeyPair) -> Optional[datetime]:
"Get certificate expiry"
if not self._should_include_details:
return None
return DateTimeField().to_representation(instance.certificate.not_valid_after)
def get_cert_subject(self, instance: CertificateKeyPair) -> Optional[str]:
def get_cert_subject(self, instance: CertificateKeyPair) -> str:
"""Get certificate subject as full rfc4514"""
if not self._should_include_details:
return None
return instance.certificate.subject.rfc4514_string()
def get_private_key_available(self, instance: CertificateKeyPair) -> bool:
@ -83,8 +52,6 @@ class CertificateKeyPairSerializer(ModelSerializer):
def get_private_key_type(self, instance: CertificateKeyPair) -> Optional[str]:
"""Get the private key's type, if set"""
if not self._should_include_details:
return None
key = instance.private_key
if key:
return key.__class__.__name__.replace("_", "").lower().replace("privatekey", "")
@ -204,14 +171,6 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
ordering = ["name"]
search_fields = ["name"]
@extend_schema(
parameters=[
OpenApiParameter("include_details", bool, default=True),
]
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@permission_required(None, ["authentik_crypto.add_certificatekeypair"])
@extend_schema(
request=CertificateGenerationSerializer(),

View File

@ -6,8 +6,7 @@ from typing import Optional
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.hazmat.primitives.asymmetric.types import PRIVATE_KEY_TYPES
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from authentik import __version__
@ -19,10 +18,7 @@ class CertificateBuilder:
common_name: str
_use_ec_private_key: bool
def __init__(self, use_ec_private_key=False):
self._use_ec_private_key = use_ec_private_key
def __init__(self):
self.__public_key = None
self.__private_key = None
self.__builder = None
@ -40,14 +36,6 @@ class CertificateBuilder:
self.cert.save()
return self.cert
def generate_private_key(self) -> PRIVATE_KEY_TYPES:
"""Generate private key"""
if self._use_ec_private_key:
return ec.generate_private_key(curve=ec.SECP256R1)
return rsa.generate_private_key(
public_exponent=65537, key_size=4096, backend=default_backend()
)
def build(
self,
validity_days: int = 365,
@ -55,7 +43,9 @@ class CertificateBuilder:
):
"""Build self-signed certificate"""
one_day = datetime.timedelta(1, 0, 0)
self.__private_key = self.generate_private_key()
self.__private_key = rsa.generate_private_key(
public_exponent=65537, key_size=4096, backend=default_backend()
)
self.__public_key = self.__private_key.public_key()
alt_names: list[x509.GeneralName] = [x509.DNSName(x) for x in subject_alt_names or []]
self.__builder = (

View File

@ -38,9 +38,9 @@ class Command(BaseCommand):
try:
serializer.validate_certificate_data(keypair.certificate_data)
if keypair.key_data != "":
serializer.validate_key_data(keypair.key_data)
serializer.validate_certificate_data(keypair.key_data)
except ValidationError as exc:
self.stderr.write(str(exc))
self.stderr.write(exc)
sys_exit(1)
if dirty:
keypair.save()

View File

@ -1,6 +1,5 @@
"""Crypto tests"""
import datetime
from json import loads
from os import makedirs
from tempfile import TemporaryDirectory
@ -87,35 +86,13 @@ class TestCrypto(APITestCase):
def test_list(self):
"""Test API List"""
cert = create_test_cert()
self.client.force_login(create_test_admin_user())
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-list",
)
+ f"?name={cert.name}"
)
self.assertEqual(200, response.status_code)
body = loads(response.content.decode())
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
self.assertEqual(api_cert["fingerprint_sha256"], cert.fingerprint_sha256)
def test_list_without_details(self):
"""Test API List (no details)"""
cert = create_test_cert()
self.client.force_login(create_test_admin_user())
response = self.client.get(
reverse(
"authentik_api:certificatekeypair-list",
)
+ f"?name={cert.name}&include_details=false"
)
self.assertEqual(200, response.status_code)
body = loads(response.content.decode())
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
self.assertEqual(api_cert["fingerprint_sha1"], None)
self.assertEqual(api_cert["fingerprint_sha256"], None)
def test_certificate_download(self):
"""Test certificate export (download)"""

View File

@ -0,0 +1,70 @@
# Generated by Django 3.0.6 on 2020-05-19 22:08
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Event",
fields=[
(
"event_uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"action",
models.TextField(
choices=[
("LOGIN", "login"),
("LOGIN_FAILED", "login_failed"),
("LOGOUT", "logout"),
("AUTHORIZE_APPLICATION", "authorize_application"),
("SUSPICIOUS_REQUEST", "suspicious_request"),
("SIGN_UP", "sign_up"),
("PASSWORD_RESET", "password_reset"),
("INVITE_CREATED", "invitation_created"),
("INVITE_USED", "invitation_used"),
("CUSTOM", "custom"),
]
),
),
("date", models.DateTimeField(auto_now_add=True)),
("app", models.TextField()),
(
"context",
models.JSONField(blank=True, default=dict),
),
("client_ip", models.GenericIPAddressField(null=True)),
("created", models.DateTimeField(auto_now_add=True)),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Event",
"verbose_name_plural": "Events",
},
),
]

View File

@ -22,8 +22,4 @@ class Migration(migrations.Migration):
default="local",
),
),
migrations.AlterModelOptions(
name="notificationwebhookmapping",
options={"verbose_name": "Webhook Mapping", "verbose_name_plural": "Webhook Mappings"},
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.1.1 on 2020-09-18 21:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("LOGIN", "login"),
("LOGIN_FAILED", "login_failed"),
("LOGOUT", "logout"),
("AUTHORIZE_APPLICATION", "authorize_application"),
("SUSPICIOUS_REQUEST", "suspicious_request"),
("SIGN_UP", "sign_up"),
("PASSWORD_RESET", "password_reset"),
("INVITE_CREATED", "invitation_created"),
("INVITE_USED", "invitation_used"),
("IMPERSONATION_STARTED", "impersonation_started"),
("IMPERSONATION_ENDED", "impersonation_ended"),
("CUSTOM", "custom"),
]
),
),
]

View File

@ -0,0 +1,60 @@
# Generated by Django 3.1.1 on 2020-09-17 11:55
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.events.models
def convert_user_to_json(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Event = apps.get_model("authentik_events", "Event")
db_alias = schema_editor.connection.alias
for event in Event.objects.using(db_alias).all():
event.delete()
# Because event objects cannot be updated, we have to re-create them
event.pk = None
event.user_json = authentik.events.models.get_user(event.user) if event.user else {}
event._state.adding = True
event.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0002_auto_20200918_2116"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("LOGIN", "login"),
("LOGIN_FAILED", "login_failed"),
("LOGOUT", "logout"),
("AUTHORIZE_APPLICATION", "authorize_application"),
("SUSPICIOUS_REQUEST", "suspicious_request"),
("SIGN_UP", "sign_up"),
("PASSWORD_RESET", "password_reset"),
("INVITE_CREATED", "invitation_created"),
("INVITE_USED", "invitation_used"),
("IMPERSONATION_STARTED", "impersonation_started"),
("IMPERSONATION_ENDED", "impersonation_ended"),
("CUSTOM", "custom"),
]
),
),
migrations.AddField(
model_name="event",
name="user_json",
field=models.JSONField(default=dict),
),
migrations.RunPython(convert_user_to_json),
migrations.RemoveField(
model_name="event",
name="user",
),
migrations.RenameField(model_name="event", old_name="user_json", new_name="user"),
]

View File

@ -0,0 +1,37 @@
# Generated by Django 3.1.1 on 2020-09-21 18:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0003_auto_20200917_1155"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("sign_up", "Sign Up"),
("authorize_application", "Authorize Application"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,37 @@
# Generated by Django 3.1.2 on 2020-10-05 21:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0004_auto_20200921_1829"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.2 on 2020-10-17 20:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0005_auto_20201005_2139"),
]
operations = [
migrations.RemoveField(
model_name="event",
name="date",
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 3.1.4 on 2020-12-15 09:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0006_auto_20201017_2024"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.4 on 2020-12-20 16:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0007_auto_20201215_0939"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.4 on 2020-12-27 12:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0008_auto_20201220_1651"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,148 @@
# Generated by Django 3.1.4 on 2021-01-11 16:36
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("authentik_policies", "0004_policy_execution_logging"),
("authentik_core", "0016_auto_20201202_2234"),
("authentik_events", "0009_auto_20201227_1210"),
]
operations = [
migrations.CreateModel(
name="NotificationTransport",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField(unique=True)),
(
"mode",
models.TextField(
choices=[
("webhook", "Generic Webhook"),
("webhook_slack", "Slack Webhook (Slack/Discord)"),
("email", "Email"),
]
),
),
("webhook_url", models.TextField(blank=True)),
],
options={
"verbose_name": "Notification Transport",
"verbose_name_plural": "Notification Transports",
},
),
migrations.CreateModel(
name="NotificationRule",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policybindingmodel",
),
),
("name", models.TextField(unique=True)),
(
"severity",
models.TextField(
choices=[
("notice", "Notice"),
("warning", "Warning"),
("alert", "Alert"),
],
default="notice",
help_text="Controls which severity level the created notifications will have.",
),
),
(
"group",
models.ForeignKey(
blank=True,
help_text="Define which group of users this notification should be sent and shown to. If left empty, Notification won't ben sent.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_core.group",
),
),
(
"transports",
models.ManyToManyField(
help_text="Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.",
to="authentik_events.NotificationTransport",
),
),
],
options={
"verbose_name": "Notification Rule",
"verbose_name_plural": "Notification Rules",
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.CreateModel(
name="Notification",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"severity",
models.TextField(
choices=[
("notice", "Notice"),
("warning", "Warning"),
("alert", "Alert"),
]
),
),
("body", models.TextField()),
("created", models.DateTimeField(auto_now_add=True)),
("seen", models.BooleanField(default=False)),
(
"event",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_events.event",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Notification",
"verbose_name_plural": "Notifications",
},
),
]

View File

@ -0,0 +1,15 @@
# Generated by Django 3.1.4 on 2021-01-10 18:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
(
"authentik_events",
"0010_notification_notificationtransport_notificationrule",
),
]
operations = []

View File

@ -0,0 +1,52 @@
# Generated by Django 3.1.6 on 2021-02-02 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0011_notification_rules_default_v1"),
]
operations = [
migrations.AddField(
model_name="notificationtransport",
name="send_once",
field=models.BooleanField(
default=False,
help_text="Only send notification once, for example when sending a webhook into a chat channel.",
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,61 @@
# Generated by Django 3.1.6 on 2021-02-09 16:57
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def token_view_to_secret_view(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.events.models import EventAction
db_alias = schema_editor.connection.alias
Event = apps.get_model("authentik_events", "Event")
events = Event.objects.using(db_alias).filter(action="token_view")
for event in events:
event.context["secret"] = event.context.pop("token")
event.action = EventAction.SECRET_VIEW
Event.objects.using(db_alias).bulk_update(events, ["context", "action"])
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0012_auto_20210202_1821"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.RunPython(token_view_to_secret_view),
]

View File

@ -0,0 +1,87 @@
# Generated by Django 3.1.7 on 2021-03-18 16:01
from datetime import timedelta
from typing import Iterable
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.events.models
# Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
def progress_bar(
iterable: Iterable,
prefix="Writing: ",
suffix=" finished",
decimals=1,
length=100,
fill="",
print_end="\r",
):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
print_end - Optional : end character (e.g. "\r", "\r\n") (Str)
"""
total = len(iterable)
if total < 1:
return
def print_progress_bar(iteration):
"""Progress Bar Printing Function"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
bar = fill * filledLength + "-" * (length - filledLength)
print(f"\r{prefix} |{bar}| {percent}% {suffix}", end=print_end)
# Initial Call
print_progress_bar(0)
# Update Progress Bar
for i, item in enumerate(iterable):
yield item
print_progress_bar(i + 1)
# Print New Line on Complete
print()
def update_expires(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Event = apps.get_model("authentik_events", "event")
all_events = Event.objects.using(db_alias).all()
if all_events.count() < 1:
return
print("\nAdding expiry to events, this might take a couple of minutes...")
for event in progress_bar(all_events):
event.expires = event.created + timedelta(days=365)
event.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0013_auto_20210209_1657"),
]
operations = [
migrations.AddField(
model_name="event",
name="expires",
field=models.DateTimeField(default=authentik.events.models.default_event_duration),
),
migrations.AddField(
model_name="event",
name="expiring",
field=models.BooleanField(default=True),
),
migrations.RunPython(update_expires),
]

View File

@ -0,0 +1,45 @@
# Generated by Django 3.2.3 on 2021-06-09 07:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0014_expiry"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.4 on 2021-06-14 15:33
from django.db import migrations, models
import authentik.events.models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0015_alter_event_action"),
]
operations = [
migrations.AddField(
model_name="event",
name="tenant",
field=models.JSONField(blank=True, default=authentik.events.models.default_tenant),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 3.2.5 on 2021-07-14 19:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0016_add_tenant"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -0,0 +1,46 @@
# Generated by Django 3.2.6 on 2021-09-11 22:17
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0028_alter_token_intent"),
("authentik_events", "0017_alter_event_action"),
]
operations = [
migrations.CreateModel(
name="NotificationWebhookMapping",
fields=[
(
"propertymapping_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.propertymapping",
),
),
],
options={
"verbose_name": "Notification Webhook Mapping",
"verbose_name_plural": "Notification Webhook Mappings",
},
bases=("authentik_core.propertymapping",),
),
migrations.AddField(
model_name="notificationtransport",
name="webhook_mapping",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_events.notificationwebhookmapping",
),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.7 on 2021-10-04 15:31
from django.db import migrations, models
import authentik.lib.models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0018_auto_20210911_2217"),
]
operations = [
migrations.AlterField(
model_name="notificationtransport",
name="webhook_url",
field=models.TextField(
blank=True, validators=[authentik.lib.models.DomainlessURLValidator()]
),
),
]

View File

@ -445,9 +445,8 @@ class NotificationTransport(SerializerModel):
subject += notification.body[:75]
mail = TemplateEmailMessage(
subject=subject,
to=[notification.user.email],
language=notification.user.locale(),
template_name="email/generic.html",
to=[notification.user.email],
template_context={
"title": subject,
"body": notification.body,
@ -561,7 +560,7 @@ class NotificationRule(SerializerModel, PolicyBindingModel):
class NotificationWebhookMapping(PropertyMapping):
"""Modify the payload of outgoing webhook requests"""
"""Modify the schema and layout of the webhook being sent"""
@property
def component(self) -> str:
@ -574,9 +573,9 @@ class NotificationWebhookMapping(PropertyMapping):
return NotificationWebhookMappingSerializer
def __str__(self):
return f"Webhook Mapping {self.name}"
return f"Notification Webhook Mapping {self.name}"
class Meta:
verbose_name = _("Webhook Mapping")
verbose_name_plural = _("Webhook Mappings")
verbose_name = _("Notification Webhook Mapping")
verbose_name_plural = _("Notification Webhook Mappings")

View File

@ -14,7 +14,6 @@ from django.views.debug import SafeExceptionReporterFilter
from geoip2.models import City
from guardian.utils import get_anonymous_user
from authentik.blueprints.v1.common import YAMLTag
from authentik.core.models import User
from authentik.events.geo import GEOIP_READER
from authentik.policies.types import PolicyRequest
@ -112,10 +111,6 @@ def sanitize_item(value: Any) -> Any:
return GEOIP_READER.city_to_dict(value)
if isinstance(value, Path):
return str(value)
if isinstance(value, Exception):
return str(value)
if isinstance(value, YAMLTag):
return str(value)
if isinstance(value, type):
return {
"type": value.__name__,

View File

@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, DictField, ListField, ReadOnlyField
from rest_framework.fields import ReadOnlyField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
@ -24,9 +24,7 @@ from authentik.core.api.utils import (
FilePathSerializer,
FileUploadSerializer,
LinkSerializer,
PassiveSerializer,
)
from authentik.events.utils import sanitize_dict
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
@ -40,9 +38,10 @@ LOGGER = get_logger()
class FlowSerializer(ModelSerializer):
"""Flow Serializer"""
cache_count = SerializerMethodField()
background = ReadOnlyField(source="background_url")
cache_count = SerializerMethodField()
export_url = SerializerMethodField()
def get_cache_count(self, flow: Flow) -> int:
@ -72,46 +71,16 @@ class FlowSerializer(ModelSerializer):
"export_url",
"layout",
"denied_action",
"authentication",
]
extra_kwargs = {
"background": {"read_only": True},
}
class FlowSetSerializer(FlowSerializer):
"""Stripped down flow serializer"""
class Meta:
model = Flow
fields = [
"pk",
"policybindingmodel_ptr_id",
"name",
"slug",
"title",
"designation",
"background",
"policy_engine_mode",
"compatibility_mode",
"export_url",
"layout",
"denied_action",
]
class FlowImportResultSerializer(PassiveSerializer):
"""Logs of an attempted flow import"""
logs = ListField(child=DictField(), read_only=True)
success = BooleanField(read_only=True)
class FlowViewSet(UsedByMixin, ModelViewSet):
"""Flow Viewset"""
queryset = Flow.objects.all().prefetch_related("stages", "policies")
queryset = Flow.objects.all()
serializer_class = FlowSerializer
lookup_field = "slug"
ordering = ["slug", "name"]
@ -161,38 +130,25 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
request={"multipart/form-data": FileUploadSerializer},
responses={
204: FlowImportResultSerializer,
400: FlowImportResultSerializer,
204: OpenApiResponse(description="Successfully imported flow"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(url_path="import", detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
@action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
def import_flow(self, request: Request) -> Response:
"""Import flow from .yaml file"""
import_response = FlowImportResultSerializer(
data={
"logs": [],
"success": False,
}
)
import_response.is_valid()
file = request.FILES.get("file", None)
if not file:
return Response(data=import_response.initial_data, status=400)
return HttpResponseBadRequest()
importer = Importer(file.read().decode())
valid, logs = importer.validate()
import_response.initial_data["logs"] = [sanitize_dict(log) for log in logs]
import_response.initial_data["success"] = valid
import_response.is_valid()
valid, _logs = importer.validate()
# TODO: return logs
if not valid:
return Response(data=import_response.initial_data, status=200)
return HttpResponseBadRequest()
successful = importer.apply()
import_response.initial_data["success"] = successful
import_response.is_valid()
if not successful:
return Response(data=import_response.initial_data, status=200)
return Response(data=import_response.initial_data, status=200)
return HttpResponseBadRequest()
return Response(status=204)
@permission_required(
"authentik_flows.export_flow",
@ -262,11 +218,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
return Response({})
if background:
flow.background = background
try:
flow.save()
except PermissionError as exc:
LOGGER.warning("Failed to save icon", exc=exc)
return HttpResponseBadRequest()
flow.save()
return Response({})
return HttpResponseBadRequest()

View File

@ -12,7 +12,7 @@ from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.types import UserSettingSerializer
from authentik.flows.api.flows import FlowSetSerializer
from authentik.flows.api.flows import FlowSerializer
from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.utils.reflection import all_subclasses
@ -23,7 +23,7 @@ class StageSerializer(ModelSerializer, MetaNameSerializer):
"""Stage Serializer"""
component = SerializerMethodField()
flow_set = FlowSetSerializer(many=True, required=False)
flow_set = FlowSerializer(many=True, required=False)
def get_component(self, obj: Stage) -> str:
"""Get object type so that we know how to edit the object"""
@ -55,13 +55,13 @@ class StageViewSet(
):
"""Stage Viewset"""
queryset = Stage.objects.none()
queryset = Stage.objects.all().select_related("flow_set")
serializer_class = StageSerializer
search_fields = ["name"]
filterset_fields = ["name"]
def get_queryset(self): # pragma: no cover
return Stage.objects.select_subclasses().prefetch_related("flow_set")
return Stage.objects.select_subclasses()
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])

View File

@ -1,6 +1,4 @@
"""flow exceptions"""
from typing import Optional
from django.utils.translation import gettext_lazy as _
from authentik.lib.sentry import SentryIgnoredException
@ -8,15 +6,15 @@ from authentik.policies.types import PolicyResult
class FlowNonApplicableException(SentryIgnoredException):
"""Flow does not apply to current user (denied by policy, or otherwise)."""
"""Flow does not apply to current user (denied by policy)."""
policy_result: Optional[PolicyResult] = None
policy_result: PolicyResult
@property
def messages(self) -> str:
"""Get messages from policy result, fallback to generic reason"""
if not self.policy_result or len(self.policy_result.messages) < 1:
return _("Flow does not apply to current user.")
if len(self.policy_result.messages) < 1:
return _("Flow does not apply to current user (denied by policy).")
return "\n".join(self.policy_result.messages)

View File

@ -0,0 +1,138 @@
# Generated by Django 3.0.6 on 2020-05-19 22:07
import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_policies", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Flow",
fields=[
(
"flow_uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField()),
("slug", models.SlugField(unique=True)),
(
"designation",
models.CharField(
choices=[
("authentication", "Authentication"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("password_change", "Password Change"),
],
max_length=100,
),
),
(
"pbm",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="+",
to="authentik_policies.PolicyBindingModel",
),
),
],
options={
"verbose_name": "Flow",
"verbose_name_plural": "Flows",
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.CreateModel(
name="Stage",
fields=[
(
"stage_uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField()),
],
),
migrations.CreateModel(
name="FlowStageBinding",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
to="authentik_policies.PolicyBindingModel",
),
),
(
"fsb_uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"re_evaluate_policies",
models.BooleanField(
default=False,
help_text="When this option is enabled, the planner will re-evaluate policies bound to this.",
),
),
("order", models.IntegerField()),
(
"flow",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_flows.Flow",
),
),
(
"stage",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_flows.Stage",
),
),
],
options={
"verbose_name": "Flow Stage Binding",
"verbose_name_plural": "Flow Stage Bindings",
"ordering": ["order", "flow"],
"unique_together": {("flow", "stage", "order")},
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.AddField(
model_name="flow",
name="stages",
field=models.ManyToManyField(
blank=True,
through="authentik_flows.FlowStageBinding",
to="authentik_flows.Stage",
),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.0.6 on 2020-05-23 11:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("password_change", "Password Change"),
],
max_length=100,
),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.0.7 on 2020-06-29 08:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0003_auto_20200523_1133"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("stage_setup", "Stage Setup"),
],
max_length=100,
),
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 3.0.7 on 2020-07-03 20:59
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies", "0002_auto_20200528_1647"),
("authentik_flows", "0006_auto_20200629_0857"),
]
operations = [
migrations.AlterModelOptions(
name="flowstagebinding",
options={
"ordering": ["order", "target"],
"verbose_name": "Flow Stage Binding",
"verbose_name_plural": "Flow Stage Bindings",
},
),
migrations.RenameField(
model_name="flowstagebinding",
old_name="flow",
new_name="target",
),
migrations.RenameField(
model_name="flow",
old_name="pbm",
new_name="policybindingmodel_ptr",
),
migrations.AlterUniqueTogether(
name="flowstagebinding",
unique_together={("target", "stage", "order")},
),
migrations.AlterField(
model_name="flow",
name="policybindingmodel_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
to="authentik_policies.PolicyBindingModel",
),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.1.1 on 2020-09-08 15:42
import django.db.models.deletion
from django.db import migrations, models
import authentik.lib.models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0011_flow_title"),
]
operations = [
migrations.AlterField(
model_name="flowstagebinding",
name="stage",
field=authentik.lib.models.InheritanceForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.stage"
),
),
migrations.AlterField(
model_name="stage",
name="name",
field=models.TextField(unique=True),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.1.1 on 2020-09-24 16:05
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.flows.models import FlowDesignation
def update_flow_designation(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Flow = apps.get_model("authentik_flows", "Flow")
db_alias = schema_editor.connection.alias
for flow in Flow.objects.using(db_alias).all():
if flow.designation == "stage_setup":
flow.designation = FlowDesignation.STAGE_CONFIGURATION
flow.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0012_auto_20200908_1542"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("stage_configuration", "Stage Configuration"),
],
max_length=100,
),
),
migrations.RunPython(update_flow_designation),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.1.1 on 2020-09-25 23:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0013_auto_20200924_1605"),
]
operations = [
migrations.AlterModelOptions(
name="flowstagebinding",
options={
"ordering": ["target", "order"],
"verbose_name": "Flow Stage Binding",
"verbose_name_plural": "Flow Stage Bindings",
},
),
migrations.AlterField(
model_name="flowstagebinding",
name="re_evaluate_policies",
field=models.BooleanField(
default=False,
help_text="When this option is enabled, the planner will re-evaluate policies bound to this binding.",
),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.1.2 on 2020-10-20 12:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0014_auto_20200925_2332"),
]
operations = [
migrations.AlterField(
model_name="flowstagebinding",
name="re_evaluate_policies",
field=models.BooleanField(
default=False,
help_text="Evaluate policies when the Stage is present to the user.",
),
),
migrations.AddField(
model_name="flowstagebinding",
name="evaluate_on_plan",
field=models.BooleanField(
default=True,
help_text="Evaluate policies during the Flow planning process. Disable this for input-based policies.",
),
),
]

View File

@ -0,0 +1,50 @@
# Generated by Django 3.1.3 on 2020-12-02 13:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0015_flowstagebinding_evaluate_on_plan"),
]
operations = [
migrations.AddField(
model_name="flow",
name="background",
field=models.FileField(
blank=True,
default="../static/dist/assets/images/flow_background.jpg",
help_text="Background shown during execution",
upload_to="flow-backgrounds/",
),
),
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("stage_configuration", "Stage Configuration"),
],
help_text="Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.",
max_length=100,
),
),
migrations.AlterField(
model_name="flow",
name="slug",
field=models.SlugField(help_text="Visible in the URL.", unique=True),
),
migrations.AlterField(
model_name="flow",
name="title",
field=models.TextField(help_text="Shown as the Title in Flow pages."),
),
]

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