diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 451112e..f1d392c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: '1.25' + go-version-file: 'go.mod' - name: Setup mockery uses: brokeyourbike/go-mockery-action@v0 diff --git a/go.mod b/go.mod index 5d23bd5..36e4759 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/slack-go/slack v0.29.0 github.com/stretchr/testify v1.12.1 github.com/wneessen/go-mail v0.8.1 - go-micro.dev/v5 v5.30.0 + go-micro.dev/v5 v5.3.0 go.mongodb.org/mongo-driver v1.17.9 go.uber.org/zap v1.28.0 golang.org/x/crypto v0.55.0 @@ -64,15 +64,18 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.41.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.48.0 // indirect github.com/aws/smithy-go v1.28.1 // indirect + github.com/bitly/go-simplejson v0.5.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.6.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/cyphar/filepath-securejoin v0.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.16.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect @@ -89,6 +92,7 @@ require ( github.com/go-openapi/validate v0.20.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -99,6 +103,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-version v1.9.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect @@ -114,6 +119,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/miekg/dns v1.1.57 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -129,6 +135,8 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rubenv/sql-migrate v1.8.1 // indirect @@ -140,16 +148,19 @@ require ( github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zclconf/go-cty v1.18.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect @@ -157,6 +168,7 @@ require ( golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index ce18c2e..45d4715 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,8 @@ github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= @@ -100,6 +102,7 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -142,6 +145,8 @@ github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZ github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -264,6 +269,7 @@ github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZX github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= @@ -304,6 +310,8 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -361,6 +369,8 @@ github.com/hashicorp/terraform-json v0.28.0 h1:dOkJT55rWfU6T1/VklHde51ym4LfNP+9x github.com/hashicorp/terraform-json v0.28.0/go.mod h1:PJIRf+Yzu5iLb52c/xYp1tUOL4jzMzfIAB5gvWWKIWE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -431,6 +441,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -481,6 +492,10 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= @@ -567,6 +582,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM= github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w= @@ -586,12 +603,16 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM= github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg= +go-micro.dev/v5 v5.3.0 h1:XjFydJiQHU14zEA/JAXXR7aJVbwIwBjUajLrqi0wrJI= +go-micro.dev/v5 v5.3.0/go.mod h1:QMSZE1zIaB1TY0JI7AsePls8+slRzqWz4MKyoQMNNcM= go-micro.dev/v5 v5.30.0 h1:OHvY8LV39Pk/pWUhxihCa26q25SpDDnfwU3n6z9R2Vs= go-micro.dev/v5 v5.30.0/go.mod h1:eWTF7KQRYXmjm9s5p4Pzrk5CObHQKlh6B/u573gd/Qo= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -793,6 +814,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= helm.sh/helm/v3 v3.21.4 h1:T/GcIEXU/gNjJnkITlIZ3e9xqkZjhFTmISuStTZ6+Qg= diff --git a/pkg/keycloak/keycloak.go b/pkg/keycloak/keycloak.go index 7a5b923..aba7e8f 100644 --- a/pkg/keycloak/keycloak.go +++ b/pkg/keycloak/keycloak.go @@ -49,6 +49,7 @@ type Client interface { */ GetGroups(ctx context.Context, token string, realm string, params gocloak.GetGroupsParams) ([]*gocloak.Group, error) GetGroup(ctx context.Context, token string, realm string, groupID string) (*gocloak.Group, error) + GetGroupByPath(ctx context.Context, token string, realm string, groupPath string) (*gocloak.Group, error) CreateGroup(ctx context.Context, token string, realm string, group gocloak.Group) (string, error) CreateChildGroup(ctx context.Context, token string, realm string, groupID string, group gocloak.Group) (string, error) GetUserGroups(ctx context.Context, token string, realm string, userID string, params gocloak.GetGroupsParams) ([]*gocloak.Group, error) @@ -56,6 +57,7 @@ type Client interface { AddUserToGroup(ctx context.Context, token string, realm string, userID string, groupID string) error DeleteUserFromGroup(ctx context.Context, token string, realm string, userID string, groupID string) error GetClientRolesByUserID(ctx context.Context, token string, realm string, idOfClient string, userID string) ([]*gocloak.Role, error) + GetUsersByClientRoleName(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) GetRealmRole(ctx context.Context, token string, realm string, roleName string) (*gocloak.Role, error) AddRealmRoleToUser(ctx context.Context, token string, realm string, userID string, roles []gocloak.Role) error LogoutUserSession(context.Context, string, string, string) error @@ -413,6 +415,12 @@ func (h *Helper) GetGroups(realm string, params gocloak.GetGroupsParams) ([]*goc }, params) } +func (h *Helper) GetGroupByPath(realm, path string) (*gocloak.Group, error) { + ctx, cancel := context.WithTimeout(wait.CtxSeconds(10)) + defer cancel() + return h.Client.GetGroupByPath(ctx, h.Token, realm, path) +} + func (h *Helper) GetUserGroups(realm, userID string) ([]*gocloak.Group, error) { return paginateGroups(func(p gocloak.GetGroupsParams) ([]*gocloak.Group, error) { ctx, cancel := context.WithTimeout(wait.CtxSeconds(10)) @@ -567,3 +575,36 @@ func (h *Helper) HasClientRole(realm, clientID, userID, roleName string) (bool, return false, nil } + +func paginateClientRoleUsers(fetch func(gocloak.GetUsersByRoleParams) ([]*gocloak.User, error), params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + if params.Max != nil { + return fetch(params) + } + + var all []*gocloak.User + first := 0 + for { + page := params + page.First = gocloak.IntP(first) + page.Max = gocloak.IntP(groupsPageSize) + + got, err := fetch(page) + if err != nil { + return nil, err + } + + all = append(all, got...) + if len(got) < groupsPageSize { + return all, nil + } + first += groupsPageSize + } +} + +func (h *Helper) GetUsersByClientRoleName(realm, clientID, roleName string) ([]*gocloak.User, error) { + return paginateClientRoleUsers(func(p gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + ctx, cancel := context.WithTimeout(wait.CtxSeconds(10)) + defer cancel() + return h.Client.GetUsersByClientRoleName(ctx, h.Token, realm, clientID, roleName, p) + }, gocloak.GetUsersByRoleParams{}) +} diff --git a/pkg/keycloak/keycloak_test.go b/pkg/keycloak/keycloak_test.go index 4cac9a0..fb14ac7 100644 --- a/pkg/keycloak/keycloak_test.go +++ b/pkg/keycloak/keycloak_test.go @@ -343,6 +343,41 @@ func TestPaginateGroups(t *testing.T) { }) } +func TestHelperGetGroupByPath(t *testing.T) { + errBoom := errors.New("boom") + + tests := []struct { + name string + group *gocloak.Group + fetchError error + expectedError error + }{ + { + name: "Should propagate an error fetching the group by path", + fetchError: errBoom, + expectedError: errBoom, + }, + { + name: "Should return the group at the given path", + group: groupAt("g1", "admin", "/cubecmp/PROJ001/admin"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := NewMockClient(t) + client.On("GetGroupByPath", mock.Anything, mock.Anything, "master", "/cubecmp/PROJ001/admin"). + Return(tc.group, tc.fetchError) + + h := &Helper{Client: client} + got, err := h.GetGroupByPath("master", "/cubecmp/PROJ001/admin") + + require.ErrorIs(t, err, tc.expectedError) + require.Equal(t, tc.group, got) + }) + } +} + func TestHelperGetUserGroups(t *testing.T) { errBoom := errors.New("boom") @@ -582,6 +617,114 @@ func TestHasClientRole(t *testing.T) { } } +func TestGetUsersByClientRoleName(t *testing.T) { + errBoom := errors.New("boom") + + tests := []struct { + name string + users []*gocloak.User + fetchError error + expectedError error + }{ + { + name: "Should propagate an error fetching the role's users", + fetchError: errBoom, + expectedError: errBoom, + }, + { + name: "Should return every user holding the role", + users: []*gocloak.User{ + {Username: strPtr("alice")}, + {Username: strPtr("bob")}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := NewMockClient(t) + client.On("GetUsersByClientRoleName", mock.Anything, mock.Anything, "master", "client-id", "super-admin", mock.Anything). + Return(tc.users, tc.fetchError) + + h := &Helper{Client: client} + got, err := h.GetUsersByClientRoleName("master", "client-id", "super-admin") + + require.ErrorIs(t, err, tc.expectedError) + require.Equal(t, tc.users, got) + }) + } +} + +func TestPaginateClientRoleUsers(t *testing.T) { + errBoom := errors.New("boom") + + tests := []struct { + name string + params gocloak.GetUsersByRoleParams + pages [][]*gocloak.User + pageError error + expectedCalls int + expectedFirsts []int + expectedLen int + expectedError error + }{ + { + name: "Should fetch a single page as-is when the caller already set Max", + params: gocloak.GetUsersByRoleParams{Max: gocloak.IntP(10)}, + pages: [][]*gocloak.User{{{Username: strPtr("alice")}}}, + pageError: nil, + expectedCalls: 1, + expectedFirsts: nil, + expectedLen: 1, + expectedError: nil, + }, + { + name: "Should keep paging while pages come back full, and stop on a short page", + params: gocloak.GetUsersByRoleParams{}, + pages: [][]*gocloak.User{make([]*gocloak.User, groupsPageSize), {{Username: strPtr("last")}}}, + pageError: nil, + expectedCalls: 2, + expectedFirsts: []int{0, groupsPageSize}, + expectedLen: groupsPageSize + 1, + expectedError: nil, + }, + { + name: "Should propagate an error from any page", + params: gocloak.GetUsersByRoleParams{}, + pages: nil, + pageError: errBoom, + expectedCalls: 1, + expectedFirsts: []int{0}, + expectedLen: 0, + expectedError: errBoom, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + calls := 0 + var seenFirsts []int + fetch := func(p gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + if p.First != nil { + seenFirsts = append(seenFirsts, *p.First) + } + calls++ + if tc.pageError != nil { + return nil, tc.pageError + } + return tc.pages[calls-1], nil + } + + got, err := paginateClientRoleUsers(fetch, tc.params) + + require.ErrorIs(t, err, tc.expectedError) + require.Equal(t, tc.expectedCalls, calls) + require.Equal(t, tc.expectedFirsts, seenFirsts) + require.Len(t, got, tc.expectedLen) + }) + } +} + func TestGetUser(t *testing.T) { errBoom := errors.New("boom") diff --git a/pkg/keycloak/mock_Client_test.go b/pkg/keycloak/mock_Client_test.go index d129806..ef92627 100644 --- a/pkg/keycloak/mock_Client_test.go +++ b/pkg/keycloak/mock_Client_test.go @@ -1458,6 +1458,86 @@ func (_c *MockClient_GetGroup_Call) RunAndReturn(run func(ctx context.Context, t return _c } +// GetGroupByPath provides a mock function for the type MockClient +func (_mock *MockClient) GetGroupByPath(ctx context.Context, token string, realm string, groupPath string) (*gocloak.Group, error) { + ret := _mock.Called(ctx, token, realm, groupPath) + + if len(ret) == 0 { + panic("no return value specified for GetGroupByPath") + } + + var r0 *gocloak.Group + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) (*gocloak.Group, error)); ok { + return returnFunc(ctx, token, realm, groupPath) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) *gocloak.Group); ok { + r0 = returnFunc(ctx, token, realm, groupPath) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gocloak.Group) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { + r1 = returnFunc(ctx, token, realm, groupPath) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GetGroupByPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetGroupByPath' +type MockClient_GetGroupByPath_Call struct { + *mock.Call +} + +// GetGroupByPath is a helper method to define mock.On call +// - ctx context.Context +// - token string +// - realm string +// - groupPath string +func (_e *MockClient_Expecter) GetGroupByPath(ctx any, token any, realm any, groupPath any) *MockClient_GetGroupByPath_Call { + return &MockClient_GetGroupByPath_Call{Call: _e.mock.On("GetGroupByPath", ctx, token, realm, groupPath)} +} + +func (_c *MockClient_GetGroupByPath_Call) Run(run func(ctx context.Context, token string, realm string, groupPath string)) *MockClient_GetGroupByPath_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *MockClient_GetGroupByPath_Call) Return(group *gocloak.Group, err error) *MockClient_GetGroupByPath_Call { + _c.Call.Return(group, err) + return _c +} + +func (_c *MockClient_GetGroupByPath_Call) RunAndReturn(run func(ctx context.Context, token string, realm string, groupPath string) (*gocloak.Group, error)) *MockClient_GetGroupByPath_Call { + _c.Call.Return(run) + return _c +} + // GetGroupMembers provides a mock function for the type MockClient func (_mock *MockClient) GetGroupMembers(ctx context.Context, token string, realm string, groupID string, params gocloak.GetGroupsParams) ([]*gocloak.User, error) { ret := _mock.Called(ctx, token, realm, groupID, params) @@ -1870,6 +1950,98 @@ func (_c *MockClient_GetUsers_Call) RunAndReturn(run func(context1 context.Conte return _c } +// GetUsersByClientRoleName provides a mock function for the type MockClient +func (_mock *MockClient) GetUsersByClientRoleName(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + ret := _mock.Called(ctx, token, realm, idOfClient, roleName, params) + + if len(ret) == 0 { + panic("no return value specified for GetUsersByClientRoleName") + } + + var r0 []*gocloak.User + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, gocloak.GetUsersByRoleParams) ([]*gocloak.User, error)); ok { + return returnFunc(ctx, token, realm, idOfClient, roleName, params) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, gocloak.GetUsersByRoleParams) []*gocloak.User); ok { + r0 = returnFunc(ctx, token, realm, idOfClient, roleName, params) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*gocloak.User) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string, gocloak.GetUsersByRoleParams) error); ok { + r1 = returnFunc(ctx, token, realm, idOfClient, roleName, params) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GetUsersByClientRoleName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUsersByClientRoleName' +type MockClient_GetUsersByClientRoleName_Call struct { + *mock.Call +} + +// GetUsersByClientRoleName is a helper method to define mock.On call +// - ctx context.Context +// - token string +// - realm string +// - idOfClient string +// - roleName string +// - params gocloak.GetUsersByRoleParams +func (_e *MockClient_Expecter) GetUsersByClientRoleName(ctx any, token any, realm any, idOfClient any, roleName any, params any) *MockClient_GetUsersByClientRoleName_Call { + return &MockClient_GetUsersByClientRoleName_Call{Call: _e.mock.On("GetUsersByClientRoleName", ctx, token, realm, idOfClient, roleName, params)} +} + +func (_c *MockClient_GetUsersByClientRoleName_Call) Run(run func(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams)) *MockClient_GetUsersByClientRoleName_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 gocloak.GetUsersByRoleParams + if args[5] != nil { + arg5 = args[5].(gocloak.GetUsersByRoleParams) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *MockClient_GetUsersByClientRoleName_Call) Return(users []*gocloak.User, err error) *MockClient_GetUsersByClientRoleName_Call { + _c.Call.Return(users, err) + return _c +} + +func (_c *MockClient_GetUsersByClientRoleName_Call) RunAndReturn(run func(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error)) *MockClient_GetUsersByClientRoleName_Call { + _c.Call.Return(run) + return _c +} + // Login provides a mock function for the type MockClient func (_mock *MockClient) Login(context1 context.Context, s string, s1 string, s2 string, s3 string, s4 string) (*gocloak.JWT, error) { ret := _mock.Called(context1, s, s1, s2, s3, s4)