From 354e81db3103c3ee1d41901d2bea26f44ff63dce Mon Sep 17 00:00:00 2001 From: Omri Gazitt Date: Fri, 24 May 2024 12:08:03 -0700 Subject: [PATCH] fusionauth plugin (#122) * fusionauth plugin * updated ci and goreleaser * fix lints * better fix * update --- .github/workflows/ci.yaml | 1 + .goreleaser.yml | 24 ++++ go.work | 1 + .../fusionauth/cmd/ds-load-fusionauth/main.go | 46 ++++++++ plugins/fusionauth/go.mod | 44 +++++++ plugins/fusionauth/go.sum | 108 ++++++++++++++++++ plugins/fusionauth/pkg/app/assets.go | 12 ++ .../pkg/app/assets/transform_template.tmpl | 99 ++++++++++++++++ plugins/fusionauth/pkg/app/cli.go | 30 +++++ plugins/fusionauth/pkg/app/constants.go | 4 + plugins/fusionauth/pkg/app/exec.go | 35 ++++++ .../fusionauth/pkg/app/export_transform.go | 21 ++++ plugins/fusionauth/pkg/app/fetch.go | 30 +++++ plugins/fusionauth/pkg/app/transform.go | 46 ++++++++ plugins/fusionauth/pkg/app/verify.go | 25 ++++ plugins/fusionauth/pkg/fetch/fetch.go | 83 ++++++++++++++ .../pkg/fusionauthclient/fusionauth.go | 83 ++++++++++++++ plugins/fusionauth/pkg/verify/verifier.go | 29 +++++ 18 files changed, 721 insertions(+) create mode 100644 plugins/fusionauth/cmd/ds-load-fusionauth/main.go create mode 100644 plugins/fusionauth/go.mod create mode 100644 plugins/fusionauth/go.sum create mode 100644 plugins/fusionauth/pkg/app/assets.go create mode 100644 plugins/fusionauth/pkg/app/assets/transform_template.tmpl create mode 100644 plugins/fusionauth/pkg/app/cli.go create mode 100644 plugins/fusionauth/pkg/app/constants.go create mode 100644 plugins/fusionauth/pkg/app/exec.go create mode 100644 plugins/fusionauth/pkg/app/export_transform.go create mode 100644 plugins/fusionauth/pkg/app/fetch.go create mode 100644 plugins/fusionauth/pkg/app/transform.go create mode 100644 plugins/fusionauth/pkg/app/verify.go create mode 100644 plugins/fusionauth/pkg/fetch/fetch.go create mode 100644 plugins/fusionauth/pkg/fusionauthclient/fusionauth.go create mode 100644 plugins/fusionauth/pkg/verify/verifier.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 29fcc97..400988c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,6 +32,7 @@ jobs: - "./plugins/auth0" - "./plugins/azuread" - "./plugins/cognito" + - "./plugins/fusionauth" - "./plugins/google" - "./plugins/ldap" - "./plugins/okta" diff --git a/.goreleaser.yml b/.goreleaser.yml index 038602f..9b21c2c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -130,6 +130,29 @@ builds: - -X github.com/{{ .Env.ORG }}/{{ .Env.REPO }}/common/version.date={{.Date}} mod_timestamp: "{{ .CommitTimestamp }}" + - id: ds-load-fusionauth + main: ./plugins/fusionauth/cmd/ds-load-fusionauth + binary: ds-load-fusionauth + goos: + - darwin + - linux + - windows + goarch: + - amd64 + - arm64 + env: + - CGO_ENABLED=0 + ignore: + - goos: windows + goarch: arm64 + ldflags: + - -s + - -w + - -X github.com/{{ .Env.ORG }}/{{ .Env.REPO }}/common/version.ver={{.Version}} + - -X github.com/{{ .Env.ORG }}/{{ .Env.REPO }}/common/version.commit={{.ShortCommit}} + - -X github.com/{{ .Env.ORG }}/{{ .Env.REPO }}/common/version.date={{.Date}} + mod_timestamp: "{{ .CommitTimestamp }}" + - id: ds-load-google main: ./plugins/google/cmd/ds-load-google binary: ds-load-google @@ -254,6 +277,7 @@ brews: bin.install "ds-load-google" bin.install "ds-load-okta" bin.install "ds-load-ldap" + bin.install "ds-load-fusionauth" dockers: # https://goreleaser.com/customization/docker/ diff --git a/go.work b/go.work index b33de18..627e4db 100644 --- a/go.work +++ b/go.work @@ -6,6 +6,7 @@ use ( ./plugins/auth0 ./plugins/azuread ./plugins/cognito + ./plugins/fusionauth ./plugins/google ./plugins/ldap ./plugins/okta diff --git a/plugins/fusionauth/cmd/ds-load-fusionauth/main.go b/plugins/fusionauth/cmd/ds-load-fusionauth/main.go new file mode 100644 index 0000000..86dd0ac --- /dev/null +++ b/plugins/fusionauth/cmd/ds-load-fusionauth/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "os" + + "github.com/alecthomas/kong" + "github.com/aserto-dev/ds-load/cli/pkg/cc" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/app" + "github.com/aserto-dev/ds-load/sdk/common" + "github.com/aserto-dev/ds-load/sdk/common/kongyaml" +) + +func main() { + cli := app.CLI{} + + defaultConfigPath := "~/.config/ds-load/cfg/fusionauth.yaml" + + yamlLoader := kongyaml.NewYAMLResolver("fusionauth") + options := []kong.Option{ + kong.Name(app.AppName), + kong.Exit(func(exitCode int) { + os.Exit(exitCode) + }), + kong.Description(app.AppDescription), + kong.UsageOnError(), + kong.Configuration(yamlLoader.Loader, defaultConfigPath), + kong.ConfigureHelp(kong.HelpOptions{ + NoAppSummary: false, + Summary: false, + Compact: true, + Tree: false, + FlagsLast: true, + Indenter: kong.SpaceIndenter, + NoExpandSubcommands: false, + }), + } + + ctx := cc.NewCommonContext(cli.Verbosity, string(cli.Config)) + + kongCtx := kong.Parse(&cli, options...) + if err := kongCtx.Run(ctx); err != nil { + kongCtx.FatalIfErrorf(err) + } + + os.Exit(common.GetExitCode()) +} diff --git a/plugins/fusionauth/go.mod b/plugins/fusionauth/go.mod new file mode 100644 index 0000000..6390792 --- /dev/null +++ b/plugins/fusionauth/go.mod @@ -0,0 +1,44 @@ +module github.com/aserto-dev/ds-load/plugins/fusionauth + +go 1.21 + +replace github.com/aserto-dev/ds-load/sdk => ../../sdk + +require ( + github.com/FusionAuth/go-client v0.0.0-20240425220342-2317e10dfcf5 + github.com/alecthomas/kong v0.9.0 + github.com/aserto-dev/ds-load/cli v0.0.0-20230808135510-07f5c2157f1f + github.com/pkg/errors v0.9.1 +) + +require github.com/aserto-dev/ds-load/sdk v0.0.0-00010101000000-000000000000 + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1 // indirect + github.com/aserto-dev/clui v0.8.3 // indirect + github.com/aserto-dev/go-directory v0.31.3 // indirect + github.com/aserto-dev/logger v0.0.4 // indirect + github.com/dongri/phonenumber v0.1.2 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect + github.com/kyokomi/emoji v2.2.4+incompatible // indirect + github.com/magefile/mage v1.15.0 // indirect + 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.15 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/zerolog v1.32.0 // indirect + github.com/samber/lo v1.39.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415151819-79826c84ba32 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415151819-79826c84ba32 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/plugins/fusionauth/go.sum b/plugins/fusionauth/go.sum new file mode 100644 index 0000000..faaa625 --- /dev/null +++ b/plugins/fusionauth/go.sum @@ -0,0 +1,108 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1 h1:2IGhRovxlsOIQgx2ekZWo4wTPAYpck41+18ICxs37is= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1/go.mod h1:Tgn5bgL220vkFOI0KPStlcClPeOJzAv4uT+V8JXGUnw= +github.com/FusionAuth/go-client v0.0.0-20240425220342-2317e10dfcf5 h1:GGDFaOr/r7FTXZj4wrgtW4LlXeTFuBVoWgzuW4cbQ+s= +github.com/FusionAuth/go-client v0.0.0-20240425220342-2317e10dfcf5/go.mod h1:SyRrXMJAzMVQLiJjKfQUR59dRI3jPyZv+BXIZ//HwE4= +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= +github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/aserto-dev/clui v0.8.3 h1:foEJuVpMFVP4La3SxUcinxRLOZx/TyS2BRuahbywYFg= +github.com/aserto-dev/clui v0.8.3/go.mod h1:KsL/g2x5LAbkEE4ofW/ZoA4FDOIdAyLes/5ullvzUt8= +github.com/aserto-dev/ds-load/cli v0.0.0-20230808135510-07f5c2157f1f h1:BB3ZNg3lBrUreDWXwzlKzvzeZVgvBx9qrLomdWhW6Sw= +github.com/aserto-dev/ds-load/cli v0.0.0-20230808135510-07f5c2157f1f/go.mod h1:ATQCrD8rBoWen8yl2/sV8aI3YSc56M6WV7yGBxy1x/E= +github.com/aserto-dev/go-directory v0.31.3 h1:Twiulwykm+ivko+H73TMVSvSlE2urIN3XiGK1F2bz1U= +github.com/aserto-dev/go-directory v0.31.3/go.mod h1:Pjb9QcswMnuIPl8iDexG4d2AcFtLWDpVisGodrKuvC0= +github.com/aserto-dev/logger v0.0.4 h1:GF+17mhn03ZnE5KHCszrzGRcZULgczsql+y+PCHjgpI= +github.com/aserto-dev/logger v0.0.4/go.mod h1:awdS/W0VnLNyP+aT5mmLx9PjOcT5IrXsYMxqwHglSLU= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dongri/phonenumber v0.1.2 h1:PI/WmOo2qP/2umPvvbbNAuNJi4Xu4KNapE92z6YD2oU= +github.com/dongri/phonenumber v0.1.2/go.mod h1:cuHFSstIxh6qh/Qs/SCV3Grb/JMYregBLuXELvSYmT4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4= +github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +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-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= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240415151819-79826c84ba32 h1:wSV4wyEpYVUdxkO7uzZ9Fr1g7a+wllr4fuQqTaR53pw= +google.golang.org/genproto/googleapis/api v0.0.0-20240415151819-79826c84ba32/go.mod h1:wTHjrkbcS8AoQbb/0v9bFIPItZQPAsyVfgG9YPUhjAM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415151819-79826c84ba32 h1:GRBZAfftU6zhUwoY2cfnbtig9cTfQKXH0rAdBFVxra4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415151819-79826c84ba32/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002 h1:V7Da7qt0MkY3noVANIMVBk28nOnijADeOR3i5Hcvpj4= +google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/plugins/fusionauth/pkg/app/assets.go b/plugins/fusionauth/pkg/app/assets.go new file mode 100644 index 0000000..53dec2c --- /dev/null +++ b/plugins/fusionauth/pkg/app/assets.go @@ -0,0 +1,12 @@ +package app + +import ( + "embed" +) + +//go:embed assets/* +var staticAssets embed.FS + +func Assets() embed.FS { + return staticAssets +} diff --git a/plugins/fusionauth/pkg/app/assets/transform_template.tmpl b/plugins/fusionauth/pkg/app/assets/transform_template.tmpl new file mode 100644 index 0000000..0700e60 --- /dev/null +++ b/plugins/fusionauth/pkg/app/assets/transform_template.tmpl @@ -0,0 +1,99 @@ +{ + {{ $displayName := $.email }} + {{ if $.firstName }} + {{ $displayName := printf "%s %s" $.firstName $.lastName }} + {{ end }} + + "objects": [ + {{ if $.email }} + { + "id": "{{ $.email }}", + "type": "user", + "displayName": "{{ $displayName }}", + "properties":{ + {{ fromEnv "connection_id" "ASERTO_CONNECTION_ID" }}, + "email": "{{ $.email }}", + {{ if $.picture }} + "picture": "{{ $.picture }}", + {{ end }} + {{ if $.data }} + "data": {{ marshal $.data }}, + {{ end }} + {{ if $.memberships }} + "memberships": {{ marshal $.memberships }}, + {{ end }} + "registrations": {{ marshal $.registrations }}, + "active": "{{ $.active }}", + "tenantId": "{{ $.tenantId }}", + "verified": {{ $.verified }} + } + }, + { + "id": "{{ $.email }}", + "type": "identity", + "properties": { + "kind": "IDENTITY_KIND_EMAIL", + "provider": "fusionauth", + {{ fromEnv "connection_id" "ASERTO_CONNECTION_ID" }}, + "verified": {{ $.verified }} + } + }, + { + "id": "{{ $.id }}", + "type": "identity", + "properties": { + "kind": "IDENTITY_KIND_PID", + "provider": "fusionauth", + {{ fromEnv "connection_id" "ASERTO_CONNECTION_ID" }}, + "verified": {{ $.verified }} + } + } + {{ end }} + + {{ if not $.email }} + { + "id": "{{ $.id }}", + "type": "group", + "displayName": "{{ $.name }}", + "properties":{ + {{ fromEnv "connection_id" "ASERTO_CONNECTION_ID" }}, + {{ if $.roles }} + "roles": {{ marshal $.roles }}, + {{ end }} + "tenantId": "{{ $.tenantId }}" + } + } + {{ end }} + ], + "relations":[ + {{ if $.email }} + { + "object_type": "identity", + "object_id": "{{ $.id }}", + "relation": "identifier", + "subject_type": "user", + "subject_id": "{{ $.email }}" + }, + { + "object_type": "identity", + "object_id": "{{ $.email }}", + "relation": "identifier", + "subject_type": "user", + "subject_id": "{{ $.email }}" + } + + {{ if $.memberships }}, {{ end }} + + {{ range $i, $element := $.memberships }} + {{ if $i }},{{ end }} + { + "object_type": "group", + "object_id": "{{ $element.groupId }}", + "relation": "member", + "subject_type": "user", + "subject_id": "{{ $.email }}" + } + {{ end }} + {{ end }} + ] +} diff --git a/plugins/fusionauth/pkg/app/cli.go b/plugins/fusionauth/pkg/app/cli.go new file mode 100644 index 0000000..f58baf7 --- /dev/null +++ b/plugins/fusionauth/pkg/app/cli.go @@ -0,0 +1,30 @@ +package app + +import ( + "fmt" + + "github.com/alecthomas/kong" + "github.com/aserto-dev/ds-load/sdk/common/version" +) + +type CLI struct { + Config kong.ConfigFlag `help:"Configuration file path" short:"c"` + Version VersionCmd `cmd:"" help:"version information"` + Fetch FetchCmd `cmd:"" help:"fetch fusionauth data"` + Transform TransformCmd `cmd:"" help:"transform fusionauth data"` + ExportTransform ExportTransformCmd `cmd:"" help:"export default transform template"` + Exec ExecCmd `cmd:"" help:"fetch and transform fusionauth data" default:"withargs"` + Verbosity int `short:"v" type:"counter" help:"Use to increase output verbosity."` + Verify VerifyCmd `cmd:"verify" help:"verify fetcher configuration and credentials"` +} + +type VersionCmd struct { +} + +func (cmd *VersionCmd) Run() error { + fmt.Printf("%s - %s\n", + AppName, + version.GetInfo().String(), + ) + return nil +} diff --git a/plugins/fusionauth/pkg/app/constants.go b/plugins/fusionauth/pkg/app/constants.go new file mode 100644 index 0000000..8c683ca --- /dev/null +++ b/plugins/fusionauth/pkg/app/constants.go @@ -0,0 +1,4 @@ +package app + +const AppName = "ds-load-fusionauth" +const AppDescription = "FusionAuth directory loader" diff --git a/plugins/fusionauth/pkg/app/exec.go b/plugins/fusionauth/pkg/app/exec.go new file mode 100644 index 0000000..3a7d26e --- /dev/null +++ b/plugins/fusionauth/pkg/app/exec.go @@ -0,0 +1,35 @@ +package app + +import ( + "github.com/aserto-dev/ds-load/cli/pkg/cc" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fetch" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fusionauthclient" + "github.com/aserto-dev/ds-load/sdk/exec" + "github.com/aserto-dev/ds-load/sdk/transform" +) + +type ExecCmd struct { + FetchCmd + TransformCmd +} + +func (cmd *ExecCmd) Run(ctx *cc.CommonCtx) error { + + fusionauthClient, err := fusionauthclient.NewFusionAuthClient(cmd.HostURL, cmd.APIKey) + if err != nil { + return err + } + + fetcher, err := fetch.New(fusionauthClient) + if err != nil { + return err + } + fetcher = fetcher.WithGroups(cmd.Groups) + + templateContent, err := cmd.getTemplateContent() + if err != nil { + return err + } + transformer := transform.NewGoTemplateTransform(templateContent) + return exec.Execute(ctx.Context, ctx.Log, transformer, fetcher) +} diff --git a/plugins/fusionauth/pkg/app/export_transform.go b/plugins/fusionauth/pkg/app/export_transform.go new file mode 100644 index 0000000..9448741 --- /dev/null +++ b/plugins/fusionauth/pkg/app/export_transform.go @@ -0,0 +1,21 @@ +package app + +import ( + "os" + + "github.com/aserto-dev/ds-load/cli/pkg/cc" + "github.com/aserto-dev/ds-load/sdk/transform" +) + +type ExportTransformCmd struct { +} + +func (t *ExportTransformCmd) Run(ctx *cc.CommonCtx) error { + templateContent, err := Assets().ReadFile("assets/transform_template.tmpl") + if err != nil { + return err + } + transformer := transform.NewGoTemplateTransform(templateContent) + + return transformer.ExportTransform(os.Stdout) +} diff --git a/plugins/fusionauth/pkg/app/fetch.go b/plugins/fusionauth/pkg/app/fetch.go new file mode 100644 index 0000000..9362278 --- /dev/null +++ b/plugins/fusionauth/pkg/app/fetch.go @@ -0,0 +1,30 @@ +package app + +import ( + "os" + + "github.com/aserto-dev/ds-load/cli/pkg/cc" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fetch" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fusionauthclient" +) + +type FetchCmd struct { + HostURL string `short:"u" help:"FusionAuth Host URL" env:"FUSIONAUTH_HOST_URL" required:""` + APIKey string `short:"k" help:"FusionAuth API Key" env:"FUSIONAUTH_API_KEY" required:""` + Groups bool `short:"g" help:"Retrieve FusionAuth groups" env:"FUSIONAUTH_GROUPS" default:"false" negatable:""` +} + +func (cmd *FetchCmd) Run(ctx *cc.CommonCtx) error { + fusionauthClient, err := fusionauthclient.NewFusionAuthClient(cmd.HostURL, cmd.APIKey) + if err != nil { + return err + } + + fetcher, err := fetch.New(fusionauthClient) + if err != nil { + return err + } + fetcher = fetcher.WithGroups(cmd.Groups).WithHost(cmd.HostURL) + + return fetcher.Fetch(ctx.Context, os.Stdout, os.Stderr) +} diff --git a/plugins/fusionauth/pkg/app/transform.go b/plugins/fusionauth/pkg/app/transform.go new file mode 100644 index 0000000..fca5816 --- /dev/null +++ b/plugins/fusionauth/pkg/app/transform.go @@ -0,0 +1,46 @@ +// get json from stdin and return json with v2 objects and v2 relations +package app + +import ( + "context" + "os" + + "github.com/aserto-dev/ds-load/cli/pkg/cc" + "github.com/aserto-dev/ds-load/sdk/plugin" + "github.com/aserto-dev/ds-load/sdk/transform" +) + +type TransformCmd struct { + Template string `name:"template" short:"t" env:"DS_TEMPLATE_FILE" help:"transformation template file path" type:"path" optional:""` +} + +func (t *TransformCmd) Run(ctx *cc.CommonCtx) error { + template, err := t.getTemplateContent() + if err != nil { + return err + } + + goTemplateTransformer := transform.NewGoTemplateTransform(template) + return t.transform(ctx.Context, goTemplateTransformer) +} + +func (t *TransformCmd) transform(ctx context.Context, transformer plugin.Transformer) error { + return transformer.Transform(ctx, os.Stdin, os.Stdout, os.Stderr) +} + +func (t *TransformCmd) getTemplateContent() ([]byte, error) { + var templateContent []byte + var err error + if t.Template == "" { + templateContent, err = Assets().ReadFile("assets/transform_template.tmpl") + if err != nil { + return nil, err + } + } else { + templateContent, err = os.ReadFile(t.Template) + if err != nil { + return nil, err + } + } + return templateContent, nil +} diff --git a/plugins/fusionauth/pkg/app/verify.go b/plugins/fusionauth/pkg/app/verify.go new file mode 100644 index 0000000..62e94cb --- /dev/null +++ b/plugins/fusionauth/pkg/app/verify.go @@ -0,0 +1,25 @@ +package app + +import ( + "github.com/aserto-dev/ds-load/cli/pkg/cc" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fusionauthclient" + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/verify" +) + +type VerifyCmd struct { + FetchCmd +} + +func (v *VerifyCmd) Run(ctx *cc.CommonCtx) error { + fusionauthClient, err := fusionauthclient.NewFusionAuthClient(v.HostURL, v.APIKey) + if err != nil { + return err + } + + verifier, err := verify.New(ctx.Context, fusionauthClient) + if err != nil { + return err + } + + return verifier.Verify(ctx.Context) +} diff --git a/plugins/fusionauth/pkg/fetch/fetch.go b/plugins/fusionauth/pkg/fetch/fetch.go new file mode 100644 index 0000000..03cd03b --- /dev/null +++ b/plugins/fusionauth/pkg/fetch/fetch.go @@ -0,0 +1,83 @@ +package fetch + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fusionauthclient" + "github.com/aserto-dev/ds-load/sdk/common/js" +) + +type Fetcher struct { + fusionauthClient *fusionauthclient.FusionAuthClient + groups bool + host string +} + +func New(client *fusionauthclient.FusionAuthClient) (*Fetcher, error) { + return &Fetcher{ + fusionauthClient: client, + }, nil +} + +func (f *Fetcher) WithGroups(groups bool) *Fetcher { + f.groups = groups + return f +} + +func (f *Fetcher) WithHost(host string) *Fetcher { + f.host = host + return f +} + +func (f *Fetcher) Fetch(ctx context.Context, outputWriter, errorWriter io.Writer) error { + writer := js.NewJSONArrayWriter(outputWriter) + defer writer.Close() + + users, err := f.fusionauthClient.ListUsers(ctx) + if err != nil { + _, _ = errorWriter.Write([]byte(err.Error())) + } + + for i := range users { + user := &users[i] + userBytes, err := json.Marshal(user) + if err != nil { + _, _ = errorWriter.Write([]byte(err.Error())) + return err + } + var obj map[string]interface{} + err = json.Unmarshal(userBytes, &obj) + if err != nil { + _, _ = errorWriter.Write([]byte(err.Error())) + return err + } + if user.ImageUrl != "" { + obj["picture"] = fmt.Sprintf("%s%s", f.host, user.ImageUrl) + } + + err = writer.Write(obj) + if err != nil { + _, _ = errorWriter.Write([]byte(err.Error())) + } + } + + if f.groups { + groups, err := f.fusionauthClient.ListGroups(ctx) + if err != nil { + _, _ = errorWriter.Write([]byte(err.Error())) + } + + for i := range groups { + group := &groups[i] + err = writer.Write(group) + if err != nil { + _, _ = errorWriter.Write([]byte(err.Error())) + } + } + } + + return nil +} diff --git a/plugins/fusionauth/pkg/fusionauthclient/fusionauth.go b/plugins/fusionauth/pkg/fusionauthclient/fusionauth.go new file mode 100644 index 0000000..f7d2a98 --- /dev/null +++ b/plugins/fusionauth/pkg/fusionauthclient/fusionauth.go @@ -0,0 +1,83 @@ +package fusionauthclient + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/FusionAuth/go-client/pkg/fusionauth" +) + +type FusionAuthClient struct { + fusionauthClient *fusionauth.FusionAuthClient + host string +} + +func NewFusionAuthClient(host, apiKey string) (*FusionAuthClient, error) { + c := &FusionAuthClient{} + + var httpClient = &http.Client{ + Timeout: time.Second * 10, + } + + var baseURL, _ = url.Parse(host) + + c.fusionauthClient = fusionauth.NewClient(httpClient, baseURL, apiKey) + c.host = host + return c, nil +} + +func (c *FusionAuthClient) ListUsers(ctx context.Context) ([]fusionauth.User, error) { + users := make([]fusionauth.User, 0) + pageSize := 100 + startRow := 0 + + for { + searchRequest := fusionauth.SearchRequest{ + Search: fusionauth.UserSearchCriteria{ + BaseElasticSearchCriteria: fusionauth.BaseElasticSearchCriteria{ + QueryString: "*", + BaseSearchCriteria: fusionauth.BaseSearchCriteria{ + NumberOfResults: pageSize, + StartRow: startRow, + }, + }, + }, + } + + response, faErrs, err := c.fusionauthClient.SearchUsersByQueryWithContext(ctx, searchRequest) + if err != nil { + fmt.Println("Failed to list users:", err) + return nil, err + } + if faErrs != nil { + fmt.Println("Failed to list users:", faErrs) + return nil, faErrs + } + + users = append(users, response.Users...) + startRow += pageSize + + if int64(startRow) >= response.Total { + break + } + } + return users, nil +} + +func (c *FusionAuthClient) ListGroups(ctx context.Context) ([]fusionauth.Group, error) { + groupResponse, err := c.fusionauthClient.RetrieveGroupsWithContext(ctx) + return groupResponse.Groups, err +} + +func (c *FusionAuthClient) GetUserByID(ctx context.Context, id string) (fusionauth.User, error) { + userResponse, _, err := c.fusionauthClient.RetrieveUserWithContext(ctx, id) + return userResponse.User, err +} + +func (c *FusionAuthClient) GetUserByEmail(ctx context.Context, email string) (fusionauth.User, error) { + userResponse, _, err := c.fusionauthClient.RetrieveUserByEmailWithContext(ctx, email) + return userResponse.User, err +} diff --git a/plugins/fusionauth/pkg/verify/verifier.go b/plugins/fusionauth/pkg/verify/verifier.go new file mode 100644 index 0000000..a74ee74 --- /dev/null +++ b/plugins/fusionauth/pkg/verify/verifier.go @@ -0,0 +1,29 @@ +package verify + +import ( + "context" + + "github.com/aserto-dev/ds-load/plugins/fusionauth/pkg/fusionauthclient" + "github.com/pkg/errors" +) + +type Verifier struct { + client *fusionauthclient.FusionAuthClient +} + +func New(ctx context.Context, client *fusionauthclient.FusionAuthClient) (*Verifier, error) { + return &Verifier{ + client: client, + }, nil + +} + +func (v *Verifier) Verify(ctx context.Context) error { + _, errReq := v.client.ListUsers(ctx) + + if errReq != nil { + return errors.Wrap(errReq, "failed to retrieve users from FusionAuth") + } + + return nil +}