From de2dfba395c58035e6c186087d3ef5c19dbf05f4 Mon Sep 17 00:00:00 2001 From: florindragos Date: Tue, 18 Feb 2025 17:46:18 +0200 Subject: [PATCH 1/5] allow inverted identity relations --- go.mod | 30 +++++++------- go.sum | 65 +++++++++++++++++++------------ pkg/app/handlers/users/delete.go | 38 +++++++++++++----- pkg/app/handlers/users/handler.go | 18 +++++++-- pkg/config/config.go | 20 +++++----- 5 files changed, 110 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 1d3305e..3679e32 100644 --- a/go.mod +++ b/go.mod @@ -1,45 +1,45 @@ module github.com/aserto-dev/scim -go 1.22.10 +go 1.22.11 toolchain go1.23.4 require ( - github.com/aserto-dev/errors v0.0.12 - github.com/aserto-dev/go-aserto v0.33.5 - github.com/aserto-dev/go-directory v0.33.3 + github.com/aserto-dev/errors v0.0.13 + github.com/aserto-dev/go-aserto v0.33.6 + github.com/aserto-dev/go-directory v0.33.4 github.com/aserto-dev/logger v0.0.6 github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 + github.com/samber/lo v1.47.0 github.com/scim2/filter-parser/v2 v2.2.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - google.golang.org/protobuf v1.36.0 + google.golang.org/protobuf v1.36.3 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1 // indirect - github.com/aserto-dev/header v0.0.8 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.3-20241127180247-a33202765966.1 // indirect + github.com/aserto-dev/header v0.0.10 // indirect github.com/di-wu/parser v0.3.0 // indirect github.com/di-wu/xsd-datetime v1.0.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/samber/lo v1.47.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -48,12 +48,12 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect - google.golang.org/grpc v1.68.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect + google.golang.org/grpc v1.70.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4ab9789..7c8fecf 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1 h1:jLd96rDDNJ+zIJxvV/L855VEtrjR0G4aePVDlCpf6kw= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1/go.mod h1:mnHCFccv4HwuIAOHNGdiIc5ZYbBCvbTWZcodLN5wITI= -github.com/aserto-dev/errors v0.0.12 h1:wjLiAlLLNu5wWDtPO09G3z2ULMj9XZDsk3L7VqPfvtQ= -github.com/aserto-dev/errors v0.0.12/go.mod h1:iEg8Q7XftdSsBLA1ok4q5Bor6G0MzsmzF3Aa0y5fUT0= -github.com/aserto-dev/go-aserto v0.33.5 h1:bQ8VsuhS5F6a8fSdvGE+FqeinN2n3Gx7qhexj7p+N9Q= -github.com/aserto-dev/go-aserto v0.33.5/go.mod h1:y5ntLwbkcOESdSSwSBjgfu9k8ecm9UKNLKxF75dwt1k= -github.com/aserto-dev/go-directory v0.33.3 h1:vlC9ScgqoysHAiHEfLBaEaWeeaaxZjC47HGIuy55IHw= -github.com/aserto-dev/go-directory v0.33.3/go.mod h1:tPA1V01LANAerbJoEPS2ZcO25Aa/ZtbqkgOgmf/jN6k= -github.com/aserto-dev/header v0.0.8 h1:T052WblWFZ/5Mg3MphHylE3sZobdIQpdj5cP3sPMhL8= -github.com/aserto-dev/header v0.0.8/go.mod h1:wmWm+omABTWf6QRRmw9yOdvgTstk/vYDqIA1duR8Pus= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.3-20241127180247-a33202765966.1 h1:cQZXKoQ+eB0kykzfJe80RP3nc+3PWbbBrUBm8XNYAQY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.3-20241127180247-a33202765966.1/go.mod h1:6VPKM8zbmgf9qsmkmKeH49a36Vtmidw3rG53B5mTenc= +github.com/aserto-dev/errors v0.0.13 h1:STx3azu5kymLiCCIwtPxqKvTF9LeyE6O3YP634Tapp8= +github.com/aserto-dev/errors v0.0.13/go.mod h1:0KXrbZV0/0mqyuUSv7IxuhIg0cHY0p1eOAXAWbXs1SM= +github.com/aserto-dev/go-aserto v0.33.6 h1:Ch64weFFYE3zMcorb3X8qkg3cs9LzJURankGkH3uwgU= +github.com/aserto-dev/go-aserto v0.33.6/go.mod h1:l8/lgmhuGJavgZIFUQ/mtl3q98zJy0gIS4d1oIm8Yq0= +github.com/aserto-dev/go-directory v0.33.4 h1:LhAL0RGKB7L2dm4hjAYVeuUhsu+EreYTUvu28t2OeXk= +github.com/aserto-dev/go-directory v0.33.4/go.mod h1:p0wsjtpBBW2huPDgi6I8OqfhwJWyMRqJHlwPVb3kTSM= +github.com/aserto-dev/header v0.0.10 h1:H6sz3F4pfv53FuyGNoZlRNHpAcOonTioQMnWRowyigU= +github.com/aserto-dev/header v0.0.10/go.mod h1:N3+nmX6nXmM9gI8VsGXOujPW6aW/8aEFa7dSu0FRerY= github.com/aserto-dev/logger v0.0.6 h1:C5u4eU6LJAlyWOjkz/IZmkIXfOH0SBomOHU74o6mUEc= github.com/aserto-dev/logger v0.0.6/go.mod h1:0wakoQsaQiagtzLxqyOus7ITaY0P5n5MWoQo6GbenWY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -29,6 +29,10 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -36,8 +40,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -53,8 +57,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -109,29 +114,39 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= -google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= -google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= -google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4 h1://y4MHaM7tNLqTeWKyfBIeoAMxwKwRm/nODb5IKA3BE= +google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/pkg/app/handlers/users/delete.go b/pkg/app/handlers/users/delete.go index 6d82f09..a0c26bb 100644 --- a/pkg/app/handlers/users/delete.go +++ b/pkg/app/handlers/users/delete.go @@ -4,11 +4,13 @@ import ( "net/http" cerr "github.com/aserto-dev/errors" + dsc "github.com/aserto-dev/go-directory/aserto/directory/common/v3" dsr "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" dsw "github.com/aserto-dev/go-directory/aserto/directory/writer/v3" "github.com/aserto-dev/go-directory/pkg/derr" serrors "github.com/elimity-com/scim/errors" "github.com/pkg/errors" + "github.com/samber/lo" ) func (u UsersResourceHandler) Delete(r *http.Request, id string) error { @@ -24,16 +26,34 @@ func (u UsersResourceHandler) Delete(r *http.Request, id string) error { return err } - for _, v := range relations.Results { - if v.Relation == u.cfg.SCIM.IdentityRelation { - _, err = u.dirClient.Writer.DeleteObject(r.Context(), &dsw.DeleteObjectRequest{ - ObjectId: v.ObjectId, - ObjectType: v.ObjectType, - WithRelations: true, - }) - if err != nil { - return err + var identities []*dsc.Relation + if u.cfg.SCIM.InvertIdentityRelation { + resp, err := u.dirClient.Reader.GetRelations(r.Context(), &dsr.GetRelationsRequest{ + SubjectType: u.cfg.SCIM.IdentityObjectType, + Relation: u.cfg.SCIM.IdentityRelation, + ObjectId: id, + }) + if err != nil { + if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { + return serrors.ScimErrorResourceNotFound(id) } + return err + } + identities = resp.Results + } else { + identities = lo.Filter(relations.Results, func(rel *dsc.Relation, i int) bool { + return rel.Relation == u.cfg.SCIM.IdentityRelation + }) + } + + for _, v := range identities { + _, err = u.dirClient.Writer.DeleteObject(r.Context(), &dsw.DeleteObjectRequest{ + ObjectId: v.ObjectId, + ObjectType: v.ObjectType, + WithRelations: true, + }) + if err != nil { + return err } } diff --git a/pkg/app/handlers/users/handler.go b/pkg/app/handlers/users/handler.go index dbc1338..e769872 100644 --- a/pkg/app/handlers/users/handler.go +++ b/pkg/app/handlers/users/handler.go @@ -155,14 +155,26 @@ func (u UsersResourceHandler) setIdentity(ctx context.Context, userID, identity return err } - _, err = u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{ - Relation: &dsc.Relation{ + var rel *dsc.Relation + if u.cfg.SCIM.InvertIdentityRelation { + rel = &dsc.Relation{ + SubjectId: identity, + SubjectType: u.cfg.SCIM.IdentityObjectType, + Relation: u.cfg.SCIM.IdentityRelation, + ObjectType: u.cfg.SCIM.UserObjectType, + ObjectId: userID, + } + } else { + rel = &dsc.Relation{ SubjectId: userID, SubjectType: u.cfg.SCIM.UserObjectType, Relation: u.cfg.SCIM.IdentityRelation, ObjectType: u.cfg.SCIM.IdentityObjectType, ObjectId: identity, - }}) + } + } + + _, err = u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{Relation: rel}) return err } diff --git a/pkg/config/config.go b/pkg/config/config.go index ec9d423..84690f7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -26,15 +26,16 @@ type Config struct { } `json:"server"` SCIM struct { - CreateEmailIdentities bool `json:"create_email_identities"` - CreateRoleGroups bool `json:"create_role_groups"` - GroupMappings []ObjectMapping `json:"group_mappings"` - UserMappings []ObjectMapping `json:"user_mappings"` - UserObjectType string `json:"user_object_type"` - GroupMemberRelation string `json:"group_member_relation"` - GroupObjectType string `json:"group_object_type"` - IdentityObjectType string `json:"identity_object_type"` - IdentityRelation string `json:"identity_relation"` + CreateEmailIdentities bool `json:"create_email_identities"` + CreateRoleGroups bool `json:"create_role_groups"` + GroupMappings []ObjectMapping `json:"group_mappings"` + UserMappings []ObjectMapping `json:"user_mappings"` + UserObjectType string `json:"user_object_type"` + GroupMemberRelation string `json:"group_member_relation"` + GroupObjectType string `json:"group_object_type"` + IdentityObjectType string `json:"identity_object_type"` + IdentityRelation string `json:"identity_relation"` + InvertIdentityRelation bool `json:"invert_identity_relation"` } `json:"scim"` } @@ -92,6 +93,7 @@ func NewConfig(configPath string) (*Config, error) { // nolint // function will v.SetDefault("scim.identity_relation", "identifier") v.SetDefault("scim.group_object_type", "group") v.SetDefault("scim.group_member_relation", "member") + v.SetDefault("scim.invert_identity_relation", false) // Allow setting via env vars. v.SetDefault("directory.api_key", "") From f8fe4db5c7704d441e8b9e501657b0cd2ff9b90e Mon Sep 17 00:00:00 2001 From: florindragos Date: Fri, 21 Feb 2025 17:09:36 +0200 Subject: [PATCH 2/5] add logging --- pkg/app/handlers/users/create.go | 13 ++++++++++--- pkg/app/handlers/users/delete.go | 8 +++++++- pkg/app/handlers/users/get.go | 8 ++++++-- pkg/app/handlers/users/handler.go | 12 ++++++++++++ pkg/app/handlers/users/patch.go | 4 ++++ pkg/app/handlers/users/replace.go | 8 +++++++- 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/pkg/app/handlers/users/create.go b/pkg/app/handlers/users/create.go index 883a38f..93af0d1 100644 --- a/pkg/app/handlers/users/create.go +++ b/pkg/app/handlers/users/create.go @@ -13,19 +13,26 @@ import ( ) func (u UsersResourceHandler) Create(r *http.Request, attributes scim.ResourceAttributes) (scim.Resource, error) { - u.logger.Trace().Any("attributes", attributes).Msg("creating user") + if attributes["id"] == nil || attributes["id"].(string) == "" { + return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") + } + + logger := u.logger.With().Str("method", "Create").Str("id", attributes["id"].(string)).Logger() + logger.Info().Msg("create user") + logger.Trace().Any("attributes", attributes).Msg("creating user") user, err := common.ResourceAttributesToUser(attributes) if err != nil { - u.logger.Error().Err(err).Msg("failed to convert attributes to user") + logger.Error().Err(err).Msg("failed to convert attributes to user") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } object, err := common.UserToObject(user) if err != nil { - u.logger.Error().Err(err).Msg("failed to convert user to object") + logger.Error().Err(err).Msg("failed to convert user to object") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } + logger.Trace().Any("object", object).Msg("creating user object") resp, err := u.dirClient.Writer.SetObject(r.Context(), &dsw.SetObjectRequest{ Object: object, }) diff --git a/pkg/app/handlers/users/delete.go b/pkg/app/handlers/users/delete.go index a0c26bb..c5d66ea 100644 --- a/pkg/app/handlers/users/delete.go +++ b/pkg/app/handlers/users/delete.go @@ -14,7 +14,12 @@ import ( ) func (u UsersResourceHandler) Delete(r *http.Request, id string) error { - u.logger.Trace().Str("user_id", id).Msg("deleting user") + if id == "" { + return serrors.ScimErrorBadRequest("missing id") + } + + logger := u.logger.With().Str("method", "Delete").Str("id", id).Logger() + logger.Info().Msg("delete user") relations, err := u.dirClient.Reader.GetRelations(r.Context(), &dsr.GetRelationsRequest{ SubjectType: u.cfg.SCIM.UserObjectType, SubjectId: id, @@ -47,6 +52,7 @@ func (u UsersResourceHandler) Delete(r *http.Request, id string) error { } for _, v := range identities { + logger.Trace().Str("id", v.ObjectId).Msg("deleting identity") _, err = u.dirClient.Writer.DeleteObject(r.Context(), &dsw.DeleteObjectRequest{ ObjectId: v.ObjectId, ObjectType: v.ObjectType, diff --git a/pkg/app/handlers/users/get.go b/pkg/app/handlers/users/get.go index c310610..ad935e5 100644 --- a/pkg/app/handlers/users/get.go +++ b/pkg/app/handlers/users/get.go @@ -15,7 +15,11 @@ import ( ) func (u UsersResourceHandler) Get(r *http.Request, id string) (scim.Resource, error) { - u.logger.Trace().Str("user_id", id).Msg("get user") + if id == "" { + return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") + } + + u.logger.Info().Str("user_id", id).Msg("get user") resp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.UserObjectType, ObjectId: id, @@ -40,7 +44,7 @@ func (u UsersResourceHandler) Get(r *http.Request, id string) (scim.Resource, er } func (u UsersResourceHandler) GetAll(r *http.Request, params scim.ListRequestParams) (scim.Page, error) { - u.logger.Trace().Msg("getall users") + u.logger.Info().Msg("getall users") var ( resources = make([]scim.Resource, 0) diff --git a/pkg/app/handlers/users/handler.go b/pkg/app/handlers/users/handler.go index e769872..c693cc9 100644 --- a/pkg/app/handlers/users/handler.go +++ b/pkg/app/handlers/users/handler.go @@ -54,6 +54,7 @@ func (u UsersResourceHandler) setUserGroups(ctx context.Context, userID string, for _, v := range relations.Results { if v.Relation == u.cfg.SCIM.GroupMemberRelation { + u.logger.Trace().Str("user_id", userID).Str("group", v.ObjectId).Msg("removing user from group") _, err = u.dirClient.Writer.DeleteRelation(ctx, &dsw.DeleteRelationRequest{ SubjectType: v.SubjectType, SubjectId: v.SubjectId, @@ -68,6 +69,7 @@ func (u UsersResourceHandler) setUserGroups(ctx context.Context, userID string, } for _, v := range groups { + u.logger.Trace().Str("user_id", userID).Str("group", v.Value).Msg("setting user group") _, err = u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{ Relation: &dsc.Relation{ SubjectId: userID, @@ -94,6 +96,7 @@ func (u UsersResourceHandler) addUserToGroup(ctx context.Context, userID, group }) if err != nil { if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrRelationNotFound) { + u.logger.Trace().Str("user_id", userID).Str("group", group).Msg("adding user to group") _, err = u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{ Relation: &dsc.Relation{ SubjectId: userID, @@ -128,6 +131,7 @@ func (u UsersResourceHandler) removeUserFromGroup(ctx context.Context, userID, g return err } + u.logger.Trace().Str("user_id", userID).Str("group", group).Msg("removing user from group") _, err = u.dirClient.Writer.DeleteRelation(ctx, &dsw.DeleteRelationRequest{ SubjectType: u.cfg.SCIM.UserObjectType, SubjectId: userID, @@ -144,6 +148,7 @@ func (u UsersResourceHandler) setIdentity(ctx context.Context, userID, identity return err } + u.logger.Trace().Str("user_id", userID).Str("identity", identity).Any("props", props).Msg("setting identity") _, err = u.dirClient.Writer.SetObject(ctx, &dsw.SetObjectRequest{ Object: &dsc.Object{ Type: u.cfg.SCIM.IdentityObjectType, @@ -174,11 +179,13 @@ func (u UsersResourceHandler) setIdentity(ctx context.Context, userID, identity } } + u.logger.Trace().Str("user_id", userID).Str("identity", identity).Any("relation", rel).Msg("setting identity relation") _, err = u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{Relation: rel}) return err } func (u UsersResourceHandler) removeIdentity(ctx context.Context, identity string) error { + u.logger.Info().Str("identity", identity).Msg("removing identity") _, err := u.dirClient.Writer.DeleteObject(ctx, &dsw.DeleteObjectRequest{ ObjectType: u.cfg.SCIM.IdentityObjectType, ObjectId: identity, @@ -189,7 +196,9 @@ func (u UsersResourceHandler) removeIdentity(ctx context.Context, identity strin } func (u UsersResourceHandler) setAllIdentities(ctx context.Context, userID string, user *common.User) error { + u.logger.Info().Str("user_id", userID).Msg("setting identities") if user.UserName != "" { + u.logger.Debug().Str("user_id", userID).Str("username", user.UserName).Msg("setting username identity") err := u.setIdentity(ctx, userID, user.UserName, map[string]interface{}{IdentityKindKey: "IDENTITY_KIND_USERNAME"}) if err != nil { return err @@ -202,6 +211,7 @@ func (u UsersResourceHandler) setAllIdentities(ctx context.Context, userID strin continue } + u.logger.Debug().Str("user_id", userID).Str("email", email.Value).Msg("setting email identity") err := u.setIdentity(ctx, userID, email.Value, map[string]interface{}{IdentityKindKey: "IDENTITY_KIND_EMAIL"}) if err != nil { return err @@ -210,6 +220,7 @@ func (u UsersResourceHandler) setAllIdentities(ctx context.Context, userID strin } if user.ExternalID != "" { + u.logger.Debug().Str("user_id", userID).Str("external_id", user.ExternalID).Msg("setting external_id identity") err := u.setIdentity(ctx, userID, user.ExternalID, map[string]interface{}{IdentityKindKey: "IDENTITY_KIND_PID"}) if err != nil { return err @@ -222,6 +233,7 @@ func (u UsersResourceHandler) setAllIdentities(ctx context.Context, userID strin func (u UsersResourceHandler) setUserMappings(ctx context.Context, userID string) error { for _, userMap := range u.cfg.SCIM.UserMappings { if userMap.SubjectID == userID { + u.logger.Trace().Str("user_id", userID).Str("object_id", userMap.ObjectID).Msg("setting user mapping") _, err := u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{ Relation: &dsc.Relation{ SubjectType: u.cfg.SCIM.UserObjectType, diff --git a/pkg/app/handlers/users/patch.go b/pkg/app/handlers/users/patch.go index 749de2a..b00b545 100644 --- a/pkg/app/handlers/users/patch.go +++ b/pkg/app/handlers/users/patch.go @@ -18,6 +18,10 @@ import ( ) func (u UsersResourceHandler) Patch(r *http.Request, id string, operations []scim.PatchOperation) (scim.Resource, error) { + if id == "" { + return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") + } + u.logger.Trace().Str("user_id", id).Any("operations", operations).Msg("patching user") getObjResp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.UserObjectType, diff --git a/pkg/app/handlers/users/replace.go b/pkg/app/handlers/users/replace.go index f6c8f20..a699b5e 100644 --- a/pkg/app/handlers/users/replace.go +++ b/pkg/app/handlers/users/replace.go @@ -14,7 +14,13 @@ import ( ) func (u UsersResourceHandler) Replace(r *http.Request, id string, attributes scim.ResourceAttributes) (scim.Resource, error) { - u.logger.Trace().Str("user_id", id).Any("attributes", attributes).Msg("replacing user") + if id == "" { + return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") + } + + logger := u.logger.With().Str("method", "Replace").Str("id", id).Logger() + logger.Info().Msg("replace user") + logger.Trace().Str("user_id", id).Any("attributes", attributes).Msg("replacing user") getObjResp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.UserObjectType, ObjectId: id, From f332eae56a9b6488a0d88b9d1606393b2016ce81 Mon Sep 17 00:00:00 2001 From: florindragos Date: Mon, 24 Feb 2025 12:53:31 +0200 Subject: [PATCH 3/5] fix list users --- .github/workflows/ci.yaml | 1 + pkg/app/handlers/users/get.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8f2cd60..bad9c40 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,6 +46,7 @@ jobs: uses: golangci/golangci-lint-action@v6 with: version: ${{ env.GO_LANGCI_LINT_VERSION }} + verify: false args: --timeout=30m push: diff --git a/pkg/app/handlers/users/get.go b/pkg/app/handlers/users/get.go index ad935e5..2ec5686 100644 --- a/pkg/app/handlers/users/get.go +++ b/pkg/app/handlers/users/get.go @@ -75,7 +75,7 @@ func (u UsersResourceHandler) GetAll(r *http.Request, params scim.ListRequestPar }) if params.FilterValidator == nil || params.FilterValidator.PassesFilter(resource.Attributes) == nil { - if skipIndex <= params.StartIndex { + if skipIndex < params.StartIndex { skipIndex++ continue } From 7dc2734b1c65b759b25ad76bae811067befbe308 Mon Sep 17 00:00:00 2001 From: florindragos Date: Mon, 24 Feb 2025 15:36:04 +0200 Subject: [PATCH 4/5] add more logging --- pkg/app/handlers/groups/create.go | 11 +++++++++++ pkg/app/handlers/groups/delete.go | 6 ++++++ pkg/app/handlers/groups/get.go | 11 +++++++++++ pkg/app/handlers/groups/handler.go | 2 ++ pkg/app/handlers/groups/patch.go | 16 +++++++++++----- pkg/app/handlers/groups/replace.go | 9 +++++++++ pkg/app/handlers/users/create.go | 14 ++++++++------ pkg/app/handlers/users/delete.go | 10 ++++++---- pkg/app/handlers/users/get.go | 16 ++++++++++------ pkg/app/handlers/users/patch.go | 14 ++++++++------ pkg/app/handlers/users/replace.go | 15 ++++++++++----- 11 files changed, 92 insertions(+), 32 deletions(-) diff --git a/pkg/app/handlers/groups/create.go b/pkg/app/handlers/groups/create.go index a3b3c10..5102123 100644 --- a/pkg/app/handlers/groups/create.go +++ b/pkg/app/handlers/groups/create.go @@ -10,8 +10,13 @@ import ( ) func (u GroupResourceHandler) Create(r *http.Request, attributes scim.ResourceAttributes) (scim.Resource, error) { + logger := u.logger.With().Str("method", "Create").Str("id", attributes["id"].(string)).Logger() + logger.Info().Msg("create group") + logger.Trace().Any("attributes", attributes).Msg("creating group") + object, err := common.ResourceAttributesToObject(attributes, u.cfg.SCIM.GroupObjectType, attributes["displayName"].(string)) if err != nil { + logger.Error().Err(err).Msg("failed to convert attributes to object") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } @@ -19,11 +24,15 @@ func (u GroupResourceHandler) Create(r *http.Request, attributes scim.ResourceAt Object: object, }) if err != nil { + logger.Error().Err(err).Msg("failed to create group") return scim.Resource{}, err } + logger.Trace().Any("response", resp.Result).Msg("group object created") + err = u.setGroupMappings(r.Context(), resp.Result.Id) if err != nil { + logger.Err(err).Msg("failed to set group mappings") return scim.Resource{}, err } @@ -35,5 +44,7 @@ func (u GroupResourceHandler) Create(r *http.Request, attributes scim.ResourceAt Version: resp.Result.Etag, }) + logger.Trace().Any("resource", resource).Msg("group created") + return resource, nil } diff --git a/pkg/app/handlers/groups/delete.go b/pkg/app/handlers/groups/delete.go index e76b4f9..145b7dd 100644 --- a/pkg/app/handlers/groups/delete.go +++ b/pkg/app/handlers/groups/delete.go @@ -11,16 +11,22 @@ import ( ) func (u GroupResourceHandler) Delete(r *http.Request, id string) error { + logger := u.logger.With().Str("method", "Delete").Str("id", id).Logger() + logger.Info().Msg("delete group") + _, err := u.dirClient.Writer.DeleteObject(r.Context(), &dsw.DeleteObjectRequest{ ObjectType: u.cfg.SCIM.GroupObjectType, ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Msg("failed to delete group") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return serrors.ScimErrorResourceNotFound(id) } } + logger.Trace().Msg("group deleted") + return err } diff --git a/pkg/app/handlers/groups/get.go b/pkg/app/handlers/groups/get.go index ea492e0..42f67a5 100644 --- a/pkg/app/handlers/groups/get.go +++ b/pkg/app/handlers/groups/get.go @@ -10,12 +10,16 @@ import ( ) func (u GroupResourceHandler) Get(r *http.Request, id string) (scim.Resource, error) { + logger := u.logger.With().Str("method", "Get").Str("id", id).Logger() + logger.Info().Msg("get group") + resp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.GroupObjectType, ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Str("id", id).Msg("failed to get group") return scim.Resource{}, err } @@ -27,10 +31,14 @@ func (u GroupResourceHandler) Get(r *http.Request, id string) (scim.Resource, er Version: resp.Result.Etag, }) + logger.Trace().Any("group", resource).Msg("group retrieved") + return resource, nil } func (u GroupResourceHandler) GetAll(r *http.Request, params scim.ListRequestParams) (scim.Page, error) { + u.logger.Info().Msg("getall groups") + var ( resources = make([]scim.Resource, 0) ) @@ -42,6 +50,7 @@ func (u GroupResourceHandler) GetAll(r *http.Request, params scim.ListRequestPar }, }) if err != nil { + u.logger.Err(err).Msg("failed to read groups") return scim.Page{}, err } @@ -56,6 +65,8 @@ func (u GroupResourceHandler) GetAll(r *http.Request, params scim.ListRequestPar resources = append(resources, resource) } + u.logger.Trace().Int("total_results", len(resources)).Msg("groups read") + return scim.Page{ TotalResults: len(resources), Resources: resources, diff --git a/pkg/app/handlers/groups/handler.go b/pkg/app/handlers/groups/handler.go index 363a2da..e20569e 100644 --- a/pkg/app/handlers/groups/handler.go +++ b/pkg/app/handlers/groups/handler.go @@ -39,6 +39,7 @@ func NewGroupResourceHandler(cfg *config.Config, logger *zerolog.Logger) (*Group func (u GroupResourceHandler) setGroupMappings(ctx context.Context, groupID string) error { for _, groupMap := range u.cfg.SCIM.GroupMappings { if groupMap.SubjectID == groupID { + u.logger.Trace().Str("groupID", groupID).Str("relation", groupMap.Relation).Str("objectID", groupMap.ObjectID).Msg("setting group mapping") _, err := u.dirClient.Writer.SetRelation(ctx, &dsw.SetRelationRequest{ Relation: &dsc.Relation{ SubjectType: u.cfg.SCIM.GroupObjectType, @@ -50,6 +51,7 @@ func (u GroupResourceHandler) setGroupMappings(ctx context.Context, groupID stri }, }) if err != nil { + u.logger.Error().Err(err).Str("groupID", groupID).Str("relation", groupMap.Relation).Str("objectID", groupMap.ObjectID).Msg("failed to set group mapping") return err } } diff --git a/pkg/app/handlers/groups/patch.go b/pkg/app/handlers/groups/patch.go index 174b811..d1da563 100644 --- a/pkg/app/handlers/groups/patch.go +++ b/pkg/app/handlers/groups/patch.go @@ -18,13 +18,17 @@ import ( ) func (u GroupResourceHandler) Patch(r *http.Request, id string, operations []scim.PatchOperation) (scim.Resource, error) { - u.logger.Trace().Str("group_id", id).Any("operations", operations).Msg("patching group") + logger := u.logger.With().Str("method", "Patch").Str("id", id).Logger() + logger.Info().Msg("patch group") + logger.Trace().Any("operations", operations).Msg("patching group") + getObjResp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.GroupObjectType, ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Str("id", id).Msg("failed to get group") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return scim.Resource{}, serrors.ScimErrorResourceNotFound(id) } @@ -38,30 +42,30 @@ func (u GroupResourceHandler) Patch(r *http.Request, id string, operations []sci case scim.PatchOperationAdd: err := u.handlePatchOPAdd(r.Context(), object, op) if err != nil { + logger.Err(err).Msg("error adding property") return scim.Resource{}, err } case scim.PatchOperationRemove: err := u.handlePatchOPRemove(r.Context(), object, op) if err != nil { + logger.Err(err).Msg("error removing property") return scim.Resource{}, err } case scim.PatchOperationReplace: err := u.handlePatchOPReplace(object, op) if err != nil { + logger.Err(err).Msg("error replacing property") return scim.Resource{}, err } } } - if err != nil { - return scim.Resource{}, err - } object.Etag = getObjResp.Result.Etag resp, err := u.dirClient.Writer.SetObject(r.Context(), &dsw.SetObjectRequest{ Object: object, }) if err != nil { - u.logger.Err(err).Msg("error setting object") + logger.Err(err).Msg("error setting object") return scim.Resource{}, err } @@ -73,6 +77,8 @@ func (u GroupResourceHandler) Patch(r *http.Request, id string, operations []sci Version: resp.Result.Etag, }) + logger.Trace().Any("group", resource).Msg("group patched") + return resource, nil } diff --git a/pkg/app/handlers/groups/replace.go b/pkg/app/handlers/groups/replace.go index 39f94f6..ccdd63a 100644 --- a/pkg/app/handlers/groups/replace.go +++ b/pkg/app/handlers/groups/replace.go @@ -14,12 +14,17 @@ import ( ) func (u GroupResourceHandler) Replace(r *http.Request, id string, attributes scim.ResourceAttributes) (scim.Resource, error) { + logger := u.logger.With().Str("method", "Replace").Str("id", id).Logger() + logger.Info().Msg("replace group") + logger.Trace().Any("attributes", attributes).Msg("replacing group") + getObjResp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: "grroup", ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Msg("failed to get group") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return scim.Resource{}, serrors.ScimErrorResourceNotFound(id) } @@ -28,6 +33,7 @@ func (u GroupResourceHandler) Replace(r *http.Request, id string, attributes sci object, err := common.ResourceAttributesToObject(attributes, u.cfg.SCIM.GroupObjectType, id) if err != nil { + logger.Err(err).Msg("failed to convert attributes to object") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } object.Id = id @@ -37,6 +43,7 @@ func (u GroupResourceHandler) Replace(r *http.Request, id string, attributes sci Object: object, }) if err != nil { + logger.Err(err).Msg("failed to replace group") return scim.Resource{}, err } @@ -48,5 +55,7 @@ func (u GroupResourceHandler) Replace(r *http.Request, id string, attributes sci Version: setResp.Result.Etag, }) + logger.Trace().Any("resource", resource).Msg("group replaced") + return resource, nil } diff --git a/pkg/app/handlers/users/create.go b/pkg/app/handlers/users/create.go index 93af0d1..b8a342b 100644 --- a/pkg/app/handlers/users/create.go +++ b/pkg/app/handlers/users/create.go @@ -13,22 +13,18 @@ import ( ) func (u UsersResourceHandler) Create(r *http.Request, attributes scim.ResourceAttributes) (scim.Resource, error) { - if attributes["id"] == nil || attributes["id"].(string) == "" { - return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") - } - logger := u.logger.With().Str("method", "Create").Str("id", attributes["id"].(string)).Logger() logger.Info().Msg("create user") logger.Trace().Any("attributes", attributes).Msg("creating user") user, err := common.ResourceAttributesToUser(attributes) if err != nil { - logger.Error().Err(err).Msg("failed to convert attributes to user") + logger.Err(err).Msg("failed to convert attributes to user") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } object, err := common.UserToObject(user) if err != nil { - logger.Error().Err(err).Msg("failed to convert user to object") + logger.Err(err).Msg("failed to convert user to object") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } @@ -37,6 +33,7 @@ func (u UsersResourceHandler) Create(r *http.Request, attributes scim.ResourceAt Object: object, }) if err != nil { + logger.Err(err).Msg("failed to create user") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrAlreadyExists) { return scim.Resource{}, serrors.ScimErrorUniqueness } @@ -53,18 +50,23 @@ func (u UsersResourceHandler) Create(r *http.Request, attributes scim.ResourceAt err = u.setAllIdentities(r.Context(), resp.Result.Id, user) if err != nil { + logger.Err(err).Msg("failed to set identities") return scim.Resource{}, err } err = u.setUserGroups(r.Context(), resp.Result.Id, user.Groups) if err != nil { + logger.Err(err).Msg("failed to set groups") return scim.Resource{}, err } err = u.setUserMappings(r.Context(), resp.Result.Id) if err != nil { + logger.Err(err).Msg("failed to set mappings") return scim.Resource{}, err } + logger.Trace().Any("resource", resource).Msg("user created") + return resource, nil } diff --git a/pkg/app/handlers/users/delete.go b/pkg/app/handlers/users/delete.go index c5d66ea..c837993 100644 --- a/pkg/app/handlers/users/delete.go +++ b/pkg/app/handlers/users/delete.go @@ -14,10 +14,6 @@ import ( ) func (u UsersResourceHandler) Delete(r *http.Request, id string) error { - if id == "" { - return serrors.ScimErrorBadRequest("missing id") - } - logger := u.logger.With().Str("method", "Delete").Str("id", id).Logger() logger.Info().Msg("delete user") relations, err := u.dirClient.Reader.GetRelations(r.Context(), &dsr.GetRelationsRequest{ @@ -25,6 +21,7 @@ func (u UsersResourceHandler) Delete(r *http.Request, id string) error { SubjectId: id, }) if err != nil { + logger.Err(err).Msg("failed to get relations") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return serrors.ScimErrorResourceNotFound(id) } @@ -39,6 +36,7 @@ func (u UsersResourceHandler) Delete(r *http.Request, id string) error { ObjectId: id, }) if err != nil { + logger.Err(err).Msg("failed to get identities") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return serrors.ScimErrorResourceNotFound(id) } @@ -59,6 +57,7 @@ func (u UsersResourceHandler) Delete(r *http.Request, id string) error { WithRelations: true, }) if err != nil { + logger.Err(err).Msg("failed to delete identity") return err } } @@ -69,10 +68,13 @@ func (u UsersResourceHandler) Delete(r *http.Request, id string) error { WithRelations: true, }) if err != nil { + logger.Err(err).Msg("failed to delete user") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return serrors.ScimErrorResourceNotFound(id) } } + logger.Trace().Msg("user deleted") + return err } diff --git a/pkg/app/handlers/users/get.go b/pkg/app/handlers/users/get.go index 2ec5686..5d2d80d 100644 --- a/pkg/app/handlers/users/get.go +++ b/pkg/app/handlers/users/get.go @@ -15,17 +15,15 @@ import ( ) func (u UsersResourceHandler) Get(r *http.Request, id string) (scim.Resource, error) { - if id == "" { - return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") - } - - u.logger.Info().Str("user_id", id).Msg("get user") + logger := u.logger.With().Str("method", "Get").Str("id", id).Logger() + logger.Info().Msg("get user") resp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.UserObjectType, ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Str("id", id).Msg("failed to get user") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return scim.Resource{}, serrors.ScimErrorResourceNotFound(id) } @@ -40,11 +38,14 @@ func (u UsersResourceHandler) Get(r *http.Request, id string) (scim.Resource, er Version: resp.Result.Etag, }) + logger.Trace().Any("user", resource).Msg("user retrieved") + return resource, nil } func (u UsersResourceHandler) GetAll(r *http.Request, params scim.ListRequestParams) (scim.Page, error) { - u.logger.Info().Msg("getall users") + logger := u.logger.With().Str("method", "GetAll").Logger() + logger.Info().Msg("getall users") var ( resources = make([]scim.Resource, 0) @@ -60,6 +61,7 @@ func (u UsersResourceHandler) GetAll(r *http.Request, params scim.ListRequestPar for { resp, err := u.getUsers(r.Context(), pageSize, pageToken) if err != nil { + logger.Err(err).Msg("failed to get users") return scim.Page{}, err } @@ -92,6 +94,8 @@ func (u UsersResourceHandler) GetAll(r *http.Request, params scim.ListRequestPar } } + logger.Trace().Int("total_results", len(resources)).Msg("users read") + return scim.Page{ TotalResults: len(resources), Resources: resources, diff --git a/pkg/app/handlers/users/patch.go b/pkg/app/handlers/users/patch.go index b00b545..5fffae2 100644 --- a/pkg/app/handlers/users/patch.go +++ b/pkg/app/handlers/users/patch.go @@ -18,17 +18,16 @@ import ( ) func (u UsersResourceHandler) Patch(r *http.Request, id string, operations []scim.PatchOperation) (scim.Resource, error) { - if id == "" { - return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") - } - - u.logger.Trace().Str("user_id", id).Any("operations", operations).Msg("patching user") + logger := u.logger.With().Str("method", "Patch").Str("id", id).Logger() + logger.Info().Msg("patch user") + logger.Trace().Str("id", id).Any("operations", operations).Msg("patching user") getObjResp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.UserObjectType, ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Msg("failed to get user") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return scim.Resource{}, serrors.ScimErrorResourceNotFound(id) } @@ -42,16 +41,19 @@ func (u UsersResourceHandler) Patch(r *http.Request, id string, operations []sci case scim.PatchOperationAdd: err := u.handlePatchOPAdd(r.Context(), object, op) if err != nil { + logger.Err(err).Msg("error adding property") return scim.Resource{}, err } case scim.PatchOperationRemove: err := u.handlePatchOPRemove(r.Context(), object, op) if err != nil { + logger.Err(err).Msg("error removing property") return scim.Resource{}, err } case scim.PatchOperationReplace: err := u.handlePatchOPReplace(object, op) if err != nil { + logger.Err(err).Msg("error replacing property") return scim.Resource{}, err } } @@ -65,7 +67,7 @@ func (u UsersResourceHandler) Patch(r *http.Request, id string, operations []sci Object: object, }) if err != nil { - u.logger.Err(err).Msg("error setting object") + logger.Err(err).Msg("error setting object") return scim.Resource{}, err } diff --git a/pkg/app/handlers/users/replace.go b/pkg/app/handlers/users/replace.go index a699b5e..e4a015a 100644 --- a/pkg/app/handlers/users/replace.go +++ b/pkg/app/handlers/users/replace.go @@ -14,19 +14,16 @@ import ( ) func (u UsersResourceHandler) Replace(r *http.Request, id string, attributes scim.ResourceAttributes) (scim.Resource, error) { - if id == "" { - return scim.Resource{}, serrors.ScimErrorBadRequest("missing id") - } - logger := u.logger.With().Str("method", "Replace").Str("id", id).Logger() logger.Info().Msg("replace user") - logger.Trace().Str("user_id", id).Any("attributes", attributes).Msg("replacing user") + logger.Trace().Any("attributes", attributes).Msg("replacing user") getObjResp, err := u.dirClient.Reader.GetObject(r.Context(), &dsr.GetObjectRequest{ ObjectType: u.cfg.SCIM.UserObjectType, ObjectId: id, WithRelations: true, }) if err != nil { + logger.Err(err).Msg("failed to get user") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return scim.Resource{}, serrors.ScimErrorResourceNotFound(id) } @@ -35,11 +32,13 @@ func (u UsersResourceHandler) Replace(r *http.Request, id string, attributes sci user, err := common.ResourceAttributesToUser(attributes) if err != nil { + logger.Err(err).Msg("failed to convert attributes to user") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } object, err := common.UserToObject(user) if err != nil { + logger.Err(err).Msg("failed to convert user to object") return scim.Resource{}, serrors.ScimErrorInvalidSyntax } object.Id = id @@ -49,21 +48,25 @@ func (u UsersResourceHandler) Replace(r *http.Request, id string, attributes sci Object: object, }) if err != nil { + logger.Err(err).Msg("failed to replace user") return scim.Resource{}, err } err = u.setAllIdentities(r.Context(), id, user) if err != nil { + logger.Err(err).Msg("failed to set identities") return scim.Resource{}, err } err = u.setUserGroups(r.Context(), id, user.Groups) if err != nil { + logger.Err(err).Msg("failed to set groups") return scim.Resource{}, err } err = u.setUserMappings(r.Context(), id) if err != nil { + logger.Err(err).Msg("failed to set mappings") return scim.Resource{}, err } @@ -75,5 +78,7 @@ func (u UsersResourceHandler) Replace(r *http.Request, id string, attributes sci Version: setResp.Result.Etag, }) + logger.Trace().Any("resource", resource).Msg("user replaced") + return resource, nil } From 07143332bffa5d404b0bb95ccee78c816a80ceb9 Mon Sep 17 00:00:00 2001 From: florindragos Date: Thu, 27 Feb 2025 16:42:45 +0200 Subject: [PATCH 5/5] support identity relations of type `object#relation` --- .golangci.yaml | 4 +- go.mod | 2 +- pkg/app/handlers/groups/create.go | 2 +- pkg/app/handlers/users/create.go | 2 +- pkg/app/handlers/users/delete.go | 58 ++++++++++++++------------- pkg/app/handlers/users/handler.go | 44 +++++++++++++-------- pkg/config/config.go | 66 +++++++++++++++++++++++++------ 7 files changed, 116 insertions(+), 62 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index f7a6f69..e78086a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -78,6 +78,7 @@ linters: - gocritic - gocyclo - godot + - godox - err113 - gofmt - goimports @@ -103,7 +104,6 @@ linters: # - dupl # - gochecknoglobals # - gocognit - # - godox # - gomnd # - lll # - nestif @@ -147,4 +147,4 @@ issues: - gocritic - text: "G404" linters: - - gosec + - gosec \ No newline at end of file diff --git a/go.mod b/go.mod index 3679e32..b0c5e11 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 - github.com/samber/lo v1.47.0 github.com/scim2/filter-parser/v2 v2.2.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -40,6 +39,7 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/samber/lo v1.47.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/pkg/app/handlers/groups/create.go b/pkg/app/handlers/groups/create.go index 5102123..0586e49 100644 --- a/pkg/app/handlers/groups/create.go +++ b/pkg/app/handlers/groups/create.go @@ -10,7 +10,7 @@ import ( ) func (u GroupResourceHandler) Create(r *http.Request, attributes scim.ResourceAttributes) (scim.Resource, error) { - logger := u.logger.With().Str("method", "Create").Str("id", attributes["id"].(string)).Logger() + logger := u.logger.With().Str("method", "Create").Str("displayName", attributes["displayName"].(string)).Logger() logger.Info().Msg("create group") logger.Trace().Any("attributes", attributes).Msg("creating group") diff --git a/pkg/app/handlers/users/create.go b/pkg/app/handlers/users/create.go index b8a342b..4be7db2 100644 --- a/pkg/app/handlers/users/create.go +++ b/pkg/app/handlers/users/create.go @@ -13,7 +13,7 @@ import ( ) func (u UsersResourceHandler) Create(r *http.Request, attributes scim.ResourceAttributes) (scim.Resource, error) { - logger := u.logger.With().Str("method", "Create").Str("id", attributes["id"].(string)).Logger() + logger := u.logger.With().Str("method", "Create").Str("userName", attributes["userName"].(string)).Logger() logger.Info().Msg("create user") logger.Trace().Any("attributes", attributes).Msg("creating user") user, err := common.ResourceAttributesToUser(attributes) diff --git a/pkg/app/handlers/users/delete.go b/pkg/app/handlers/users/delete.go index c837993..06f4d68 100644 --- a/pkg/app/handlers/users/delete.go +++ b/pkg/app/handlers/users/delete.go @@ -10,50 +10,52 @@ import ( "github.com/aserto-dev/go-directory/pkg/derr" serrors "github.com/elimity-com/scim/errors" "github.com/pkg/errors" - "github.com/samber/lo" ) func (u UsersResourceHandler) Delete(r *http.Request, id string) error { logger := u.logger.With().Str("method", "Delete").Str("id", id).Logger() logger.Info().Msg("delete user") - relations, err := u.dirClient.Reader.GetRelations(r.Context(), &dsr.GetRelationsRequest{ - SubjectType: u.cfg.SCIM.UserObjectType, - SubjectId: id, + + identityRelation, err := u.getIdentityRelation(id, "") + if err != nil { + u.logger.Err(err).Msg("failed to get identity relation") + return err + } + + var identities []*dsc.Relation + + resp, err := u.dirClient.Reader.GetRelations(r.Context(), &dsr.GetRelationsRequest{ + SubjectType: identityRelation.SubjectType, + SubjectId: identityRelation.SubjectId, + Relation: identityRelation.Relation, + ObjectId: identityRelation.ObjectId, + ObjectType: identityRelation.ObjectType, }) if err != nil { - logger.Err(err).Msg("failed to get relations") + logger.Err(err).Msg("failed to get identities") if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { return serrors.ScimErrorResourceNotFound(id) } return err } + identities = resp.Results - var identities []*dsc.Relation - if u.cfg.SCIM.InvertIdentityRelation { - resp, err := u.dirClient.Reader.GetRelations(r.Context(), &dsr.GetRelationsRequest{ - SubjectType: u.cfg.SCIM.IdentityObjectType, - Relation: u.cfg.SCIM.IdentityRelation, - ObjectId: id, - }) - if err != nil { - logger.Err(err).Msg("failed to get identities") - if errors.Is(cerr.UnwrapAsertoError(err), derr.ErrObjectNotFound) { - return serrors.ScimErrorResourceNotFound(id) - } - return err + for _, v := range identities { + var objectID string + switch v.ObjectType { + case u.cfg.SCIM.IdentityObjectType: + objectID = v.ObjectId + case u.cfg.SCIM.UserObjectType: + objectID = v.SubjectId + default: + logger.Error().Str("object_type", v.ObjectType).Msg("unexpected object type") + return serrors.ScimErrorBadRequest("unexpected object type in identity relation") } - identities = resp.Results - } else { - identities = lo.Filter(relations.Results, func(rel *dsc.Relation, i int) bool { - return rel.Relation == u.cfg.SCIM.IdentityRelation - }) - } - for _, v := range identities { - logger.Trace().Str("id", v.ObjectId).Msg("deleting identity") + logger.Trace().Str("identity", objectID).Msg("deleting identity") _, err = u.dirClient.Writer.DeleteObject(r.Context(), &dsw.DeleteObjectRequest{ - ObjectId: v.ObjectId, - ObjectType: v.ObjectType, + ObjectId: objectID, + ObjectType: u.cfg.SCIM.IdentityObjectType, WithRelations: true, }) if err != nil { diff --git a/pkg/app/handlers/users/handler.go b/pkg/app/handlers/users/handler.go index c693cc9..2e4638c 100644 --- a/pkg/app/handlers/users/handler.go +++ b/pkg/app/handlers/users/handler.go @@ -160,23 +160,10 @@ func (u UsersResourceHandler) setIdentity(ctx context.Context, userID, identity return err } - var rel *dsc.Relation - if u.cfg.SCIM.InvertIdentityRelation { - rel = &dsc.Relation{ - SubjectId: identity, - SubjectType: u.cfg.SCIM.IdentityObjectType, - Relation: u.cfg.SCIM.IdentityRelation, - ObjectType: u.cfg.SCIM.UserObjectType, - ObjectId: userID, - } - } else { - rel = &dsc.Relation{ - SubjectId: userID, - SubjectType: u.cfg.SCIM.UserObjectType, - Relation: u.cfg.SCIM.IdentityRelation, - ObjectType: u.cfg.SCIM.IdentityObjectType, - ObjectId: identity, - } + rel, err := u.getIdentityRelation(userID, identity) + if err != nil { + u.logger.Err(err).Msg("failed to get identity relation") + return err } u.logger.Trace().Str("user_id", userID).Str("identity", identity).Any("relation", rel).Msg("setting identity relation") @@ -251,3 +238,26 @@ func (u UsersResourceHandler) setUserMappings(ctx context.Context, userID string } return nil } + +func (u UsersResourceHandler) getIdentityRelation(userID, identity string) (*dsc.Relation, error) { + switch u.cfg.SCIM.Identity.ObjectType { + case u.cfg.SCIM.IdentityObjectType: + return &dsc.Relation{ + SubjectId: userID, + SubjectType: u.cfg.SCIM.UserObjectType, + Relation: u.cfg.SCIM.Identity.Relation, + ObjectType: u.cfg.SCIM.IdentityObjectType, + ObjectId: identity, + }, nil + case u.cfg.SCIM.UserObjectType: + return &dsc.Relation{ + SubjectId: identity, + SubjectType: u.cfg.SCIM.IdentityObjectType, + Relation: u.cfg.SCIM.Identity.Relation, + ObjectType: u.cfg.SCIM.UserObjectType, + ObjectId: userID, + }, nil + default: + return nil, errors.New("invalid identity relation") + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 84690f7..bb0c021 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -14,6 +14,7 @@ import ( var ( DefaultTLSGenDir = os.ExpandEnv("$HOME/.config/aserto/scim/certs") + ErrInvalidConfig = errors.New("invalid config") ) type Config struct { @@ -26,16 +27,19 @@ type Config struct { } `json:"server"` SCIM struct { - CreateEmailIdentities bool `json:"create_email_identities"` - CreateRoleGroups bool `json:"create_role_groups"` - GroupMappings []ObjectMapping `json:"group_mappings"` - UserMappings []ObjectMapping `json:"user_mappings"` - UserObjectType string `json:"user_object_type"` - GroupMemberRelation string `json:"group_member_relation"` - GroupObjectType string `json:"group_object_type"` - IdentityObjectType string `json:"identity_object_type"` - IdentityRelation string `json:"identity_relation"` - InvertIdentityRelation bool `json:"invert_identity_relation"` + CreateEmailIdentities bool `json:"create_email_identities"` + CreateRoleGroups bool `json:"create_role_groups"` + GroupMappings []ObjectMapping `json:"group_mappings"` + UserMappings []ObjectMapping `json:"user_mappings"` + UserObjectType string `json:"user_object_type"` + GroupMemberRelation string `json:"group_member_relation"` + GroupObjectType string `json:"group_object_type"` + IdentityObjectType string `json:"identity_object_type"` + IdentityRelation string `json:"identity_relation"` + Identity struct { + ObjectType string + Relation string + } `json:"-"` } `json:"scim"` } @@ -90,10 +94,9 @@ func NewConfig(configPath string) (*Config, error) { // nolint // function will v.SetDefault("scim.create_email_identities", true) v.SetDefault("scim.user_object_type", "user") v.SetDefault("scim.identity_object_type", "identity") - v.SetDefault("scim.identity_relation", "identifier") + v.SetDefault("scim.identity_relation", "user#identifier") v.SetDefault("scim.group_object_type", "group") v.SetDefault("scim.group_member_relation", "member") - v.SetDefault("scim.invert_identity_relation", false) // Allow setting via env vars. v.SetDefault("directory.api_key", "") @@ -130,9 +133,48 @@ func NewConfig(configPath string) (*Config, error) { // nolint // function will } } + err = cfg.Validate() + if err != nil { + return nil, errors.Wrap(err, "config validation failed") + } + return cfg, nil } +func (cfg *Config) Validate() error { + if cfg.SCIM.UserObjectType == "" { + return errors.Wrap(ErrInvalidConfig, "scim.user_object_type is required") + } + if cfg.SCIM.IdentityObjectType == "" { + return errors.Wrap(ErrInvalidConfig, "scim.identity_object_type is required") + } + if cfg.SCIM.IdentityRelation == "" { + return errors.Wrap(ErrInvalidConfig, "scim.identity_relation is required") + } else { + object, relation, found := strings.Cut(cfg.SCIM.IdentityRelation, "#") + if !found { + return errors.Wrap(ErrInvalidConfig, "identity relation must be in the format object#relation") + } + if object != cfg.SCIM.IdentityObjectType && object != cfg.SCIM.UserObjectType { + return errors.Wrapf(ErrInvalidConfig, "identity relation object type [%s] doesn't match user or identity type", object) + } + if relation == "" { + return errors.Wrap(ErrInvalidConfig, "identity relation relation is required") + } + + cfg.SCIM.Identity.ObjectType = object + cfg.SCIM.Identity.Relation = relation + } + if cfg.SCIM.GroupObjectType == "" { + return errors.Wrap(ErrInvalidConfig, "scim.group_object_type is required") + } + if cfg.SCIM.GroupMemberRelation == "" { + return errors.Wrap(ErrInvalidConfig, "scim.group_member_relation is required") + } + + return nil +} + func fileExists(path string) (bool, error) { if _, err := os.Stat(path); err == nil { return true, nil