antlr for scim filter parsing, why

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2022-06-11 23:59:57 +02:00 committed by Jens Langhammer
parent 7f12b14145
commit 16bc7408e7
No known key found for this signature in database
23 changed files with 4539 additions and 27 deletions

View File

@ -57,6 +57,6 @@ class SCIMSourceViewSet(UsedByMixin, ModelViewSet):
queryset = SCIMSource.objects.all()
serializer_class = SCIMSourceSerializer
lookup_field = "slug"
filterset_fields = "__all__"
filterset_fields = ["name", "slug"]
search_fields = ["name", "slug", "token__identifier", "token__user__username"]
ordering = ["name"]

View File

@ -0,0 +1,8 @@
"""SCIM Errors"""
from authentik.lib.sentry import SentryIgnoredException
class PatchError(SentryIgnoredException):
"""Error raised within an atomic block when an error happened
so nothing is saved"""

View File

@ -0,0 +1,61 @@
grammar ScimFilter;
parse
: filter
;
filter
: attrPath SP PR #presentExp
| attrPath SP COMPAREOPERATOR SP VALUE #operatorExp
| NOT? SP* '(' filter ')' #braceExp
| attrPath '[' valPathFilter ']' #valPathExp
| filter SP AND SP filter #andExp
| filter SP OR SP filter #orExp
;
valPathFilter
: attrPath SP PR #valPathPresentExp
| attrPath SP COMPAREOPERATOR SP VALUE #valPathOperatorExp
| NOT? SP* '(' valPathFilter ')' #valPathBraceExp
| valPathFilter SP AND SP valPathFilter #valPathAndExp
| valPathFilter SP OR SP valPathFilter #valPathOrExp
;
attrPath
: (SCHEMA)? ATTRNAME ('.' ATTRNAME)?
;
COMPAREOPERATOR : EQ | NE | CO | SW | EW | GT | GE | LT | LE;
EQ : [eE][qQ];
NE : [nN][eE];
CO : [cC][oO];
SW : [sS][wW];
EW : [eE][wW];
PR : [pP][rR];
GT : [gG][tT];
GE : [gG][eE];
LT : [lL][tT];
LE : [lL][eE];
NOT : [nN][oO][tT];
AND : [aA][nN][dD];
OR : [oO][rR];
SP : ' ';
SCHEMA : 'urn:' (SEGMENT ':')+;
ATTRNAME : ALPHA (ALPHA | DIGIT | '_' | '-')+;
fragment SEGMENT : (ALPHA | DIGIT | '_' | '-' | '.')+;
fragment DIGIT : [0-9];
fragment ALPHA : [a-z] | [A-Z];
ESCAPED_QUOTE : '\\"';
VALUE : '"'(ESCAPED_QUOTE | ~'"')*'"' | 'true' | 'false' | 'null' | DIGIT+('.'DIGIT+)?;
EXCLUDE : [\b | \t | \r | \n]+ -> skip;

View File

