From 744185449eb5ccdc0b8007252d114a4f57921c24 Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Wed, 17 Feb 2021 15:31:47 +0100 Subject: [PATCH] feat: token introspection, api clients and auth method private_key_jwt (#1276) * introspect * testingapplication key * date * client keys * fix client keys * fix client keys * access tokens only for users * AuthMethodPrivateKeyJWT * client keys * set introspection info correctly * managae apis * update oidc pkg * cleanup * merge msater * set current sequence in migration * set current sequence in migration * set current sequence in migration * Apply suggestions from code review Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * DeleteAuthNKeysByObjectID * ensure authn keys uptodate * update oidc version * merge master * merge master Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> --- cmd/zitadel/startup.yaml | 4 + cmd/zitadel/system-defaults.yaml | 1 + go.mod | 6 +- go.sum | 51 +++- .../api/grpc/auth/user_machine_converter.go | 36 +-- internal/api/grpc/management/application.go | 52 ++++ .../grpc/management/application_converter.go | 187 +++++++++++++ .../grpc/management/user_machine_converter.go | 31 ++- internal/api/oidc/client.go | 74 +++++- internal/api/oidc/client_converter.go | 16 +- internal/api/oidc/op.go | 15 +- .../eventsourcing/eventstore/org.go | 9 + .../eventsourcing/eventstore/token.go | 9 +- .../eventsourcing/eventstore/user.go | 8 +- .../eventsourcing/handler/application.go | 2 + .../eventsourcing/handler/authn_keys.go | 120 +++++++++ .../eventsourcing/handler/handler.go | 2 +- .../eventsourcing/handler/machine_keys.go | 110 -------- .../repository/eventsourcing/handler/token.go | 4 +- .../repository/eventsourcing/repository.go | 6 +- .../eventsourcing/view/authn_keys.go | 74 ++++++ .../eventsourcing/view/machine_keys.go | 74 ------ internal/auth/repository/org.go | 2 + internal/auth/repository/user.go | 3 +- .../config/systemdefaults/system_defaults.go | 1 + internal/key/model/authn_key.go | 101 ++++++++ .../key/repository/view/authn_key_view.go | 77 ++++++ .../key/repository/view/model/authn_key.go | 171 ++++++++++++ .../repository/view/model/authn_key_query.go | 63 +++++ .../eventsourcing/eventstore/project.go | 68 +++++ .../eventsourcing/eventstore/user.go | 20 +- .../eventsourcing/handler/application.go | 2 + .../eventsourcing/handler/authn_keys.go | 120 +++++++++ .../eventsourcing/handler/handler.go | 2 +- .../eventsourcing/handler/machine_keys.go | 110 -------- .../eventsourcing/view/authn_keys.go | 74 ++++++ .../eventsourcing/view/machine_keys.go | 70 ----- internal/management/repository/project.go | 7 + internal/management/repository/user.go | 5 +- internal/project/model/api_config.go | 62 +++++ internal/project/model/application.go | 20 +- internal/project/model/oidc_config.go | 30 ++- .../repository/eventsourcing/eventstore.go | 197 ++++++++++++-- .../eventsourcing/model/api_config.go | 87 +++++++ .../eventsourcing/model/application.go | 11 +- .../eventsourcing/model/oidc_config.go | 146 ++++++++++- .../repository/eventsourcing/model/project.go | 8 + .../repository/eventsourcing/model/types.go | 7 + .../repository/eventsourcing/project.go | 77 ++++++ .../repository/view/model/application.go | 22 +- internal/static/i18n/de.yaml | 6 +- internal/static/i18n/en.yaml | 8 +- .../login/handler/external_login_handler.go | 6 +- .../handler/external_register_handler.go | 2 +- internal/user/model/machine_key.go | 54 ---- internal/user/model/user_machine.go | 10 +- .../repository/eventsourcing/eventstore.go | 8 +- .../eventsourcing/model/user_machine.go | 3 +- .../user/repository/view/machine_key_view.go | 77 ------ .../user/repository/view/model/machine_key.go | 84 ------ .../view/model/machine_key_query.go | 61 ----- internal/user/repository/view/model/token.go | 1 + migrations/cockroach/V1.30__auth_keys.sql | 92 +++++++ pkg/grpc/management/proto/management.proto | 245 +++++++++++++++--- 64 files changed, 2275 insertions(+), 836 deletions(-) create mode 100644 internal/auth/repository/eventsourcing/handler/authn_keys.go delete mode 100644 internal/auth/repository/eventsourcing/handler/machine_keys.go create mode 100644 internal/auth/repository/eventsourcing/view/authn_keys.go delete mode 100644 internal/auth/repository/eventsourcing/view/machine_keys.go create mode 100644 internal/key/model/authn_key.go create mode 100644 internal/key/repository/view/authn_key_view.go create mode 100644 internal/key/repository/view/model/authn_key.go create mode 100644 internal/key/repository/view/model/authn_key_query.go create mode 100644 internal/management/repository/eventsourcing/handler/authn_keys.go delete mode 100644 internal/management/repository/eventsourcing/handler/machine_keys.go create mode 100644 internal/management/repository/eventsourcing/view/authn_keys.go delete mode 100644 internal/management/repository/eventsourcing/view/machine_keys.go create mode 100644 internal/project/model/api_config.go create mode 100644 internal/project/repository/eventsourcing/model/api_config.go delete mode 100644 internal/user/model/machine_key.go delete mode 100644 internal/user/repository/view/machine_key_view.go delete mode 100644 internal/user/repository/view/model/machine_key.go delete mode 100644 internal/user/repository/view/model/machine_key_query.go create mode 100644 migrations/cockroach/V1.30__auth_keys.sql diff --git a/cmd/zitadel/startup.yaml b/cmd/zitadel/startup.yaml index 4e0d66735f..ac50da1cc7 100644 --- a/cmd/zitadel/startup.yaml +++ b/cmd/zitadel/startup.yaml @@ -194,6 +194,7 @@ API: Issuer: $ZITADEL_ISSUER DefaultLogoutRedirectURI: $ZITADEL_ACCOUNTS/logout/done CodeMethodS256: true + AuthMethodPrivateKeyJWT: true StorageConfig: DefaultLoginURL: $ZITADEL_ACCOUNTS/login?authRequestID= DefaultAccessTokenLifetime: 12h @@ -215,6 +216,9 @@ API: Token: Path: 'token' URL: '$ZITADEL_OAUTH/token' + Introspection: + Path: 'introspect' + URL: '$ZITADEL_OAUTH/introspect' EndSession: Path: 'endsession' URL: '$ZITADEL_AUTHORIZE/endsession' diff --git a/cmd/zitadel/system-defaults.yaml b/cmd/zitadel/system-defaults.yaml index cd84668dce..5207f6507c 100644 --- a/cmd/zitadel/system-defaults.yaml +++ b/cmd/zitadel/system-defaults.yaml @@ -45,6 +45,7 @@ SystemDefaults: IncludeDigits: true IncludeSymbols: false MachineKeySize: 2048 + ClientKeySize: 2048 Multifactors: OTP: Issuer: 'ZITADEL' diff --git a/go.mod b/go.mod index 1818ff6466..0b9c99f2f8 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc github.com/caos/logging v0.0.2 - github.com/caos/oidc v0.13.2 + github.com/caos/oidc v0.14.0 github.com/caos/orbos v1.5.14-0.20210205131708-6dc812182dc0 github.com/cockroachdb/cockroach-go/v2 v2.1.0 github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43 @@ -55,7 +55,7 @@ require ( github.com/rs/cors v1.7.0 github.com/sony/sonyflake v1.0.0 github.com/spf13/cobra v0.0.7 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect github.com/ttacon/libphonenumber v1.1.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.13.0 @@ -68,7 +68,7 @@ require ( golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect - golang.org/x/text v0.3.4 + golang.org/x/text v0.3.5 golang.org/x/tools v0.0.0-20201103235415-b653051172e4 google.golang.org/api v0.34.0 google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 23295c9dd8..d29ce3be4f 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,7 @@ github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKS github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -116,6 +117,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -124,6 +126,7 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -137,12 +140,8 @@ github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuG github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo= github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= github.com/caos/oidc v0.6.2/go.mod h1:ozoi3b+aY33gzdvjz4w90VZShIHGsmDa0goruuV0arQ= -github.com/caos/oidc v0.13.2 h1:52oP3KB1UrZuwraBTLuwM9ItRIhJQMYOm1J5uQ0sYXw= -github.com/caos/oidc v0.13.2/go.mod h1:dLvfYUiAt9ORfl77L/KkcWuR/N0ll8Ry1nD2ERsamDY= -github.com/caos/orbos v1.5.14-0.20210128140136-842933949472 h1:iti4tAKxBknjJkQcDKWaxlj9Jbng5kz5TpQzzyda49o= -github.com/caos/orbos v1.5.14-0.20210128140136-842933949472/go.mod h1:ZLxNgPuYIlSvr80trezGGUIXng9gY2hHEdky/m0B/P0= -github.com/caos/orbos v1.5.14-0.20210202122121-ad32524ffc73 h1:usYmCT11HvwxBCk1+DSCmEU6CVYhzY8jHaQHSJMrxlg= -github.com/caos/orbos v1.5.14-0.20210202122121-ad32524ffc73/go.mod h1:ZLxNgPuYIlSvr80trezGGUIXng9gY2hHEdky/m0B/P0= +github.com/caos/oidc v0.14.0 h1:l7mTqYDpqNRZF9Vwzq5KAQd1wQCThdceL5HpsEMGoao= +github.com/caos/oidc v0.14.0/go.mod h1:CPsubVrA110OyLnCKwVZjTdsAVwq67DTbYIvux7UgbY= github.com/caos/orbos v1.5.14-0.20210205131708-6dc812182dc0 h1:N+KYBwuQO3QPr/nTUaNwjAetjp3NU4MP8Nv9Iue53UE= github.com/caos/orbos v1.5.14-0.20210205131708-6dc812182dc0/go.mod h1:ZLxNgPuYIlSvr80trezGGUIXng9gY2hHEdky/m0B/P0= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -154,9 +153,11 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -186,12 +187,15 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -211,6 +215,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -223,23 +228,28 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -297,6 +307,7 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -307,6 +318,7 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -366,8 +378,10 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -382,6 +396,8 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -389,6 +405,7 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/googleinterns/cloud-operations-api-mock v0.0.0-20200709193332-a1e58c29bdd3 h1:eHv/jVY/JNop1xg2J9cBb4EzyMpWZoNCP1BslSAIkOI= github.com/googleinterns/cloud-operations-api-mock v0.0.0-20200709193332-a1e58c29bdd3/go.mod h1:h/KNeRx7oYU4SpA4SoY7W2/NxDKEEVuwA6j9A27L4OI= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -441,7 +458,9 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -454,6 +473,7 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 h1:KUDFlmBg2buRWNzIcwLlKvfcnujcHQRQ1As1LoaCLAM= github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -509,6 +529,7 @@ github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBef github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -522,6 +543,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= @@ -552,7 +574,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/landoop/tableprinter v0.0.0-20200805134727-ea32388e35c1/go.mod h1:f0X1c0za3TbET/rl5ThtCSel0+G3/yZ8iuU9BxnyVK0= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -600,6 +624,7 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -642,6 +667,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nicksnyder/go-i18n/v2 v2.1.1 h1:ATCOanRDlrfKVB4WHAdJnLEqZtDmKYsweqsOUYflnBU= github.com/nicksnyder/go-i18n/v2 v2.1.1/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -651,9 +677,11 @@ github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FW github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -790,6 +818,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -906,6 +936,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -1051,6 +1082,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1152,6 +1185,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1229,9 +1263,11 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= @@ -1244,9 +1280,11 @@ gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -1299,6 +1337,7 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kubectl v0.18.3/go.mod h1:k/EpvXBDgEsHBzWr0A44l9+ArvYi3txBBnzXBjQasUQ= k8s.io/metrics v0.18.3/go.mod h1:TkuJE3ezDZ1ym8pYkZoEzJB7HDiFE7qxl+EmExEBoPA= diff --git a/internal/api/grpc/auth/user_machine_converter.go b/internal/api/grpc/auth/user_machine_converter.go index b868354d35..003c0eee11 100644 --- a/internal/api/grpc/auth/user_machine_converter.go +++ b/internal/api/grpc/auth/user_machine_converter.go @@ -2,9 +2,10 @@ package auth import ( "github.com/caos/logging" + "github.com/golang/protobuf/ptypes" + usr_model "github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/pkg/grpc/auth" - "github.com/golang/protobuf/ptypes" ) func machineViewFromModel(machine *usr_model.MachineView) *auth.MachineView { @@ -16,36 +17,3 @@ func machineViewFromModel(machine *usr_model.MachineView) *auth.MachineView { LastKeyAdded: lastKeyAdded, } } - -func machineKeyViewsFromModel(keys ...*usr_model.MachineKeyView) []*auth.MachineKeyView { - keyViews := make([]*auth.MachineKeyView, len(keys)) - for i, key := range keys { - keyViews[i] = machineKeyViewFromModel(key) - } - return keyViews -} - -func machineKeyViewFromModel(key *usr_model.MachineKeyView) *auth.MachineKeyView { - creationDate, err := ptypes.TimestampProto(key.CreationDate) - logging.Log("MANAG-gluk7").OnError(err).Debug("unable to parse timestamp") - - expirationDate, err := ptypes.TimestampProto(key.CreationDate) - logging.Log("MANAG-gluk7").OnError(err).Debug("unable to parse timestamp") - - return &auth.MachineKeyView{ - Id: key.ID, - CreationDate: creationDate, - ExpirationDate: expirationDate, - Sequence: key.Sequence, - Type: machineKeyTypeFromModel(key.Type), - } -} - -func machineKeyTypeFromModel(typ usr_model.MachineKeyType) auth.MachineKeyType { - switch typ { - case usr_model.MachineKeyTypeJSON: - return auth.MachineKeyType_MACHINEKEY_JSON - default: - return auth.MachineKeyType_MACHINEKEY_UNSPECIFIED - } -} diff --git a/internal/api/grpc/management/application.go b/internal/api/grpc/management/application.go index 71c6aa12f3..efd5256bcb 100644 --- a/internal/api/grpc/management/application.go +++ b/internal/api/grpc/management/application.go @@ -31,6 +31,13 @@ func (s *Server) CreateOIDCApplication(ctx context.Context, in *management.OIDCA } return appFromModel(app), nil } +func (s *Server) CreateAPIApplication(ctx context.Context, in *management.APIApplicationCreate) (*management.Application, error) { + app, err := s.project.AddApplication(ctx, apiAppCreateToModel(in)) + if err != nil { + return nil, err + } + return appFromModel(app), nil +} func (s *Server) UpdateApplication(ctx context.Context, in *management.ApplicationUpdate) (*management.Application, error) { app, err := s.project.ChangeApplication(ctx, appUpdateToModel(in)) if err != nil { @@ -66,6 +73,14 @@ func (s *Server) UpdateApplicationOIDCConfig(ctx context.Context, in *management return oidcConfigFromModel(config), nil } +func (s *Server) UpdateApplicationAPIConfig(ctx context.Context, in *management.APIConfigUpdate) (*management.APIConfig, error) { + config, err := s.project.ChangeAPIConfig(ctx, apiConfigUpdateToModel(in)) + if err != nil { + return nil, err + } + return apiConfigFromModel(config), nil +} + func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, in *management.ApplicationID) (*management.ClientSecret, error) { config, err := s.project.ChangeOIDConfigSecret(ctx, in.ProjectId, in.Id) if err != nil { @@ -74,6 +89,14 @@ func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, in *management. return &management.ClientSecret{ClientSecret: config.ClientSecretString}, nil } +func (s *Server) RegenerateAPIClientSecret(ctx context.Context, in *management.ApplicationID) (*management.ClientSecret, error) { + config, err := s.project.ChangeAPIConfigSecret(ctx, in.ProjectId, in.Id) + if err != nil { + return nil, err + } + return &management.ClientSecret{ClientSecret: config.ClientSecretString}, nil +} + func (s *Server) ApplicationChanges(ctx context.Context, changesRequest *management.ChangeRequest) (*management.Changes, error) { response, err := s.project.ApplicationChanges(ctx, changesRequest.Id, changesRequest.SecId, changesRequest.SequenceOffset, changesRequest.Limit, changesRequest.Asc) if err != nil { @@ -81,3 +104,32 @@ func (s *Server) ApplicationChanges(ctx context.Context, changesRequest *managem } return appChangesToResponse(response, changesRequest.GetSequenceOffset(), changesRequest.GetLimit()), nil } + +func (s *Server) SearchClientKeys(ctx context.Context, req *management.ClientKeySearchRequest) (*management.ClientKeySearchResponse, error) { + result, err := s.project.SearchClientKeys(ctx, clientKeySearchRequestToModel(req)) + if err != nil { + return nil, err + } + return clientKeySearchResponseFromModel(result), nil +} + +func (s *Server) GetClientKey(ctx context.Context, req *management.ClientKeyIDRequest) (*management.ClientKeyView, error) { + key, err := s.project.GetClientKey(ctx, req.ProjectId, req.ApplicationId, req.KeyId) + if err != nil { + return nil, err + } + return clientKeyViewFromModel(key), nil +} + +func (s *Server) AddClientKey(ctx context.Context, req *management.AddClientKeyRequest) (*management.AddClientKeyResponse, error) { + key, err := s.project.AddClientKey(ctx, addClientKeyToModel(req)) + if err != nil { + return nil, err + } + return addClientKeyFromModel(key), nil +} + +func (s *Server) DeleteClientKey(ctx context.Context, req *management.ClientKeyIDRequest) (*empty.Empty, error) { + err := s.project.RemoveClientKey(ctx, req.ProjectId, req.ApplicationId, req.KeyId) + return &empty.Empty{}, err +} diff --git a/internal/api/grpc/management/application_converter.go b/internal/api/grpc/management/application_converter.go index e3ebafa396..98f590a9ac 100644 --- a/internal/api/grpc/management/application_converter.go +++ b/internal/api/grpc/management/application_converter.go @@ -2,6 +2,7 @@ package management import ( "encoding/json" + "time" "github.com/caos/logging" "github.com/golang/protobuf/ptypes" @@ -10,6 +11,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/zitadel/internal/model" proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/pkg/grpc/management" @@ -40,6 +42,11 @@ func appConfigFromModel(app *proj_model.Application) management.AppConfig { OidcConfig: oidcConfigFromModel(app.OIDCConfig), } } + if app.Type == proj_model.AppTypeAPI { + return &management.Application_ApiConfig{ + ApiConfig: apiConfigFromModel(app.APIConfig), + } + } return nil } @@ -65,6 +72,14 @@ func oidcConfigFromModel(config *proj_model.OIDCConfig) *management.OIDCConfig { } } +func apiConfigFromModel(config *proj_model.APIConfig) *management.APIConfig { + return &management.APIConfig{ + ClientId: config.ClientID, + ClientSecret: config.ClientSecretString, + AuthMethodType: apiAuthMethodTypeFromModel(config.AuthMethodType), + } +} + func oidcConfigFromApplicationViewModel(app *proj_model.ApplicationView) *management.OIDCConfig { return &management.OIDCConfig{ RedirectUris: app.OIDCRedirectUris, @@ -120,6 +135,19 @@ func oidcAppCreateToModel(app *management.OIDCApplicationCreate) *proj_model.App } } +func apiAppCreateToModel(app *management.APIApplicationCreate) *proj_model.Application { + return &proj_model.Application{ + ObjectRoot: models.ObjectRoot{ + AggregateID: app.ProjectId, + }, + Name: app.Name, + Type: proj_model.AppTypeAPI, + APIConfig: &proj_model.APIConfig{ + AuthMethodType: apiAuthMethodTypeToModel(app.AuthMethodType), + }, + } +} + func appUpdateToModel(app *management.ApplicationUpdate) *proj_model.Application { return &proj_model.Application{ ObjectRoot: models.ObjectRoot{ @@ -151,6 +179,16 @@ func oidcConfigUpdateToModel(app *management.OIDCConfigUpdate) *proj_model.OIDCC } } +func apiConfigUpdateToModel(app *management.APIConfigUpdate) *proj_model.APIConfig { + return &proj_model.APIConfig{ + ObjectRoot: models.ObjectRoot{ + AggregateID: app.ProjectId, + }, + AppID: app.ApplicationId, + AuthMethodType: apiAuthMethodTypeToModel(app.AuthMethodType), + } +} + func applicationSearchRequestsToModel(request *management.ApplicationSearchRequest) *proj_model.ApplicationSearchRequest { return &proj_model.ApplicationSearchRequest{ Offset: request.Offset, @@ -354,11 +392,24 @@ func oidcAuthMethodTypeToModel(authType management.OIDCAuthMethodType) proj_mode return proj_model.OIDCAuthMethodTypePost case management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_NONE: return proj_model.OIDCAuthMethodTypeNone + case management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_PRIVATE_KEY_JWT: + return proj_model.OIDCAuthMethodTypePrivateKeyJWT default: return proj_model.OIDCAuthMethodTypeBasic } } +func apiAuthMethodTypeToModel(authType management.APIAuthMethodType) proj_model.APIAuthMethodType { + switch authType { + case management.APIAuthMethodType_APIAUTHMETHODTYPE_BASIC: + return proj_model.APIAuthMethodTypeBasic + case management.APIAuthMethodType_APIAUTHMETHODTYPE_PRIVATE_KEY_JWT: + return proj_model.APIAuthMethodTypePrivateKeyJWT + default: + return proj_model.APIAuthMethodTypeBasic + } +} + func oidcAuthMethodTypeFromModel(authType proj_model.OIDCAuthMethodType) management.OIDCAuthMethodType { switch authType { case proj_model.OIDCAuthMethodTypeBasic: @@ -367,11 +418,24 @@ func oidcAuthMethodTypeFromModel(authType proj_model.OIDCAuthMethodType) managem return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_POST case proj_model.OIDCAuthMethodTypeNone: return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_NONE + case proj_model.OIDCAuthMethodTypePrivateKeyJWT: + return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_PRIVATE_KEY_JWT default: return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_BASIC } } +func apiAuthMethodTypeFromModel(authType proj_model.APIAuthMethodType) management.APIAuthMethodType { + switch authType { + case proj_model.APIAuthMethodTypeBasic: + return management.APIAuthMethodType_APIAUTHMETHODTYPE_BASIC + case proj_model.APIAuthMethodTypePrivateKeyJWT: + return management.APIAuthMethodType_APIAUTHMETHODTYPE_PRIVATE_KEY_JWT + default: + return management.APIAuthMethodType_APIAUTHMETHODTYPE_BASIC + } +} + func oidcTokenTypeToModel(tokenType management.OIDCTokenType) proj_model.OIDCTokenType { switch tokenType { case management.OIDCTokenType_OIDCTokenType_Bearer: @@ -432,3 +496,126 @@ func appChangesToMgtAPI(changes *proj_model.ApplicationChanges) (_ []*management return result } + +func clientKeyViewsFromModel(keys ...*key_model.AuthNKeyView) []*management.ClientKeyView { + keyViews := make([]*management.ClientKeyView, len(keys)) + for i, key := range keys { + keyViews[i] = clientKeyViewFromModel(key) + } + return keyViews +} + +func clientKeyViewFromModel(key *key_model.AuthNKeyView) *management.ClientKeyView { + creationDate, err := ptypes.TimestampProto(key.CreationDate) + logging.Log("MANAG-DAs2t").OnError(err).Debug("unable to parse timestamp") + + expirationDate, err := ptypes.TimestampProto(key.ExpirationDate) + logging.Log("MANAG-BDgh4").OnError(err).Debug("unable to parse timestamp") + + return &management.ClientKeyView{ + Id: key.ID, + CreationDate: creationDate, + ExpirationDate: expirationDate, + Sequence: key.Sequence, + Type: authNKeyTypeFromModel(key.Type), + } +} + +func addClientKeyToModel(key *management.AddClientKeyRequest) *proj_model.ClientKey { + expirationDate := time.Time{} + if key.ExpirationDate != nil { + var err error + expirationDate, err = ptypes.Timestamp(key.ExpirationDate) + logging.Log("MANAG-Dgt42").OnError(err).Debug("unable to parse expiration date") + } + + return &proj_model.ClientKey{ + ExpirationDate: expirationDate, + Type: authNKeyTypeToModel(key.Type), + ApplicationID: key.ApplicationId, + ObjectRoot: models.ObjectRoot{AggregateID: key.ProjectId}, + } +} + +func addClientKeyFromModel(key *proj_model.ClientKey) *management.AddClientKeyResponse { + creationDate, err := ptypes.TimestampProto(key.CreationDate) + logging.Log("MANAG-FBzz4").OnError(err).Debug("unable to parse cretaion date") + + expirationDate, err := ptypes.TimestampProto(key.ExpirationDate) + logging.Log("MANAG-sag21").OnError(err).Debug("unable to parse cretaion date") + + detail, err := json.Marshal(struct { + Type string `json:"type"` + KeyID string `json:"keyId"` + Key string `json:"key"` + AppID string `json:"appId"` + ClientID string `json:"clientID"` + }{ + Type: "application", + KeyID: key.KeyID, + Key: string(key.PrivateKey), + AppID: key.ApplicationID, + ClientID: key.ClientID, + }) + logging.Log("MANAG-adt42").OnError(err).Warn("unable to marshall key") + + return &management.AddClientKeyResponse{ + Id: key.KeyID, + CreationDate: creationDate, + ExpirationDate: expirationDate, + Sequence: key.Sequence, + KeyDetails: detail, + Type: authNKeyTypeFromModel(key.Type), + } +} + +func authNKeyTypeToModel(typ management.AuthNKeyType) key_model.AuthNKeyType { + switch typ { + case management.AuthNKeyType_AUTHNKEY_JSON: + return key_model.AuthNKeyTypeJSON + default: + return key_model.AuthNKeyTypeNONE + } +} + +func authNKeyTypeFromModel(typ key_model.AuthNKeyType) management.AuthNKeyType { + switch typ { + case key_model.AuthNKeyTypeJSON: + return management.AuthNKeyType_AUTHNKEY_JSON + default: + return management.AuthNKeyType_AUTHNKEY_UNSPECIFIED + } +} + +func clientKeySearchRequestToModel(req *management.ClientKeySearchRequest) *key_model.AuthNKeySearchRequest { + return &key_model.AuthNKeySearchRequest{ + Offset: req.Offset, + Limit: req.Limit, + Asc: req.Asc, + Queries: []*key_model.AuthNKeySearchQuery{ + { + Key: key_model.AuthNKeyObjectType, + Method: model.SearchMethodEquals, + Value: key_model.AuthNKeyObjectTypeApplication, + }, { + Key: key_model.AuthNKeyObjectID, + Method: model.SearchMethodEquals, + Value: req.ApplicationId, + }, + }, + } +} + +func clientKeySearchResponseFromModel(req *key_model.AuthNKeySearchResponse) *management.ClientKeySearchResponse { + viewTimestamp, err := ptypes.TimestampProto(req.Timestamp) + logging.Log("MANAG-Sk9ds").OnError(err).Debug("unable to parse cretaion date") + + return &management.ClientKeySearchResponse{ + Offset: req.Offset, + Limit: req.Limit, + TotalResult: req.TotalResult, + ProcessedSequence: req.Sequence, + ViewTimestamp: viewTimestamp, + Result: clientKeyViewsFromModel(req.Result...), + } +} diff --git a/internal/api/grpc/management/user_machine_converter.go b/internal/api/grpc/management/user_machine_converter.go index cc53440691..645ba6c9cb 100644 --- a/internal/api/grpc/management/user_machine_converter.go +++ b/internal/api/grpc/management/user_machine_converter.go @@ -6,6 +6,7 @@ import ( "github.com/caos/logging" "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/zitadel/internal/model" usr_model "github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/pkg/grpc/management" @@ -43,7 +44,7 @@ func machineViewFromModel(machine *usr_model.MachineView) *management.MachineVie } } -func machineKeyViewsFromModel(keys ...*usr_model.MachineKeyView) []*management.MachineKeyView { +func authnKeyViewsFromModel(keys ...*key_model.AuthNKeyView) []*management.MachineKeyView { keyViews := make([]*management.MachineKeyView, len(keys)) for i, key := range keys { keyViews[i] = machineKeyViewFromModel(key) @@ -51,7 +52,7 @@ func machineKeyViewsFromModel(keys ...*usr_model.MachineKeyView) []*management.M return keyViews } -func machineKeyViewFromModel(key *usr_model.MachineKeyView) *management.MachineKeyView { +func machineKeyViewFromModel(key *key_model.AuthNKeyView) *management.MachineKeyView { creationDate, err := ptypes.TimestampProto(key.CreationDate) logging.Log("MANAG-gluk7").OnError(err).Debug("unable to parse timestamp") @@ -112,32 +113,36 @@ func addMachineKeyFromModel(key *usr_model.MachineKey) *management.AddMachineKey } } -func machineKeyTypeToModel(typ management.MachineKeyType) usr_model.MachineKeyType { +func machineKeyTypeToModel(typ management.MachineKeyType) key_model.AuthNKeyType { switch typ { case management.MachineKeyType_MACHINEKEY_JSON: - return usr_model.MachineKeyTypeJSON + return key_model.AuthNKeyTypeJSON default: - return usr_model.MachineKeyTypeNONE + return key_model.AuthNKeyTypeNONE } } -func machineKeyTypeFromModel(typ usr_model.MachineKeyType) management.MachineKeyType { +func machineKeyTypeFromModel(typ key_model.AuthNKeyType) management.MachineKeyType { switch typ { - case usr_model.MachineKeyTypeJSON: + case key_model.AuthNKeyTypeJSON: return management.MachineKeyType_MACHINEKEY_JSON default: return management.MachineKeyType_MACHINEKEY_UNSPECIFIED } } -func machineKeySearchRequestToModel(req *management.MachineKeySearchRequest) *usr_model.MachineKeySearchRequest { - return &usr_model.MachineKeySearchRequest{ +func machineKeySearchRequestToModel(req *management.MachineKeySearchRequest) *key_model.AuthNKeySearchRequest { + return &key_model.AuthNKeySearchRequest{ Offset: req.Offset, Limit: req.Limit, Asc: req.Asc, - Queries: []*usr_model.MachineKeySearchQuery{ + Queries: []*key_model.AuthNKeySearchQuery{ { - Key: usr_model.MachineKeyKeyUserID, + Key: key_model.AuthNKeyObjectType, + Method: model.SearchMethodEquals, + Value: key_model.AuthNKeyObjectTypeUser, + }, { + Key: key_model.AuthNKeyObjectID, Method: model.SearchMethodEquals, Value: req.UserId, }, @@ -145,7 +150,7 @@ func machineKeySearchRequestToModel(req *management.MachineKeySearchRequest) *us } } -func machineKeySearchResponseFromModel(req *usr_model.MachineKeySearchResponse) *management.MachineKeySearchResponse { +func machineKeySearchResponseFromModel(req *key_model.AuthNKeySearchResponse) *management.MachineKeySearchResponse { viewTimestamp, err := ptypes.TimestampProto(req.Timestamp) logging.Log("MANAG-Sk9ds").OnError(err).Debug("unable to parse cretaion date") @@ -155,6 +160,6 @@ func machineKeySearchResponseFromModel(req *usr_model.MachineKeySearchResponse) TotalResult: req.TotalResult, ProcessedSequence: req.Sequence, ViewTimestamp: viewTimestamp, - Result: machineKeyViewsFromModel(req.Result...), + Result: authnKeyViewsFromModel(req.Result...), } } diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index 39fffdba0d..8d7a653a90 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -12,6 +12,7 @@ import ( "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/http" "github.com/caos/zitadel/internal/auth_request/model" + authreq_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" proj_model "github.com/caos/zitadel/internal/project/model" @@ -55,13 +56,17 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl } func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) { + return o.GetKeyByIDAndIssuer(ctx, keyID, userID) +} + +func (o *OPStorage) GetKeyByIDAndIssuer(ctx context.Context, keyID, issuer string) (_ *jose.JSONWebKey, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() key, err := o.repo.MachineKeyByID(ctx, keyID) if err != nil { return nil, err } - if key.UserID != userID { + if key.AuthIdentifier != issuer { return nil, errors.ThrowPermissionDenied(nil, "OIDC-24jm3", "key from different user") } publicKey, err := crypto.BytesToPublicKey(key.PublicKey) @@ -75,6 +80,29 @@ func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID strin }, nil } +func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string, scopes oidc.Scopes) (oidc.Scopes, error) { + user, err := o.repo.UserByID(ctx, subject) + if err != nil { + return nil, err + } + for i := len(scopes) - 1; i >= 0; i-- { + scope := scopes[i] + if strings.HasPrefix(scope, authreq_model.OrgDomainPrimaryScope) { + var orgID string + org, err := o.repo.OrgByPrimaryDomain(strings.TrimPrefix(scope, authreq_model.OrgDomainPrimaryScope)) + if err == nil { + orgID = org.ID + } + if orgID != user.ResourceOwner { + scopes[i] = scopes[len(scopes)-1] + scopes[len(scopes)-1] = "" + scopes = scopes[:len(scopes)-1] + } + } + } + return scopes, nil +} + func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -85,33 +113,32 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr return o.repo.AuthorizeOIDCApplication(ctx, id, secret) } -func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (_ oidc.UserInfo, err error) { +func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.UserInfoSetter, tokenID, subject, origin string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() token, err := o.repo.TokenByID(ctx, subject, tokenID) if err != nil { - return nil, errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") + return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") } if token.ApplicationID != "" { app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID) if err != nil { - return nil, err + return err } if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) { - return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed") + return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed") } } - return o.GetUserinfoFromScopes(ctx, token.UserID, token.ApplicationID, token.Scopes) + return o.SetUserinfoFromScopes(ctx, userInfo, token.UserID, token.ApplicationID, token.Scopes) } -func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID, applicationID string, scopes []string) (_ oidc.UserInfo, err error) { +func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() user, err := o.repo.UserByID(ctx, userID) if err != nil { - return nil, err + return err } - userInfo := oidc.NewUserInfo() roles := make([]string, 0) for _, scope := range scopes { switch scope { @@ -160,17 +187,40 @@ func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID, applicati } if len(roles) == 0 || applicationID == "" { - return userInfo, nil + return nil } projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles) if err != nil { - return nil, err + return err } if len(projectRoles) > 0 { userInfo.AppendClaims(ClaimProjectRoles, projectRoles) } - return userInfo, nil + return nil +} + +func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error { + token, err := o.repo.TokenByID(ctx, subject, tokenID) + if err != nil { + return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") + } + app, err := o.repo.ApplicationByClientID(ctx, clientID) + if err != nil { + return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found") + } + for _, aud := range token.Audience { + if aud == clientID || aud == app.ProjectID { + err := o.SetUserinfoFromScopes(ctx, introspection, token.UserID, clientID, token.Scopes) + if err != nil { + return err + } + introspection.SetScopes(token.Scopes) + introspection.SetClientID(token.ApplicationID) + return nil + } + } + return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client") } func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) { diff --git a/internal/api/oidc/client_converter.go b/internal/api/oidc/client_converter.go index a1d9153826..6098dd43c7 100644 --- a/internal/api/oidc/client_converter.go +++ b/internal/api/oidc/client_converter.go @@ -1,13 +1,13 @@ package oidc import ( - authreq_model "github.com/caos/zitadel/internal/auth_request/model" "strings" "time" "github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/op" + authreq_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/project/model" ) @@ -37,7 +37,7 @@ func (c *Client) ApplicationType() op.ApplicationType { return op.ApplicationType(c.OIDCApplicationType) } -func (c *Client) AuthMethod() op.AuthMethod { +func (c *Client) AuthMethod() oidc.AuthMethod { return authMethodToOIDC(c.OIDCAuthMethodType) } @@ -129,16 +129,18 @@ func accessTokenTypeToOIDC(tokenType model.OIDCTokenType) op.AccessTokenType { } } -func authMethodToOIDC(authType model.OIDCAuthMethodType) op.AuthMethod { +func authMethodToOIDC(authType model.OIDCAuthMethodType) oidc.AuthMethod { switch authType { case model.OIDCAuthMethodTypeBasic: - return op.AuthMethodBasic + return oidc.AuthMethodBasic case model.OIDCAuthMethodTypePost: - return op.AuthMethodPost + return oidc.AuthMethodPost case model.OIDCAuthMethodTypeNone: - return op.AuthMethodNone + return oidc.AuthMethodNone + case model.OIDCAuthMethodTypePrivateKeyJWT: + return oidc.AuthMethodPrivateKeyJWT default: - return op.AuthMethodBasic + return oidc.AuthMethodBasic } } diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index f5a11012bf..cf48b8031e 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -2,9 +2,10 @@ package oidc import ( "context" - "github.com/caos/zitadel/internal/telemetry/metrics" "time" + "github.com/caos/zitadel/internal/telemetry/metrics" + "github.com/caos/logging" "github.com/caos/oidc/pkg/op" @@ -32,11 +33,12 @@ type StorageConfig struct { } type EndpointConfig struct { - Auth *Endpoint - Token *Endpoint - Userinfo *Endpoint - EndSession *Endpoint - Keys *Endpoint + Auth *Endpoint + Token *Endpoint + Introspection *Endpoint + Userinfo *Endpoint + EndSession *Endpoint + Keys *Endpoint } type Endpoint struct { @@ -70,6 +72,7 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Re ), op.WithCustomAuthEndpoint(op.NewEndpointWithURL(config.Endpoints.Auth.Path, config.Endpoints.Auth.URL)), op.WithCustomTokenEndpoint(op.NewEndpointWithURL(config.Endpoints.Token.Path, config.Endpoints.Token.URL)), + op.WithCustomIntrospectionEndpoint(op.NewEndpointWithURL(config.Endpoints.Introspection.Path, config.Endpoints.Introspection.URL)), op.WithCustomUserinfoEndpoint(op.NewEndpointWithURL(config.Endpoints.Userinfo.Path, config.Endpoints.Userinfo.URL)), op.WithCustomEndSessionEndpoint(op.NewEndpointWithURL(config.Endpoints.EndSession.Path, config.Endpoints.EndSession.URL)), op.WithCustomKeysEndpoint(op.NewEndpointWithURL(config.Endpoints.Keys.Path, config.Endpoints.Keys.URL)), diff --git a/internal/auth/repository/eventsourcing/eventstore/org.go b/internal/auth/repository/eventsourcing/eventstore/org.go index c5f3ecced0..03b6462ff1 100644 --- a/internal/auth/repository/eventsourcing/eventstore/org.go +++ b/internal/auth/repository/eventsourcing/eventstore/org.go @@ -4,6 +4,7 @@ import ( "context" "github.com/caos/logging" + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/errors" @@ -55,6 +56,14 @@ func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.Or return result, nil } +func (repo *OrgRepository) OrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) { + org, err := repo.View.OrgByPrimaryDomain(primaryDomain) + if err != nil { + return nil, err + } + return model.OrgToModel(org), nil +} + func (repo *OrgRepository) RegisterOrg(ctx context.Context, register *auth_model.RegisterOrg) (*auth_model.RegisterOrg, error) { pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) if err != nil { diff --git a/internal/auth/repository/eventsourcing/eventstore/token.go b/internal/auth/repository/eventsourcing/eventstore/token.go index ec3e81aa7b..82a99262e3 100644 --- a/internal/auth/repository/eventsourcing/eventstore/token.go +++ b/internal/auth/repository/eventsourcing/eventstore/token.go @@ -3,23 +3,26 @@ package eventstore import ( "context" "strings" + "time" "github.com/caos/logging" + auth_req_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" + proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" "github.com/caos/zitadel/internal/user/repository/view/model" - "time" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" ) type TokenRepo struct { - UserEvents *user_event.UserEventstore - View *view.View + UserEvents *user_event.UserEventstore + ProjectEvents *proj_event.ProjectEventstore + View *view.View } func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*usr_model.Token, error) { diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index d4e96c32e3..27a424f932 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/config/systemdefaults" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/logging" @@ -14,6 +15,7 @@ import ( "github.com/caos/zitadel/internal/eventstore" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/sdk" + key_view_model "github.com/caos/zitadel/internal/key/repository/view/model" org_model "github.com/caos/zitadel/internal/org/model" org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -515,10 +517,10 @@ func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error { return nil } -func (repo *UserRepo) MachineKeyByID(ctx context.Context, keyID string) (*model.MachineKeyView, error) { - key, err := repo.View.MachineKeyByID(keyID) +func (repo *UserRepo) MachineKeyByID(ctx context.Context, keyID string) (*key_model.AuthNKeyView, error) { + key, err := repo.View.AuthNKeyByID(keyID) if err != nil { return nil, err } - return usr_view_model.MachineKeyToModel(key), nil + return key_view_model.AuthNKeyToModel(key), nil } diff --git a/internal/auth/repository/eventsourcing/handler/application.go b/internal/auth/repository/eventsourcing/handler/application.go index f8bd45d33a..49fa802825 100644 --- a/internal/auth/repository/eventsourcing/handler/application.go +++ b/internal/auth/repository/eventsourcing/handler/application.go @@ -84,6 +84,8 @@ func (a *Application) Reduce(event *models.Event) (err error) { case es_model.ApplicationChanged, es_model.OIDCConfigAdded, es_model.OIDCConfigChanged, + es_model.APIConfigAdded, + es_model.APIConfigChanged, es_model.ApplicationDeactivated, es_model.ApplicationReactivated: err = app.SetData(event) diff --git a/internal/auth/repository/eventsourcing/handler/authn_keys.go b/internal/auth/repository/eventsourcing/handler/authn_keys.go new file mode 100644 index 0000000000..0300b6b1b3 --- /dev/null +++ b/internal/auth/repository/eventsourcing/handler/authn_keys.go @@ -0,0 +1,120 @@ +package handler + +import ( + "time" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/eventstore" + es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/eventstore/query" + "github.com/caos/zitadel/internal/eventstore/spooler" + key_model "github.com/caos/zitadel/internal/key/repository/view/model" + proj_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + user_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" +) + +const ( + authnKeysTable = "auth.authn_keys" +) + +type AuthNKeys struct { + handler + subscription *eventstore.Subscription +} + +func newAuthNKeys(handler handler) *AuthNKeys { + h := &AuthNKeys{ + handler: handler, + } + + h.subscribe() + + return h +} + +func (k *AuthNKeys) subscribe() { + k.subscription = k.es.Subscribe(k.AggregateTypes()...) + go func() { + for event := range k.subscription.Events { + query.ReduceEvent(k, event) + } + }() +} + +func (k *AuthNKeys) ViewModel() string { + return authnKeysTable +} + +func (_ *AuthNKeys) AggregateTypes() []es_models.AggregateType { + return []es_models.AggregateType{user_model.UserAggregate, proj_model.ProjectAggregate} +} + +func (k *AuthNKeys) CurrentSequence() (uint64, error) { + sequence, err := k.view.GetLatestAuthNKeySequence() + if err != nil { + return 0, err + } + return sequence.CurrentSequence, nil +} + +func (k *AuthNKeys) EventQuery() (*es_models.SearchQuery, error) { + sequence, err := k.view.GetLatestAuthNKeySequence() + if err != nil { + return nil, err + } + return es_models.NewSearchQuery(). + AggregateTypeFilter(k.AggregateTypes()...). + LatestSequenceFilter(sequence.CurrentSequence), nil +} + +func (k *AuthNKeys) Reduce(event *es_models.Event) (err error) { + switch event.AggregateType { + case user_model.UserAggregate, + proj_model.ProjectAggregate: + err = k.processAuthNKeys(event) + } + return err +} + +func (k *AuthNKeys) processAuthNKeys(event *es_models.Event) (err error) { + key := new(key_model.AuthNKeyView) + switch event.Type { + case user_model.MachineKeyAdded, + proj_model.ClientKeyAdded: + err = key.AppendEvent(event) + if key.ExpirationDate.Before(time.Now()) { + return k.view.ProcessedAuthNKeySequence(event) + } + case user_model.MachineKeyRemoved: + err = key.SetUserData(event) + if err != nil { + return err + } + return k.view.DeleteAuthNKey(key.ID, event) + case proj_model.ClientKeyRemoved: + err = key.SetClientData(event) + if err != nil { + return err + } + return k.view.DeleteAuthNKey(key.ID, event) + case user_model.UserRemoved, + proj_model.ApplicationRemoved: + return k.view.DeleteAuthNKeysByObjectID(event.AggregateID, event) + default: + return k.view.ProcessedAuthNKeySequence(event) + } + if err != nil { + return err + } + return k.view.PutAuthNKey(key, event) +} + +func (k *AuthNKeys) OnError(event *es_models.Event, err error) error { + logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in authn key handler") + return spooler.HandleError(event, err, k.view.GetLatestAuthNKeyFailedEvent, k.view.ProcessedAuthNKeyFailedEvent, k.view.ProcessedAuthNKeySequence, k.errorCountUntilSkip) +} + +func (k *AuthNKeys) OnSuccess() error { + return spooler.HandleSuccess(k.view.UpdateAuthNKeySpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index c5825c5abe..b22983587d 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -71,7 +71,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es repos.OrgEvents, repos.IamEvents, systemDefaults.IamID), - newMachineKeys( + newAuthNKeys( handler{view, bulkLimit, configs.cycleDuration("MachineKey"), errorCount, es}), newLoginPolicy( handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount, es}), diff --git a/internal/auth/repository/eventsourcing/handler/machine_keys.go b/internal/auth/repository/eventsourcing/handler/machine_keys.go deleted file mode 100644 index bf877564af..0000000000 --- a/internal/auth/repository/eventsourcing/handler/machine_keys.go +++ /dev/null @@ -1,110 +0,0 @@ -package handler - -import ( - "time" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/eventstore" - es_models "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/eventstore/query" - "github.com/caos/zitadel/internal/eventstore/spooler" - "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" - usr_model "github.com/caos/zitadel/internal/user/repository/view/model" -) - -const ( - machineKeysTable = "auth.machine_keys" -) - -type MachineKeys struct { - handler - subscription *eventstore.Subscription -} - -func newMachineKeys(handler handler) *MachineKeys { - h := &MachineKeys{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (k *MachineKeys) subscribe() { - k.subscription = k.es.Subscribe(k.AggregateTypes()...) - go func() { - for event := range k.subscription.Events { - query.ReduceEvent(k, event) - } - }() -} - -func (k *MachineKeys) ViewModel() string { - return machineKeysTable -} - -func (_ *MachineKeys) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.UserAggregate} -} - -func (k *MachineKeys) CurrentSequence() (uint64, error) { - sequence, err := k.view.GetLatestMachineKeySequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (k *MachineKeys) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := k.view.GetLatestMachineKeySequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(k.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (k *MachineKeys) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.UserAggregate: - err = k.processMachineKeys(event) - } - return err -} - -func (k *MachineKeys) processMachineKeys(event *es_models.Event) (err error) { - key := new(usr_model.MachineKeyView) - switch event.Type { - case model.MachineKeyAdded: - err = key.AppendEvent(event) - if key.ExpirationDate.Before(time.Now()) { - return k.view.ProcessedMachineKeySequence(event) - } - case model.MachineKeyRemoved: - err = key.SetData(event) - if err != nil { - return err - } - return k.view.DeleteMachineKey(key.ID, event) - case model.UserRemoved: - return k.view.DeleteMachineKeysByUserID(event.AggregateID, event) - default: - return k.view.ProcessedMachineKeySequence(event) - } - if err != nil { - return err - } - return k.view.PutMachineKey(key, event) -} - -func (k *MachineKeys) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in machine key handler") - return spooler.HandleError(event, err, k.view.GetLatestMachineKeyFailedEvent, k.view.ProcessedMachineKeyFailedEvent, k.view.ProcessedMachineKeySequence, k.errorCountUntilSkip) -} - -func (k *MachineKeys) OnSuccess() error { - return spooler.HandleSuccess(k.view.UpdateMachineKeySpoolerRunTimestamp) -} diff --git a/internal/auth/repository/eventsourcing/handler/token.go b/internal/auth/repository/eventsourcing/handler/token.go index 6a365580ac..1b28d5fd1d 100644 --- a/internal/auth/repository/eventsourcing/handler/token.go +++ b/internal/auth/repository/eventsourcing/handler/token.go @@ -5,6 +5,7 @@ import ( "encoding/json" "github.com/caos/logging" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/models" @@ -71,9 +72,6 @@ func (t *Token) EventQuery() (*models.SearchQuery, error) { if err != nil { return nil, err } - if err != nil { - return nil, err - } return es_models.NewSearchQuery(). AggregateTypeFilter(user_es_model.UserAggregate, project_es_model.ProjectAggregate). LatestSequenceFilter(sequence.CurrentSequence), nil diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 5f05be73ed..9687c52db6 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -2,6 +2,7 @@ package eventsourcing import ( "context" + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/eventstore" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler" @@ -144,8 +145,9 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au IAMID: systemDefaults.IamID, }, eventstore.TokenRepo{ - UserEvents: user, - View: view, + UserEvents: user, + ProjectEvents: project, + View: view, }, eventstore.KeyRepository{ KeyEvents: key, diff --git a/internal/auth/repository/eventsourcing/view/authn_keys.go b/internal/auth/repository/eventsourcing/view/authn_keys.go new file mode 100644 index 0000000000..4873c64786 --- /dev/null +++ b/internal/auth/repository/eventsourcing/view/authn_keys.go @@ -0,0 +1,74 @@ +package view + +import ( + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" + "github.com/caos/zitadel/internal/key/repository/view" + "github.com/caos/zitadel/internal/key/repository/view/model" + "github.com/caos/zitadel/internal/view/repository" +) + +const ( + authNKeyTable = "auth.authn_keys" +) + +func (v *View) AuthNKeyByIDs(userID, keyID string) (*model.AuthNKeyView, error) { + return view.AuthNKeyByIDs(v.Db, authNKeyTable, userID, keyID) +} + +func (v *View) AuthNKeysByObjectID(objectID string) ([]*model.AuthNKeyView, error) { + return view.AuthNKeysByObjectID(v.Db, authNKeyTable, objectID) +} + +func (v *View) AuthNKeyByID(keyID string) (*model.AuthNKeyView, error) { + return view.AuthNKeyByID(v.Db, authNKeyTable, keyID) +} + +func (v *View) SearchAuthNKeys(request *key_model.AuthNKeySearchRequest) ([]*model.AuthNKeyView, uint64, error) { + return view.SearchAuthNKeys(v.Db, authNKeyTable, request) +} + +func (v *View) PutAuthNKey(key *model.AuthNKeyView, event *models.Event) error { + err := view.PutAuthNKey(v.Db, authNKeyTable, key) + if err != nil { + return err + } + return v.ProcessedAuthNKeySequence(event) +} + +func (v *View) DeleteAuthNKey(keyID string, event *models.Event) error { + err := view.DeleteAuthNKey(v.Db, authNKeyTable, keyID) + if err != nil && !errors.IsNotFound(err) { + return err + } + return v.ProcessedAuthNKeySequence(event) +} + +func (v *View) DeleteAuthNKeysByObjectID(objectID string, event *models.Event) error { + err := view.DeleteAuthNKey(v.Db, authNKeyTable, objectID) + if err != nil && !errors.IsNotFound(err) { + return err + } + return v.ProcessedAuthNKeySequence(event) +} + +func (v *View) GetLatestAuthNKeySequence() (*repository.CurrentSequence, error) { + return v.latestSequence(authNKeyTable) +} + +func (v *View) ProcessedAuthNKeySequence(event *models.Event) error { + return v.saveCurrentSequence(authNKeyTable, event) +} + +func (v *View) UpdateAuthNKeySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(authNKeyTable) +} + +func (v *View) GetLatestAuthNKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { + return v.latestFailedEvent(authNKeyTable, sequence) +} + +func (v *View) ProcessedAuthNKeyFailedEvent(failedEvent *repository.FailedEvent) error { + return v.saveFailedEvent(failedEvent) +} diff --git a/internal/auth/repository/eventsourcing/view/machine_keys.go b/internal/auth/repository/eventsourcing/view/machine_keys.go deleted file mode 100644 index 908dc54d8c..0000000000 --- a/internal/auth/repository/eventsourcing/view/machine_keys.go +++ /dev/null @@ -1,74 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" - usr_model "github.com/caos/zitadel/internal/user/model" - "github.com/caos/zitadel/internal/user/repository/view" - "github.com/caos/zitadel/internal/user/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - machineKeyTable = "auth.machine_keys" -) - -func (v *View) MachineKeyByIDs(userID, keyID string) (*model.MachineKeyView, error) { - return view.MachineKeyByIDs(v.Db, machineKeyTable, userID, keyID) -} - -func (v *View) MachineKeysByUserID(userID string) ([]*model.MachineKeyView, error) { - return view.MachineKeysByUserID(v.Db, machineKeyTable, userID) -} - -func (v *View) MachineKeyByID(keyID string) (*model.MachineKeyView, error) { - return view.MachineKeyByID(v.Db, machineKeyTable, keyID) -} - -func (v *View) SearchMachineKeys(request *usr_model.MachineKeySearchRequest) ([]*model.MachineKeyView, uint64, error) { - return view.SearchMachineKeys(v.Db, machineKeyTable, request) -} - -func (v *View) PutMachineKey(key *model.MachineKeyView, event *models.Event) error { - err := view.PutMachineKey(v.Db, machineKeyTable, key) - if err != nil { - return err - } - return v.ProcessedMachineKeySequence(event) -} - -func (v *View) DeleteMachineKey(keyID string, event *models.Event) error { - err := view.DeleteMachineKey(v.Db, machineKeyTable, keyID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedMachineKeySequence(event) -} - -func (v *View) DeleteMachineKeysByUserID(userID string, event *models.Event) error { - err := view.DeleteMachineKey(v.Db, machineKeyTable, userID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedMachineKeySequence(event) -} - -func (v *View) GetLatestMachineKeySequence() (*repository.CurrentSequence, error) { - return v.latestSequence(machineKeyTable) -} - -func (v *View) ProcessedMachineKeySequence(event *models.Event) error { - return v.saveCurrentSequence(machineKeyTable, event) -} - -func (v *View) UpdateMachineKeySpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(machineKeyTable) -} - -func (v *View) GetLatestMachineKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(machineKeyTable, sequence) -} - -func (v *View) ProcessedMachineKeyFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/auth/repository/org.go b/internal/auth/repository/org.go index 2139987a18..d3809f53b2 100644 --- a/internal/auth/repository/org.go +++ b/internal/auth/repository/org.go @@ -4,10 +4,12 @@ import ( "context" auth_model "github.com/caos/zitadel/internal/auth/model" iam_model "github.com/caos/zitadel/internal/iam/model" + org_model "github.com/caos/zitadel/internal/org/model" ) type OrgRepository interface { RegisterOrg(context.Context, *auth_model.RegisterOrg) (*auth_model.RegisterOrg, error) + OrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) GetOrgIAMPolicy(ctx context.Context, orgID string) (*iam_model.OrgIAMPolicyView, error) GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error) diff --git a/internal/auth/repository/user.go b/internal/auth/repository/user.go index 07b51bbdc9..20c03f0c04 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -3,6 +3,7 @@ package repository import ( "context" + key_model "github.com/caos/zitadel/internal/key/model" org_model "github.com/caos/zitadel/internal/org/model" "github.com/caos/zitadel/internal/user/model" @@ -43,7 +44,7 @@ type UserRepository interface { UserByID(ctx context.Context, userID string) (*model.UserView, error) - MachineKeyByID(ctx context.Context, keyID string) (*model.MachineKeyView, error) + MachineKeyByID(ctx context.Context, keyID string) (*key_model.AuthNKeyView, error) } type myUserRepo interface { diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 1e6bab7a22..18006f2856 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -39,6 +39,7 @@ type SecretGenerators struct { PhoneVerificationCode crypto.GeneratorConfig PasswordVerificationCode crypto.GeneratorConfig MachineKeySize uint32 + ClientKeySize uint32 } type MultifactorConfig struct { diff --git a/internal/key/model/authn_key.go b/internal/key/model/authn_key.go new file mode 100644 index 0000000000..417c812a3e --- /dev/null +++ b/internal/key/model/authn_key.go @@ -0,0 +1,101 @@ +package model + +import ( + "time" + + "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/model" +) + +const ( + yearLayout = "2006-01-02" + defaultExpirationDate = "9999-01-01" +) + +type AuthNKeyView struct { + ID string + ObjectID string + ObjectType ObjectType + AuthIdentifier string + Type AuthNKeyType + Sequence uint64 + CreationDate time.Time + ExpirationDate time.Time + PublicKey []byte + State AuthNKeyState +} + +type AuthNKey struct { + models.ObjectRoot + + KeyID string + ObjectType ObjectType + Type AuthNKeyType + ExpirationDate time.Time + PrivateKey []byte +} + +type AuthNKeyType int32 + +const ( + AuthNKeyTypeNONE = iota + AuthNKeyTypeJSON +) + +type AuthNKeyState int32 + +const ( + AuthNKeyStateActive AuthNKeyState = iota + AuthNKeyStateInactive + AuthNKeyStateRemoved +) + +type AuthNKeySearchRequest struct { + Offset uint64 + Limit uint64 + SortingColumn AuthNKeySearchKey + Asc bool + Queries []*AuthNKeySearchQuery +} + +type AuthNKeySearchKey int32 + +const ( + AuthNKeyKeyUnspecified AuthNKeySearchKey = iota + AuthNKeyKeyID + AuthNKeyObjectID + AuthNKeyObjectType +) + +type ObjectType int32 + +const ( + AuthNKeyObjectTypeUnspecified ObjectType = iota + AuthNKeyObjectTypeUser + AuthNKeyObjectTypeApplication +) + +type AuthNKeySearchQuery struct { + Key AuthNKeySearchKey + Method model.SearchMethod + Value interface{} +} + +type AuthNKeySearchResponse struct { + Offset uint64 + Limit uint64 + TotalResult uint64 + Result []*AuthNKeyView + Sequence uint64 + Timestamp time.Time +} + +func (r *AuthNKeySearchRequest) EnsureLimit(limit uint64) { + if r.Limit == 0 || r.Limit > limit { + r.Limit = limit + } +} + +func DefaultExpiration() (time.Time, error) { + return time.Parse(yearLayout, defaultExpirationDate) +} diff --git a/internal/key/repository/view/authn_key_view.go b/internal/key/repository/view/authn_key_view.go new file mode 100644 index 0000000000..5bd299150e --- /dev/null +++ b/internal/key/repository/view/authn_key_view.go @@ -0,0 +1,77 @@ +package view + +import ( + caos_errs "github.com/caos/zitadel/internal/errors" + key_model "github.com/caos/zitadel/internal/key/model" + "github.com/caos/zitadel/internal/key/repository/view/model" + global_model "github.com/caos/zitadel/internal/model" + "github.com/caos/zitadel/internal/view/repository" + "github.com/jinzhu/gorm" +) + +func AuthNKeyByIDs(db *gorm.DB, table, objectID, keyID string) (*model.AuthNKeyView, error) { + key := new(model.AuthNKeyView) + query := repository.PrepareGetByQuery(table, + model.AuthNKeySearchQuery{Key: key_model.AuthNKeyObjectID, Method: global_model.SearchMethodEquals, Value: objectID}, + model.AuthNKeySearchQuery{Key: key_model.AuthNKeyKeyID, Method: global_model.SearchMethodEquals, Value: keyID}, + ) + err := query(db, key) + if caos_errs.IsNotFound(err) { + return nil, caos_errs.ThrowNotFound(nil, "VIEW-3Dk9s", "Errors.User.KeyNotFound") + } + return key, err +} + +func SearchAuthNKeys(db *gorm.DB, table string, req *key_model.AuthNKeySearchRequest) ([]*model.AuthNKeyView, uint64, error) { + keys := make([]*model.AuthNKeyView, 0) + query := repository.PrepareSearchQuery(table, model.AuthNKeySearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries}) + count, err := query(db, &keys) + if err != nil { + return nil, 0, err + } + return keys, count, nil +} + +func AuthNKeysByObjectID(db *gorm.DB, table string, objectID string) ([]*model.AuthNKeyView, error) { + keys := make([]*model.AuthNKeyView, 0) + queries := []*key_model.AuthNKeySearchQuery{ + { + Key: key_model.AuthNKeyObjectID, + Value: objectID, + Method: global_model.SearchMethodEquals, + }, + } + query := repository.PrepareSearchQuery(table, model.AuthNKeySearchRequest{Queries: queries}) + _, err := query(db, &keys) + if err != nil { + return nil, err + } + return keys, nil +} + +func AuthNKeyByID(db *gorm.DB, table string, keyID string) (*model.AuthNKeyView, error) { + key := new(model.AuthNKeyView) + query := repository.PrepareGetByQuery(table, + model.AuthNKeySearchQuery{Key: key_model.AuthNKeyKeyID, Method: global_model.SearchMethodEquals, Value: keyID}, + ) + err := query(db, key) + if caos_errs.IsNotFound(err) { + return nil, caos_errs.ThrowNotFound(nil, "VIEW-BjN6x", "Errors.User.KeyNotFound") + } + return key, err +} + +func PutAuthNKey(db *gorm.DB, table string, role *model.AuthNKeyView) error { + save := repository.PrepareSave(table) + return save(db, role) +} + +func DeleteAuthNKey(db *gorm.DB, table, keyID string) error { + delete := repository.PrepareDeleteByKey(table, model.AuthNKeySearchKey(key_model.AuthNKeyKeyID), keyID) + return delete(db) +} + +func DeleteAuthNKeysByObjectID(db *gorm.DB, table, objectID string) error { + delete := repository.PrepareDeleteByKey(table, model.AuthNKeySearchKey(key_model.AuthNKeyObjectID), objectID) + return delete(db) +} diff --git a/internal/key/repository/view/model/authn_key.go b/internal/key/repository/view/model/authn_key.go new file mode 100644 index 0000000000..200c5b36be --- /dev/null +++ b/internal/key/repository/view/model/authn_key.go @@ -0,0 +1,171 @@ +package model + +import ( + "encoding/json" + "time" + + "github.com/caos/logging" + + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/key/model" + proj_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model" + user_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" +) + +const ( + AuthNKeyKeyID = "key_id" + AuthNKeyObjectID = "object_id" + AuthNKeyObjectType = "object_type" +) + +type AuthNKeyView struct { + ID string `json:"keyId" gorm:"column:key_id;primary_key"` + ObjectID string `json:"-" gorm:"column:object_id;primary_key"` + ObjectType int32 `json:"-" gorm:"column:object_type;primary_key"` + AuthIdentifier string `json:"-" gorm:"column:auth_identifier;primary_key"` + Type int32 `json:"type" gorm:"column:key_type"` + ExpirationDate time.Time `json:"expirationDate" gorm:"column:expiration_date"` + Sequence uint64 `json:"-" gorm:"column:sequence"` + CreationDate time.Time `json:"-" gorm:"column:creation_date"` + PublicKey []byte `json:"publicKey" gorm:"column:public_key"` + State int32 `json:"-" gorm:"column:state"` +} + +func AuthNKeyViewFromModel(key *model.AuthNKeyView) *AuthNKeyView { + return &AuthNKeyView{ + ID: key.ID, + ObjectID: key.ObjectID, + ObjectType: int32(key.ObjectType), + Type: int32(key.Type), + ExpirationDate: key.ExpirationDate, + Sequence: key.Sequence, + CreationDate: key.CreationDate, + State: int32(key.State), + } +} + +func AuthNKeyToModel(key *AuthNKeyView) *model.AuthNKeyView { + return &model.AuthNKeyView{ + ID: key.ID, + ObjectID: key.ObjectID, + ObjectType: model.ObjectType(key.ObjectType), + AuthIdentifier: key.AuthIdentifier, + Type: model.AuthNKeyType(key.Type), + ExpirationDate: key.ExpirationDate, + Sequence: key.Sequence, + CreationDate: key.CreationDate, + PublicKey: key.PublicKey, + State: model.AuthNKeyState(key.State), + } +} + +func AuthNKeysToModel(keys []*AuthNKeyView) []*model.AuthNKeyView { + result := make([]*model.AuthNKeyView, len(keys)) + for i, key := range keys { + result[i] = AuthNKeyToModel(key) + } + return result +} + +func (k *AuthNKeyView) AppendEventIfMyClientKey(event *models.Event) (err error) { + switch event.Type { + case proj_model.ApplicationDeactivated, + proj_model.ApplicationReactivated, + proj_model.ApplicationRemoved: + a := new(proj_view_model.ApplicationView) + if err := a.AppendEvent(event); err != nil { + return err + } + if a.ID == k.ObjectID { + return k.AppendEvent(event) + } + case proj_model.ProjectDeactivated, + proj_model.ProjectReactivated, + proj_model.ProjectRemoved: + return k.AppendEvent(event) + case user_model.UserLocked, + user_model.UserDeactivated, + user_model.UserUnlocked, + user_model.UserReactivated, + user_model.UserRemoved: + return k.AppendEvent(event) + case proj_model.ClientKeyRemoved, + user_model.MachineKeyRemoved: + view := new(AuthNKeyView) + if view.ID == k.ID { + return k.AppendEvent(event) + } + default: + return nil + } + return nil +} + +func (k *AuthNKeyView) AppendEvent(event *models.Event) (err error) { + k.Sequence = event.Sequence + switch event.Type { + case user_model.MachineKeyAdded: + k.setRootData(event) + k.CreationDate = event.CreationDate + err = k.SetUserData(event) + case proj_model.ClientKeyAdded: + k.setRootData(event) + k.CreationDate = event.CreationDate + err = k.SetClientData(event) + case proj_model.ClientKeyRemoved, + proj_model.ApplicationRemoved, + proj_model.ProjectRemoved, + user_model.MachineKeyRemoved, + user_model.UserRemoved: + k.State = int32(model.AuthNKeyStateRemoved) + case proj_model.ProjectDeactivated, + proj_model.ApplicationDeactivated, + user_model.UserDeactivated, + user_model.UserLocked: + k.State = int32(model.AuthNKeyStateInactive) + case proj_model.ProjectReactivated, + proj_model.ApplicationReactivated, + user_model.UserReactivated, + user_model.UserUnlocked: + if k.State != int32(model.AuthNKeyStateRemoved) { + k.State = int32(model.AuthNKeyStateActive) + } + } + return err +} + +func (k *AuthNKeyView) setRootData(event *models.Event) { + switch event.AggregateType { + case user_model.UserAggregate: + k.ObjectType = int32(model.AuthNKeyObjectTypeUser) + k.ObjectID = event.AggregateID + k.AuthIdentifier = event.AggregateID + case proj_model.ProjectAggregate: + k.ObjectType = int32(model.AuthNKeyObjectTypeApplication) + } +} + +func (k *AuthNKeyView) SetUserData(event *models.Event) error { + if err := json.Unmarshal(event.Data, k); err != nil { + logging.Log("EVEN-Sj90d").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-lub6s", "Could not unmarshal data") + } + return nil +} + +func (k *AuthNKeyView) SetClientData(event *models.Event) error { + key := new(proj_model.ClientKey) + if err := json.Unmarshal(event.Data, key); err != nil { + logging.Log("EVEN-Dgsgg").WithError(err).Error("could not unmarshal event data") + return caos_errs.ThrowInternal(err, "MODEL-ADbfz", "Could not unmarshal data") + } + k.ObjectID = key.ApplicationID + k.AuthIdentifier = key.ClientID + k.ID = key.KeyID + k.ExpirationDate = key.ExpirationDate + k.PublicKey = key.PublicKey + k.Type = key.Type + return nil +} diff --git a/internal/key/repository/view/model/authn_key_query.go b/internal/key/repository/view/model/authn_key_query.go new file mode 100644 index 0000000000..e26c58977e --- /dev/null +++ b/internal/key/repository/view/model/authn_key_query.go @@ -0,0 +1,63 @@ +package model + +import ( + key_model "github.com/caos/zitadel/internal/key/model" + global_model "github.com/caos/zitadel/internal/model" + "github.com/caos/zitadel/internal/view/repository" +) + +type AuthNKeySearchRequest key_model.AuthNKeySearchRequest +type AuthNKeySearchQuery key_model.AuthNKeySearchQuery +type AuthNKeySearchKey key_model.AuthNKeySearchKey + +func (req AuthNKeySearchRequest) GetLimit() uint64 { + return req.Limit +} + +func (req AuthNKeySearchRequest) GetOffset() uint64 { + return req.Offset +} + +func (req AuthNKeySearchRequest) GetSortingColumn() repository.ColumnKey { + if req.SortingColumn == key_model.AuthNKeyKeyUnspecified { + return nil + } + return AuthNKeySearchKey(req.SortingColumn) +} + +func (req AuthNKeySearchRequest) GetAsc() bool { + return req.Asc +} + +func (req AuthNKeySearchRequest) GetQueries() []repository.SearchQuery { + result := make([]repository.SearchQuery, len(req.Queries)) + for i, q := range req.Queries { + result[i] = AuthNKeySearchQuery{Key: q.Key, Value: q.Value, Method: q.Method} + } + return result +} + +func (req AuthNKeySearchQuery) GetKey() repository.ColumnKey { + return AuthNKeySearchKey(req.Key) +} + +func (req AuthNKeySearchQuery) GetMethod() global_model.SearchMethod { + return req.Method +} + +func (req AuthNKeySearchQuery) GetValue() interface{} { + return req.Value +} + +func (key AuthNKeySearchKey) ToColumnName() string { + switch key_model.AuthNKeySearchKey(key) { + case key_model.AuthNKeyKeyID: + return AuthNKeyKeyID + case key_model.AuthNKeyObjectID: + return AuthNKeyObjectID + case key_model.AuthNKeyObjectType: + return AuthNKeyObjectType + default: + return "" + } +} diff --git a/internal/management/repository/eventsourcing/eventstore/project.go b/internal/management/repository/eventsourcing/eventstore/project.go index 5a9343ff11..65c9628579 100644 --- a/internal/management/repository/eventsourcing/eventstore/project.go +++ b/internal/management/repository/eventsourcing/eventstore/project.go @@ -13,6 +13,8 @@ import ( es_models "github.com/caos/zitadel/internal/eventstore/models" es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing" + key_model "github.com/caos/zitadel/internal/key/model" + key_view_model "github.com/caos/zitadel/internal/key/repository/view/model" "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" global_model "github.com/caos/zitadel/internal/model" proj_model "github.com/caos/zitadel/internal/project/model" @@ -405,14 +407,80 @@ func (repo *ProjectRepo) ApplicationChanges(ctx context.Context, id string, appI return changes, nil } +func (repo *ProjectRepo) SearchClientKeys(ctx context.Context, request *key_model.AuthNKeySearchRequest) (*key_model.AuthNKeySearchResponse, error) { + request.EnsureLimit(repo.SearchLimit) + sequence, sequenceErr := repo.View.GetLatestAuthNKeySequence() + logging.Log("EVENT-ADwgw").OnError(sequenceErr).Warn("could not read latest authn key sequence") + keys, count, err := repo.View.SearchAuthNKeys(request) + if err != nil { + return nil, err + } + result := &key_model.AuthNKeySearchResponse{ + Offset: request.Offset, + Limit: request.Limit, + TotalResult: count, + Result: key_view_model.AuthNKeysToModel(keys), + } + if sequenceErr == nil { + result.Sequence = sequence.CurrentSequence + result.Timestamp = sequence.LastSuccessfulSpoolerRun + } + return result, nil +} + +func (repo *ProjectRepo) GetClientKey(ctx context.Context, projectID, applicationID, keyID string) (*key_model.AuthNKeyView, error) { + key, viewErr := repo.View.AuthNKeyByIDs(applicationID, keyID) + if viewErr != nil { + return nil, viewErr + } + + events, esErr := repo.ProjectEvents.ProjectEventsByID(ctx, projectID, key.Sequence) + if caos_errs.IsNotFound(viewErr) && len(events) == 0 { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-SFf2g", "Errors.User.KeyNotFound") + } + + if esErr != nil { + logging.Log("EVENT-ADbf2").WithError(viewErr).Debug("error retrieving new events") + return key_view_model.AuthNKeyToModel(key), nil + } + + viewKey := *key + for _, event := range events { + err := key.AppendEventIfMyClientKey(event) + if err != nil { + return key_view_model.AuthNKeyToModel(&viewKey), nil + } + if key.State != int32(proj_model.AppStateActive) { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-Adfg3", "Errors.User.KeyNotFound") + } + } + return key_view_model.AuthNKeyToModel(key), nil +} + +func (repo *ProjectRepo) AddClientKey(ctx context.Context, key *proj_model.ClientKey) (*proj_model.ClientKey, error) { + return repo.ProjectEvents.AddClientKey(ctx, key) +} + +func (repo *ProjectRepo) RemoveClientKey(ctx context.Context, projectID, applicationID, keyID string) error { + return repo.ProjectEvents.RemoveApplicationKey(ctx, projectID, applicationID, keyID) +} + func (repo *ProjectRepo) ChangeOIDCConfig(ctx context.Context, config *proj_model.OIDCConfig) (*proj_model.OIDCConfig, error) { return repo.ProjectEvents.ChangeOIDCConfig(ctx, config) } +func (repo *ProjectRepo) ChangeAPIConfig(ctx context.Context, config *proj_model.APIConfig) (*proj_model.APIConfig, error) { + return repo.ProjectEvents.ChangeAPIConfig(ctx, config) +} + func (repo *ProjectRepo) ChangeOIDConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.OIDCConfig, error) { return repo.ProjectEvents.ChangeOIDCConfigSecret(ctx, projectID, appID) } +func (repo *ProjectRepo) ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.APIConfig, error) { + return repo.ProjectEvents.ChangeAPIConfigSecret(ctx, projectID, appID) +} + func (repo *ProjectRepo) ProjectGrantByID(ctx context.Context, grantID string) (*proj_model.ProjectGrantView, error) { grant, err := repo.View.ProjectGrantByID(grantID) if err != nil { diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 1cc0c69772..12a4e1e37d 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -12,6 +12,8 @@ import ( es_models "github.com/caos/zitadel/internal/eventstore/models" es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" + key_model "github.com/caos/zitadel/internal/key/model" + key_view_model "github.com/caos/zitadel/internal/key/repository/view/model" "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" global_model "github.com/caos/zitadel/internal/model" org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" @@ -301,27 +303,27 @@ func (repo *UserRepo) ChangeMachine(ctx context.Context, machine *usr_model.Mach return repo.UserEvents.ChangeMachine(ctx, machine) } -func (repo *UserRepo) GetMachineKey(ctx context.Context, userID, keyID string) (*usr_model.MachineKeyView, error) { - key, err := repo.View.MachineKeyByIDs(userID, keyID) +func (repo *UserRepo) GetMachineKey(ctx context.Context, userID, keyID string) (*key_model.AuthNKeyView, error) { + key, err := repo.View.AuthNKeyByIDs(userID, keyID) if err != nil { return nil, err } - return model.MachineKeyToModel(key), nil + return key_view_model.AuthNKeyToModel(key), nil } -func (repo *UserRepo) SearchMachineKeys(ctx context.Context, request *usr_model.MachineKeySearchRequest) (*usr_model.MachineKeySearchResponse, error) { +func (repo *UserRepo) SearchMachineKeys(ctx context.Context, request *key_model.AuthNKeySearchRequest) (*key_model.AuthNKeySearchResponse, error) { request.EnsureLimit(repo.SearchLimit) - sequence, seqErr := repo.View.GetLatestMachineKeySequence() - logging.Log("EVENT-Sk8fs").OnError(seqErr).Warn("could not read latest user sequence") - keys, count, err := repo.View.SearchMachineKeys(request) + sequence, seqErr := repo.View.GetLatestAuthNKeySequence() + logging.Log("EVENT-Sk8fs").OnError(seqErr).Warn("could not read latest authn key sequence") + keys, count, err := repo.View.SearchAuthNKeys(request) if err != nil { return nil, err } - result := &usr_model.MachineKeySearchResponse{ + result := &key_model.AuthNKeySearchResponse{ Offset: request.Offset, Limit: request.Limit, TotalResult: count, - Result: model.MachineKeysToModel(keys), + Result: key_view_model.AuthNKeysToModel(keys), } if seqErr == nil { result.Sequence = sequence.CurrentSequence diff --git a/internal/management/repository/eventsourcing/handler/application.go b/internal/management/repository/eventsourcing/handler/application.go index c18a055728..71e4c2af07 100644 --- a/internal/management/repository/eventsourcing/handler/application.go +++ b/internal/management/repository/eventsourcing/handler/application.go @@ -87,6 +87,8 @@ func (a *Application) Reduce(event *models.Event) (err error) { case es_model.ApplicationChanged, es_model.OIDCConfigAdded, es_model.OIDCConfigChanged, + es_model.APIConfigAdded, + es_model.APIConfigChanged, es_model.ApplicationDeactivated, es_model.ApplicationReactivated: err = app.SetData(event) diff --git a/internal/management/repository/eventsourcing/handler/authn_keys.go b/internal/management/repository/eventsourcing/handler/authn_keys.go new file mode 100644 index 0000000000..0003010033 --- /dev/null +++ b/internal/management/repository/eventsourcing/handler/authn_keys.go @@ -0,0 +1,120 @@ +package handler + +import ( + "time" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/eventstore" + es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/eventstore/query" + "github.com/caos/zitadel/internal/eventstore/spooler" + key_model "github.com/caos/zitadel/internal/key/repository/view/model" + proj_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + user_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" +) + +const ( + authnKeysTable = "management.authn_keys" +) + +type AuthNKeys struct { + handler + subscription *eventstore.Subscription +} + +func newAuthNKeys(handler handler) *AuthNKeys { + h := &AuthNKeys{ + handler: handler, + } + + h.subscribe() + + return h +} + +func (k *AuthNKeys) subscribe() { + k.subscription = k.es.Subscribe(k.AggregateTypes()...) + go func() { + for event := range k.subscription.Events { + query.ReduceEvent(k, event) + } + }() +} + +func (k *AuthNKeys) ViewModel() string { + return authnKeysTable +} + +func (_ *AuthNKeys) AggregateTypes() []es_models.AggregateType { + return []es_models.AggregateType{user_model.UserAggregate, proj_model.ProjectAggregate} +} + +func (k *AuthNKeys) CurrentSequence() (uint64, error) { + sequence, err := k.view.GetLatestAuthNKeySequence() + if err != nil { + return 0, err + } + return sequence.CurrentSequence, nil +} + +func (k *AuthNKeys) EventQuery() (*es_models.SearchQuery, error) { + sequence, err := k.view.GetLatestAuthNKeySequence() + if err != nil { + return nil, err + } + return es_models.NewSearchQuery(). + AggregateTypeFilter(k.AggregateTypes()...). + LatestSequenceFilter(sequence.CurrentSequence), nil +} + +func (k *AuthNKeys) Reduce(event *es_models.Event) (err error) { + switch event.AggregateType { + case user_model.UserAggregate, + proj_model.ProjectAggregate: + err = k.processAuthNKeys(event) + } + return err +} + +func (k *AuthNKeys) processAuthNKeys(event *es_models.Event) (err error) { + key := new(key_model.AuthNKeyView) + switch event.Type { + case user_model.MachineKeyAdded, + proj_model.ClientKeyAdded: + err = key.AppendEvent(event) + if key.ExpirationDate.Before(time.Now()) { + return k.view.ProcessedAuthNKeySequence(event) + } + case user_model.MachineKeyRemoved: + err = key.SetUserData(event) + if err != nil { + return err + } + return k.view.DeleteAuthNKey(key.ID, event) + case proj_model.ClientKeyRemoved: + err = key.SetClientData(event) + if err != nil { + return err + } + return k.view.DeleteAuthNKey(key.ID, event) + case user_model.UserRemoved, + proj_model.ApplicationRemoved: + return k.view.DeleteAuthNKeysByObjectID(event.AggregateID, event) + default: + return k.view.ProcessedAuthNKeySequence(event) + } + if err != nil { + return err + } + return k.view.PutAuthNKey(key, event) +} + +func (d *AuthNKeys) OnError(event *es_models.Event, err error) error { + logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in machine key handler") + return spooler.HandleError(event, err, d.view.GetLatestAuthNKeyFailedEvent, d.view.ProcessedAuthNKeyFailedEvent, d.view.ProcessedAuthNKeySequence, d.errorCountUntilSkip) +} + +func (d *AuthNKeys) OnSuccess() error { + return spooler.HandleSuccess(d.view.UpdateAuthNKeySpoolerRunTimestamp) +} diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go index adf3ec26fd..10aa729250 100644 --- a/internal/management/repository/eventsourcing/handler/handler.go +++ b/internal/management/repository/eventsourcing/handler/handler.go @@ -75,7 +75,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("UserMembership"), errorCount, es}, repos.OrgEvents, repos.ProjectEvents), - newMachineKeys( + newAuthNKeys( handler{view, bulkLimit, configs.cycleDuration("MachineKeys"), errorCount, es}), newIDPConfig( handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount, es}), diff --git a/internal/management/repository/eventsourcing/handler/machine_keys.go b/internal/management/repository/eventsourcing/handler/machine_keys.go deleted file mode 100644 index cabe5f0cdf..0000000000 --- a/internal/management/repository/eventsourcing/handler/machine_keys.go +++ /dev/null @@ -1,110 +0,0 @@ -package handler - -import ( - "time" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore" - "github.com/caos/zitadel/internal/eventstore/models" - es_models "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/eventstore/query" - "github.com/caos/zitadel/internal/eventstore/spooler" - "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" - usr_model "github.com/caos/zitadel/internal/user/repository/view/model" -) - -const ( - machineKeysTable = "management.machine_keys" -) - -type MachineKeys struct { - handler - subscription *eventstore.Subscription -} - -func newMachineKeys(handler handler) *MachineKeys { - h := &MachineKeys{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *MachineKeys) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -func (d *MachineKeys) ViewModel() string { - return machineKeysTable -} - -func (_ *MachineKeys) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.UserAggregate} -} - -func (k *MachineKeys) CurrentSequence() (uint64, error) { - sequence, err := k.view.GetLatestMachineKeySequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (d *MachineKeys) EventQuery() (*models.SearchQuery, error) { - sequence, err := d.view.GetLatestMachineKeySequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(d.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (d *MachineKeys) Reduce(event *models.Event) (err error) { - switch event.AggregateType { - case model.UserAggregate: - err = d.processMachineKeys(event) - } - return err -} - -func (d *MachineKeys) processMachineKeys(event *models.Event) (err error) { - key := new(usr_model.MachineKeyView) - switch event.Type { - case model.MachineKeyAdded: - err = key.AppendEvent(event) - if key.ExpirationDate.Before(time.Now()) { - return d.view.ProcessedMachineKeySequence(event) - } - case model.MachineKeyRemoved: - err = key.SetData(event) - if err != nil { - return err - } - return d.view.DeleteMachineKey(key.ID, event) - case model.UserRemoved: - return d.view.DeleteMachineKeysByUserID(event.AggregateID, event) - default: - return d.view.ProcessedMachineKeySequence(event) - } - if err != nil { - return err - } - return d.view.PutMachineKey(key, event) -} - -func (d *MachineKeys) OnError(event *models.Event, err error) error { - logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in machine key handler") - return spooler.HandleError(event, err, d.view.GetLatestMachineKeyFailedEvent, d.view.ProcessedMachineKeyFailedEvent, d.view.ProcessedMachineKeySequence, d.errorCountUntilSkip) -} - -func (d *MachineKeys) OnSuccess() error { - return spooler.HandleSuccess(d.view.UpdateMachineKeySpoolerRunTimestamp) -} diff --git a/internal/management/repository/eventsourcing/view/authn_keys.go b/internal/management/repository/eventsourcing/view/authn_keys.go new file mode 100644 index 0000000000..cc982dc4c7 --- /dev/null +++ b/internal/management/repository/eventsourcing/view/authn_keys.go @@ -0,0 +1,74 @@ +package view + +import ( + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" + "github.com/caos/zitadel/internal/key/repository/view" + "github.com/caos/zitadel/internal/key/repository/view/model" + "github.com/caos/zitadel/internal/view/repository" +) + +const ( + authNKeyTable = "management.authn_keys" +) + +func (v *View) AuthNKeyByIDs(objectID, keyID string) (*model.AuthNKeyView, error) { + return view.AuthNKeyByIDs(v.Db, authNKeyTable, objectID, keyID) +} + +func (v *View) AuthNKeysByObjectID(objectID string) ([]*model.AuthNKeyView, error) { + return view.AuthNKeysByObjectID(v.Db, authNKeyTable, objectID) +} + +func (v *View) AuthNKeyByID(keyID string) (*model.AuthNKeyView, error) { + return view.AuthNKeyByID(v.Db, authNKeyTable, keyID) +} + +func (v *View) SearchAuthNKeys(request *key_model.AuthNKeySearchRequest) ([]*model.AuthNKeyView, uint64, error) { + return view.SearchAuthNKeys(v.Db, authNKeyTable, request) +} + +func (v *View) PutAuthNKey(key *model.AuthNKeyView, event *models.Event) error { + err := view.PutAuthNKey(v.Db, authNKeyTable, key) + if err != nil { + return err + } + return v.ProcessedAuthNKeySequence(event) +} + +func (v *View) DeleteAuthNKey(keyID string, event *models.Event) error { + err := view.DeleteAuthNKey(v.Db, authNKeyTable, keyID) + if err != nil && !errors.IsNotFound(err) { + return err + } + return v.ProcessedAuthNKeySequence(event) +} + +func (v *View) DeleteAuthNKeysByObjectID(objectID string, event *models.Event) error { + err := view.DeleteAuthNKey(v.Db, authNKeyTable, objectID) + if err != nil && !errors.IsNotFound(err) { + return err + } + return v.ProcessedAuthNKeySequence(event) +} + +func (v *View) GetLatestAuthNKeySequence() (*repository.CurrentSequence, error) { + return v.latestSequence(authNKeyTable) +} + +func (v *View) ProcessedAuthNKeySequence(event *models.Event) error { + return v.saveCurrentSequence(authNKeyTable, event) +} + +func (v *View) UpdateAuthNKeySpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(authNKeyTable) +} + +func (v *View) GetLatestAuthNKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { + return v.latestFailedEvent(authNKeyTable, sequence) +} + +func (v *View) ProcessedAuthNKeyFailedEvent(failedEvent *repository.FailedEvent) error { + return v.saveFailedEvent(failedEvent) +} diff --git a/internal/management/repository/eventsourcing/view/machine_keys.go b/internal/management/repository/eventsourcing/view/machine_keys.go deleted file mode 100644 index 075e15fe8e..0000000000 --- a/internal/management/repository/eventsourcing/view/machine_keys.go +++ /dev/null @@ -1,70 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" - usr_model "github.com/caos/zitadel/internal/user/model" - "github.com/caos/zitadel/internal/user/repository/view" - "github.com/caos/zitadel/internal/user/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - machineKeyTable = "management.machine_keys" -) - -func (v *View) MachineKeyByIDs(userID, keyID string) (*model.MachineKeyView, error) { - return view.MachineKeyByIDs(v.Db, machineKeyTable, userID, keyID) -} - -func (v *View) MachineKeysByUserID(userID string) ([]*model.MachineKeyView, error) { - return view.MachineKeysByUserID(v.Db, machineKeyTable, userID) -} - -func (v *View) SearchMachineKeys(request *usr_model.MachineKeySearchRequest) ([]*model.MachineKeyView, uint64, error) { - return view.SearchMachineKeys(v.Db, machineKeyTable, request) -} - -func (v *View) PutMachineKey(org *model.MachineKeyView, event *models.Event) error { - err := view.PutMachineKey(v.Db, machineKeyTable, org) - if err != nil { - return err - } - return v.ProcessedMachineKeySequence(event) -} - -func (v *View) DeleteMachineKey(keyID string, event *models.Event) error { - err := view.DeleteMachineKey(v.Db, machineKeyTable, keyID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedMachineKeySequence(event) -} - -func (v *View) DeleteMachineKeysByUserID(userID string, event *models.Event) error { - err := view.DeleteMachineKey(v.Db, machineKeyTable, userID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedMachineKeySequence(event) -} - -func (v *View) GetLatestMachineKeySequence() (*repository.CurrentSequence, error) { - return v.latestSequence(machineKeyTable) -} - -func (v *View) ProcessedMachineKeySequence(event *models.Event) error { - return v.saveCurrentSequence(machineKeyTable, event) -} - -func (v *View) UpdateMachineKeySpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(machineKeyTable) -} - -func (v *View) GetLatestMachineKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(machineKeyTable, sequence) -} - -func (v *View) ProcessedMachineKeyFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/project.go b/internal/management/repository/project.go index 06c1e2225a..c1524521c6 100644 --- a/internal/management/repository/project.go +++ b/internal/management/repository/project.go @@ -3,6 +3,7 @@ package repository import ( "context" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/zitadel/internal/project/model" ) @@ -39,9 +40,15 @@ type ProjectRepository interface { ReactivateApplication(ctx context.Context, projectID, appID string) (*model.Application, error) RemoveApplication(ctx context.Context, projectID, appID string) error ChangeOIDCConfig(ctx context.Context, config *model.OIDCConfig) (*model.OIDCConfig, error) + ChangeAPIConfig(ctx context.Context, config *model.APIConfig) (*model.APIConfig, error) ChangeOIDConfigSecret(ctx context.Context, projectID, appID string) (*model.OIDCConfig, error) + ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*model.APIConfig, error) SearchApplications(ctx context.Context, request *model.ApplicationSearchRequest) (*model.ApplicationSearchResponse, error) ApplicationChanges(ctx context.Context, id string, secId string, lastSequence uint64, limit uint64, sortAscending bool) (*model.ApplicationChanges, error) + SearchClientKeys(ctx context.Context, request *key_model.AuthNKeySearchRequest) (*key_model.AuthNKeySearchResponse, error) + GetClientKey(ctx context.Context, projectID, applicationID, keyID string) (*key_model.AuthNKeyView, error) + AddClientKey(ctx context.Context, key *model.ClientKey) (*model.ClientKey, error) + RemoveClientKey(ctx context.Context, projectID, applicationID, keyID string) error ProjectGrantByID(ctx context.Context, grantID string) (*model.ProjectGrantView, error) AddProjectGrant(ctx context.Context, grant *model.ProjectGrant) (*model.ProjectGrant, error) diff --git a/internal/management/repository/user.go b/internal/management/repository/user.go index da8d81b359..d347cfc8ab 100644 --- a/internal/management/repository/user.go +++ b/internal/management/repository/user.go @@ -3,6 +3,7 @@ package repository import ( "context" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/zitadel/internal/user/model" ) @@ -40,8 +41,8 @@ type UserRepository interface { SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) RemoveExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error - SearchMachineKeys(ctx context.Context, request *model.MachineKeySearchRequest) (*model.MachineKeySearchResponse, error) - GetMachineKey(ctx context.Context, userID, keyID string) (*model.MachineKeyView, error) + SearchMachineKeys(ctx context.Context, request *key_model.AuthNKeySearchRequest) (*key_model.AuthNKeySearchResponse, error) + GetMachineKey(ctx context.Context, userID, keyID string) (*key_model.AuthNKeyView, error) ChangeMachine(ctx context.Context, machine *model.Machine) (*model.Machine, error) AddMachineKey(ctx context.Context, key *model.MachineKey) (*model.MachineKey, error) RemoveMachineKey(ctx context.Context, userID, keyID string) error diff --git a/internal/project/model/api_config.go b/internal/project/model/api_config.go new file mode 100644 index 0000000000..4a3a31202b --- /dev/null +++ b/internal/project/model/api_config.go @@ -0,0 +1,62 @@ +package model + +import ( + "fmt" + "strings" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/errors" + es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/id" +) + +type APIConfig struct { + es_models.ObjectRoot + AppID string + ClientID string + ClientSecret *crypto.CryptoValue + ClientSecretString string + AuthMethodType APIAuthMethodType + ClientKeys []*ClientKey +} + +type APIAuthMethodType int32 + +const ( + APIAuthMethodTypeBasic APIAuthMethodType = iota + APIAuthMethodTypePrivateKeyJWT +) + +func (c *APIConfig) IsValid() bool { + return true +} + +//ClientID random_number@projectname (eg. 495894098234@zitadel) +func (c *APIConfig) GenerateNewClientID(idGenerator id.Generator, project *Project) error { + rndID, err := idGenerator.Next() + if err != nil { + return err + } + + c.ClientID = fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_")) + return nil +} + +func (c *APIConfig) GenerateClientSecretIfNeeded(generator crypto.Generator) (string, error) { + if c.AuthMethodType == APIAuthMethodTypeBasic { + return c.GenerateNewClientSecret(generator) + } + return "", nil +} + +func (c *APIConfig) GenerateNewClientSecret(generator crypto.Generator) (string, error) { + cryptoValue, stringSecret, err := crypto.NewCode(generator) + if err != nil { + logging.Log("MODEL-ADvd2").OnError(err).Error("unable to create client secret") + return "", errors.ThrowInternal(err, "MODEL-dsvr43", "Errors.Project.CouldNotGenerateClientSecret") + } + c.ClientSecret = cryptoValue + return stringSecret, nil +} diff --git a/internal/project/model/application.go b/internal/project/model/application.go index d3dd028810..eba494caab 100644 --- a/internal/project/model/application.go +++ b/internal/project/model/application.go @@ -1,8 +1,9 @@ package model import ( - es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/golang/protobuf/ptypes/timestamp" + + es_models "github.com/caos/zitadel/internal/eventstore/models" ) type Application struct { @@ -13,6 +14,7 @@ type Application struct { Name string Type AppType OIDCConfig *OIDCConfig + APIConfig *APIConfig } type ApplicationChanges struct { Changes []*ApplicationChange @@ -42,6 +44,7 @@ const ( AppTypeUnspecified AppType = iota AppTypeOIDC AppTypeSAML + AppTypeAPI ) func NewApplication(projectID, appID string) *Application { @@ -58,5 +61,20 @@ func (a *Application) IsValid(includeConfig bool) bool { if a.Type == AppTypeOIDC && !a.OIDCConfig.IsValid() { return false } + if a.Type == AppTypeAPI && !a.APIConfig.IsValid() { + return false + } return true } + +func (a *Application) GetKey(keyID string) (int, *ClientKey) { + if a.OIDCConfig == nil { + return -1, nil + } + for i, k := range a.OIDCConfig.ClientKeys { + if k.KeyID == keyID { + return i, k + } + } + return -1, nil +} diff --git a/internal/project/model/oidc_config.go b/internal/project/model/oidc_config.go index 8c04b9fba5..647c8ac0a8 100644 --- a/internal/project/model/oidc_config.go +++ b/internal/project/model/oidc_config.go @@ -11,6 +11,7 @@ import ( "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/id" + key_model "github.com/caos/zitadel/internal/key/model" ) const ( @@ -40,6 +41,7 @@ type OIDCConfig struct { IDTokenRoleAssertion bool IDTokenUserinfoAssertion bool ClockSkew time.Duration + ClientKeys []*ClientKey } type OIDCVersion int32 @@ -78,6 +80,7 @@ const ( OIDCAuthMethodTypeBasic OIDCAuthMethodType = iota OIDCAuthMethodTypePost OIDCAuthMethodTypeNone + OIDCAuthMethodTypePrivateKeyJWT ) type Compliance struct { @@ -92,6 +95,27 @@ const ( OIDCTokenTypeJWT ) +type ClientKey struct { + es_models.ObjectRoot + + ApplicationID string + ClientID string + KeyID string + Type key_model.AuthNKeyType + ExpirationDate time.Time + PrivateKey []byte +} + +type Token struct { + es_models.ObjectRoot + + TokenID string + ClientID string + Audience []string + Expiration time.Time + Scopes []string +} + func (c *OIDCConfig) IsValid() bool { grantTypes := c.getRequiredGrantTypes() for _, grantType := range grantTypes { @@ -115,10 +139,10 @@ func (c *OIDCConfig) GenerateNewClientID(idGenerator id.Generator, project *Proj } func (c *OIDCConfig) GenerateClientSecretIfNeeded(generator crypto.Generator) (string, error) { - if c.AuthMethodType == OIDCAuthMethodTypeNone { - return "", nil + if c.AuthMethodType == OIDCAuthMethodTypeBasic || c.AuthMethodType == OIDCAuthMethodTypePost { + return c.GenerateNewClientSecret(generator) } - return c.GenerateNewClientSecret(generator) + return "", nil } func (c *OIDCConfig) GenerateNewClientSecret(generator crypto.Generator) (string, error) { diff --git a/internal/project/repository/eventsourcing/eventstore.go b/internal/project/repository/eventsourcing/eventstore.go index 81d60dbcf8..e0b41d4591 100644 --- a/internal/project/repository/eventsourcing/eventstore.go +++ b/internal/project/repository/eventsourcing/eventstore.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "strings" + "time" "github.com/caos/logging" "github.com/golang/protobuf/ptypes" @@ -18,6 +19,7 @@ import ( es_models "github.com/caos/zitadel/internal/eventstore/models" es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" "github.com/caos/zitadel/internal/id" + key_model "github.com/caos/zitadel/internal/key/model" proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -30,10 +32,11 @@ const ( type ProjectEventstore struct { es_int.Eventstore - projectCache *ProjectCache - passwordAlg crypto.HashAlgorithm - pwGenerator crypto.Generator - idGenerator id.Generator + projectCache *ProjectCache + passwordAlg crypto.HashAlgorithm + pwGenerator crypto.Generator + idGenerator id.Generator + ClientKeySize int } type ProjectConfig struct { @@ -49,11 +52,12 @@ func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*Projec passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost) pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg) return &ProjectEventstore{ - Eventstore: conf.Eventstore, - projectCache: projectCache, - passwordAlg: passwordAlg, - pwGenerator: pwGenerator, - idGenerator: id.SonyFlakeGenerator, + Eventstore: conf.Eventstore, + projectCache: projectCache, + passwordAlg: passwordAlg, + pwGenerator: pwGenerator, + idGenerator: id.SonyFlakeGenerator, + ClientKeySize: int(systemDefaults.SecretGenerators.ClientKeySize), }, nil } @@ -513,15 +517,14 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model if err != nil { return nil, err } - id, err := es.idGenerator.Next() + app.AppID, err = es.idGenerator.Next() if err != nil { return nil, err } - app.AppID = id var stringPw string if app.OIDCConfig != nil { - app.OIDCConfig.AppID = id + app.OIDCConfig.AppID = app.AppID err := app.OIDCConfig.GenerateNewClientID(es.idGenerator, existingProject) if err != nil { return nil, err @@ -531,6 +534,17 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model return nil, err } } + if app.APIConfig != nil { + app.APIConfig.AppID = app.AppID + err := app.APIConfig.GenerateNewClientID(es.idGenerator, existingProject) + if err != nil { + return nil, err + } + stringPw, err = app.APIConfig.GenerateClientSecretIfNeeded(es.pwGenerator) + if err != nil { + return nil, err + } + } repoProject := model.ProjectFromModel(existingProject) repoApp := model.AppFromModel(app) @@ -542,8 +556,13 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { converted := model.AppToModel(a) - converted.OIDCConfig.ClientSecretString = stringPw - converted.OIDCConfig.FillCompliance() + if converted.OIDCConfig != nil { + converted.OIDCConfig.ClientSecretString = stringPw + converted.OIDCConfig.FillCompliance() + } + if converted.APIConfig != nil { + converted.APIConfig.ClientSecretString = stringPw + } return converted, nil } return nil, caos_errs.ThrowInternal(nil, "EVENT-GvPct", "Errors.Internal") @@ -748,6 +767,36 @@ func (es *ProjectEventstore) ChangeOIDCConfig(ctx context.Context, config *proj_ return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal") } +func (es *ProjectEventstore) ChangeAPIConfig(ctx context.Context, config *proj_model.APIConfig) (*proj_model.APIConfig, error) { + if config == nil || !config.IsValid() { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-SDg54", "Errors.Project.APIConfigInvalid") + } + existingProject, err := es.ProjectByID(ctx, config.AggregateID) + if err != nil { + return nil, err + } + var app *proj_model.Application + if _, app = existingProject.GetApp(config.AppID); app == nil { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Rgu63", "Errors.Project.AppNotExisting") + } + if app.Type != proj_model.AppTypeAPI { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-RHj63", "Errors.Project.AppIsNotAPI") + } + repoProject := model.ProjectFromModel(existingProject) + repoConfig := model.APIConfigFromModel(config) + + projectAggregate := APIConfigChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoConfig) + err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } + es.projectCache.cacheProject(repoProject) + if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { + return model.APIConfigToModel(a.APIConfig), nil + } + return nil, caos_errs.ThrowInternal(nil, "EVENT-aebn5", "Errors.Internal") +} + func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.OIDCConfig, error) { if appID == "" { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-7ue34", "Errors.Project.OIDCConfigInvalid") @@ -763,8 +812,8 @@ func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, project if app.Type != proj_model.AppTypeOIDC { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dile4", "Errors.Project.AppIsNotOIDC") } - if app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypeNone { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-GDrg2", "Errors.Project.OIDCAuthMethodNoneSecret") + if app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypeNone || app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypePrivateKeyJWT { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-GDrg2", "Errors.Project.OIDCAuthMethodNoSecret") } repoProject := model.ProjectFromModel(existingProject) @@ -789,6 +838,47 @@ func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, project return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal") } +func (es *ProjectEventstore) ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.APIConfig, error) { + if appID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdfb3", "Errors.Project.APIConfigInvalid") + } + existingProject, err := es.ProjectByID(ctx, projectID) + if err != nil { + return nil, err + } + var app *proj_model.Application + if _, app = existingProject.GetApp(appID); app == nil { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADbg3", "Errors.Project.AppNotExisting") + } + if app.Type != proj_model.AppTypeAPI { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Ntwqw", "Errors.Project.AppIsNotAPI") + } + if app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypeBasic { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-HW4tw", "Errors.Project.APIAuthMethodNoSecret") + } + repoProject := model.ProjectFromModel(existingProject) + + stringPw, err := app.APIConfig.GenerateNewClientSecret(es.pwGenerator) + if err != nil { + return nil, err + } + + projectAggregate := APIConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, app.APIConfig.ClientSecret) + err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } + es.projectCache.cacheProject(repoProject) + + if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { + config := model.APIConfigToModel(a.APIConfig) + config.ClientSecretString = stringPw + return config, nil + } + + return nil, caos_errs.ThrowInternal(nil, "EVENT-HBfju", "Errors.Internal") +} + func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, projectID, appID string, secret string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -831,6 +921,81 @@ func (es *ProjectEventstore) setOIDCClientSecretCheckResult(ctx context.Context, return nil } +func (es *ProjectEventstore) AddClientKey(ctx context.Context, key *proj_model.ClientKey) (*proj_model.ClientKey, error) { + existingProject, err := es.ProjectByID(ctx, key.AggregateID) + if err != nil { + return nil, err + } + var app *proj_model.Application + if _, app = existingProject.GetApp(key.ApplicationID); app == nil { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dbf32", "Errors.Project.AppNoExisting") + } + if (app.OIDCConfig == nil || app.OIDCConfig != nil && app.OIDCConfig.AuthMethodType != proj_model.OIDCAuthMethodTypePrivateKeyJWT) && + (app.APIConfig == nil || app.APIConfig != nil && app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypePrivateKeyJWT) { + return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dff54", "Errors.Project.AuthMethodNoPrivateKeyJWT") + } + if app.OIDCConfig != nil { + key.ClientID = app.OIDCConfig.ClientID + } + if app.APIConfig != nil { + key.ClientID = app.APIConfig.ClientID + } + key.KeyID, err = es.idGenerator.Next() + if err != nil { + return nil, err + } + if key.ExpirationDate.IsZero() { + key.ExpirationDate, err = key_model.DefaultExpiration() + if err != nil { + logging.Log("EVENT-Adgf2").WithError(err).Warn("unable to set default date") + return nil, errors.ThrowInternal(err, "EVENT-j68fg", "Errors.Internal") + } + } + if key.ExpirationDate.Before(time.Now()) { + return nil, errors.ThrowInvalidArgument(nil, "EVENT-C6YV5", "Errors.MachineKey.ExpireBeforeNow") + } + + repoProject := model.ProjectFromModel(existingProject) + repoKey := model.ClientKeyFromModel(key) + err = repoKey.GenerateClientKeyPair(es.ClientKeySize) + if err != nil { + return nil, err + } + agg := OIDCApplicationKeyAddedAggregate(es.AggregateCreator(), repoProject, repoKey) + err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg) + if err != nil { + return nil, err + } + es.projectCache.cacheProject(repoProject) + + return model.ClientKeyToModel(repoKey), nil +} + +func (es *ProjectEventstore) RemoveApplicationKey(ctx context.Context, projectID, applicationID, keyID string) error { + existingProject, err := es.ProjectByID(ctx, projectID) + if err != nil { + return err + } + var app *proj_model.Application + if _, app = existingProject.GetApp(applicationID); app == nil { + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADfzz", "Errors.Project.AppNotExisting") + } + if app.Type != proj_model.AppTypeOIDC { + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADffh", "Errors.Project.AppIsNotOIDC") + } + if _, key := app.GetKey(keyID); key == nil { + return caos_errs.ThrowPreconditionFailed(nil, "EVENT-D2Sff", "Errors.Project.AppKeyNotExisting") + } + repoProject := model.ProjectFromModel(existingProject) + agg := OIDCApplicationKeyRemovedAggregate(es.AggregateCreator(), repoProject, keyID) + err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg) + if err != nil { + return err + } + es.projectCache.cacheProject(repoProject) + return nil +} + func (es *ProjectEventstore) ProjectGrantByIDs(ctx context.Context, projectID, grantID string) (*proj_model.ProjectGrant, error) { if grantID == "" { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-e8die", "Errors.Project.IDMissing") diff --git a/internal/project/repository/eventsourcing/model/api_config.go b/internal/project/repository/eventsourcing/model/api_config.go new file mode 100644 index 0000000000..493b817c14 --- /dev/null +++ b/internal/project/repository/eventsourcing/model/api_config.go @@ -0,0 +1,87 @@ +package model + +import ( + "encoding/json" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/crypto" + es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/project/model" +) + +type APIConfig struct { + es_models.ObjectRoot + AppID string `json:"appId"` + ClientID string `json:"clientId,omitempty"` + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + AuthMethodType int32 `json:"authMethodType,omitempty"` + ClientKeys []*ClientKey `json:"-"` +} + +func (c *APIConfig) Changes(changed *APIConfig) map[string]interface{} { + changes := make(map[string]interface{}, 1) + changes["appId"] = c.AppID + if c.AuthMethodType != changed.AuthMethodType { + changes["authMethodType"] = changed.AuthMethodType + } + return changes +} + +func APIConfigFromModel(config *model.APIConfig) *APIConfig { + return &APIConfig{ + ObjectRoot: config.ObjectRoot, + AppID: config.AppID, + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + AuthMethodType: int32(config.AuthMethodType), + } +} + +func APIConfigToModel(config *APIConfig) *model.APIConfig { + oidcConfig := &model.APIConfig{ + ObjectRoot: config.ObjectRoot, + AppID: config.AppID, + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + AuthMethodType: model.APIAuthMethodType(config.AuthMethodType), + ClientKeys: ClientKeysToModel(config.ClientKeys), + } + return oidcConfig +} + +func (p *Project) appendAddAPIConfigEvent(event *es_models.Event) error { + config := new(APIConfig) + err := config.setData(event) + if err != nil { + return err + } + config.ObjectRoot.CreationDate = event.CreationDate + if i, a := GetApplication(p.Applications, config.AppID); a != nil { + p.Applications[i].Type = int32(model.AppTypeAPI) + p.Applications[i].APIConfig = config + } + return nil +} + +func (p *Project) appendChangeAPIConfigEvent(event *es_models.Event) error { + config := new(APIConfig) + err := config.setData(event) + if err != nil { + return err + } + + if i, a := GetApplication(p.Applications, config.AppID); a != nil { + return p.Applications[i].APIConfig.setData(event) + } + return nil +} + +func (o *APIConfig) setData(event *es_models.Event) error { + o.ObjectRoot.AppendEvent(event) + if err := json.Unmarshal(event.Data, o); err != nil { + logging.Log("EVEN-d8e3s").WithError(err).Error("could not unmarshal event data") + return err + } + return nil +} diff --git a/internal/project/repository/eventsourcing/model/application.go b/internal/project/repository/eventsourcing/model/application.go index 72da706244..a08228c5da 100644 --- a/internal/project/repository/eventsourcing/model/application.go +++ b/internal/project/repository/eventsourcing/model/application.go @@ -2,7 +2,9 @@ package model import ( "encoding/json" + "github.com/caos/logging" + es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/project/model" ) @@ -14,6 +16,7 @@ type Application struct { Name string `json:"name,omitempty"` Type int32 `json:"appType,omitempty"` OIDCConfig *OIDCConfig `json:"-"` + APIConfig *APIConfig `json:"-"` } type ApplicationID struct { @@ -66,6 +69,9 @@ func AppFromModel(app *model.Application) *Application { if app.OIDCConfig != nil { converted.OIDCConfig = OIDCConfigFromModel(app.OIDCConfig) } + if app.APIConfig != nil { + converted.APIConfig = APIConfigFromModel(app.APIConfig) + } return converted } @@ -80,6 +86,9 @@ func AppToModel(app *Application) *model.Application { if app.OIDCConfig != nil { converted.OIDCConfig = OIDCConfigToModel(app.OIDCConfig) } + if app.APIConfig != nil { + converted.APIConfig = APIConfigToModel(app.APIConfig) + } return converted } @@ -101,7 +110,7 @@ func (p *Project) appendChangeAppEvent(event *es_models.Event) error { return err } if i, a := GetApplication(p.Applications, app.AppID); a != nil { - p.Applications[i].setData(event) + return p.Applications[i].setData(event) } return nil } diff --git a/internal/project/repository/eventsourcing/model/oidc_config.go b/internal/project/repository/eventsourcing/model/oidc_config.go index c489742936..635d78d2fd 100644 --- a/internal/project/repository/eventsourcing/model/oidc_config.go +++ b/internal/project/repository/eventsourcing/model/oidc_config.go @@ -8,7 +8,9 @@ import ( "github.com/caos/logging" "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/zitadel/internal/project/model" ) @@ -30,6 +32,7 @@ type OIDCConfig struct { IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"` IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"` ClockSkew time.Duration `json:"clockSkew,omitempty"` + ClientKeys []*ClientKey `json:"-"` } func (c *OIDCConfig) Changes(changed *OIDCConfig) map[string]interface{} { @@ -134,6 +137,7 @@ func OIDCConfigToModel(config *OIDCConfig) *model.OIDCConfig { IDTokenRoleAssertion: config.IDTokenRoleAssertion, IDTokenUserinfoAssertion: config.IDTokenUserinfoAssertion, ClockSkew: config.ClockSkew, + ClientKeys: ClientKeysToModel(config.ClientKeys), } oidcConfig.FillCompliance() return oidcConfig @@ -161,7 +165,50 @@ func (p *Project) appendChangeOIDCConfigEvent(event *es_models.Event) error { } if i, a := GetApplication(p.Applications, config.AppID); a != nil { - p.Applications[i].OIDCConfig.setData(event) + return p.Applications[i].OIDCConfig.setData(event) + } + return nil +} + +func (p *Project) appendAddClientKeyEvent(event *es_models.Event) error { + key := new(ClientKey) + err := key.SetData(event) + if err != nil { + return err + } + + if i, a := GetApplication(p.Applications, key.ApplicationID); a != nil { + if a.OIDCConfig != nil { + p.Applications[i].OIDCConfig.ClientKeys = append(p.Applications[i].OIDCConfig.ClientKeys, key) + } + if a.APIConfig != nil { + p.Applications[i].APIConfig.ClientKeys = append(p.Applications[i].APIConfig.ClientKeys, key) + } + } + return nil +} + +func (p *Project) appendRemoveClientKeyEvent(event *es_models.Event) error { + key := new(ClientKey) + err := key.SetData(event) + if err != nil { + return err + } + if i, a := GetApplication(p.Applications, key.ApplicationID); a != nil { + if a.OIDCConfig != nil { + if j, k := GetClientKey(p.Applications[i].OIDCConfig.ClientKeys, key.KeyID); k != nil { + p.Applications[i].OIDCConfig.ClientKeys[j] = p.Applications[i].OIDCConfig.ClientKeys[len(p.Applications[i].OIDCConfig.ClientKeys)-1] + p.Applications[i].OIDCConfig.ClientKeys[len(p.Applications[i].OIDCConfig.ClientKeys)-1] = nil + p.Applications[i].OIDCConfig.ClientKeys = p.Applications[i].OIDCConfig.ClientKeys[:len(p.Applications[i].OIDCConfig.ClientKeys)-1] + } + } + if a.APIConfig != nil { + if j, k := GetClientKey(p.Applications[i].APIConfig.ClientKeys, key.KeyID); k != nil { + p.Applications[i].APIConfig.ClientKeys[j] = p.Applications[i].APIConfig.ClientKeys[len(p.Applications[i].APIConfig.ClientKeys)-1] + p.Applications[i].APIConfig.ClientKeys[len(p.Applications[i].APIConfig.ClientKeys)-1] = nil + p.Applications[i].APIConfig.ClientKeys = p.Applications[i].APIConfig.ClientKeys[:len(p.Applications[i].APIConfig.ClientKeys)-1] + } + } } return nil } @@ -174,3 +221,100 @@ func (o *OIDCConfig) setData(event *es_models.Event) error { } return nil } + +func GetClientKey(keys []*ClientKey, id string) (int, *ClientKey) { + for i, k := range keys { + if k.KeyID == id { + return i, k + } + } + return -1, nil +} + +type ClientKey struct { + es_models.ObjectRoot `json:"-"` + ApplicationID string `json:"applicationId,omitempty"` + ClientID string `json:"clientId,omitempty"` + KeyID string `json:"keyId,omitempty"` + Type int32 `json:"type,omitempty"` + ExpirationDate time.Time `json:"expirationDate,omitempty"` + PublicKey []byte `json:"publicKey,omitempty"` + privateKey []byte +} + +func (key *ClientKey) SetData(event *es_models.Event) error { + key.ObjectRoot.AppendEvent(event) + if err := json.Unmarshal(event.Data, key); err != nil { + logging.Log("EVEN-SADdg").WithError(err).Error("could not unmarshal event data") + return err + } + return nil +} + +func (key *ClientKey) AppendEvents(events ...*es_models.Event) error { + for _, event := range events { + err := key.AppendEvent(event) + if err != nil { + return err + } + } + return nil +} + +func (key *ClientKey) AppendEvent(event *es_models.Event) (err error) { + key.ObjectRoot.AppendEvent(event) + switch event.Type { + case ClientKeyAdded: + err = json.Unmarshal(event.Data, key) + if err != nil { + return errors.ThrowInternal(err, "MODEL-Fetg3", "Errors.Internal") + } + case ClientKeyRemoved: + key.ExpirationDate = event.CreationDate + } + return err +} + +func ClientKeyFromModel(key *model.ClientKey) *ClientKey { + return &ClientKey{ + ObjectRoot: key.ObjectRoot, + ExpirationDate: key.ExpirationDate, + ApplicationID: key.ApplicationID, + ClientID: key.ClientID, + KeyID: key.KeyID, + Type: int32(key.Type), + } +} + +func ClientKeysToModel(keys []*ClientKey) []*model.ClientKey { + clientKeys := make([]*model.ClientKey, len(keys)) + for i, key := range keys { + clientKeys[i] = ClientKeyToModel(key) + } + return clientKeys +} + +func ClientKeyToModel(key *ClientKey) *model.ClientKey { + return &model.ClientKey{ + ObjectRoot: key.ObjectRoot, + ExpirationDate: key.ExpirationDate, + ApplicationID: key.ApplicationID, + ClientID: key.ClientID, + KeyID: key.KeyID, + PrivateKey: key.privateKey, + Type: key_model.AuthNKeyType(key.Type), + } +} + +func (key *ClientKey) GenerateClientKeyPair(keySize int) error { + privateKey, publicKey, err := crypto.GenerateKeyPair(keySize) + if err != nil { + return err + } + key.PublicKey, err = crypto.PublicKeyToBytes(publicKey) + if err != nil { + return err + } + key.privateKey = crypto.PrivateKeyToBytes(privateKey) + return nil +} diff --git a/internal/project/repository/eventsourcing/model/project.go b/internal/project/repository/eventsourcing/model/project.go index c0f8d8e35a..bdf133c300 100644 --- a/internal/project/repository/eventsourcing/model/project.go +++ b/internal/project/repository/eventsourcing/model/project.go @@ -139,6 +139,14 @@ func (p *Project) AppendEvent(event *es_models.Event) error { return p.appendAddOIDCConfigEvent(event) case OIDCConfigChanged, OIDCConfigSecretChanged: return p.appendChangeOIDCConfigEvent(event) + case APIConfigAdded: + return p.appendAddAPIConfigEvent(event) + case APIConfigChanged, APIConfigSecretChanged: + return p.appendChangeAPIConfigEvent(event) + case ClientKeyAdded: + return p.appendAddClientKeyEvent(event) + case ClientKeyRemoved: + return p.appendRemoveClientKeyEvent(event) case ProjectGrantAdded: return p.appendAddGrantEvent(event) case ProjectGrantChanged, ProjectGrantCascadeChanged: diff --git a/internal/project/repository/eventsourcing/model/types.go b/internal/project/repository/eventsourcing/model/types.go index 2bf2f3845a..581627142d 100644 --- a/internal/project/repository/eventsourcing/model/types.go +++ b/internal/project/repository/eventsourcing/model/types.go @@ -41,4 +41,11 @@ const ( OIDCConfigSecretChanged models.EventType = "project.application.config.oidc.secret.changed" OIDCClientSecretCheckSucceeded models.EventType = "project.application.oidc.secret.check.succeeded" OIDCClientSecretCheckFailed models.EventType = "project.application.oidc.secret.check.failed" + + APIConfigAdded models.EventType = "project.application.config.api.added" + APIConfigChanged models.EventType = "project.application.config.api.changed" + APIConfigSecretChanged models.EventType = "project.application.config.api.secret.changed" + + ClientKeyAdded models.EventType = "project.application.oidc.key.added" + ClientKeyRemoved models.EventType = "project.application.oidc.key.removed" ) diff --git a/internal/project/repository/eventsourcing/project.go b/internal/project/repository/eventsourcing/project.go index 5d41db2e4a..363231612b 100644 --- a/internal/project/repository/eventsourcing/project.go +++ b/internal/project/repository/eventsourcing/project.go @@ -36,6 +36,14 @@ func ProjectAggregate(ctx context.Context, aggCreator *es_models.AggregateCreato return aggCreator.NewAggregate(ctx, project.AggregateID, model.ProjectAggregate, model.ProjectVersion, project.Sequence) } +func ProjectAggregateOverwriteContext(ctx context.Context, aggCreator *es_models.AggregateCreator, project *model.Project, resourceOwnerID string, userID string) (*es_models.Aggregate, error) { + if project == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ADv2r", "Errors.Internal") + } + + return aggCreator.NewAggregate(ctx, project.AggregateID, model.ProjectAggregate, model.ProjectVersion, project.Sequence, es_models.OverwriteResourceOwner(resourceOwnerID), es_models.OverwriteEditorUser(userID)) +} + func ProjectCreateAggregate(aggCreator *es_models.AggregateCreator, project *model.Project, member *model.ProjectMember) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { if project == nil || member == nil { @@ -229,6 +237,9 @@ func ApplicationAddedAggregate(aggCreator *es_models.AggregateCreator, existingP if app.OIDCConfig != nil { agg.AppendEvent(model.OIDCConfigAdded, app.OIDCConfig) } + if app.APIConfig != nil { + agg.AppendEvent(model.APIConfigAdded, app.APIConfig) + } return agg, nil } } @@ -322,6 +333,29 @@ func OIDCConfigChangedAggregate(aggCreator *es_models.AggregateCreator, existing } } +func APIConfigChangedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, config *model.APIConfig) func(ctx context.Context) (*es_models.Aggregate, error) { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if config == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-slf32", "Errors.Internal") + } + agg, err := ProjectAggregate(ctx, aggCreator, existingProject) + if err != nil { + return nil, err + } + var changes map[string]interface{} + for _, a := range existingProject.Applications { + if a.AppID == config.AppID { + if a.APIConfig != nil { + changes = a.APIConfig.Changes(config) + } + } + } + agg.AppendEvent(model.APIConfigChanged, changes) + + return agg, nil + } +} + func OIDCConfigSecretChangedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, appID string, secret *crypto.CryptoValue) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { agg, err := ProjectAggregate(ctx, aggCreator, existingProject) @@ -338,6 +372,22 @@ func OIDCConfigSecretChangedAggregate(aggCreator *es_models.AggregateCreator, ex } } +func APIConfigSecretChangedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, appID string, secret *crypto.CryptoValue) func(ctx context.Context) (*es_models.Aggregate, error) { + return func(ctx context.Context) (*es_models.Aggregate, error) { + agg, err := ProjectAggregate(ctx, aggCreator, existingProject) + if err != nil { + return nil, err + } + changes := make(map[string]interface{}, 2) + changes["appId"] = appID + changes["clientSecret"] = secret + + agg.AppendEvent(model.APIConfigSecretChanged, changes) + + return agg, nil + } +} + func OIDCClientSecretCheckSucceededAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, appID string) es_sdk.AggregateFunc { return func(ctx context.Context) (*es_models.Aggregate, error) { agg, err := ProjectAggregate(ctx, aggCreator, existingProject) @@ -368,6 +418,33 @@ func OIDCClientSecretCheckFailedAggregate(aggCreator *es_models.AggregateCreator } } +func OIDCApplicationKeyAddedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, key *model.ClientKey) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + agg, err := ProjectAggregate(ctx, aggCreator, existingProject) + if err != nil { + return nil, err + } + agg.AppendEvent(model.ClientKeyAdded, key) + + return agg, nil + } +} + +func OIDCApplicationKeyRemovedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, keyID string) es_sdk.AggregateFunc { + return func(ctx context.Context) (*es_models.Aggregate, error) { + agg, err := ProjectAggregate(ctx, aggCreator, existingProject) + if err != nil { + return nil, err + } + changes := make(map[string]interface{}, 1) + changes["keyId"] = keyID + + agg.AppendEvent(model.ClientKeyRemoved, changes) + + return agg, nil + } +} + func ProjectGrantAddedAggregate(aggCreator *es_models.AggregateCreator, project *model.Project, grant *model.ProjectGrant) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { if grant == nil { diff --git a/internal/project/repository/view/model/application.go b/internal/project/repository/view/model/application.go index 9cfa7d2684..fe682d8f02 100644 --- a/internal/project/repository/view/model/application.go +++ b/internal/project/repository/view/model/application.go @@ -116,17 +116,25 @@ func (a *ApplicationView) AppendEventIfMyApp(event *models.Event) (err error) { switch event.Type { case es_model.ApplicationAdded: err = view.SetData(event) + if err != nil { + return err + } case es_model.ApplicationChanged, es_model.OIDCConfigAdded, es_model.OIDCConfigChanged, + es_model.APIConfigAdded, + es_model.APIConfigChanged, es_model.ApplicationDeactivated, es_model.ApplicationReactivated: - err := view.SetData(event) + err = view.SetData(event) if err != nil { return err } case es_model.ApplicationRemoved: - return view.SetData(event) + err = view.SetData(event) + if err != nil { + return err + } case es_model.ProjectChanged: return a.AppendEvent(event) case es_model.ProjectRemoved: @@ -156,14 +164,20 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) { } a.setCompliance() return a.setOriginAllowList() - case es_model.OIDCConfigChanged, - es_model.ApplicationChanged: + case es_model.APIConfigAdded: + a.IsOIDC = false + return a.SetData(event) + case es_model.ApplicationChanged: + return a.SetData(event) + case es_model.OIDCConfigChanged: err = a.SetData(event) if err != nil { return err } a.setCompliance() return a.setOriginAllowList() + case es_model.APIConfigChanged: + return a.SetData(event) case es_model.ProjectChanged: return a.SetData(event) case es_model.ApplicationDeactivated: diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 46a32e4fee..a5740c9f1c 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -156,8 +156,12 @@ Errors: AppInvalid: Applikation ist ungültig AppNotExisting: Applikation exisitert nicht OIDCConfigInvalid: OIDC Konfiguration ist ungültig + APIConfigInvalid: API Konfiguration ist ungültig AppIsNotOIDC: Applikation ist nicht vom Typ OIDC - OIDCAuthMethodNoneSecret: OIDC Auth Method None benötigt kein Secret + AppIsNotAPI: Applikation ist nicht vom Typ API + OIDCAuthMethodNoSecret: Gewählte OIDC Auth Method benötigt kein Secret + APIAuthMethodNoSecret: Gewählte API Auth Method benötigt kein Secret + AuthMethodNoPrivateKeyJWT: Gewählte Auth Method benötigt keinen Key RequiredFieldsMissing: Benötigte Felder fehlen GrantNotFound: Grant konnte nicht gefunden werden GrantInvalid: Projekt Grant ist ungültig diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index f1f9c5781f..69a49383e1 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -156,8 +156,12 @@ Errors: AppInvalid: Application invalid AppNotExisting: Application doesn't exist OIDCConfigInvalid: OIDC configuration is invalid - AppIsNotOIDC: Application is not type oidc - OIDCAuthMethodNoneSecret: OIDC Auth Method None does not require a secret + APIConfigInvalid: OIDC configuration is invalid + AppIsNotOIDC: Application is not type OIDC + AppIsNotAPI: Application is not type API + OIDCAuthMethodNoSecret: Chosen OIDC Auth Method does not require a secret + APIAuthMethodNoSecret: Chosen API Auth Method does not require a secret + AuthMethodNoPrivateKeyJWT: Chosen Auth Method does not require a key RequiredFieldsMissing: Some required fields are missing GrantNotFound: Grant not found GrantInvalid: Project grant is invalid diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 1d7455e91f..c0ac8ce6eb 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -1,8 +1,8 @@ package handler import ( + "github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/pkg/oidc" - "github.com/caos/oidc/pkg/rp" http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/crypto" @@ -116,13 +116,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens) } -func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelayingParty { +func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelyingParty { oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.IDPConfigAesCrypto) if err != nil { l.renderError(w, r, authReq, err) return nil } - provider, err := rp.NewRelayingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second))) + provider, err := rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second))) if err != nil { l.renderError(w, r, authReq, err) return nil diff --git a/internal/ui/login/handler/external_register_handler.go b/internal/ui/login/handler/external_register_handler.go index 3d2834bb47..20b8e3230e 100644 --- a/internal/ui/login/handler/external_register_handler.go +++ b/internal/ui/login/handler/external_register_handler.go @@ -4,8 +4,8 @@ import ( "net/http" "strings" + "github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/pkg/oidc" - "github.com/caos/oidc/pkg/rp" "golang.org/x/text/language" http_mw "github.com/caos/zitadel/internal/api/http/middleware" diff --git a/internal/user/model/machine_key.go b/internal/user/model/machine_key.go deleted file mode 100644 index c38aa1b630..0000000000 --- a/internal/user/model/machine_key.go +++ /dev/null @@ -1,54 +0,0 @@ -package model - -import ( - "time" - - "github.com/caos/zitadel/internal/model" -) - -type MachineKeyView struct { - ID string - UserID string - Type MachineKeyType - Sequence uint64 - CreationDate time.Time - ExpirationDate time.Time - PublicKey []byte -} - -type MachineKeySearchRequest struct { - Offset uint64 - Limit uint64 - SortingColumn MachineKeySearchKey - Asc bool - Queries []*MachineKeySearchQuery -} - -type MachineKeySearchKey int32 - -const ( - MachineKeyKeyUnspecified MachineKeySearchKey = iota - MachineKeyKeyID - MachineKeyKeyUserID -) - -type MachineKeySearchQuery struct { - Key MachineKeySearchKey - Method model.SearchMethod - Value interface{} -} - -type MachineKeySearchResponse struct { - Offset uint64 - Limit uint64 - TotalResult uint64 - Result []*MachineKeyView - Sequence uint64 - Timestamp time.Time -} - -func (r *MachineKeySearchRequest) EnsureLimit(limit uint64) { - if r.Limit == 0 || r.Limit > limit { - r.Limit = limit - } -} diff --git a/internal/user/model/user_machine.go b/internal/user/model/user_machine.go index d635f81506..1f1c06a696 100644 --- a/internal/user/model/user_machine.go +++ b/internal/user/model/user_machine.go @@ -4,6 +4,7 @@ import ( "time" "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" ) type Machine struct { @@ -21,14 +22,7 @@ type MachineKey struct { models.ObjectRoot KeyID string - Type MachineKeyType + Type key_model.AuthNKeyType ExpirationDate time.Time PrivateKey []byte } - -type MachineKeyType int32 - -const ( - MachineKeyTypeNONE = iota - MachineKeyTypeJSON -) diff --git a/internal/user/repository/eventsourcing/eventstore.go b/internal/user/repository/eventsourcing/eventstore.go index 12688f855d..6b5bdc7d57 100644 --- a/internal/user/repository/eventsourcing/eventstore.go +++ b/internal/user/repository/eventsourcing/eventstore.go @@ -20,6 +20,7 @@ import ( es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/id" + key_model "github.com/caos/zitadel/internal/key/model" global_model "github.com/caos/zitadel/internal/model" "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" @@ -27,11 +28,6 @@ import ( webauthn_helper "github.com/caos/zitadel/internal/webauthn" ) -const ( - yearLayout = "2006-01-02" - defaultExpirationDate = "9999-01-01" -) - type UserEventstore struct { es_int.Eventstore userCache *UserCache @@ -1630,7 +1626,7 @@ func (es *UserEventstore) AddMachineKey(ctx context.Context, key *usr_model.Mach } if key.ExpirationDate.IsZero() { - key.ExpirationDate, err = time.Parse(yearLayout, defaultExpirationDate) + key.ExpirationDate, err = key_model.DefaultExpiration() if err != nil { logging.Log("EVENT-vzibi").WithError(err).Warn("unable to set default date") return nil, errors.ThrowInternal(err, "EVENT-j68fg", "Errors.Internal") diff --git a/internal/user/repository/eventsourcing/model/user_machine.go b/internal/user/repository/eventsourcing/model/user_machine.go index f73c75c5f0..a82fad1c1b 100644 --- a/internal/user/repository/eventsourcing/model/user_machine.go +++ b/internal/user/repository/eventsourcing/model/user_machine.go @@ -9,6 +9,7 @@ import ( "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" + key_model "github.com/caos/zitadel/internal/key/model" "github.com/caos/zitadel/internal/user/model" ) @@ -115,7 +116,7 @@ func MachineKeyToModel(machine *MachineKey) *model.MachineKey { ExpirationDate: machine.ExpirationDate, KeyID: machine.KeyID, PrivateKey: machine.privateKey, - Type: model.MachineKeyType(machine.Type), + Type: key_model.AuthNKeyType(machine.Type), } } diff --git a/internal/user/repository/view/machine_key_view.go b/internal/user/repository/view/machine_key_view.go deleted file mode 100644 index 479859eab4..0000000000 --- a/internal/user/repository/view/machine_key_view.go +++ /dev/null @@ -1,77 +0,0 @@ -package view - -import ( - caos_errs "github.com/caos/zitadel/internal/errors" - global_model "github.com/caos/zitadel/internal/model" - usr_model "github.com/caos/zitadel/internal/user/model" - "github.com/caos/zitadel/internal/user/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" - "github.com/jinzhu/gorm" -) - -func MachineKeyByIDs(db *gorm.DB, table, userID, keyID string) (*model.MachineKeyView, error) { - key := new(model.MachineKeyView) - query := repository.PrepareGetByQuery(table, - model.MachineKeySearchQuery{Key: usr_model.MachineKeyKeyUserID, Method: global_model.SearchMethodEquals, Value: userID}, - model.MachineKeySearchQuery{Key: usr_model.MachineKeyKeyID, Method: global_model.SearchMethodEquals, Value: keyID}, - ) - err := query(db, key) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-3Dk9s", "Errors.User.KeyNotFound") - } - return key, err -} - -func SearchMachineKeys(db *gorm.DB, table string, req *usr_model.MachineKeySearchRequest) ([]*model.MachineKeyView, uint64, error) { - members := make([]*model.MachineKeyView, 0) - query := repository.PrepareSearchQuery(table, model.MachineKeySearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries}) - count, err := query(db, &members) - if err != nil { - return nil, 0, err - } - return members, count, nil -} - -func MachineKeysByUserID(db *gorm.DB, table string, userID string) ([]*model.MachineKeyView, error) { - keys := make([]*model.MachineKeyView, 0) - queries := []*usr_model.MachineKeySearchQuery{ - { - Key: usr_model.MachineKeyKeyUserID, - Value: userID, - Method: global_model.SearchMethodEquals, - }, - } - query := repository.PrepareSearchQuery(table, model.MachineKeySearchRequest{Queries: queries}) - _, err := query(db, &keys) - if err != nil { - return nil, err - } - return keys, nil -} - -func MachineKeyByID(db *gorm.DB, table string, keyID string) (*model.MachineKeyView, error) { - key := new(model.MachineKeyView) - query := repository.PrepareGetByQuery(table, - model.MachineKeySearchQuery{Key: usr_model.MachineKeyKeyID, Method: global_model.SearchMethodEquals, Value: keyID}, - ) - err := query(db, key) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-BjN6x", "Errors.User.KeyNotFound") - } - return key, err -} - -func PutMachineKey(db *gorm.DB, table string, role *model.MachineKeyView) error { - save := repository.PrepareSave(table) - return save(db, role) -} - -func DeleteMachineKey(db *gorm.DB, table, keyID string) error { - delete := repository.PrepareDeleteByKey(table, model.MachineKeySearchKey(usr_model.MachineKeyKeyID), keyID) - return delete(db) -} - -func DeleteMachineKeysByUserID(db *gorm.DB, table, userID string) error { - delete := repository.PrepareDeleteByKey(table, model.MachineKeySearchKey(usr_model.MachineKeyKeyUserID), userID) - return delete(db) -} diff --git a/internal/user/repository/view/model/machine_key.go b/internal/user/repository/view/model/machine_key.go deleted file mode 100644 index ddd921d599..0000000000 --- a/internal/user/repository/view/model/machine_key.go +++ /dev/null @@ -1,84 +0,0 @@ -package model - -import ( - "encoding/json" - "time" - - "github.com/caos/logging" - - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/user/model" - es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" -) - -const ( - MachineKeyKeyID = "id" - MachineKeyKeyUserID = "user_id" -) - -type MachineKeyView struct { - ID string `json:"keyId" gorm:"column:id;primary_key"` - UserID string `json:"-" gorm:"column:user_id;primary_key"` - Type int32 `json:"type" gorm:"column:machine_type"` - ExpirationDate time.Time `json:"expirationDate" gorm:"column:expiration_date"` - Sequence uint64 `json:"-" gorm:"column:sequence"` - - CreationDate time.Time `json:"-" gorm:"column:creation_date"` - - PublicKey []byte `json:"publicKey" gorm:"column:public_key"` -} - -func MachineKeyViewFromModel(key *model.MachineKeyView) *MachineKeyView { - return &MachineKeyView{ - ID: key.ID, - UserID: key.UserID, - Type: int32(key.Type), - ExpirationDate: key.ExpirationDate, - Sequence: key.Sequence, - CreationDate: key.CreationDate, - } -} - -func MachineKeyToModel(key *MachineKeyView) *model.MachineKeyView { - return &model.MachineKeyView{ - ID: key.ID, - UserID: key.UserID, - Type: model.MachineKeyType(key.Type), - ExpirationDate: key.ExpirationDate, - Sequence: key.Sequence, - CreationDate: key.CreationDate, - PublicKey: key.PublicKey, - } -} - -func MachineKeysToModel(keys []*MachineKeyView) []*model.MachineKeyView { - result := make([]*model.MachineKeyView, len(keys)) - for i, key := range keys { - result[i] = MachineKeyToModel(key) - } - return result -} - -func (k *MachineKeyView) AppendEvent(event *models.Event) (err error) { - k.Sequence = event.Sequence - switch event.Type { - case es_model.MachineKeyAdded: - k.setRootData(event) - k.CreationDate = event.CreationDate - err = k.SetData(event) - } - return err -} - -func (k *MachineKeyView) setRootData(event *models.Event) { - k.UserID = event.AggregateID -} - -func (r *MachineKeyView) SetData(event *models.Event) error { - if err := json.Unmarshal(event.Data, r); err != nil { - logging.Log("EVEN-Sj90d").WithError(err).Error("could not unmarshal event data") - return caos_errs.ThrowInternal(err, "MODEL-lub6s", "Could not unmarshal data") - } - return nil -} diff --git a/internal/user/repository/view/model/machine_key_query.go b/internal/user/repository/view/model/machine_key_query.go deleted file mode 100644 index c5a876ce53..0000000000 --- a/internal/user/repository/view/model/machine_key_query.go +++ /dev/null @@ -1,61 +0,0 @@ -package model - -import ( - global_model "github.com/caos/zitadel/internal/model" - usr_model "github.com/caos/zitadel/internal/user/model" - "github.com/caos/zitadel/internal/view/repository" -) - -type MachineKeySearchRequest usr_model.MachineKeySearchRequest -type MachineKeySearchQuery usr_model.MachineKeySearchQuery -type MachineKeySearchKey usr_model.MachineKeySearchKey - -func (req MachineKeySearchRequest) GetLimit() uint64 { - return req.Limit -} - -func (req MachineKeySearchRequest) GetOffset() uint64 { - return req.Offset -} - -func (req MachineKeySearchRequest) GetSortingColumn() repository.ColumnKey { - if req.SortingColumn == usr_model.MachineKeyKeyUnspecified { - return nil - } - return MachineKeySearchKey(req.SortingColumn) -} - -func (req MachineKeySearchRequest) GetAsc() bool { - return req.Asc -} - -func (req MachineKeySearchRequest) GetQueries() []repository.SearchQuery { - result := make([]repository.SearchQuery, len(req.Queries)) - for i, q := range req.Queries { - result[i] = MachineKeySearchQuery{Key: q.Key, Value: q.Value, Method: q.Method} - } - return result -} - -func (req MachineKeySearchQuery) GetKey() repository.ColumnKey { - return MachineKeySearchKey(req.Key) -} - -func (req MachineKeySearchQuery) GetMethod() global_model.SearchMethod { - return req.Method -} - -func (req MachineKeySearchQuery) GetValue() interface{} { - return req.Value -} - -func (key MachineKeySearchKey) ToColumnName() string { - switch usr_model.MachineKeySearchKey(key) { - case usr_model.MachineKeyKeyID: - return MachineKeyKeyID - case usr_model.MachineKeyKeyUserID: - return MachineKeyKeyUserID - default: - return "" - } -} diff --git a/internal/user/repository/view/model/token.go b/internal/user/repository/view/model/token.go index eb20cf76c9..24906d2a56 100644 --- a/internal/user/repository/view/model/token.go +++ b/internal/user/repository/view/model/token.go @@ -5,6 +5,7 @@ import ( "time" "github.com/caos/logging" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" diff --git a/migrations/cockroach/V1.30__auth_keys.sql b/migrations/cockroach/V1.30__auth_keys.sql new file mode 100644 index 0000000000..5c091d1b50 --- /dev/null +++ b/migrations/cockroach/V1.30__auth_keys.sql @@ -0,0 +1,92 @@ +CREATE TABLE auth.authn_keys +( + key_id TEXT, + object_id TEXT, + object_type SMALLINT, + auth_identifier TEXT, + + key_type SMALLINT, + sequence BIGINT, + expiration_date TIMESTAMPTZ, + creation_date TIMESTAMPTZ, + public_key BYTES, + state SMALLINT, + + PRIMARY KEY (key_id, object_id, object_type, auth_identifier) +); + +INSERT INTO auth.authn_keys ( + key_id, + object_id, + object_type, + auth_identifier, + key_type, + sequence, + expiration_date, + creation_date, + public_key, + state + ) + SELECT + id, + user_id, + 0, + user_id, + machine_type, + sequence, + expiration_date, + creation_date, + public_key, + 0 + FROM auth.machine_keys; + +CREATE TABLE management.authn_keys +( + key_id TEXT, + object_id TEXT, + object_type SMALLINT, + auth_identifier TEXT, + + key_type SMALLINT, + sequence BIGINT, + expiration_date TIMESTAMPTZ, + creation_date TIMESTAMPTZ, + public_key BYTES, + state SMALLINT, + + PRIMARY KEY (key_id, object_id, object_type, auth_identifier) +); + +INSERT INTO management.authn_keys ( + key_id, + object_id, + object_type, + auth_identifier, + key_type, + sequence, + expiration_date, + creation_date, + public_key, + state +) +SELECT + id, + user_id, + 0, + user_id, + machine_type, + sequence, + expiration_date, + creation_date, + public_key, + 0 +FROM management.machine_keys; + +INSERT INTO auth.current_sequences (view_name, event_timestamp, current_sequence, last_successful_spooler_run, aggregate_type) + SELECT 'auth.authn_keys', event_timestamp, current_sequence, last_successful_spooler_run, aggregate_type FROM auth.current_sequences WHERE view_name = 'auth.machine_keys'; + +INSERT INTO management.current_sequences (view_name, event_timestamp, current_sequence, last_successful_spooler_run, aggregate_type) + SELECT 'management.authn_keys', event_timestamp, current_sequence, last_successful_spooler_run, aggregate_type FROM management.current_sequences WHERE view_name = 'management.machine_keys'; + +ALTER TABLE auth.authn_keys OWNER TO admin; +ALTER TABLE management.authn_keys OWNER TO admin; diff --git a/pkg/grpc/management/proto/management.proto b/pkg/grpc/management/proto/management.proto index d64b3d3cc3..9b9da1edce 100644 --- a/pkg/grpc/management/proto/management.proto +++ b/pkg/grpc/management/proto/management.proto @@ -946,6 +946,18 @@ service ManagementService { }; } + rpc CreateAPIApplication(APIApplicationCreate) returns (Application) { + option (google.api.http) = { + post: "/projects/{project_id}/applications/api" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.write" + check_field_name: "ProjectId" + }; + } + rpc UpdateApplication(ApplicationUpdate) returns (Application) { option (google.api.http) = { put: "/projects/{project_id}/applications/{id}" @@ -1017,6 +1029,76 @@ service ManagementService { }; } + rpc UpdateApplicationAPIConfig(APIConfigUpdate) returns (APIConfig) { + option (google.api.http) = { + put: "/projects/{project_id}/applications/{application_id}/apiconfig" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.write" + check_field_name: "ProjectId" + }; + } + + rpc RegenerateAPIClientSecret(ApplicationID) returns (ClientSecret) { + option (google.api.http) = { + put: "/projects/{project_id}/applications/{id}/apiconfig/_changeclientsecret" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.write" + check_field_name: "ProjectId" + }; + } + + rpc AddClientKey(AddClientKeyRequest) returns (AddClientKeyResponse){ + option (google.api.http) = { + post: "/projects/{project_id}/applications/{application_id}/keys" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.write" + check_field_name: "ProjectId" + }; + } + + rpc DeleteClientKey(ClientKeyIDRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/projects/{project_id}/applications/{application_id}/keys/{key_id}" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.write" + check_field_name: "ProjectId" + }; + } + + rpc SearchClientKeys(ClientKeySearchRequest) returns (ClientKeySearchResponse) { + option (google.api.http) = { + post: "/projects/{project_id}/applications/{application_id}/keys/_search" + body: "*" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.read" + check_field_name: "ProjectId" + }; + } + + rpc GetClientKey(ClientKeyIDRequest) returns (ClientKeyView) { + option (google.api.http) = { + get: "/projects/{project_id}/applications/{application_id}/keys/{key_id}" + }; + + option (caos.zitadel.utils.v1.auth_option) = { + permission: "project.app.read" + check_field_name: "ProjectId" + }; + } + rpc SearchProjectGrants(ProjectGrantSearchRequest) returns (ProjectGrantSearchResponse) { option (google.api.http) = { post: "/projects/{project_id}/grants/_search" @@ -1674,7 +1756,7 @@ service ManagementService { }; } - + rpc GetMailTexts(google.protobuf.Empty) returns (MailTextsView) { option (google.api.http) = { get: "/orgs/me/policies/mailtexts" @@ -1726,7 +1808,7 @@ service ManagementService { permission: "policy.delete" }; } - + } message ZitadelDocs { @@ -2640,6 +2722,7 @@ message Application { string name = 5; oneof app_config { OIDCConfig oidc_config = 8; + APIConfig api_config = 10; } uint64 sequence = 9; } @@ -2688,6 +2771,18 @@ message OIDCApplicationCreate { google.protobuf.Duration clock_skew = 15 [(validate.rules).duration = {gte: {}, lte: {seconds: 5}}]; } +message APIApplicationCreate { + string project_id = 1 [(validate.rules).string = {min_len: 1}]; + string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; + APIAuthMethodType auth_method_type = 3; +} + +message APIConfig { + string client_id = 1; + string client_secret = 2; + APIAuthMethodType auth_method_type = 3; +} + enum OIDCVersion { OIDCV1_0 = 0; } @@ -2714,6 +2809,12 @@ message OIDCConfigUpdate { google.protobuf.Duration clock_skew = 14 [(validate.rules).duration = {gte: {}, lte: {seconds: 5}}]; } +message APIConfigUpdate { + string project_id = 1 [(validate.rules).string = {min_len: 1}]; + string application_id = 2 [(validate.rules).string = {min_len: 1}]; + APIAuthMethodType auth_method_type = 7; +} + enum OIDCResponseType { OIDCRESPONSETYPE_CODE = 0; OIDCRESPONSETYPE_ID_TOKEN = 1; @@ -2736,6 +2837,12 @@ enum OIDCAuthMethodType { OIDCAUTHMETHODTYPE_BASIC = 0; OIDCAUTHMETHODTYPE_POST = 1; OIDCAUTHMETHODTYPE_NONE = 2; + OIDCAUTHMETHODTYPE_PRIVATE_KEY_JWT = 3; +} + +enum APIAuthMethodType { + APIAUTHMETHODTYPE_BASIC = 0; + APIAUTHMETHODTYPE_PRIVATE_KEY_JWT = 1; } message ClientSecret { @@ -2782,6 +2889,60 @@ enum ApplicationSearchKey { APPLICATIONSEARCHKEY_APP_NAME = 1; } +message AddClientKeyRequest { + string project_id = 1 [(validate.rules).string.min_len = 1]; + string application_id = 2 [(validate.rules).string.min_len = 1]; + AuthNKeyType type = 3 [(validate.rules).enum = {not_in: [0]}]; + google.protobuf.Timestamp expiration_date = 4; +} + +message AddClientKeyResponse { + string id = 1; + google.protobuf.Timestamp creation_date = 2; + uint64 sequence = 3; + + AuthNKeyType type = 4; + google.protobuf.Timestamp expiration_date = 5; + bytes key_details = 6; +} + +message ClientKeyIDRequest { + string project_id = 1 [(validate.rules).string.min_len = 1]; + string application_id = 2 [(validate.rules).string.min_len = 1]; + string key_id = 3 [(validate.rules).string.min_len = 1]; +} + +message ClientKeyView { + string id = 1; + AuthNKeyType type = 2; + uint64 sequence = 3; + + google.protobuf.Timestamp creation_date = 4; + google.protobuf.Timestamp expiration_date = 5; +} + +enum AuthNKeyType { + AUTHNKEY_UNSPECIFIED = 0; + AUTHNKEY_JSON = 1; +} + +message ClientKeySearchRequest { + uint64 offset = 1; + uint64 limit = 2; + bool asc = 3; + string project_id = 4 [(validate.rules).string.min_len = 1]; + string application_id = 5 [(validate.rules).string.min_len = 1]; +} + +message ClientKeySearchResponse { + uint64 offset = 1; + uint64 limit = 2; + uint64 total_result = 3; + repeated ClientKeyView result = 4; + uint64 processed_sequence = 5; + google.protobuf.Timestamp view_timestamp = 6; +} + message ProjectGrant { string id = 1; string project_id = 2; @@ -3433,65 +3594,65 @@ message PasswordLockoutPolicyView { google.protobuf.Timestamp change_date = 6; } message MailTemplate { - bytes template = 1; - google.protobuf.Timestamp creation_date = 2; - google.protobuf.Timestamp change_date = 3; + bytes template = 1; + google.protobuf.Timestamp creation_date = 2; + google.protobuf.Timestamp change_date = 3; } message MailTemplateUpdate { - bytes template = 1; + bytes template = 1; } message MailTemplateView { bool default = 1; - bytes template = 2; - google.protobuf.Timestamp creation_date = 3; - google.protobuf.Timestamp change_date = 4; + bytes template = 2; + google.protobuf.Timestamp creation_date = 3; + google.protobuf.Timestamp change_date = 4; } message MailText { - string mail_text_type = 1; - string language = 2; - string title = 3; - string pre_header = 4; - string subject = 5; - string greeting = 6; - string text = 7; - string button_text = 8; - google.protobuf.Timestamp creation_date = 9; - google.protobuf.Timestamp change_date = 10; + string mail_text_type = 1; + string language = 2; + string title = 3; + string pre_header = 4; + string subject = 5; + string greeting = 6; + string text = 7; + string button_text = 8; + google.protobuf.Timestamp creation_date = 9; + google.protobuf.Timestamp change_date = 10; } -message MailTextUpdate { - string mail_text_type = 1; - string language = 2; - string title = 3; - string pre_header = 4; - string subject = 5; - string greeting = 6; - string text = 7; - string button_text = 8; +message MailTextUpdate { + string mail_text_type = 1; + string language = 2; + string title = 3; + string pre_header = 4; + string subject = 5; + string greeting = 6; + string text = 7; + string button_text = 8; } -message MailTextRemove { - string mail_text_type = 1; - string language = 2; +message MailTextRemove { + string mail_text_type = 1; + string language = 2; } message MailTextsView{ - repeated MailTextView texts = 1; + repeated MailTextView texts = 1; } message MailTextView { bool default = 1; - string mail_text_type = 2; - string language = 3; - string title = 4; - string pre_header = 5; - string subject = 6; - string greeting = 7; - string text = 8; - string button_text = 9; - google.protobuf.Timestamp creation_date = 10; - google.protobuf.Timestamp change_date = 11; + string mail_text_type = 2; + string language = 3; + string title = 4; + string pre_header = 5; + string subject = 6; + string greeting = 7; + string text = 8; + string button_text = 9; + google.protobuf.Timestamp creation_date = 10; + google.protobuf.Timestamp change_date = 11; }