From 16bc7408e76761b0842b5525e2d836fff3422c44 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 11 Jun 2022 23:59:57 +0200 Subject: [PATCH] antlr for scim filter parsing, why Signed-off-by: Jens Langhammer Signed-off-by: Jens Langhammer --- authentik/sources/scim/api.py | 2 +- authentik/sources/scim/errors.py | 8 + authentik/sources/scim/filters/ScimFilter.g4 | 61 + .../sources/scim/filters/ScimFilter.interp | 65 + .../sources/scim/filters/ScimFilter.tokens | 32 + .../scim/filters/ScimFilterLexer.interp | 95 + .../sources/scim/filters/ScimFilterLexer.py | 2072 +++++++++++++++++ .../scim/filters/ScimFilterLexer.tokens | 32 + .../scim/filters/ScimFilterListener.py | 118 + .../sources/scim/filters/ScimFilterParser.py | 1814 +++++++++++++++ authentik/sources/scim/filters/__init__.py | 0 authentik/sources/scim/filters/django.py | 101 + authentik/sources/scim/views/v2/auth.py | 2 +- authentik/sources/scim/views/v2/base.py | 42 + authentik/sources/scim/views/v2/groups.py | 25 +- poetry.lock | 35 +- pyproject.toml | 3 +- test.py | 14 + .../oauth/index.md => docs/sources/oauth.md} | 0 .../saml/index.md => docs/sources/saml.md} | 2 +- website/docs/sources/scim.md | 19 + website/sidebars.js | 23 + website/sidebarsIntegrations.js | 1 + 23 files changed, 4539 insertions(+), 27 deletions(-) create mode 100644 authentik/sources/scim/errors.py create mode 100644 authentik/sources/scim/filters/ScimFilter.g4 create mode 100644 authentik/sources/scim/filters/ScimFilter.interp create mode 100644 authentik/sources/scim/filters/ScimFilter.tokens create mode 100644 authentik/sources/scim/filters/ScimFilterLexer.interp create mode 100644 authentik/sources/scim/filters/ScimFilterLexer.py create mode 100644 authentik/sources/scim/filters/ScimFilterLexer.tokens create mode 100644 authentik/sources/scim/filters/ScimFilterListener.py create mode 100644 authentik/sources/scim/filters/ScimFilterParser.py create mode 100644 authentik/sources/scim/filters/__init__.py create mode 100644 authentik/sources/scim/filters/django.py create mode 100644 test.py rename website/{integrations/sources/oauth/index.md => docs/sources/oauth.md} (100%) rename website/{integrations/sources/saml/index.md => docs/sources/saml.md} (99%) create mode 100644 website/docs/sources/scim.md diff --git a/authentik/sources/scim/api.py b/authentik/sources/scim/api.py index 4a867fd84..e5ba4a495 100644 --- a/authentik/sources/scim/api.py +++ b/authentik/sources/scim/api.py @@ -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"] diff --git a/authentik/sources/scim/errors.py b/authentik/sources/scim/errors.py new file mode 100644 index 000000000..76c3f3a01 --- /dev/null +++ b/authentik/sources/scim/errors.py @@ -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""" diff --git a/authentik/sources/scim/filters/ScimFilter.g4 b/authentik/sources/scim/filters/ScimFilter.g4 new file mode 100644 index 000000000..bc516dafe --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilter.g4 @@ -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; diff --git a/authentik/sources/scim/filters/ScimFilter.interp b/authentik/sources/scim/filters/ScimFilter.interp new file mode 100644 index 000000000..4f065110a --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilter.interp @@ -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] \ No newline at end of file diff --git a/authentik/sources/scim/filters/ScimFilter.tokens b/authentik/sources/scim/filters/ScimFilter.tokens new file mode 100644 index 000000000..28b93c87f --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilter.tokens @@ -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 diff --git a/authentik/sources/scim/filters/ScimFilterLexer.interp b/authentik/sources/scim/filters/ScimFilterLexer.interp new file mode 100644 index 000000000..2c69ebd23 --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilterLexer.interp @@ -0,0 +1,95 @@ +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: +T__0 +T__1 +T__2 +T__3 +T__4 +COMPAREOPERATOR +EQ +NE +CO +SW +EW +PR +GT +GE +LT +LE +NOT +AND +OR +SP +SCHEMA +ATTRNAME +SEGMENT +DIGIT +ALPHA +ESCAPED_QUOTE +VALUE +EXCLUDE + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 25, 200, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 77, 8, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 4, 20, 130, 8, 20, 11, 20, 12, 20, 131, 1, 21, 1, 21, 1, 21, 1, 21, 4, 21, 138, 8, 21, 11, 21, 12, 21, 139, 1, 22, 1, 22, 1, 22, 4, 22, 145, 8, 22, 11, 22, 12, 22, 146, 1, 23, 1, 23, 1, 24, 3, 24, 152, 8, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 5, 26, 160, 8, 26, 10, 26, 12, 26, 163, 9, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 4, 26, 180, 8, 26, 11, 26, 12, 26, 181, 1, 26, 1, 26, 4, 26, 186, 8, 26, 11, 26, 12, 26, 187, 3, 26, 190, 8, 26, 3, 26, 192, 8, 26, 1, 27, 4, 27, 195, 8, 27, 11, 27, 12, 27, 196, 1, 27, 1, 27, 0, 0, 28, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 0, 47, 0, 49, 0, 51, 23, 53, 24, 55, 25, 1, 0, 20, 2, 0, 69, 69, 101, 101, 2, 0, 81, 81, 113, 113, 2, 0, 78, 78, 110, 110, 2, 0, 67, 67, 99, 99, 2, 0, 79, 79, 111, 111, 2, 0, 83, 83, 115, 115, 2, 0, 87, 87, 119, 119, 2, 0, 80, 80, 112, 112, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0, 84, 84, 116, 116, 2, 0, 76, 76, 108, 108, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 45, 45, 95, 95, 2, 0, 45, 46, 95, 95, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 1, 0, 34, 34, 4, 0, 8, 10, 13, 13, 32, 32, 124, 124, 221, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 1, 57, 1, 0, 0, 0, 3, 59, 1, 0, 0, 0, 5, 61, 1, 0, 0, 0, 7, 63, 1, 0, 0, 0, 9, 65, 1, 0, 0, 0, 11, 76, 1, 0, 0, 0, 13, 78, 1, 0, 0, 0, 15, 81, 1, 0, 0, 0, 17, 84, 1, 0, 0, 0, 19, 87, 1, 0, 0, 0, 21, 90, 1, 0, 0, 0, 23, 93, 1, 0, 0, 0, 25, 96, 1, 0, 0, 0, 27, 99, 1, 0, 0, 0, 29, 102, 1, 0, 0, 0, 31, 105, 1, 0, 0, 0, 33, 108, 1, 0, 0, 0, 35, 112, 1, 0, 0, 0, 37, 116, 1, 0, 0, 0, 39, 119, 1, 0, 0, 0, 41, 121, 1, 0, 0, 0, 43, 133, 1, 0, 0, 0, 45, 144, 1, 0, 0, 0, 47, 148, 1, 0, 0, 0, 49, 151, 1, 0, 0, 0, 51, 153, 1, 0, 0, 0, 53, 191, 1, 0, 0, 0, 55, 194, 1, 0, 0, 0, 57, 58, 5, 40, 0, 0, 58, 2, 1, 0, 0, 0, 59, 60, 5, 41, 0, 0, 60, 4, 1, 0, 0, 0, 61, 62, 5, 91, 0, 0, 62, 6, 1, 0, 0, 0, 63, 64, 5, 93, 0, 0, 64, 8, 1, 0, 0, 0, 65, 66, 5, 46, 0, 0, 66, 10, 1, 0, 0, 0, 67, 77, 3, 13, 6, 0, 68, 77, 3, 15, 7, 0, 69, 77, 3, 17, 8, 0, 70, 77, 3, 19, 9, 0, 71, 77, 3, 21, 10, 0, 72, 77, 3, 25, 12, 0, 73, 77, 3, 27, 13, 0, 74, 77, 3, 29, 14, 0, 75, 77, 3, 31, 15, 0, 76, 67, 1, 0, 0, 0, 76, 68, 1, 0, 0, 0, 76, 69, 1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 76, 71, 1, 0, 0, 0, 76, 72, 1, 0, 0, 0, 76, 73, 1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 76, 75, 1, 0, 0, 0, 77, 12, 1, 0, 0, 0, 78, 79, 7, 0, 0, 0, 79, 80, 7, 1, 0, 0, 80, 14, 1, 0, 0, 0, 81, 82, 7, 2, 0, 0, 82, 83, 7, 0, 0, 0, 83, 16, 1, 0, 0, 0, 84, 85, 7, 3, 0, 0, 85, 86, 7, 4, 0, 0, 86, 18, 1, 0, 0, 0, 87, 88, 7, 5, 0, 0, 88, 89, 7, 6, 0, 0, 89, 20, 1, 0, 0, 0, 90, 91, 7, 0, 0, 0, 91, 92, 7, 6, 0, 0, 92, 22, 1, 0, 0, 0, 93, 94, 7, 7, 0, 0, 94, 95, 7, 8, 0, 0, 95, 24, 1, 0, 0, 0, 96, 97, 7, 9, 0, 0, 97, 98, 7, 10, 0, 0, 98, 26, 1, 0, 0, 0, 99, 100, 7, 9, 0, 0, 100, 101, 7, 0, 0, 0, 101, 28, 1, 0, 0, 0, 102, 103, 7, 11, 0, 0, 103, 104, 7, 10, 0, 0, 104, 30, 1, 0, 0, 0, 105, 106, 7, 11, 0, 0, 106, 107, 7, 0, 0, 0, 107, 32, 1, 0, 0, 0, 108, 109, 7, 2, 0, 0, 109, 110, 7, 4, 0, 0, 110, 111, 7, 10, 0, 0, 111, 34, 1, 0, 0, 0, 112, 113, 7, 12, 0, 0, 113, 114, 7, 2, 0, 0, 114, 115, 7, 13, 0, 0, 115, 36, 1, 0, 0, 0, 116, 117, 7, 4, 0, 0, 117, 118, 7, 8, 0, 0, 118, 38, 1, 0, 0, 0, 119, 120, 5, 32, 0, 0, 120, 40, 1, 0, 0, 0, 121, 122, 5, 117, 0, 0, 122, 123, 5, 114, 0, 0, 123, 124, 5, 110, 0, 0, 124, 125, 5, 58, 0, 0, 125, 129, 1, 0, 0, 0, 126, 127, 3, 45, 22, 0, 127, 128, 5, 58, 0, 0, 128, 130, 1, 0, 0, 0, 129, 126, 1, 0, 0, 0, 130, 131, 1, 0, 0, 0, 131, 129, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 42, 1, 0, 0, 0, 133, 137, 3, 49, 24, 0, 134, 138, 3, 49, 24, 0, 135, 138, 3, 47, 23, 0, 136, 138, 7, 14, 0, 0, 137, 134, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 137, 136, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 44, 1, 0, 0, 0, 141, 145, 3, 49, 24, 0, 142, 145, 3, 47, 23, 0, 143, 145, 7, 15, 0, 0, 144, 141, 1, 0, 0, 0, 144, 142, 1, 0, 0, 0, 144, 143, 1, 0, 0, 0, 145, 146, 1, 0, 0, 0, 146, 144, 1, 0, 0, 0, 146, 147, 1, 0, 0, 0, 147, 46, 1, 0, 0, 0, 148, 149, 7, 16, 0, 0, 149, 48, 1, 0, 0, 0, 150, 152, 7, 17, 0, 0, 151, 150, 1, 0, 0, 0, 152, 50, 1, 0, 0, 0, 153, 154, 5, 92, 0, 0, 154, 155, 5, 34, 0, 0, 155, 52, 1, 0, 0, 0, 156, 161, 5, 34, 0, 0, 157, 160, 3, 51, 25, 0, 158, 160, 8, 18, 0, 0, 159, 157, 1, 0, 0, 0, 159, 158, 1, 0, 0, 0, 160, 163, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 164, 1, 0, 0, 0, 163, 161, 1, 0, 0, 0, 164, 192, 5, 34, 0, 0, 165, 166, 5, 116, 0, 0, 166, 167, 5, 114, 0, 0, 167, 168, 5, 117, 0, 0, 168, 192, 5, 101, 0, 0, 169, 170, 5, 102, 0, 0, 170, 171, 5, 97, 0, 0, 171, 172, 5, 108, 0, 0, 172, 173, 5, 115, 0, 0, 173, 192, 5, 101, 0, 0, 174, 175, 5, 110, 0, 0, 175, 176, 5, 117, 0, 0, 176, 177, 5, 108, 0, 0, 177, 192, 5, 108, 0, 0, 178, 180, 3, 47, 23, 0, 179, 178, 1, 0, 0, 0, 180, 181, 1, 0, 0, 0, 181, 179, 1, 0, 0, 0, 181, 182, 1, 0, 0, 0, 182, 189, 1, 0, 0, 0, 183, 185, 5, 46, 0, 0, 184, 186, 3, 47, 23, 0, 185, 184, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 185, 1, 0, 0, 0, 187, 188, 1, 0, 0, 0, 188, 190, 1, 0, 0, 0, 189, 183, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 192, 1, 0, 0, 0, 191, 156, 1, 0, 0, 0, 191, 165, 1, 0, 0, 0, 191, 169, 1, 0, 0, 0, 191, 174, 1, 0, 0, 0, 191, 179, 1, 0, 0, 0, 192, 54, 1, 0, 0, 0, 193, 195, 7, 19, 0, 0, 194, 193, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 194, 1, 0, 0, 0, 196, 197, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 199, 6, 27, 0, 0, 199, 56, 1, 0, 0, 0, 15, 0, 76, 131, 137, 139, 144, 146, 151, 159, 161, 181, 187, 189, 191, 196, 1, 6, 0, 0] \ No newline at end of file diff --git a/authentik/sources/scim/filters/ScimFilterLexer.py b/authentik/sources/scim/filters/ScimFilterLexer.py new file mode 100644 index 000000000..55e0cb27b --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilterLexer.py @@ -0,0 +1,2072 @@ +# pylint: skip-file +# Generated from ScimFilter.g4 by ANTLR 4.10.1 +import sys +from io import StringIO + +from antlr4 import * + +if sys.version_info[1] > 5: + from typing import TextIO +else: + from typing.io import TextIO + + +def serializedATN(): + return [ + 4, + 0, + 25, + 200, + 6, + -1, + 2, + 0, + 7, + 0, + 2, + 1, + 7, + 1, + 2, + 2, + 7, + 2, + 2, + 3, + 7, + 3, + 2, + 4, + 7, + 4, + 2, + 5, + 7, + 5, + 2, + 6, + 7, + 6, + 2, + 7, + 7, + 7, + 2, + 8, + 7, + 8, + 2, + 9, + 7, + 9, + 2, + 10, + 7, + 10, + 2, + 11, + 7, + 11, + 2, + 12, + 7, + 12, + 2, + 13, + 7, + 13, + 2, + 14, + 7, + 14, + 2, + 15, + 7, + 15, + 2, + 16, + 7, + 16, + 2, + 17, + 7, + 17, + 2, + 18, + 7, + 18, + 2, + 19, + 7, + 19, + 2, + 20, + 7, + 20, + 2, + 21, + 7, + 21, + 2, + 22, + 7, + 22, + 2, + 23, + 7, + 23, + 2, + 24, + 7, + 24, + 2, + 25, + 7, + 25, + 2, + 26, + 7, + 26, + 2, + 27, + 7, + 27, + 1, + 0, + 1, + 0, + 1, + 1, + 1, + 1, + 1, + 2, + 1, + 2, + 1, + 3, + 1, + 3, + 1, + 4, + 1, + 4, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 3, + 5, + 77, + 8, + 5, + 1, + 6, + 1, + 6, + 1, + 6, + 1, + 7, + 1, + 7, + 1, + 7, + 1, + 8, + 1, + 8, + 1, + 8, + 1, + 9, + 1, + 9, + 1, + 9, + 1, + 10, + 1, + 10, + 1, + 10, + 1, + 11, + 1, + 11, + 1, + 11, + 1, + 12, + 1, + 12, + 1, + 12, + 1, + 13, + 1, + 13, + 1, + 13, + 1, + 14, + 1, + 14, + 1, + 14, + 1, + 15, + 1, + 15, + 1, + 15, + 1, + 16, + 1, + 16, + 1, + 16, + 1, + 16, + 1, + 17, + 1, + 17, + 1, + 17, + 1, + 17, + 1, + 18, + 1, + 18, + 1, + 18, + 1, + 19, + 1, + 19, + 1, + 20, + 1, + 20, + 1, + 20, + 1, + 20, + 1, + 20, + 1, + 20, + 1, + 20, + 1, + 20, + 4, + 20, + 130, + 8, + 20, + 11, + 20, + 12, + 20, + 131, + 1, + 21, + 1, + 21, + 1, + 21, + 1, + 21, + 4, + 21, + 138, + 8, + 21, + 11, + 21, + 12, + 21, + 139, + 1, + 22, + 1, + 22, + 1, + 22, + 4, + 22, + 145, + 8, + 22, + 11, + 22, + 12, + 22, + 146, + 1, + 23, + 1, + 23, + 1, + 24, + 3, + 24, + 152, + 8, + 24, + 1, + 25, + 1, + 25, + 1, + 25, + 1, + 26, + 1, + 26, + 1, + 26, + 5, + 26, + 160, + 8, + 26, + 10, + 26, + 12, + 26, + 163, + 9, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 1, + 26, + 4, + 26, + 180, + 8, + 26, + 11, + 26, + 12, + 26, + 181, + 1, + 26, + 1, + 26, + 4, + 26, + 186, + 8, + 26, + 11, + 26, + 12, + 26, + 187, + 3, + 26, + 190, + 8, + 26, + 3, + 26, + 192, + 8, + 26, + 1, + 27, + 4, + 27, + 195, + 8, + 27, + 11, + 27, + 12, + 27, + 196, + 1, + 27, + 1, + 27, + 0, + 0, + 28, + 1, + 1, + 3, + 2, + 5, + 3, + 7, + 4, + 9, + 5, + 11, + 6, + 13, + 7, + 15, + 8, + 17, + 9, + 19, + 10, + 21, + 11, + 23, + 12, + 25, + 13, + 27, + 14, + 29, + 15, + 31, + 16, + 33, + 17, + 35, + 18, + 37, + 19, + 39, + 20, + 41, + 21, + 43, + 22, + 45, + 0, + 47, + 0, + 49, + 0, + 51, + 23, + 53, + 24, + 55, + 25, + 1, + 0, + 20, + 2, + 0, + 69, + 69, + 101, + 101, + 2, + 0, + 81, + 81, + 113, + 113, + 2, + 0, + 78, + 78, + 110, + 110, + 2, + 0, + 67, + 67, + 99, + 99, + 2, + 0, + 79, + 79, + 111, + 111, + 2, + 0, + 83, + 83, + 115, + 115, + 2, + 0, + 87, + 87, + 119, + 119, + 2, + 0, + 80, + 80, + 112, + 112, + 2, + 0, + 82, + 82, + 114, + 114, + 2, + 0, + 71, + 71, + 103, + 103, + 2, + 0, + 84, + 84, + 116, + 116, + 2, + 0, + 76, + 76, + 108, + 108, + 2, + 0, + 65, + 65, + 97, + 97, + 2, + 0, + 68, + 68, + 100, + 100, + 2, + 0, + 45, + 45, + 95, + 95, + 2, + 0, + 45, + 46, + 95, + 95, + 1, + 0, + 48, + 57, + 2, + 0, + 65, + 90, + 97, + 122, + 1, + 0, + 34, + 34, + 4, + 0, + 8, + 10, + 13, + 13, + 32, + 32, + 124, + 124, + 221, + 0, + 1, + 1, + 0, + 0, + 0, + 0, + 3, + 1, + 0, + 0, + 0, + 0, + 5, + 1, + 0, + 0, + 0, + 0, + 7, + 1, + 0, + 0, + 0, + 0, + 9, + 1, + 0, + 0, + 0, + 0, + 11, + 1, + 0, + 0, + 0, + 0, + 13, + 1, + 0, + 0, + 0, + 0, + 15, + 1, + 0, + 0, + 0, + 0, + 17, + 1, + 0, + 0, + 0, + 0, + 19, + 1, + 0, + 0, + 0, + 0, + 21, + 1, + 0, + 0, + 0, + 0, + 23, + 1, + 0, + 0, + 0, + 0, + 25, + 1, + 0, + 0, + 0, + 0, + 27, + 1, + 0, + 0, + 0, + 0, + 29, + 1, + 0, + 0, + 0, + 0, + 31, + 1, + 0, + 0, + 0, + 0, + 33, + 1, + 0, + 0, + 0, + 0, + 35, + 1, + 0, + 0, + 0, + 0, + 37, + 1, + 0, + 0, + 0, + 0, + 39, + 1, + 0, + 0, + 0, + 0, + 41, + 1, + 0, + 0, + 0, + 0, + 43, + 1, + 0, + 0, + 0, + 0, + 51, + 1, + 0, + 0, + 0, + 0, + 53, + 1, + 0, + 0, + 0, + 0, + 55, + 1, + 0, + 0, + 0, + 1, + 57, + 1, + 0, + 0, + 0, + 3, + 59, + 1, + 0, + 0, + 0, + 5, + 61, + 1, + 0, + 0, + 0, + 7, + 63, + 1, + 0, + 0, + 0, + 9, + 65, + 1, + 0, + 0, + 0, + 11, + 76, + 1, + 0, + 0, + 0, + 13, + 78, + 1, + 0, + 0, + 0, + 15, + 81, + 1, + 0, + 0, + 0, + 17, + 84, + 1, + 0, + 0, + 0, + 19, + 87, + 1, + 0, + 0, + 0, + 21, + 90, + 1, + 0, + 0, + 0, + 23, + 93, + 1, + 0, + 0, + 0, + 25, + 96, + 1, + 0, + 0, + 0, + 27, + 99, + 1, + 0, + 0, + 0, + 29, + 102, + 1, + 0, + 0, + 0, + 31, + 105, + 1, + 0, + 0, + 0, + 33, + 108, + 1, + 0, + 0, + 0, + 35, + 112, + 1, + 0, + 0, + 0, + 37, + 116, + 1, + 0, + 0, + 0, + 39, + 119, + 1, + 0, + 0, + 0, + 41, + 121, + 1, + 0, + 0, + 0, + 43, + 133, + 1, + 0, + 0, + 0, + 45, + 144, + 1, + 0, + 0, + 0, + 47, + 148, + 1, + 0, + 0, + 0, + 49, + 151, + 1, + 0, + 0, + 0, + 51, + 153, + 1, + 0, + 0, + 0, + 53, + 191, + 1, + 0, + 0, + 0, + 55, + 194, + 1, + 0, + 0, + 0, + 57, + 58, + 5, + 40, + 0, + 0, + 58, + 2, + 1, + 0, + 0, + 0, + 59, + 60, + 5, + 41, + 0, + 0, + 60, + 4, + 1, + 0, + 0, + 0, + 61, + 62, + 5, + 91, + 0, + 0, + 62, + 6, + 1, + 0, + 0, + 0, + 63, + 64, + 5, + 93, + 0, + 0, + 64, + 8, + 1, + 0, + 0, + 0, + 65, + 66, + 5, + 46, + 0, + 0, + 66, + 10, + 1, + 0, + 0, + 0, + 67, + 77, + 3, + 13, + 6, + 0, + 68, + 77, + 3, + 15, + 7, + 0, + 69, + 77, + 3, + 17, + 8, + 0, + 70, + 77, + 3, + 19, + 9, + 0, + 71, + 77, + 3, + 21, + 10, + 0, + 72, + 77, + 3, + 25, + 12, + 0, + 73, + 77, + 3, + 27, + 13, + 0, + 74, + 77, + 3, + 29, + 14, + 0, + 75, + 77, + 3, + 31, + 15, + 0, + 76, + 67, + 1, + 0, + 0, + 0, + 76, + 68, + 1, + 0, + 0, + 0, + 76, + 69, + 1, + 0, + 0, + 0, + 76, + 70, + 1, + 0, + 0, + 0, + 76, + 71, + 1, + 0, + 0, + 0, + 76, + 72, + 1, + 0, + 0, + 0, + 76, + 73, + 1, + 0, + 0, + 0, + 76, + 74, + 1, + 0, + 0, + 0, + 76, + 75, + 1, + 0, + 0, + 0, + 77, + 12, + 1, + 0, + 0, + 0, + 78, + 79, + 7, + 0, + 0, + 0, + 79, + 80, + 7, + 1, + 0, + 0, + 80, + 14, + 1, + 0, + 0, + 0, + 81, + 82, + 7, + 2, + 0, + 0, + 82, + 83, + 7, + 0, + 0, + 0, + 83, + 16, + 1, + 0, + 0, + 0, + 84, + 85, + 7, + 3, + 0, + 0, + 85, + 86, + 7, + 4, + 0, + 0, + 86, + 18, + 1, + 0, + 0, + 0, + 87, + 88, + 7, + 5, + 0, + 0, + 88, + 89, + 7, + 6, + 0, + 0, + 89, + 20, + 1, + 0, + 0, + 0, + 90, + 91, + 7, + 0, + 0, + 0, + 91, + 92, + 7, + 6, + 0, + 0, + 92, + 22, + 1, + 0, + 0, + 0, + 93, + 94, + 7, + 7, + 0, + 0, + 94, + 95, + 7, + 8, + 0, + 0, + 95, + 24, + 1, + 0, + 0, + 0, + 96, + 97, + 7, + 9, + 0, + 0, + 97, + 98, + 7, + 10, + 0, + 0, + 98, + 26, + 1, + 0, + 0, + 0, + 99, + 100, + 7, + 9, + 0, + 0, + 100, + 101, + 7, + 0, + 0, + 0, + 101, + 28, + 1, + 0, + 0, + 0, + 102, + 103, + 7, + 11, + 0, + 0, + 103, + 104, + 7, + 10, + 0, + 0, + 104, + 30, + 1, + 0, + 0, + 0, + 105, + 106, + 7, + 11, + 0, + 0, + 106, + 107, + 7, + 0, + 0, + 0, + 107, + 32, + 1, + 0, + 0, + 0, + 108, + 109, + 7, + 2, + 0, + 0, + 109, + 110, + 7, + 4, + 0, + 0, + 110, + 111, + 7, + 10, + 0, + 0, + 111, + 34, + 1, + 0, + 0, + 0, + 112, + 113, + 7, + 12, + 0, + 0, + 113, + 114, + 7, + 2, + 0, + 0, + 114, + 115, + 7, + 13, + 0, + 0, + 115, + 36, + 1, + 0, + 0, + 0, + 116, + 117, + 7, + 4, + 0, + 0, + 117, + 118, + 7, + 8, + 0, + 0, + 118, + 38, + 1, + 0, + 0, + 0, + 119, + 120, + 5, + 32, + 0, + 0, + 120, + 40, + 1, + 0, + 0, + 0, + 121, + 122, + 5, + 117, + 0, + 0, + 122, + 123, + 5, + 114, + 0, + 0, + 123, + 124, + 5, + 110, + 0, + 0, + 124, + 125, + 5, + 58, + 0, + 0, + 125, + 129, + 1, + 0, + 0, + 0, + 126, + 127, + 3, + 45, + 22, + 0, + 127, + 128, + 5, + 58, + 0, + 0, + 128, + 130, + 1, + 0, + 0, + 0, + 129, + 126, + 1, + 0, + 0, + 0, + 130, + 131, + 1, + 0, + 0, + 0, + 131, + 129, + 1, + 0, + 0, + 0, + 131, + 132, + 1, + 0, + 0, + 0, + 132, + 42, + 1, + 0, + 0, + 0, + 133, + 137, + 3, + 49, + 24, + 0, + 134, + 138, + 3, + 49, + 24, + 0, + 135, + 138, + 3, + 47, + 23, + 0, + 136, + 138, + 7, + 14, + 0, + 0, + 137, + 134, + 1, + 0, + 0, + 0, + 137, + 135, + 1, + 0, + 0, + 0, + 137, + 136, + 1, + 0, + 0, + 0, + 138, + 139, + 1, + 0, + 0, + 0, + 139, + 137, + 1, + 0, + 0, + 0, + 139, + 140, + 1, + 0, + 0, + 0, + 140, + 44, + 1, + 0, + 0, + 0, + 141, + 145, + 3, + 49, + 24, + 0, + 142, + 145, + 3, + 47, + 23, + 0, + 143, + 145, + 7, + 15, + 0, + 0, + 144, + 141, + 1, + 0, + 0, + 0, + 144, + 142, + 1, + 0, + 0, + 0, + 144, + 143, + 1, + 0, + 0, + 0, + 145, + 146, + 1, + 0, + 0, + 0, + 146, + 144, + 1, + 0, + 0, + 0, + 146, + 147, + 1, + 0, + 0, + 0, + 147, + 46, + 1, + 0, + 0, + 0, + 148, + 149, + 7, + 16, + 0, + 0, + 149, + 48, + 1, + 0, + 0, + 0, + 150, + 152, + 7, + 17, + 0, + 0, + 151, + 150, + 1, + 0, + 0, + 0, + 152, + 50, + 1, + 0, + 0, + 0, + 153, + 154, + 5, + 92, + 0, + 0, + 154, + 155, + 5, + 34, + 0, + 0, + 155, + 52, + 1, + 0, + 0, + 0, + 156, + 161, + 5, + 34, + 0, + 0, + 157, + 160, + 3, + 51, + 25, + 0, + 158, + 160, + 8, + 18, + 0, + 0, + 159, + 157, + 1, + 0, + 0, + 0, + 159, + 158, + 1, + 0, + 0, + 0, + 160, + 163, + 1, + 0, + 0, + 0, + 161, + 159, + 1, + 0, + 0, + 0, + 161, + 162, + 1, + 0, + 0, + 0, + 162, + 164, + 1, + 0, + 0, + 0, + 163, + 161, + 1, + 0, + 0, + 0, + 164, + 192, + 5, + 34, + 0, + 0, + 165, + 166, + 5, + 116, + 0, + 0, + 166, + 167, + 5, + 114, + 0, + 0, + 167, + 168, + 5, + 117, + 0, + 0, + 168, + 192, + 5, + 101, + 0, + 0, + 169, + 170, + 5, + 102, + 0, + 0, + 170, + 171, + 5, + 97, + 0, + 0, + 171, + 172, + 5, + 108, + 0, + 0, + 172, + 173, + 5, + 115, + 0, + 0, + 173, + 192, + 5, + 101, + 0, + 0, + 174, + 175, + 5, + 110, + 0, + 0, + 175, + 176, + 5, + 117, + 0, + 0, + 176, + 177, + 5, + 108, + 0, + 0, + 177, + 192, + 5, + 108, + 0, + 0, + 178, + 180, + 3, + 47, + 23, + 0, + 179, + 178, + 1, + 0, + 0, + 0, + 180, + 181, + 1, + 0, + 0, + 0, + 181, + 179, + 1, + 0, + 0, + 0, + 181, + 182, + 1, + 0, + 0, + 0, + 182, + 189, + 1, + 0, + 0, + 0, + 183, + 185, + 5, + 46, + 0, + 0, + 184, + 186, + 3, + 47, + 23, + 0, + 185, + 184, + 1, + 0, + 0, + 0, + 186, + 187, + 1, + 0, + 0, + 0, + 187, + 185, + 1, + 0, + 0, + 0, + 187, + 188, + 1, + 0, + 0, + 0, + 188, + 190, + 1, + 0, + 0, + 0, + 189, + 183, + 1, + 0, + 0, + 0, + 189, + 190, + 1, + 0, + 0, + 0, + 190, + 192, + 1, + 0, + 0, + 0, + 191, + 156, + 1, + 0, + 0, + 0, + 191, + 165, + 1, + 0, + 0, + 0, + 191, + 169, + 1, + 0, + 0, + 0, + 191, + 174, + 1, + 0, + 0, + 0, + 191, + 179, + 1, + 0, + 0, + 0, + 192, + 54, + 1, + 0, + 0, + 0, + 193, + 195, + 7, + 19, + 0, + 0, + 194, + 193, + 1, + 0, + 0, + 0, + 195, + 196, + 1, + 0, + 0, + 0, + 196, + 194, + 1, + 0, + 0, + 0, + 196, + 197, + 1, + 0, + 0, + 0, + 197, + 198, + 1, + 0, + 0, + 0, + 198, + 199, + 6, + 27, + 0, + 0, + 199, + 56, + 1, + 0, + 0, + 0, + 15, + 0, + 76, + 131, + 137, + 139, + 144, + 146, + 151, + 159, + 161, + 181, + 187, + 189, + 191, + 196, + 1, + 6, + 0, + 0, + ] + + +class ScimFilterLexer(Lexer): + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [DFA(ds, i) for i, ds in enumerate(atn.decisionToState)] + + 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 + + channelNames = ["DEFAULT_TOKEN_CHANNEL", "HIDDEN"] + + modeNames = ["DEFAULT_MODE"] + + literalNames = ["", "'('", "')'", "'['", "']'", "'.'", "' '", "'\\\"'"] + + symbolicNames = [ + "", + "COMPAREOPERATOR", + "EQ", + "NE", + "CO", + "SW", + "EW", + "PR", + "GT", + "GE", + "LT", + "LE", + "NOT", + "AND", + "OR", + "SP", + "SCHEMA", + "ATTRNAME", + "ESCAPED_QUOTE", + "VALUE", + "EXCLUDE", + ] + + ruleNames = [ + "T__0", + "T__1", + "T__2", + "T__3", + "T__4", + "COMPAREOPERATOR", + "EQ", + "NE", + "CO", + "SW", + "EW", + "PR", + "GT", + "GE", + "LT", + "LE", + "NOT", + "AND", + "OR", + "SP", + "SCHEMA", + "ATTRNAME", + "SEGMENT", + "DIGIT", + "ALPHA", + "ESCAPED_QUOTE", + "VALUE", + "EXCLUDE", + ] + + grammarFileName = "ScimFilter.g4" + + def __init__(self, input=None, output: TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.10.1") + self._interp = LexerATNSimulator( + self, self.atn, self.decisionsToDFA, PredictionContextCache() + ) + self._actions = None + self._predicates = None diff --git a/authentik/sources/scim/filters/ScimFilterLexer.tokens b/authentik/sources/scim/filters/ScimFilterLexer.tokens new file mode 100644 index 000000000..28b93c87f --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilterLexer.tokens @@ -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 diff --git a/authentik/sources/scim/filters/ScimFilterListener.py b/authentik/sources/scim/filters/ScimFilterListener.py new file mode 100644 index 000000000..833f0f305 --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilterListener.py @@ -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 diff --git a/authentik/sources/scim/filters/ScimFilterParser.py b/authentik/sources/scim/filters/ScimFilterParser.py new file mode 100644 index 000000000..afd00cf1d --- /dev/null +++ b/authentik/sources/scim/filters/ScimFilterParser.py @@ -0,0 +1,1814 @@ +# pylint: skip-file +# Generated from ScimFilter.g4 by ANTLR 4.10.1 +# encoding: utf-8 +import sys +from io import StringIO + +from antlr4 import * + +if sys.version_info[1] > 5: + from typing import TextIO +else: + from typing.io import TextIO + + +def serializedATN(): + return [ + 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, + ] + + +class ScimFilterParser(Parser): + + grammarFileName = "ScimFilter.g4" + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [DFA(ds, i) for i, ds in enumerate(atn.decisionToState)] + + sharedContextCache = PredictionContextCache() + + literalNames = [ + "", + "'('", + "')'", + "'['", + "']'", + "'.'", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "' '", + "", + "", + "'\\\"'", + ] + + symbolicNames = [ + "", + "", + "", + "", + "", + "", + "COMPAREOPERATOR", + "EQ", + "NE", + "CO", + "SW", + "EW", + "PR", + "GT", + "GE", + "LT", + "LE", + "NOT", + "AND", + "OR", + "SP", + "SCHEMA", + "ATTRNAME", + "ESCAPED_QUOTE", + "VALUE", + "EXCLUDE", + ] + + RULE_parse = 0 + RULE_filter = 1 + RULE_valPathFilter = 2 + RULE_attrPath = 3 + + ruleNames = ["parse", "filter", "valPathFilter", "attrPath"] + + EOF = Token.EOF + 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 + + def __init__(self, input: TokenStream, output: TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.10.1") + self._interp = ParserATNSimulator( + self, self.atn, self.decisionsToDFA, self.sharedContextCache + ) + self._predicates = None + + class ParseContext(ParserRuleContext): + __slots__ = "parser" + + def __init__(self, parser, parent: ParserRuleContext = None, invokingState: int = -1): + super().__init__(parent, invokingState) + self.parser = parser + + def filter_(self): + return self.getTypedRuleContext(ScimFilterParser.FilterContext, 0) + + def getRuleIndex(self): + return ScimFilterParser.RULE_parse + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterParse"): + listener.enterParse(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitParse"): + listener.exitParse(self) + + def parse(self): + + localctx = ScimFilterParser.ParseContext(self, self._ctx, self.state) + self.enterRule(localctx, 0, self.RULE_parse) + try: + self.enterOuterAlt(localctx, 1) + self.state = 8 + self.filter_(0) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class FilterContext(ParserRuleContext): + __slots__ = "parser" + + def __init__(self, parser, parent: ParserRuleContext = None, invokingState: int = -1): + super().__init__(parent, invokingState) + self.parser = parser + + def getRuleIndex(self): + return ScimFilterParser.RULE_filter + + def copyFrom(self, ctx: ParserRuleContext): + super().copyFrom(ctx) + + class AndExpContext(FilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.FilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def filter_(self, i: int = None): + if i is None: + return self.getTypedRuleContexts(ScimFilterParser.FilterContext) + else: + return self.getTypedRuleContext(ScimFilterParser.FilterContext, i) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def AND(self): + return self.getToken(ScimFilterParser.AND, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterAndExp"): + listener.enterAndExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitAndExp"): + listener.exitAndExp(self) + + class ValPathExpContext(FilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.FilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def attrPath(self): + return self.getTypedRuleContext(ScimFilterParser.AttrPathContext, 0) + + def valPathFilter(self): + return self.getTypedRuleContext(ScimFilterParser.ValPathFilterContext, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterValPathExp"): + listener.enterValPathExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitValPathExp"): + listener.exitValPathExp(self) + + class PresentExpContext(FilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.FilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def attrPath(self): + return self.getTypedRuleContext(ScimFilterParser.AttrPathContext, 0) + + def SP(self): + return self.getToken(ScimFilterParser.SP, 0) + + def PR(self): + return self.getToken(ScimFilterParser.PR, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterPresentExp"): + listener.enterPresentExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitPresentExp"): + listener.exitPresentExp(self) + + class OperatorExpContext(FilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.FilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def attrPath(self): + return self.getTypedRuleContext(ScimFilterParser.AttrPathContext, 0) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def COMPAREOPERATOR(self): + return self.getToken(ScimFilterParser.COMPAREOPERATOR, 0) + + def VALUE(self): + return self.getToken(ScimFilterParser.VALUE, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterOperatorExp"): + listener.enterOperatorExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitOperatorExp"): + listener.exitOperatorExp(self) + + class BraceExpContext(FilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.FilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def filter_(self): + return self.getTypedRuleContext(ScimFilterParser.FilterContext, 0) + + def NOT(self): + return self.getToken(ScimFilterParser.NOT, 0) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterBraceExp"): + listener.enterBraceExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitBraceExp"): + listener.exitBraceExp(self) + + class OrExpContext(FilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.FilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def filter_(self, i: int = None): + if i is None: + return self.getTypedRuleContexts(ScimFilterParser.FilterContext) + else: + return self.getTypedRuleContext(ScimFilterParser.FilterContext, i) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def OR(self): + return self.getToken(ScimFilterParser.OR, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterOrExp"): + listener.enterOrExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitOrExp"): + listener.exitOrExp(self) + + def filter_(self, _p: int = 0): + _parentctx = self._ctx + _parentState = self.state + localctx = ScimFilterParser.FilterContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 2 + self.enterRecursionRule(localctx, 2, self.RULE_filter, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 39 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input, 2, self._ctx) + if la_ == 1: + localctx = ScimFilterParser.PresentExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 11 + self.attrPath() + self.state = 12 + self.match(ScimFilterParser.SP) + self.state = 13 + self.match(ScimFilterParser.PR) + pass + + elif la_ == 2: + localctx = ScimFilterParser.OperatorExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 15 + self.attrPath() + self.state = 16 + self.match(ScimFilterParser.SP) + self.state = 17 + self.match(ScimFilterParser.COMPAREOPERATOR) + self.state = 18 + self.match(ScimFilterParser.SP) + self.state = 19 + self.match(ScimFilterParser.VALUE) + pass + + elif la_ == 3: + localctx = ScimFilterParser.BraceExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 22 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la == ScimFilterParser.NOT: + self.state = 21 + self.match(ScimFilterParser.NOT) + + self.state = 27 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la == ScimFilterParser.SP: + self.state = 24 + self.match(ScimFilterParser.SP) + self.state = 29 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 30 + self.match(ScimFilterParser.T__0) + self.state = 31 + self.filter_(0) + self.state = 32 + self.match(ScimFilterParser.T__1) + pass + + elif la_ == 4: + localctx = ScimFilterParser.ValPathExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 34 + self.attrPath() + self.state = 35 + self.match(ScimFilterParser.T__2) + self.state = 36 + self.valPathFilter(0) + self.state = 37 + self.match(ScimFilterParser.T__3) + pass + + self._ctx.stop = self._input.LT(-1) + self.state = 53 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 4, self._ctx) + while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: + if _alt == 1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 51 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input, 3, self._ctx) + if la_ == 1: + localctx = ScimFilterParser.AndExpContext( + self, ScimFilterParser.FilterContext(self, _parentctx, _parentState) + ) + self.pushNewRecursionContext(localctx, _startState, self.RULE_filter) + self.state = 41 + if not self.precpred(self._ctx, 2): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + self.state = 42 + self.match(ScimFilterParser.SP) + self.state = 43 + self.match(ScimFilterParser.AND) + self.state = 44 + self.match(ScimFilterParser.SP) + self.state = 45 + self.filter_(3) + pass + + elif la_ == 2: + localctx = ScimFilterParser.OrExpContext( + self, ScimFilterParser.FilterContext(self, _parentctx, _parentState) + ) + self.pushNewRecursionContext(localctx, _startState, self.RULE_filter) + self.state = 46 + if not self.precpred(self._ctx, 1): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException(self, "self.precpred(self._ctx, 1)") + self.state = 47 + self.match(ScimFilterParser.SP) + self.state = 48 + self.match(ScimFilterParser.OR) + self.state = 49 + self.match(ScimFilterParser.SP) + self.state = 50 + self.filter_(2) + pass + + self.state = 55 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 4, self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + class ValPathFilterContext(ParserRuleContext): + __slots__ = "parser" + + def __init__(self, parser, parent: ParserRuleContext = None, invokingState: int = -1): + super().__init__(parent, invokingState) + self.parser = parser + + def getRuleIndex(self): + return ScimFilterParser.RULE_valPathFilter + + def copyFrom(self, ctx: ParserRuleContext): + super().copyFrom(ctx) + + class ValPathOperatorExpContext(ValPathFilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.ValPathFilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def attrPath(self): + return self.getTypedRuleContext(ScimFilterParser.AttrPathContext, 0) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def COMPAREOPERATOR(self): + return self.getToken(ScimFilterParser.COMPAREOPERATOR, 0) + + def VALUE(self): + return self.getToken(ScimFilterParser.VALUE, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterValPathOperatorExp"): + listener.enterValPathOperatorExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitValPathOperatorExp"): + listener.exitValPathOperatorExp(self) + + class ValPathPresentExpContext(ValPathFilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.ValPathFilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def attrPath(self): + return self.getTypedRuleContext(ScimFilterParser.AttrPathContext, 0) + + def SP(self): + return self.getToken(ScimFilterParser.SP, 0) + + def PR(self): + return self.getToken(ScimFilterParser.PR, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterValPathPresentExp"): + listener.enterValPathPresentExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitValPathPresentExp"): + listener.exitValPathPresentExp(self) + + class ValPathAndExpContext(ValPathFilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.ValPathFilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def valPathFilter(self, i: int = None): + if i is None: + return self.getTypedRuleContexts(ScimFilterParser.ValPathFilterContext) + else: + return self.getTypedRuleContext(ScimFilterParser.ValPathFilterContext, i) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def AND(self): + return self.getToken(ScimFilterParser.AND, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterValPathAndExp"): + listener.enterValPathAndExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitValPathAndExp"): + listener.exitValPathAndExp(self) + + class ValPathOrExpContext(ValPathFilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.ValPathFilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def valPathFilter(self, i: int = None): + if i is None: + return self.getTypedRuleContexts(ScimFilterParser.ValPathFilterContext) + else: + return self.getTypedRuleContext(ScimFilterParser.ValPathFilterContext, i) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def OR(self): + return self.getToken(ScimFilterParser.OR, 0) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterValPathOrExp"): + listener.enterValPathOrExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitValPathOrExp"): + listener.exitValPathOrExp(self) + + class ValPathBraceExpContext(ValPathFilterContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ScimFilterParser.ValPathFilterContext + super().__init__(parser) + self.copyFrom(ctx) + + def valPathFilter(self): + return self.getTypedRuleContext(ScimFilterParser.ValPathFilterContext, 0) + + def NOT(self): + return self.getToken(ScimFilterParser.NOT, 0) + + def SP(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.SP) + else: + return self.getToken(ScimFilterParser.SP, i) + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterValPathBraceExp"): + listener.enterValPathBraceExp(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitValPathBraceExp"): + listener.exitValPathBraceExp(self) + + def valPathFilter(self, _p: int = 0): + _parentctx = self._ctx + _parentState = self.state + localctx = ScimFilterParser.ValPathFilterContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 4 + self.enterRecursionRule(localctx, 4, self.RULE_valPathFilter, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 80 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input, 7, self._ctx) + if la_ == 1: + localctx = ScimFilterParser.ValPathPresentExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 57 + self.attrPath() + self.state = 58 + self.match(ScimFilterParser.SP) + self.state = 59 + self.match(ScimFilterParser.PR) + pass + + elif la_ == 2: + localctx = ScimFilterParser.ValPathOperatorExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 61 + self.attrPath() + self.state = 62 + self.match(ScimFilterParser.SP) + self.state = 63 + self.match(ScimFilterParser.COMPAREOPERATOR) + self.state = 64 + self.match(ScimFilterParser.SP) + self.state = 65 + self.match(ScimFilterParser.VALUE) + pass + + elif la_ == 3: + localctx = ScimFilterParser.ValPathBraceExpContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 68 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la == ScimFilterParser.NOT: + self.state = 67 + self.match(ScimFilterParser.NOT) + + self.state = 73 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la == ScimFilterParser.SP: + self.state = 70 + self.match(ScimFilterParser.SP) + self.state = 75 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 76 + self.match(ScimFilterParser.T__0) + self.state = 77 + self.valPathFilter(0) + self.state = 78 + self.match(ScimFilterParser.T__1) + pass + + self._ctx.stop = self._input.LT(-1) + self.state = 94 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) + while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: + if _alt == 1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 92 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input, 8, self._ctx) + if la_ == 1: + localctx = ScimFilterParser.ValPathAndExpContext( + self, + ScimFilterParser.ValPathFilterContext(self, _parentctx, _parentState), + ) + self.pushNewRecursionContext(localctx, _startState, self.RULE_valPathFilter) + self.state = 82 + if not self.precpred(self._ctx, 2): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + self.state = 83 + self.match(ScimFilterParser.SP) + self.state = 84 + self.match(ScimFilterParser.AND) + self.state = 85 + self.match(ScimFilterParser.SP) + self.state = 86 + self.valPathFilter(3) + pass + + elif la_ == 2: + localctx = ScimFilterParser.ValPathOrExpContext( + self, + ScimFilterParser.ValPathFilterContext(self, _parentctx, _parentState), + ) + self.pushNewRecursionContext(localctx, _startState, self.RULE_valPathFilter) + self.state = 87 + if not self.precpred(self._ctx, 1): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException(self, "self.precpred(self._ctx, 1)") + self.state = 88 + self.match(ScimFilterParser.SP) + self.state = 89 + self.match(ScimFilterParser.OR) + self.state = 90 + self.match(ScimFilterParser.SP) + self.state = 91 + self.valPathFilter(2) + pass + + self.state = 96 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + class AttrPathContext(ParserRuleContext): + __slots__ = "parser" + + def __init__(self, parser, parent: ParserRuleContext = None, invokingState: int = -1): + super().__init__(parent, invokingState) + self.parser = parser + + def ATTRNAME(self, i: int = None): + if i is None: + return self.getTokens(ScimFilterParser.ATTRNAME) + else: + return self.getToken(ScimFilterParser.ATTRNAME, i) + + def SCHEMA(self): + return self.getToken(ScimFilterParser.SCHEMA, 0) + + def getRuleIndex(self): + return ScimFilterParser.RULE_attrPath + + def enterRule(self, listener: ParseTreeListener): + if hasattr(listener, "enterAttrPath"): + listener.enterAttrPath(self) + + def exitRule(self, listener: ParseTreeListener): + if hasattr(listener, "exitAttrPath"): + listener.exitAttrPath(self) + + def attrPath(self): + + localctx = ScimFilterParser.AttrPathContext(self, self._ctx, self.state) + self.enterRule(localctx, 6, self.RULE_attrPath) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 98 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la == ScimFilterParser.SCHEMA: + self.state = 97 + self.match(ScimFilterParser.SCHEMA) + + self.state = 100 + self.match(ScimFilterParser.ATTRNAME) + self.state = 103 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la == ScimFilterParser.T__4: + self.state = 101 + self.match(ScimFilterParser.T__4) + self.state = 102 + self.match(ScimFilterParser.ATTRNAME) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + def sempred(self, localctx: RuleContext, ruleIndex: int, predIndex: int): + if self._predicates == None: + self._predicates = dict() + self._predicates[1] = self.filter_sempred + self._predicates[2] = self.valPathFilter_sempred + pred = self._predicates.get(ruleIndex, None) + if pred is None: + raise Exception("No predicate with index:" + str(ruleIndex)) + else: + return pred(localctx, predIndex) + + def filter_sempred(self, localctx: FilterContext, predIndex: int): + if predIndex == 0: + return self.precpred(self._ctx, 2) + + if predIndex == 1: + return self.precpred(self._ctx, 1) + + def valPathFilter_sempred(self, localctx: ValPathFilterContext, predIndex: int): + if predIndex == 2: + return self.precpred(self._ctx, 2) + + if predIndex == 3: + return self.precpred(self._ctx, 1) diff --git a/authentik/sources/scim/filters/__init__.py b/authentik/sources/scim/filters/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/sources/scim/filters/django.py b/authentik/sources/scim/filters/django.py new file mode 100644 index 000000000..348b1ab89 --- /dev/null +++ b/authentik/sources/scim/filters/django.py @@ -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() diff --git a/authentik/sources/scim/views/v2/auth.py b/authentik/sources/scim/views/v2/auth.py index 9ff46eec0..990d43cda 100644 --- a/authentik/sources/scim/views/v2/auth.py +++ b/authentik/sources/scim/views/v2/auth.py @@ -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) diff --git a/authentik/sources/scim/views/v2/base.py b/authentik/sources/scim/views/v2/base.py index c35d9fa56..dba7347b6 100644 --- a/authentik/sources/scim/views/v2/base.py +++ b/authentik/sources/scim/views/v2/base.py @@ -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""" diff --git a/authentik/sources/scim/views/v2/groups.py b/authentik/sources/scim/views/v2/groups.py index e3effd3ba..16680ea2c 100644 --- a/authentik/sources/scim/views/v2/groups.py +++ b/authentik/sources/scim/views/v2/groups.py @@ -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""" diff --git a/poetry.lock b/poetry.lock index 034ec6daa..63a99771e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml index 596c0a9b4..b247378af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,6 +118,7 @@ description = "" authors = ["authentik Team "] [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 = "*" diff --git a/test.py b/test.py new file mode 100644 index 000000000..582e51aba --- /dev/null +++ b/test.py @@ -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) diff --git a/website/integrations/sources/oauth/index.md b/website/docs/sources/oauth.md similarity index 100% rename from website/integrations/sources/oauth/index.md rename to website/docs/sources/oauth.md diff --git a/website/integrations/sources/saml/index.md b/website/docs/sources/saml.md similarity index 99% rename from website/integrations/sources/saml/index.md rename to website/docs/sources/saml.md index 7bc945345..8b33e0c90 100644 --- a/website/integrations/sources/saml/index.md +++ b/website/docs/sources/saml.md @@ -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. diff --git a/website/docs/sources/scim.md b/website/docs/sources/scim.md new file mode 100644 index 000000000..32897c409 --- /dev/null +++ b/website/docs/sources/scim.md @@ -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//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. diff --git a/website/sidebars.js b/website/sidebars.js index 310eeba17..b202f7e43 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -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", + ], + }, ], }, { diff --git a/website/sidebarsIntegrations.js b/website/sidebarsIntegrations.js index bc857116b..2223c11d5 100644 --- a/website/sidebarsIntegrations.js +++ b/website/sidebarsIntegrations.js @@ -166,6 +166,7 @@ module.exports = { "sources/ldap/index", "sources/oauth/index", "sources/saml/index", + "sources/scim/index", ], }, {