@ -0,0 +1,65 @@
token literal names:
null
'('
')'
'['
']'
'.'
null
null
null
null
null
null
null
null
null
null
null
null
null
null
' '
null
null
'\\"'
null
null
token symbolic names:
null
null
null
null
null
null
COMPAREOPERATOR
EQ
NE
CO
SW
EW
PR
GT
GE
LT
LE
NOT
AND
OR
SP
SCHEMA
ATTRNAME
ESCAPED_QUOTE
VALUE
EXCLUDE
rule names:
parse
filter
valPathFilter
attrPath
atn:
[4, 1, 25, 106, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 23, 8, 1, 1, 1, 5, 1, 26, 8, 1, 10, 1, 12, 1, 29, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 40, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 52, 8, 1, 10, 1, 12, 1, 55, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 69, 8, 2, 1, 2, 5, 2, 72, 8, 2, 10, 2, 12, 2, 75, 9, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 81, 8, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 93, 8, 2, 10, 2, 12, 2, 96, 9, 2, 1, 3, 3, 3, 99, 8, 3, 1, 3, 1, 3, 1, 3, 3, 3, 104, 8, 3, 1, 3, 0, 2, 2, 4, 4, 0, 2, 4, 6, 0, 0, 116, 0, 8, 1, 0, 0, 0, 2, 39, 1, 0, 0, 0, 4, 80, 1, 0, 0, 0, 6, 98, 1, 0, 0, 0, 8, 9, 3, 2, 1, 0, 9, 1, 1, 0, 0, 0, 10, 11, 6, 1, -1, 0, 11, 12, 3, 6, 3, 0, 12, 13, 5, 20, 0, 0, 13, 14, 5, 12, 0, 0, 14, 40, 1, 0, 0, 0, 15, 16, 3, 6, 3, 0, 16, 17, 5, 20, 0, 0, 17, 18, 5, 6, 0, 0, 18, 19, 5, 20, 0, 0, 19, 20, 5, 24, 0, 0, 20, 40, 1, 0, 0, 0, 21, 23, 5, 17, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 27, 1, 0, 0, 0, 24, 26, 5, 20, 0, 0, 25, 24, 1, 0, 0, 0, 26, 29, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 1, 0, 0, 0, 29, 27, 1, 0, 0, 0, 30, 31, 5, 1, 0, 0, 31, 32, 3, 2, 1, 0, 32, 33, 5, 2, 0, 0, 33, 40, 1, 0, 0, 0, 34, 35, 3, 6, 3, 0, 35, 36, 5, 3, 0, 0, 36, 37, 3, 4, 2, 0, 37, 38, 5, 4, 0, 0, 38, 40, 1, 0, 0, 0, 39, 10, 1, 0, 0, 0, 39, 15, 1, 0, 0, 0, 39, 22, 1, 0, 0, 0, 39, 34, 1, 0, 0, 0, 40, 53, 1, 0, 0, 0, 41, 42, 10, 2, 0, 0, 42, 43, 5, 20, 0, 0, 43, 44, 5, 18, 0, 0, 44, 45, 5, 20, 0, 0, 45, 52, 3, 2, 1, 3, 46, 47, 10, 1, 0, 0, 47, 48, 5, 20, 0, 0, 48, 49, 5, 19, 0, 0, 49, 50, 5, 20, 0, 0, 50, 52, 3, 2, 1, 2, 51, 41, 1, 0, 0, 0, 51, 46, 1, 0, 0, 0, 52, 55, 1, 0, 0, 0, 53, 51, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54, 3, 1, 0, 0, 0, 55, 53, 1, 0, 0, 0, 56, 57, 6, 2, -1, 0, 57, 58, 3, 6, 3, 0, 58, 59, 5, 20, 0, 0, 59, 60, 5, 12, 0, 0, 60, 81, 1, 0, 0, 0, 61, 62, 3, 6, 3, 0, 62, 63, 5, 20, 0, 0, 63, 64, 5, 6, 0, 0, 64, 65, 5, 20, 0, 0, 65, 66, 5, 24, 0, 0, 66, 81, 1, 0, 0, 0, 67, 69, 5, 17, 0, 0, 68, 67, 1, 0, 0, 0, 68, 69, 1, 0, 0, 0, 69, 73, 1, 0, 0, 0, 70, 72, 5, 20, 0, 0, 71, 70, 1, 0, 0, 0, 72, 75, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 76, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 76, 77, 5, 1, 0, 0, 77, 78, 3, 4, 2, 0, 78, 79, 5, 2, 0, 0, 79, 81, 1, 0, 0, 0, 80, 56, 1, 0, 0, 0, 80, 61, 1, 0, 0, 0, 80, 68, 1, 0, 0, 0, 81, 94, 1, 0, 0, 0, 82, 83, 10, 2, 0, 0, 83, 84, 5, 20, 0, 0, 84, 85, 5, 18, 0, 0, 85, 86, 5, 20, 0, 0, 86, 93, 3, 4, 2, 3, 87, 88, 10, 1, 0, 0, 88, 89, 5, 20, 0, 0, 89, 90, 5, 19, 0, 0, 90, 91, 5, 20, 0, 0, 91, 93, 3, 4, 2, 2, 92, 82, 1, 0, 0, 0, 92, 87, 1, 0, 0, 0, 93, 96, 1, 0, 0, 0, 94, 92, 1, 0, 0, 0, 94, 95, 1, 0, 0, 0, 95, 5, 1, 0, 0, 0, 96, 94, 1, 0, 0, 0, 97, 99, 5, 21, 0, 0, 98, 97, 1, 0, 0, 0, 98, 99, 1, 0, 0, 0, 99, 100, 1, 0, 0, 0, 100, 103, 5, 22, 0, 0, 101, 102, 5, 5, 0, 0, 102, 104, 5, 22, 0, 0, 103, 101, 1, 0, 0, 0, 103, 104, 1, 0, 0, 0, 104, 7, 1, 0, 0, 0, 12, 22, 27, 39, 51, 53, 68, 73, 80, 92, 94, 98, 103]

