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:
parent
7f12b14145
commit
16bc7408e7
|
@ -57,6 +57,6 @@ class SCIMSourceViewSet(UsedByMixin, ModelViewSet):
|
||||||
queryset = SCIMSource.objects.all()
|
queryset = SCIMSource.objects.all()
|
||||||
serializer_class = SCIMSourceSerializer
|
serializer_class = SCIMSourceSerializer
|
||||||
lookup_field = "slug"
|
lookup_field = "slug"
|
||||||
filterset_fields = "__all__"
|
filterset_fields = ["name", "slug"]
|
||||||
search_fields = ["name", "slug", "token__identifier", "token__user__username"]
|
search_fields = ["name", "slug", "token__identifier", "token__user__username"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
8
authentik/sources/scim/errors.py
Normal file
8
authentik/sources/scim/errors.py
Normal 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"""
|
61
authentik/sources/scim/filters/ScimFilter.g4
Normal file
61
authentik/sources/scim/filters/ScimFilter.g4
Normal 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;
|
65
authentik/sources/scim/filters/ScimFilter.interp
Normal file
65
authentik/sources/scim/filters/ScimFilter.interp
Normal 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]
|
32
authentik/sources/scim/filters/ScimFilter.tokens
Normal file
32
authentik/sources/scim/filters/ScimFilter.tokens
Normal 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
|
95
authentik/sources/scim/filters/ScimFilterLexer.interp
Normal file
95
authentik/sources/scim/filters/ScimFilterLexer.interp
Normal file
File diff suppressed because one or more lines are too long
2072
authentik/sources/scim/filters/ScimFilterLexer.py
Normal file
2072
authentik/sources/scim/filters/ScimFilterLexer.py
Normal file
File diff suppressed because it is too large
Load diff
32
authentik/sources/scim/filters/ScimFilterLexer.tokens
Normal file
32
authentik/sources/scim/filters/ScimFilterLexer.tokens
Normal 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
|
118
authentik/sources/scim/filters/ScimFilterListener.py
Normal file
118
authentik/sources/scim/filters/ScimFilterListener.py
Normal 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
|
1814
authentik/sources/scim/filters/ScimFilterParser.py
Normal file
1814
authentik/sources/scim/filters/ScimFilterParser.py
Normal file
File diff suppressed because it is too large
Load diff
0
authentik/sources/scim/filters/__init__.py
Normal file
0
authentik/sources/scim/filters/__init__.py
Normal file
101
authentik/sources/scim/filters/django.py
Normal file
101
authentik/sources/scim/filters/django.py
Normal 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()
|
|
@ -14,7 +14,7 @@ class SCIMTokenAuth(BaseAuthentication):
|
||||||
|
|
||||||
def legacy(self, key: str, source_slug: str) -> Optional[Token]: # pragma: no cover
|
def legacy(self, key: str, source_slug: str) -> Optional[Token]: # pragma: no cover
|
||||||
"""Legacy HTTP-Basic auth for testing"""
|
"""Legacy HTTP-Basic auth for testing"""
|
||||||
if not settings.TEST or not settings.DEBUG:
|
if not settings.TEST and not settings.DEBUG:
|
||||||
return None
|
return None
|
||||||
_username, _, password = b64decode(key.encode()).decode().partition(":")
|
_username, _, password = b64decode(key.encode()).decode().partition(":")
|
||||||
token = self.check_token(password, source_slug)
|
token = self.check_token(password, source_slug)
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
"""SCIM Utils"""
|
"""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.parsers import JSONParser
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.renderers import JSONRenderer
|
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.response import Response
|
||||||
from rest_framework.views import APIView
|
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
|
from authentik.sources.scim.views.v2.auth import SCIMTokenAuth
|
||||||
|
|
||||||
SCIM_CONTENT_TYPE = "application/scim+json"
|
SCIM_CONTENT_TYPE = "application/scim+json"
|
||||||
|
@ -31,6 +40,39 @@ class SCIMView(APIView):
|
||||||
parser_classes = [SCIMParser]
|
parser_classes = [SCIMParser]
|
||||||
renderer_classes = [SCIMRenderer]
|
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):
|
class SCIMRootView(SCIMView):
|
||||||
"""Root SCIM View"""
|
"""Root SCIM View"""
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.transaction import atomic
|
||||||
from django.http import Http404, QueryDict
|
from django.http import Http404, QueryDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
@ -9,6 +10,7 @@ from rest_framework.response import Response
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import Group
|
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
|
from authentik.sources.scim.views.v2.base import SCIM_CONTENT_TYPE, SCIMView
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
@ -77,7 +79,28 @@ class GroupsView(SCIMView):
|
||||||
|
|
||||||
def patch(self, request: Request, group_id: str, **kwargs) -> Response:
|
def patch(self, request: Request, group_id: str, **kwargs) -> Response:
|
||||||
"""Update group handler"""
|
"""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:
|
def put(self, request: Request, group_id: str, **kwargs) -> Response:
|
||||||
"""Update group handler"""
|
"""Update group handler"""
|
||||||
|
|
35
poetry.lock
generated
35
poetry.lock
generated
|
@ -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]]
|
[[package]]
|
||||||
name = "aiohttp"
|
name = "aiohttp"
|
||||||
|
@ -161,6 +161,17 @@ files = [
|
||||||
{file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"},
|
{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]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "4.0.0"
|
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-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-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
|
||||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
|
{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-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_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"},
|
{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_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_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-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-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
|
||||||
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
|
{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"},
|
{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_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_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-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-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
|
||||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
{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-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_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
|
||||||
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
|
{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_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_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-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-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
|
||||||
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
|
{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"},
|
{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_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_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-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-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
|
||||||
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
|
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
|
||||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||||
|
@ -4313,4 +4304,4 @@ files = [
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "2fc746976187f4674f04575cffd6a367744723bf78c356b6951c2370bc47ceae"
|
content-hash = "a41a75fc3dac5e552ed99670c10a5a5a0947449701893fafd81d59c6439f324a"
|
||||||
|
|
|
@ -118,6 +118,7 @@ description = ""
|
||||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
|
antlr4-python3-runtime = "*"
|
||||||
argon2-cffi = "*"
|
argon2-cffi = "*"
|
||||||
celery = "*"
|
celery = "*"
|
||||||
channels = { version = "*", extras = ["daphne"] }
|
channels = { version = "*", extras = ["daphne"] }
|
||||||
|
@ -143,6 +144,7 @@ facebook-sdk = "*"
|
||||||
flower = "*"
|
flower = "*"
|
||||||
geoip2 = "*"
|
geoip2 = "*"
|
||||||
gunicorn = "*"
|
gunicorn = "*"
|
||||||
|
jsonpatch = "*"
|
||||||
kubernetes = "*"
|
kubernetes = "*"
|
||||||
ldap3 = "*"
|
ldap3 = "*"
|
||||||
lxml = "*"
|
lxml = "*"
|
||||||
|
@ -171,7 +173,6 @@ webauthn = "*"
|
||||||
wsproto = "*"
|
wsproto = "*"
|
||||||
xmlsec = "*"
|
xmlsec = "*"
|
||||||
zxcvbn = "*"
|
zxcvbn = "*"
|
||||||
jsonpatch = "*"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
bandit = "*"
|
bandit = "*"
|
||||||
|
|
14
test.py
Normal file
14
test.py
Normal 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)
|
|
@ -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.
|
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.
|
19
website/docs/sources/scim.md
Normal file
19
website/docs/sources/scim.md
Normal 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.
|
|
@ -165,6 +165,29 @@ const docsSidebar = {
|
||||||
"flow/executors/headless",
|
"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",
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -166,6 +166,7 @@ module.exports = {
|
||||||
"sources/ldap/index",
|
"sources/ldap/index",
|
||||||
"sources/oauth/index",
|
"sources/oauth/index",
|
||||||
"sources/saml/index",
|
"sources/saml/index",
|
||||||
|
"sources/scim/index",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Reference in a new issue