View File

@ -0,0 +1,32 @@
T__0=1
T__1=2
T__2=3
T__3=4
T__4=5
COMPAREOPERATOR=6
EQ=7
NE=8
CO=9
SW=10
EW=11
PR=12
GT=13
GE=14
LT=15
LE=16
NOT=17
AND=18
OR=19
SP=20
SCHEMA=21
ATTRNAME=22
ESCAPED_QUOTE=23
VALUE=24
EXCLUDE=25
'('=1
')'=2
'['=3
']'=4
'.'=5
' '=20
'\\"'=23

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
T__0=1
T__1=2
T__2=3
T__3=4
T__4=5
COMPAREOPERATOR=6
EQ=7
NE=8
CO=9
SW=10
EW=11
PR=12
GT=13
GE=14
LT=15
LE=16
NOT=17
AND=18
OR=19
SP=20
SCHEMA=21
ATTRNAME=22
ESCAPED_QUOTE=23
VALUE=24
EXCLUDE=25
'('=1
')'=2
'['=3
']'=4
'.'=5
' '=20
'\\"'=23

View File

@ -0,0 +1,118 @@
# pylint: skip-file
# Generated from ScimFilter.g4 by ANTLR 4.10.1
from antlr4 import *
if __name__ is not None and "." in __name__:
from .ScimFilterParser import ScimFilterParser
else:
from ScimFilterParser import ScimFilterParser
# This class defines a complete listener for a parse tree produced by ScimFilterParser.
class ScimFilterListener(ParseTreeListener):
# Enter a parse tree produced by ScimFilterParser#parse.
def enterParse(self, ctx: ScimFilterParser.ParseContext):
pass
# Exit a parse tree produced by ScimFilterParser#parse.
def exitParse(self, ctx: ScimFilterParser.ParseContext):
pass
# Enter a parse tree produced by ScimFilterParser#andExp.
def enterAndExp(self, ctx: ScimFilterParser.AndExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#andExp.
def exitAndExp(self, ctx: ScimFilterParser.AndExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#valPathExp.
def enterValPathExp(self, ctx: ScimFilterParser.ValPathExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#valPathExp.
def exitValPathExp(self, ctx: ScimFilterParser.ValPathExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#presentExp.
def enterPresentExp(self, ctx: ScimFilterParser.PresentExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#presentExp.
def exitPresentExp(self, ctx: ScimFilterParser.PresentExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#operatorExp.
def enterOperatorExp(self, ctx: ScimFilterParser.OperatorExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#operatorExp.
def exitOperatorExp(self, ctx: ScimFilterParser.OperatorExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#braceExp.
def enterBraceExp(self, ctx: ScimFilterParser.BraceExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#braceExp.
def exitBraceExp(self, ctx: ScimFilterParser.BraceExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#orExp.
def enterOrExp(self, ctx: ScimFilterParser.OrExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#orExp.
def exitOrExp(self, ctx: ScimFilterParser.OrExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#valPathOperatorExp.
def enterValPathOperatorExp(self, ctx: ScimFilterParser.ValPathOperatorExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#valPathOperatorExp.
def exitValPathOperatorExp(self, ctx: ScimFilterParser.ValPathOperatorExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#valPathPresentExp.
def enterValPathPresentExp(self, ctx: ScimFilterParser.ValPathPresentExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#valPathPresentExp.
def exitValPathPresentExp(self, ctx: ScimFilterParser.ValPathPresentExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#valPathAndExp.
def enterValPathAndExp(self, ctx: ScimFilterParser.ValPathAndExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#valPathAndExp.
def exitValPathAndExp(self, ctx: ScimFilterParser.ValPathAndExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#valPathOrExp.
def enterValPathOrExp(self, ctx: ScimFilterParser.ValPathOrExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#valPathOrExp.
def exitValPathOrExp(self, ctx: ScimFilterParser.ValPathOrExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#valPathBraceExp.
def enterValPathBraceExp(self, ctx: ScimFilterParser.ValPathBraceExpContext):
pass
# Exit a parse tree produced by ScimFilterParser#valPathBraceExp.
def exitValPathBraceExp(self, ctx: ScimFilterParser.ValPathBraceExpContext):
pass
# Enter a parse tree produced by ScimFilterParser#attrPath.
def enterAttrPath(self, ctx: ScimFilterParser.AttrPathContext):
pass
# Exit a parse tree produced by ScimFilterParser#attrPath.
def exitAttrPath(self, ctx: ScimFilterParser.AttrPathContext):
pass
del ScimFilterParser

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
"""Django listener"""
from django.db.models import Q
from django.utils.tree import Node
from authentik.sources.scim.filters.ScimFilterListener import ScimFilterListener
from authentik.sources.scim.filters.ScimFilterParser import ScimFilterParser
class DjangoQueryListener(ScimFilterListener):
"""SCIM filter listener that converts it to a query"""
_query: Node
_last_node: Node
def __init__(self) -> None:
super().__init__()
self._query = Q()
self._last_node = Q()
@property
def query(self) -> Node:
return self._query
def enterParse(self, ctx: ScimFilterParser.ParseContext):
print("enterParse", ctx)
def exitParse(self, ctx: ScimFilterParser.ParseContext):
print("exitParse", ctx)
def enterAndExp(self, ctx: ScimFilterParser.AndExpContext):
print("enterAndExp", ctx)
def exitAndExp(self, ctx: ScimFilterParser.AndExpContext):
print("exitAndExp", ctx)
def enterValPathExp(self, ctx: ScimFilterParser.ValPathExpContext):
print("enterValPathExp", ctx.getText())
def exitValPathExp(self, ctx: ScimFilterParser.ValPathExpContext):
print("exitValPathExp", ctx)
def enterPresentExp(self, ctx: ScimFilterParser.PresentExpContext):
print("enterPresentExp", ctx)
def exitPresentExp(self, ctx: ScimFilterParser.PresentExpContext):
print("exitPresentExp", ctx)
def enterOperatorExp(self, ctx: ScimFilterParser.OperatorExpContext):
print("enterOperatorExp", ctx)
def exitOperatorExp(self, ctx: ScimFilterParser.OperatorExpContext):
print("exitOperatorExp", ctx)
def enterBraceExp(self, ctx: ScimFilterParser.BraceExpContext):
print("enterBraceExp", ctx)
def exitBraceExp(self, ctx: ScimFilterParser.BraceExpContext):
print("exitBraceExp", ctx)
def enterOrExp(self, ctx: ScimFilterParser.OrExpContext):
print("enterOrExp", ctx)
def exitOrExp(self, ctx: ScimFilterParser.OrExpContext):
print("exitOrExp", ctx)
def enterValPathOperatorExp(self, ctx: ScimFilterParser.ValPathOperatorExpContext):
print("enterValPathOperatorExp", ctx)
def exitValPathOperatorExp(self, ctx: ScimFilterParser.ValPathOperatorExpContext):
print("exitValPathOperatorExp", ctx)
def enterValPathPresentExp(self, ctx: ScimFilterParser.ValPathPresentExpContext):
print("enterValPathPresentExp", ctx)
def exitValPathPresentExp(self, ctx: ScimFilterParser.ValPathPresentExpContext):
print("exitValPathPresentExp", ctx)
def enterValPathAndExp(self, ctx: ScimFilterParser.ValPathAndExpContext):
print("enterValPathAndExp", ctx.getText())
def exitValPathAndExp(self, ctx: ScimFilterParser.ValPathAndExpContext):
print("exitValPathAndExp", ctx)
def enterValPathOrExp(self, ctx: ScimFilterParser.ValPathOrExpContext):
print("enterValPathOrExp", ctx)
def exitValPathOrExp(self, ctx: ScimFilterParser.ValPathOrExpContext):
print("exitValPathOrExp", ctx)
def enterValPathBraceExp(self, ctx: ScimFilterParser.ValPathBraceExpContext):
print("enterValPathBraceExp", ctx)
def exitValPathBraceExp(self, ctx: ScimFilterParser.ValPathBraceExpContext):
print("exitValPathBraceExp", ctx)
def enterAttrPath(self, ctx: ScimFilterParser.AttrPathContext):
self._last_node = Q(ctx.getText())
def exitAttrPath(self, ctx: ScimFilterParser.AttrPathContext):
self._query = self._last_node
self._last_node = Q()

View File

@ -14,7 +14,7 @@ class SCIMTokenAuth(BaseAuthentication):
def legacy(self, key: str, source_slug: str) -> Optional[Token]: # pragma: no cover
"""Legacy HTTP-Basic auth for testing"""
if not settings.TEST or not settings.DEBUG:
if not settings.TEST and not settings.DEBUG:
return None
_username, _, password = b64decode(key.encode()).decode().partition(":")
token = self.check_token(password, source_slug)

View File

@ -1,4 +1,9 @@
"""SCIM Utils"""
from typing import Optional
from urllib.parse import urlparse
from antlr4 import CommonTokenStream, InputStream, ParseTreeWalker
from django.urls import resolve
from rest_framework.parsers import JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
@ -6,6 +11,10 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.core.models import Group, User
from authentik.sources.scim.filters.django import DjangoQueryListener
from authentik.sources.scim.filters.ScimFilterLexer import ScimFilterLexer
from authentik.sources.scim.filters.ScimFilterParser import ScimFilterParser
from authentik.sources.scim.views.v2.auth import SCIMTokenAuth
SCIM_CONTENT_TYPE = "application/scim+json"
@ -31,6 +40,39 @@ class SCIMView(APIView):
parser_classes = [SCIMParser]
renderer_classes = [SCIMRenderer]
def patch_resolve_value(self, raw_value: dict) -> Optional[User | Group]:
"""Attempt to resolve a raw `value` attribute of a patch operation into
a database model"""
model = User
query = {}
if "$ref" in raw_value:
url = urlparse(raw_value["$ref"])
if match := resolve(url.path):
if match.url_name == "v2-users":
model = User
query = {"pk": int(match.kwargs["user_id"])}
elif "type" in raw_value:
match raw_value["tyoe"]:
case "User":
model = User
query = {"pk": int(raw_value["value"])}
case "Group":
model = Group
else:
return None
return model.objects.filter(**query).first()
def patch_parse_path(self, path: str):
"""Parse the path of a Patch Operation"""
lexer = ScimFilterLexer(InputStream(path))
stream = CommonTokenStream(lexer)
parser = ScimFilterParser(stream)
tree = parser.filter_()
listener = DjangoQueryListener()
walker = ParseTreeWalker()
walker.walk(listener, tree)
return listener.query
class SCIMRootView(SCIMView):
"""Root SCIM View"""

View File

@ -2,6 +2,7 @@
from typing import Optional
from django.core.paginator import Paginator
from django.db.transaction import atomic
from django.http import Http404, QueryDict
from django.urls import reverse
from rest_framework.request import Request
@ -9,6 +10,7 @@ from rest_framework.response import Response
from structlog.stdlib import get_logger
from authentik.core.models import Group
from authentik.sources.scim.errors import PatchError
from authentik.sources.scim.views.v2.base import SCIM_CONTENT_TYPE, SCIMView
LOGGER = get_logger()
@ -77,7 +79,28 @@ class GroupsView(SCIMView):
def patch(self, request: Request, group_id: str, **kwargs) -> Response:
"""Update group handler"""
return self.put(request, group_id, **kwargs)
group: Optional[Group] = Group.objects.filter(pk=group_id).first()
if not group:
raise Http404
if request.data.get("schemas", []) != ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]:
return Response(status=400)
try:
with atomic():
for op in request.data.get("Operations", []):
path = self.patch_parse_path(op["path"])
operation = op["op"]
raw_value = op.get("value", None)
values = []
for value in raw_value:
values.append(self.patch_resolve_value(value))
match operation:
case "add":
group.users.add(*[x.pk for x in values])
case "remove":
pass
return Response(self.group_to_scim(group), status=200)
except (KeyError, PatchError):
return Response(status=400)
def put(self, request: Request, group_id: str, **kwargs) -> Response:
"""Update group handler"""

35
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]]
name = "aiohttp"
@ -161,6 +161,17 @@ files = [
{file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"},
]
[[package]]
name = "antlr4-python3-runtime"
version = "4.13.1"
description = "ANTLR 4.13.1 runtime for Python 3"
optional = false
python-versions = "*"
files = [
{file = "antlr4-python3-runtime-4.13.1.tar.gz", hash = "sha256:3cd282f5ea7cfb841537fe01f143350fdb1c0b1ce7981443a2fa8513fddb6d1a"},
{file = "antlr4_python3_runtime-4.13.1-py3-none-any.whl", hash = "sha256:78ec57aad12c97ac039ca27403ad61cb98aaec8a3f9bb8144f889aa0fa28b943"},
]
[[package]]
name = "anyio"
version = "4.0.0"
@ -2096,16 +2107,6 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@ -3095,7 +3096,6 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@ -3103,15 +3103,8 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@ -3128,7 +3121,6 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@ -3136,7 +3128,6 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@ -4313,4 +4304,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "2fc746976187f4674f04575cffd6a367744723bf78c356b6951c2370bc47ceae"
content-hash = "a41a75fc3dac5e552ed99670c10a5a5a0947449701893fafd81d59c6439f324a"

View File

@ -118,6 +118,7 @@ description = ""
authors = ["authentik Team <hello@goauthentik.io>"]
[tool.poetry.dependencies]
antlr4-python3-runtime = "*"
argon2-cffi = "*"
celery = "*"
channels = { version = "*", extras = ["daphne"] }
@ -143,6 +144,7 @@ facebook-sdk = "*"
flower = "*"
geoip2 = "*"
gunicorn = "*"
jsonpatch = "*"
kubernetes = "*"
ldap3 = "*"
lxml = "*"
@ -171,7 +173,6 @@ webauthn = "*"
wsproto = "*"
xmlsec = "*"
zxcvbn = "*"
jsonpatch = "*"
[tool.poetry.dev-dependencies]
bandit = "*"

14
test.py Normal file
View File

@ -0,0 +1,14 @@
from antlr4 import CommonTokenStream, InputStream, ParseTreeWalker
from authentik.sources.scim.filters.django import DjangoQueryListener
from authentik.sources.scim.filters.ScimFilterLexer import ScimFilterLexer
from authentik.sources.scim.filters.ScimFilterParser import ScimFilterParser
lexer = ScimFilterLexer(InputStream('emails[type eq "work" and value ew "example.com"]'))
stream = CommonTokenStream(lexer)
parser = ScimFilterParser(stream)
tree = parser.filter_()
listener = DjangoQueryListener()
walker = ParseTreeWalker()
walker.walk(listener, tree)
print(listener.query)

View File

@ -1,5 +1,5 @@
---
title: SAML
title: SAML Source
---
This source allows authentik to act as a SAML Service Provider. Just like the SAML Provider, it supports signed requests. Vendor-specific documentation can be found in the Integrations Section.

View File

@ -0,0 +1,19 @@
---
title: SCIM
---
The SCIM source allows other applications to directly create users and groups within authentik. SCIM is supported by applications such as Microsoft Azure AD, Google Workspace and Okta.
The base SCIM URL is in the format of `https://authentik.company/source/scim/<source-slug>/v2`. Authentication is done via Bearer tokens generated by authentik. When an SCIM source is created, a service account is created and a matching token.
## Supported Options & Resource types
### `/v2/Users`
Endpoint to list, create, patch and delete users.
### `/v2/Groups`
Endpoint to list, create, patch and delete groups.
There is also the `/v2/ServiceProviderConfig` and `/v2/ResourceTypes`, which is used by SCIM-enabled applications to find out which features authentik supports.

View File

@ -165,6 +165,29 @@ const docsSidebar = {
"flow/executors/headless",
],
},
{
type: "category",
label: "Stages",
items: [
"flow/stages/authenticator_duo/index",
"flow/stages/authenticator_sms/index",
"flow/stages/authenticator_static/index",
"flow/stages/authenticator_totp/index",
"flow/stages/authenticator_validate/index",
"flow/stages/authenticator_webauthn/index",
"flow/stages/captcha/index",
"flow/stages/deny",
"flow/stages/email/index",
"flow/stages/identification/index",
"flow/stages/invitation/index",
"flow/stages/password/index",
"flow/stages/prompt/index",
"flow/stages/user_delete",
"flow/stages/user_login",
"flow/stages/user_logout",
"flow/stages/user_write",
],
},
],
},
{

View File

@ -166,6 +166,7 @@ module.exports = {
"sources/ldap/index",
"sources/oauth/index",
"sources/saml/index",
"sources/scim/index",
],
},
{