Merge pull request #21 from caos/basics

Basics
This commit is contained in:
Fabi 2020-03-30 17:18:09 +02:00 committed by GitHub
commit e3f9e49ef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 5780 additions and 77 deletions

295
cmd/zitadel/authz.yaml Normal file
View File

@ -0,0 +1,295 @@
AuthZ:
RolePermissionMappings:
- Role: 'IAM_OWNER'
Permissions:
- "org.read"
- "org.write"
- "org.member.read"
- "org.member.write"
- "org.member.delete"
- "user.read"
- "user.write"
- "user.delete"
- "user.grant.read"
- "user.grant.write"
- "policy.read"
- "policy.write"
- "policy.delete"
- "project.read"
- "project.write"
- "project.member.read"
- "project.member.write"
- "project.member.delete"
- "project.role.read"
- "project.role.write"
- "project.role.delete"
- "project.app.read"
- "project.app.write"
- "project.grant.read"
- "project.grant.write"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'ORG_OWNER'
Permissions:
- "org.read"
- "org.write"
- "org.member.read"
- "org.member.write"
- "org.member.delete"
- "user.read"
- "user.write"
- "user.delete"
- "user.grant.read"
- "user.grant.write"
- "policy.read"
- "policy.write"
- "policy.delete"
- "project.read"
- "project.write"
- "project.member.read"
- "project.member.write"
- "project.member.delete"
- "project.role.read"
- "project.role.write"
- "project.role.delete"
- "project.app.read"
- "project.app.write"
- "project.grant.read"
- "project.grant.write"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'ORG_EDITOR'
Permissions:
- "org.read"
- "org.write"
- Role: 'ORG_VIEWER'
Permissions:
- "org.read"
- Role: 'ORG_MEMBER_EDITOR'
Permissions:
- "org.read"
- "org.member.read"
- "org.member.write"
- "org.member.delete"
- Role: 'ORG_MEMBER_VIEWER'
Permissions:
- "org.read"
- "org.member.read"
- Role: 'ORG_PROJECT_CREATOR'
Permissions:
- "project.read:self"
- "project.write"
- Role: 'ORG_PROJECT_EDITOR'
Permissions:
- "project.read"
- "project.write"
- "project.member.read"
- "project.member.write"
- "project.member.delete"
- "project.role.read"
- "project.role.write"
- "project.role.delete"
- "project.app.read"
- "project.app.write"
- "project.grant.read"
- "project.grant.write"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'ORG_PROJECT_VIEWER'
Permissions:
- "project.read"
- "project.member.read"
- "project.role.read"
- "project.app.read"
- "project.grant.read"
- "project.grant.member.read"
- Role: 'ORG_PROJECT_MEMBER_EDITOR'
Permissions:
- "project.read"
- "project.member.read"
- "project.member.write"
- "project.grant.member.delete"
- Role: 'ORG_PROJECT_MEMBER_VIEWER'
Permissions:
- "project.read"
- "project.member.read"
- Role: 'ORG_PROJECT_ROLE_EDITOR'
Permissions:
- "project.read"
- "project.role.read"
- "project.role.write"
- "project.role.delete"
- Role: 'ORG_PROJECT_ROLE_VIEWER'
Permissions:
- "project.read"
- "project.role.read"
- Role: 'ORG_PROJECT_APP_EDITOR'
Permissions:
- "project.read"
- "project.app.read"
- "project.app.write"
- Role: 'ORG_PROJECT_APP_VIEWER'
Permissions:
- "project.read"
- "project.app.read"
- Role: 'ORG_PROJECT_GRANT_EDITOR'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.write"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'ORG_PROJECT_GRANT_VIEWER'
Permissions:
- "project.read"
- "project.grant.read"
- Role: 'ORG_PROJECT_GRANT_MEMBER_EDITOR'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'ORG_PROJECT_GRANT_MEMBER_VIEWER'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- Role: 'ORG_USER_EDITOR'
Permissions:
- "user.read"
- "user.write"
- "user.delete"
- Role: 'ORG_USER_VIEWER'
Permissions:
- "user.read"
- Role: 'ORG_USER_GRANT_EDITOR'
Permissions:
- "user.read"
- "user.grant.read"
- "user.grant.write"
- "project.read"
- Role: 'ORG_USER_GRANT_VIEWER'
Permissions:
- "user.read"
- "user.grant.read"
- Role: 'ORG_POLICY_EDITOR'
Permissions:
- "policy.read"
- "policy.write"
- "policy.delete"
- Role: 'ORG_POLICY_VIEWER'
Permissions:
- "policy.read"
- Role: 'PROJECT_OWNER'
Permissions:
- "project.read"
- "project.write"
- "project.member.read"
- "project.member.write"
- "project.member.delete"
- "project.role.read"
- "project.role.write"
- "project.role.delete"
- "project.app.read"
- "project.app.write"
- "project.grant.read"
- "project.grant.write"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- "project.user.grant.read"
- "project.user.grant.write"
- "project.user.grant.delete"
- Role: 'PROJECT_MEMBER_EDITOR'
Permissions:
- "project.read"
- "project.member.read"
- "project.member.write"
- "project.member.delete"
- Role: 'PROJECT_MEMBER_VIEWER'
Permissions:
- "project.read"
- "project.member.read"
- Role: 'PROJECT_ROLE_EDITOR'
Permissions:
- "project.read"
- "project.role.read"
- "project.role.write"
- "project.role.delete"
- Role: 'PROJECT_APP_EDITOR'
Permissions:
- "project.read"
- "project.app.read"
- "project.app.write"
- Role: 'PROJECT_APP_VIEWER'
Permissions:
- "project.read"
- "project.app.read"
- Role: 'PROJECT_GRANT_EDITOR'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.write"
- Role: 'PROJECT_GRANT_VIEWER'
Permissions:
- "project.read"
- "project.grant.read"
- Role: 'PROJECT_GRANT_MEMBER_EDITOR'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'PROJECT_GRANT_MEMBER_VIEWER'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- Role: 'PROJECT_USER_GRANT_EDITOR'
Permissions:
- "project.read"
- "project.user.grant.read"
- "project.user.grant.write"
- "project.user.grant.delete"
- Role: 'PROJECT_USER_GRANT_VIEWER'
Permissions:
- "project.read"
- "project.user.grant.read"
- Role: 'PROJECT_GRANT_OWNER'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.write"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'PROJECT_GRANT_MEMBER_EDITOR'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- "project.grant.member.write"
- "project.grant.member.delete"
- Role: 'PROJECT_GRANT_MEMBER_VIEWER'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.member.read"
- Role: 'PROJECT_GRANT_USER_GRANT_EDITOR'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.user.grant.read"
- "project.grant.user.grant.write"
- "project.grant.user.grant.delete"
- Role: 'PROJECT_GRANT_USER_GRANT_VIEWER'
Permissions:
- "project.read"
- "project.grant.read"
- "project.grant.user.grant.read"

View File

@ -6,50 +6,63 @@ import (
"github.com/caos/logging"
authz "github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/config"
tracing "github.com/caos/zitadel/internal/tracing/config"
"github.com/caos/zitadel/pkg/admin"
"github.com/caos/zitadel/pkg/auth"
"github.com/caos/zitadel/pkg/eventstore"
"github.com/caos/zitadel/pkg/console"
"github.com/caos/zitadel/pkg/login"
"github.com/caos/zitadel/pkg/management"
)
type Config struct {
Eventstore eventstore.Config
Management management.Config
Auth auth.Config
Admin admin.Config
Mgmt management.Config
Auth auth.Config
Login login.Config
Admin admin.Config
Console console.Config
Log logging.Config
Tracing tracing.TracingConfig
AuthZ authz.Config
}
func main() {
configPath := flag.String("config-file", "/zitadel/config/startup.yaml", "path to the config file")
eventstoreEnabled := flag.Bool("eventstore", true, "enable eventstore")
var configPaths config.ArrayFlags
flag.Var(&configPaths, "config-files", "path to the config files")
managementEnabled := flag.Bool("management", true, "enable management api")
authEnabled := flag.Bool("auth", true, "enable auth api")
loginEnabled := flag.Bool("login", true, "enable login ui")
adminEnabled := flag.Bool("admin", true, "enable admin api")
consoleEnabled := flag.Bool("console", true, "enable console ui")
flag.Parse()
conf := new(Config)
err := config.Read(conf, *configPath)
err := config.Read(conf, configPaths...)
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
ctx := context.Background()
if *eventstoreEnabled {
err = eventstore.Start(ctx, conf.Eventstore)
logging.Log("MAIN-sj2Sd").OnError(err).Fatal("error starting eventstore")
}
if *managementEnabled {
err = management.Start(ctx, conf.Management)
err = management.Start(ctx, conf.Mgmt, conf.AuthZ)
logging.Log("MAIN-39Nv5").OnError(err).Fatal("error starting management api")
}
if *authEnabled {
err = auth.Start(ctx, conf.Auth)
err = auth.Start(ctx, conf.Auth, conf.AuthZ)
logging.Log("MAIN-x0nD2").OnError(err).Fatal("error starting auth api")
}
if *loginEnabled {
err = login.Start(ctx, conf.Login)
logging.Log("MAIN-53RF2").OnError(err).Fatal("error starting login ui")
}
if *adminEnabled {
err = admin.Start(ctx, conf.Admin)
err = admin.Start(ctx, conf.Admin, conf.AuthZ)
logging.Log("MAIN-0na71").OnError(err).Fatal("error starting admin api")
}
if *consoleEnabled {
err = console.Start(ctx, conf.Console)
logging.Log("MAIN-3Dfuc").OnError(err).Fatal("error starting console ui")
}
<-ctx.Done()
logging.Log("MAIN-s8d2h").Info("stopping zitadel")
}

42
cmd/zitadel/startup.yaml Normal file
View File

@ -0,0 +1,42 @@
Tracing:
Type: google
Config:
ProjectID: $TRACING_PROJECT_ID
MetricPrefix: ZITADEL-V1
Fraction: 1
Log:
Level: debug
Formatter:
Format: text
Mgmt:
API:
GRPC:
ServerPort: 50010
GatewayPort: 50011
CustomHeaders:
- x-caos-
Auth:
API:
GRPC:
ServerPort: 50020
GatewayPort: 50021
CustomHeaders:
- x-caos-
Login:
# will get port range 5003x
Admin:
API:
GRPC:
ServerPort: 50040
GatewayPort: 50041
CustomHeaders:
- x-caos-
Console:
Port: 50050
StaticDir: /app/console/dist

34
go.mod
View File

@ -3,10 +3,38 @@ module github.com/caos/zitadel
go 1.14
require (
cloud.google.com/go v0.53.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.0
github.com/BurntSushi/toml v0.3.1
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a
github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e // indirect
github.com/Masterminds/goutils v1.1.0 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/aws/aws-sdk-go v1.29.16 // indirect
github.com/caos/logging v0.0.1
github.com/ghodss/yaml v1.0.0
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.3.5
github.com/google/uuid v1.1.1 // indirect
github.com/gorilla/schema v1.1.0
github.com/gorilla/securecookie v1.1.1
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
github.com/grpc-ecosystem/grpc-gateway v1.14.3
github.com/huandu/xstrings v1.3.0 // indirect
github.com/imdario/mergo v0.3.8 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/nicksnyder/go-i18n/v2 v2.0.3
github.com/rs/cors v1.7.0
github.com/sirupsen/logrus v1.5.0 // indirect
github.com/stretchr/testify v1.5.1
go.opencensus.io v0.22.3
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df
golang.org/x/net v0.0.0-20200320220750-118fecf932d8 // indirect
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 // indirect
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56
google.golang.org/api v0.20.0 // indirect
google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c // indirect
google.golang.org/grpc v1.28.0
gopkg.in/yaml.v2 v2.2.8 // indirect
)

93
go.sum
View File

@ -6,6 +6,7 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0 h1:MZQCQQaRwOrAcuKjiHWHrgKykt4fZyuwF2dtiG3fGW8=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@ -14,31 +15,38 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
contrib.go.opencensus.io/exporter/stackdriver v0.13.0 h1:Jaz7WbqjtfoCPa1KbfisCX+P5aM3DizEY9pQMU0oAQo=
contrib.go.opencensus.io/exporter/stackdriver v0.13.0/go.mod h1:z2tyTZtPmQ2HvWH4cOmVDgtY+1lomfKdbLnkJvZdc8c=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.29.16 h1:Gbtod7Y4W/Ai7wPtesdvgGVTkFN8JxAaGouRLlcQfQs=
github.com/aws/aws-sdk-go v1.29.16/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg=
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a h1:HOU/3xL/afsZ+2aCstfJlrzRkwYMTFR1TIEgps5ny8s=
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e h1:QSbTeoLPW7c1rWNJA2GOKunDJnRAfyg8+cb73qMYESM=
github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e/go.mod h1:CLEkNe7rs12GkdBWZxadA/mFiKeF6HzuA1BOKq+fX+Y=
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -49,39 +57,57 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
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=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
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=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/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=
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo=
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@ -91,9 +117,17 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/nicksnyder/go-i18n/v2 v2.0.3 h1:ks/JkQiOEhhuF6jpNvx+Wih1NIiXzUnZeZVnJuI8R8M=
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -102,14 +136,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -117,14 +153,18 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU=
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -149,6 +189,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -165,17 +206,20 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg=
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -192,12 +236,12 @@ golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX
golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44=
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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=
@ -210,6 +254,7 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@ -226,9 +271,11 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 h1:DFtSed2q3HtNuVazwVDZ4nSRS/JrZEig0gz2BY4VNrg=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -239,12 +286,14 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/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=
@ -260,19 +309,25 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200303153909-beee998c1893/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c h1:5aI3/f/3eCZps9xwoEnmgfDJDhMbnJpfqeGpjVNgVEI=
google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
@ -285,5 +340,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

4
internal/admin/config.go Normal file
View File

@ -0,0 +1,4 @@
package admin
type Config struct {
}

View File

@ -0,0 +1,108 @@
package auth
import (
"context"
"fmt"
"reflect"
"strings"
"github.com/caos/zitadel/internal/errors"
)
const (
authenticated = "authenticated"
)
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID string, verifier TokenVerifier, authConfig *Config, requiredAuthOption Option) (context.Context, error) {
ctx, err := VerifyTokenAndWriteCtxData(ctx, token, orgID, verifier)
if err != nil {
return nil, err
}
if requiredAuthOption.Permission == authenticated {
return ctx, nil
}
ctx, perms, err := getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig)
if err != nil {
return nil, err
}
err = checkUserPermissions(req, perms, requiredAuthOption)
if err != nil {
return nil, err
}
return ctx, nil
}
func checkUserPermissions(req interface{}, userPerms []string, authOpt Option) error {
if len(userPerms) == 0 {
return errors.ThrowPermissionDenied(nil, "AUTH-5mWD2", "No matching permissions found")
}
if authOpt.CheckParam == "" {
return nil
}
if HasGlobalPermission(userPerms) {
return nil
}
if hasContextPermission(req, authOpt.CheckParam, userPerms) {
return nil
}
return errors.ThrowPermissionDenied(nil, "AUTH-3jknH", "No matching permissions found")
}
func SplitPermission(perm string) (string, string) {
splittedPerm := strings.Split(perm, ":")
if len(splittedPerm) == 1 {
return splittedPerm[0], ""
}
return splittedPerm[0], splittedPerm[1]
}
func hasContextPermission(req interface{}, fieldName string, permissions []string) bool {
for _, perm := range permissions {
_, ctxID := SplitPermission(perm)
if checkPermissionContext(req, fieldName, ctxID) {
return true
}
}
return false
}
func checkPermissionContext(req interface{}, fieldName, roleContextID string) bool {
field := getFieldFromReq(req, fieldName)
return field != "" && field == roleContextID
}
func getFieldFromReq(req interface{}, field string) string {
v := reflect.Indirect(reflect.ValueOf(req)).FieldByName(field)
if reflect.ValueOf(v).IsZero() {
return ""
}
return fmt.Sprintf("%v", v.Interface())
}
func HasGlobalPermission(perms []string) bool {
for _, perm := range perms {
_, ctxID := SplitPermission(perm)
if ctxID == "" {
return true
}
}
return false
}
func GetPermissionCtxIDs(perms []string) []string {
ctxIDs := make([]string, 0)
for _, perm := range perms {
_, ctxID := SplitPermission(perm)
if ctxID != "" {
ctxIDs = append(ctxIDs, ctxID)
}
}
return ctxIDs
}

View File

@ -0,0 +1,278 @@
package auth
import (
"testing"
"github.com/caos/zitadel/internal/errors"
)
type TestRequest struct {
Test string
}
func Test_CheckUserPermissions(t *testing.T) {
type args struct {
req *TestRequest
perms []string
authOpt Option
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "no permissions",
args: args{
req: &TestRequest{},
perms: []string{},
},
wantErr: true,
},
{
name: "has permission and no context requested",
args: args{
req: &TestRequest{},
perms: []string{"project.read"},
authOpt: Option{CheckParam: ""},
},
wantErr: false,
},
{
name: "context requested and has global permission",
args: args{
req: &TestRequest{Test: "Test"},
perms: []string{"project.read", "project.read:1"},
authOpt: Option{CheckParam: "Test"},
},
wantErr: false,
},
{
name: "context requested and has specific permission",
args: args{
req: &TestRequest{Test: "Test"},
perms: []string{"project.read:Test"},
authOpt: Option{CheckParam: "Test"},
},
wantErr: false,
},
{
name: "context requested and has no permission",
args: args{
req: &TestRequest{Test: "Hodor"},
perms: []string{"project.read:Test"},
authOpt: Option{CheckParam: "Test"},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := checkUserPermissions(tt.args.req, tt.args.perms, tt.args.authOpt)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)
}
if !tt.wantErr && err != nil {
t.Errorf("shouldn't get err: %v ", err)
}
if tt.wantErr && !errors.IsPermissionDenied(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func Test_SplitPermission(t *testing.T) {
type args struct {
perm string
}
tests := []struct {
name string
args args
permName string
permCtxID string
}{
{
name: "permission with context id",
args: args{
perm: "project.read:ctxID",
},
permName: "project.read",
permCtxID: "ctxID",
},
{
name: "permission without context id",
args: args{
perm: "project.read",
},
permName: "project.read",
permCtxID: "",
},
{
name: "permission to many parts",
args: args{
perm: "project.read:1:0",
},
permName: "project.read",
permCtxID: "1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
name, id := SplitPermission(tt.args.perm)
if name != tt.permName {
t.Errorf("got wrong result on name, expecting: %v, actual: %v ", tt.permName, name)
}
if id != tt.permCtxID {
t.Errorf("got wrong result on id, expecting: %v, actual: %v ", tt.permCtxID, id)
}
})
}
}
func Test_HasContextPermission(t *testing.T) {
type args struct {
req *TestRequest
fieldname string
perms []string
}
tests := []struct {
name string
args args
result bool
}{
{
name: "existing context permission",
args: args{
req: &TestRequest{Test: "right"},
fieldname: "Test",
perms: []string{"test:wrong", "test:right"},
},
result: true,
},
{
name: "not existing context permission",
args: args{
req: &TestRequest{Test: "test"},
fieldname: "Test",
perms: []string{"test:wrong", "test:wrong2"},
},
result: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := hasContextPermission(tt.args.req, tt.args.fieldname, tt.args.perms)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_GetFieldFromReq(t *testing.T) {
type args struct {
req *TestRequest
fieldname string
}
tests := []struct {
name string
args args
result string
}{
{
name: "existing field",
args: args{
req: &TestRequest{Test: "TestValue"},
fieldname: "Test",
},
result: "TestValue",
},
{
name: "not existing field",
args: args{
req: &TestRequest{Test: "TestValue"},
fieldname: "Test2",
},
result: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getFieldFromReq(tt.args.req, tt.args.fieldname)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_HasGlobalPermission(t *testing.T) {
type args struct {
perms []string
}
tests := []struct {
name string
args args
result bool
}{
{
name: "global perm existing",
args: args{
perms: []string{"perm:1", "perm:2", "perm"},
},
result: true,
},
{
name: "global perm not existing",
args: args{
perms: []string{"perm:1", "perm:2", "perm:3"},
},
result: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := HasGlobalPermission(tt.args.perms)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_GetPermissionCtxIDs(t *testing.T) {
type args struct {
perms []string
}
tests := []struct {
name string
args args
result []string
}{
{
name: "no specific permission",
args: args{
perms: []string{"perm"},
},
result: []string{},
},
{
name: "ctx id",
args: args{
perms: []string{"perm:1", "perm", "perm:3"},
},
result: []string{"1", "3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := GetPermissionCtxIDs(tt.args.perms)
if !equalStringArray(result, tt.result) {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}

View File

@ -0,0 +1,26 @@
package auth
type Config struct {
RolePermissionMappings []RoleMapping
}
type RoleMapping struct {
Role string
Permissions []string
}
type MethodMapping map[string]Option
type Option struct {
Permission string
CheckParam string
}
func (a *Config) getPermissionsFromRole(role string) []string {
for _, roleMap := range a.RolePermissionMappings {
if roleMap.Role == role {
return roleMap.Permissions
}
}
return nil
}

View File

@ -0,0 +1,60 @@
package auth
import (
"context"
"github.com/caos/logging"
)
type key int
var (
permissionsKey key
dataKey key
)
type CtxData struct {
UserID string
OrgID string
ProjectID string
AgentID string
}
func (ctxData CtxData) IsZero() bool {
return ctxData.UserID == "" || ctxData.OrgID == ""
}
type Grants []*Grant
type Grant struct {
OrgID string
Roles []string
}
type TokenVerifier interface {
VerifyAccessToken(ctx context.Context, token string) (string, string, string, error)
ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error)
GetProjectIDByClientID(ctx context.Context, clientID string) (string, error)
}
func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t TokenVerifier) (_ context.Context, err error) {
userID, clientID, agentID, err := verifyAccessToken(ctx, token, t)
if err != nil {
return nil, err
}
projectID, err := t.GetProjectIDByClientID(ctx, clientID)
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid")
return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil
}
func GetCtxData(ctx context.Context) CtxData {
ctxData, _ := ctx.Value(dataKey).(CtxData)
return ctxData
}
func GetPermissionsFromCtx(ctx context.Context) []string {
ctxPermission, _ := ctx.Value(permissionsKey).([]string)
return ctxPermission
}

View File

@ -0,0 +1,61 @@
package auth
import (
"context"
"github.com/caos/zitadel/internal/errors"
)
func getUserMethodPermissions(ctx context.Context, t TokenVerifier, requiredPerm string, authConfig *Config) (context.Context, []string, error) {
ctxData := GetCtxData(ctx)
if ctxData.IsZero() {
return nil, nil, errors.ThrowUnauthenticated(nil, "AUTH-rKLWEH", "context missing")
}
grants, err := t.ResolveGrants(ctx, ctxData.UserID, ctxData.OrgID)
if err != nil {
return nil, nil, err
}
permissions := mapGrantsToPermissions(requiredPerm, grants, authConfig)
return context.WithValue(ctx, permissionsKey, permissions), permissions, nil
}
func mapGrantsToPermissions(requiredPerm string, grants []*Grant, authConfig *Config) []string {
resolvedPermissions := make([]string, 0)
for _, grant := range grants {
for _, role := range grant.Roles {
resolvedPermissions = mapRoleToPerm(requiredPerm, role, authConfig, resolvedPermissions)
}
}
return resolvedPermissions
}
func mapRoleToPerm(requiredPerm, actualRole string, authConfig *Config, resolvedPermissions []string) []string {
roleName, roleContextID := SplitPermission(actualRole)
perms := authConfig.getPermissionsFromRole(roleName)
for _, p := range perms {
if p == requiredPerm {
p = addRoleContextIDToPerm(p, roleContextID)
if !existsPerm(resolvedPermissions, p) {
resolvedPermissions = append(resolvedPermissions, p)
}
}
}
return resolvedPermissions
}
func addRoleContextIDToPerm(perm, roleContextID string) string {
if roleContextID != "" {
perm = perm + ":" + roleContextID
}
return perm
}
func existsPerm(existing []string, perm string) bool {
for _, e := range existing {
if e == perm {
return true
}
}
return false
}

View File

@ -0,0 +1,428 @@
package auth
import (
"context"
"testing"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func getTestCtx(userID, orgID string) context.Context {
return context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
}
type testVerifier struct {
grants []*Grant
}
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
return "", "", "", nil
}
func (v *testVerifier) ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error) {
return v.grants, nil
}
func (v *testVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
}
func equalStringArray(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func Test_GetUserMethodPermissions(t *testing.T) {
type args struct {
ctx context.Context
verifier TokenVerifier
requiredPerm string
authConfig *Config
}
tests := []struct {
name string
args args
wantErr bool
errFunc func(err error) bool
result []string
}{
{
name: "Empty Context",
args: args{
ctx: getTestCtx("", ""),
verifier: &testVerifier{grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}}},
requiredPerm: "project.read",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
wantErr: true,
errFunc: caos_errs.IsUnauthenticated,
result: []string{"project.read"},
},
{
name: "No Grants",
args: args{
ctx: getTestCtx("", ""),
verifier: &testVerifier{grants: []*Grant{}},
requiredPerm: "project.read",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
result: make([]string, 0),
},
{
name: "Get Permissions",
args: args{
ctx: getTestCtx("userID", "orgID"),
verifier: &testVerifier{grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}}},
requiredPerm: "project.read",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
result: []string{"project.read"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, perms, err := getUserMethodPermissions(tt.args.ctx, tt.args.verifier, tt.args.requiredPerm, tt.args.authConfig)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)
}
if tt.wantErr && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
if !tt.wantErr && !equalStringArray(perms, tt.result) {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, perms)
}
})
}
}
func Test_MapGrantsToPermissions(t *testing.T) {
type args struct {
requiredPerm string
grants []*Grant
authConfig *Config
}
tests := []struct {
name string
args args
result []string
}{
{
name: "One Role existing perm",
args: args{
requiredPerm: "project.read",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
result: []string{"project.read"},
},
{
name: "One Role not existing perm",
args: args{
requiredPerm: "project.write",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
result: []string{},
},
{
name: "Multiple Roles one existing",
args: args{
requiredPerm: "project.read",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER", "IAM_OWNER"}}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
result: []string{"project.read"},
},
{
name: "Multiple Roles, global and specific",
args: args{
requiredPerm: "project.read",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "PROJECT_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
},
result: []string{"project.read", "project.read:1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := mapGrantsToPermissions(tt.args.requiredPerm, tt.args.grants, tt.args.authConfig)
if !equalStringArray(result, tt.result) {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_MapRoleToPerm(t *testing.T) {
type args struct {
requiredPerm string
actualRole string
authConfig *Config
resolvedPermissions []string
}
tests := []struct {
name string
args args
result []string
}{
{
name: "first perm without context id",
args: args{
requiredPerm: "project.read",
actualRole: "ORG_OWNER",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
resolvedPermissions: []string{},
},
result: []string{"project.read"},
},
{
name: "existing perm without context id",
args: args{
requiredPerm: "project.read",
actualRole: "ORG_OWNER",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "IAM_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
resolvedPermissions: []string{"project.read"},
},
result: []string{"project.read"},
},
{
name: "first perm with context id",
args: args{
requiredPerm: "project.read",
actualRole: "PROJECT_OWNER:1",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "PROJECT_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
resolvedPermissions: []string{},
},
result: []string{"project.read:1"},
},
{
name: "perm with context id, existing global",
args: args{
requiredPerm: "project.read",
actualRole: "PROJECT_OWNER:1",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
Role: "PROJECT_OWNER",
Permissions: []string{"project.read"},
},
RoleMapping{
Role: "ORG_OWNER",
Permissions: []string{"org.read", "project.read"},
},
},
},
resolvedPermissions: []string{"project.read"},
},
result: []string{"project.read", "project.read:1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := mapRoleToPerm(tt.args.requiredPerm, tt.args.actualRole, tt.args.authConfig, tt.args.resolvedPermissions)
if !equalStringArray(result, tt.result) {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_AddRoleContextIDToPerm(t *testing.T) {
type args struct {
perm string
ctxID string
}
tests := []struct {
name string
args args
result string
}{
{
name: "with ctx id",
args: args{
perm: "perm1",
ctxID: "2",
},
result: "perm1:2",
},
{
name: "with ctx id",
args: args{
perm: "perm1",
ctxID: "",
},
result: "perm1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := addRoleContextIDToPerm(tt.args.perm, tt.args.ctxID)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}
func Test_ExistisPerm(t *testing.T) {
type args struct {
existing []string
perm string
}
tests := []struct {
name string
args args
result bool
}{
{
name: "not existing perm",
args: args{
existing: []string{"perm1", "perm2", "perm3"},
perm: "perm4",
},
result: false,
},
{
name: "existing perm",
args: args{
existing: []string{"perm1", "perm2", "perm3"},
perm: "perm2",
},
result: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := existsPerm(tt.args.existing, tt.args.perm)
if result != tt.result {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}
})
}
}

View File

@ -0,0 +1,20 @@
package auth
import (
"context"
"strings"
"github.com/caos/zitadel/internal/errors"
)
const (
BearerPrefix = "Bearer "
)
func verifyAccessToken(ctx context.Context, token string, t TokenVerifier) (string, string, string, error) {
parts := strings.Split(token, BearerPrefix)
if len(parts) != 2 {
return "", "", "", errors.ThrowUnauthenticated(nil, "AUTH-7fs1e", "invalid auth header")
}
return t.VerifyAccessToken(ctx, parts[1])
}

View File

@ -0,0 +1,63 @@
package auth
import (
"context"
"testing"
"github.com/caos/zitadel/internal/errors"
)
func Test_VerifyAccessToken(t *testing.T) {
type args struct {
ctx context.Context
token string
verifier *testVerifier
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "no auth header set",
args: args{
ctx: context.Background(),
token: "",
},
wantErr: true,
},
{
name: "wrong auth header set",
args: args{
ctx: context.Background(),
token: "Basic sds",
},
wantErr: true,
},
{
name: "auth header set",
args: args{
ctx: context.Background(),
token: "Bearer AUTH",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, _, err := verifyAccessToken(tt.args.ctx, tt.args.token, tt.args.verifier)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)
}
if !tt.wantErr && err != nil {
t.Errorf("got wrong result, should not get err: actual: %v ", err)
}
if tt.wantErr && !errors.IsUnauthenticated(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@ -0,0 +1,46 @@
package grpc
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func CaosToGRPCError(err error) error {
if err == nil {
return nil
}
code, msg, ok := Extract(err)
if !ok {
return status.Convert(err).Err()
}
return status.Error(code, msg)
}
func Extract(err error) (c codes.Code, msg string, ok bool) {
switch caosErr := err.(type) {
case *caos_errs.AlreadyExistsError:
return codes.AlreadyExists, caosErr.GetMessage(), true
case *caos_errs.DeadlineExceededError:
return codes.DeadlineExceeded, caosErr.GetMessage(), true
case caos_errs.InternalError:
return codes.Internal, caosErr.GetMessage(), true
case *caos_errs.InvalidArgumentError:
return codes.InvalidArgument, caosErr.GetMessage(), true
case *caos_errs.NotFoundError:
return codes.NotFound, caosErr.GetMessage(), true
case *caos_errs.PermissionDeniedError:
return codes.PermissionDenied, caosErr.GetMessage(), true
case *caos_errs.PreconditionFailedError:
return codes.FailedPrecondition, caosErr.GetMessage(), true
case *caos_errs.UnauthenticatedError:
return codes.Unauthenticated, caosErr.GetMessage(), true
case *caos_errs.UnavailableError:
return codes.Unavailable, caosErr.GetMessage(), true
case *caos_errs.UnimplementedError:
return codes.Unimplemented, caosErr.GetMessage(), true
default:
return codes.Unknown, err.Error(), false
}
}

View File

@ -0,0 +1,38 @@
package middleware
import (
"context"
"strings"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/stats"
"github.com/caos/zitadel/internal/api"
"github.com/caos/zitadel/internal/tracing"
)
type GRPCMethod string
func TracingStatsClient(ignoredMethods ...GRPCMethod) grpc.DialOption {
return grpc.WithStatsHandler(&tracingClientHandler{ignoredMethods, ocgrpc.ClientHandler{StartOptions: trace.StartOptions{Sampler: tracing.Sampler(), SpanKind: trace.SpanKindClient}}})
}
func DefaultTracingStatsClient() grpc.DialOption {
return TracingStatsClient(api.Healthz, api.Readiness, api.Validation)
}
type tracingClientHandler struct {
IgnoredMethods []GRPCMethod
ocgrpc.ClientHandler
}
func (s *tracingClientHandler) TagRPC(ctx context.Context, tagInfo *stats.RPCTagInfo) context.Context {
for _, method := range s.IgnoredMethods {
if strings.HasSuffix(tagInfo.FullMethodName, string(method)) {
return ctx
}
}
return s.ClientHandler.TagRPC(ctx, tagInfo)
}

View File

@ -0,0 +1,31 @@
package grpc
type Config struct {
ServerPort string
GatewayPort string
CustomHeaders []string
}
func (c Config) ToServerConfig() ServerConfig {
return ServerConfig{
Port: c.ServerPort,
}
}
func (c Config) ToGatewayConfig() GatewayConfig {
return GatewayConfig{
Port: c.GatewayPort,
GRPCEndpoint: c.ServerPort,
CustomHeaders: c.CustomHeaders,
}
}
type ServerConfig struct {
Port string
}
type GatewayConfig struct {
Port string
GRPCEndpoint string
CustomHeaders []string
}

View File

@ -0,0 +1,17 @@
package grpc
import (
"context"
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/caos/zitadel/internal/api"
)
func GetHeader(ctx context.Context, headername string) string {
return metautils.ExtractIncoming(ctx).Get(headername)
}
func GetAuthorizationHeader(ctx context.Context) string {
return GetHeader(ctx, api.Authorization)
}

View File

@ -0,0 +1,110 @@
package server
import (
"context"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
"github.com/caos/logging"
client_middleware "github.com/caos/zitadel/internal/api/grpc/client/middleware"
http_util "github.com/caos/zitadel/internal/api/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)
const (
defaultGatewayPort = "8080"
mimeWildcard = "*/*"
)
var (
DefaultJSONMarshaler = &runtime.JSONPb{OrigName: false, EmitDefaults: false}
DefaultServeMuxOptions = []runtime.ServeMuxOption{
runtime.WithMarshalerOption(DefaultJSONMarshaler.ContentType(), DefaultJSONMarshaler),
runtime.WithMarshalerOption(mimeWildcard, DefaultJSONMarshaler),
runtime.WithMarshalerOption(runtime.MIMEWildcard, DefaultJSONMarshaler),
runtime.WithIncomingHeaderMatcher(runtime.DefaultHeaderMatcher),
runtime.WithOutgoingHeaderMatcher(runtime.DefaultHeaderMatcher),
}
)
type Gateway interface {
GRPCEndpoint() string
GatewayPort() string
Gateway() GatewayFunc
}
type GatewayFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error
type gatewayCustomServeMuxOptions interface {
GatewayServeMuxOptions() []runtime.ServeMuxOption
}
type grpcGatewayCustomInterceptor interface {
GatewayHTTPInterceptor(http.Handler) http.Handler
}
type gatewayCustomCallOptions interface {
GatewayCallOptions() []grpc.DialOption
}
func StartGateway(ctx context.Context, g Gateway) {
mux := createMux(ctx, g)
serveGateway(ctx, mux, gatewayPort(g.GatewayPort()), g)
}
func createMux(ctx context.Context, g Gateway) *runtime.ServeMux {
muxOptions := DefaultServeMuxOptions
if customOpts, ok := g.(gatewayCustomServeMuxOptions); ok {
muxOptions = customOpts.GatewayServeMuxOptions()
}
mux := runtime.NewServeMux(muxOptions...)
opts := []grpc.DialOption{grpc.WithInsecure()}
opts = append(opts, client_middleware.DefaultTracingStatsClient())
if customOpts, ok := g.(gatewayCustomCallOptions); ok {
opts = append(opts, customOpts.GatewayCallOptions()...)
}
err := g.Gateway()(ctx, mux, g.GRPCEndpoint(), opts)
logging.Log("SERVE-7B7G0E").OnError(err).Panic("failed to create mux for grpc gateway")
return mux
}
func addInterceptors(handler http.Handler, g Gateway) http.Handler {
handler = http_mw.DefaultTraceHandler(handler)
if interceptor, ok := g.(grpcGatewayCustomInterceptor); ok {
handler = interceptor.GatewayHTTPInterceptor(handler)
}
return http_mw.CORSInterceptorOpts(http_mw.DefaultCORSOptions, handler)
}
func serveGateway(ctx context.Context, handler http.Handler, port string, g Gateway) {
server := &http.Server{
Handler: addInterceptors(handler, g),
}
listener := http_util.CreateListener(port)
go func() {
<-ctx.Done()
err := server.Shutdown(ctx)
logging.Log("SERVE-m7kBlq").OnError(err).Warn("error during graceful shutdown of grpc gateway")
}()
go func() {
err := server.Serve(listener)
logging.Log("SERVE-tBHR60").OnError(err).Panic("grpc gateway serve failed")
}()
logging.LogWithFields("SERVE-KHh0Cb", "port", port).Info("grpc gateway is listening")
}
func gatewayPort(port string) string {
if port == "" {
return defaultGatewayPort
}
return port
}

View File

@ -0,0 +1,36 @@
package middleware
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/caos/zitadel/internal/api"
"github.com/caos/zitadel/internal/api/auth"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
)
func AuthorizationInterceptor(verifier auth.TokenVerifier, authConfig *auth.Config, authMethods auth.MethodMapping) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
authOpt, needsToken := authMethods[info.FullMethod]
if !needsToken {
return handler(ctx, req)
}
authToken := grpc_util.GetAuthorizationHeader(ctx)
if authToken == "" {
return nil, status.Error(codes.Unauthenticated, "auth header missing")
}
orgID := grpc_util.GetHeader(ctx, api.ZitadelOrgID)
ctx, err := auth.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt)
if err != nil {
return nil, err
}
return handler(ctx, req)
}
}

View File

@ -0,0 +1,16 @@
package middleware
import (
"context"
"google.golang.org/grpc"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
)
func ErrorHandler() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
resp, err := handler(ctx, req)
return resp, grpc_util.CaosToGRPCError(err)
}
}

View File

@ -0,0 +1,33 @@
package middleware
import (
"context"
"strings"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/stats"
"github.com/caos/zitadel/internal/tracing"
)
type GRPCMethod string
func TracingStatsServer(ignoredMethods ...GRPCMethod) grpc.ServerOption {
return grpc.StatsHandler(&tracingServerHandler{ignoredMethods, ocgrpc.ServerHandler{StartOptions: trace.StartOptions{Sampler: tracing.Sampler(), SpanKind: trace.SpanKindServer}}})
}
type tracingServerHandler struct {
IgnoredMethods []GRPCMethod
ocgrpc.ServerHandler
}
func (s *tracingServerHandler) TagRPC(ctx context.Context, tagInfo *stats.RPCTagInfo) context.Context {
for _, method := range s.IgnoredMethods {
if strings.HasSuffix(tagInfo.FullMethodName, string(method)) {
return ctx
}
}
return s.ServerHandler.TagRPC(ctx, tagInfo)
}

View File

@ -0,0 +1,53 @@
package server
import (
"context"
"github.com/caos/logging"
"github.com/golang/protobuf/ptypes/empty"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/proto"
)
type ValidationFunction func(ctx context.Context) error
type Validator struct {
validations map[string]ValidationFunction
}
func NewValidator(validations map[string]ValidationFunction) *Validator {
return &Validator{validations: validations}
}
func (v *Validator) Healthz(_ context.Context, e *empty.Empty) (*empty.Empty, error) {
return e, nil
}
func (v *Validator) Ready(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {
return e, ready(ctx, v.validations)
}
func (v *Validator) Validate(ctx context.Context, _ *empty.Empty) (*structpb.Struct, error) {
validations := validate(ctx, v.validations)
return proto.ToPBStruct(validations)
}
func ready(ctx context.Context, validations map[string]ValidationFunction) error {
if len(validate(ctx, validations)) == 0 {
return nil
}
return errors.ThrowInternal(nil, "API-2jD9a", "not ready")
}
func validate(ctx context.Context, validations map[string]ValidationFunction) map[string]error {
errors := make(map[string]error)
for id, validation := range validations {
if err := validation(ctx); err != nil {
logging.Log("API-vf823").WithError(err).Error("validation failed")
errors[id] = err
}
}
return errors
}

View File

@ -0,0 +1,53 @@
package server
import (
"context"
"net"
"github.com/caos/logging"
"google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/http"
)
const (
defaultGrpcPort = "80"
)
type Server interface {
GRPCPort() string
GRPCServer() (*grpc.Server, error)
}
func StartServer(ctx context.Context, s Server) {
port := grpcPort(s.GRPCPort())
listener := http.CreateListener(port)
server := createGrpcServer(s)
serveServer(ctx, server, listener, port)
}
func createGrpcServer(s Server) *grpc.Server {
grpcServer, err := s.GRPCServer()
logging.Log("SERVE-k280HZ").OnError(err).Panic("failed to create grpc server")
return grpcServer
}
func serveServer(ctx context.Context, server *grpc.Server, listener net.Listener, port string) {
go func() {
<-ctx.Done()
server.GracefulStop()
}()
go func() {
err := server.Serve(listener)
logging.Log("SERVE-Ga3e94").OnError(err).Panic("grpc server serve failed")
}()
logging.LogWithFields("SERVE-bZ44QM", "port", port).Info("grpc server is listening")
}
func grpcPort(port string) string {
if port == "" {
return defaultGrpcPort
}
return port
}

12
internal/api/header.go Normal file
View File

@ -0,0 +1,12 @@
package api
const (
Authorization = "authorization"
Accept = "accept"
AcceptLanguage = "accept-language"
ContentType = "content-type"
Location = "location"
Origin = "origin"
ZitadelOrgID = "x-zitadel-orgid"
)

109
internal/api/html/i18n.go Normal file
View File

@ -0,0 +1,109 @@
package html
import (
"encoding/json"
"io/ioutil"
"net/http"
"path"
"github.com/BurntSushi/toml"
"github.com/caos/logging"
"github.com/ghodss/yaml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/errors"
)
type Translator struct {
bundle *i18n.Bundle
cookieName string
cookieHandler *http_util.CookieHandler
}
type TranslatorConfig struct {
Path string
DefaultLanguage language.Tag
CookieName string
}
func NewTranslator(config TranslatorConfig) (*Translator, error) {
t := new(Translator)
var err error
t.bundle, err = newBundle(config.Path, config.DefaultLanguage)
if err != nil {
return nil, err
}
t.cookieHandler = http_util.NewCookieHandler()
t.cookieName = config.CookieName
return t, nil
}
func newBundle(i18nDir string, defaultLanguage language.Tag) (*i18n.Bundle, error) {
bundle := i18n.NewBundle(defaultLanguage)
yamlUnmarshal := func(data []byte, v interface{}) error { return yaml.Unmarshal(data, v) }
bundle.RegisterUnmarshalFunc("yaml", yamlUnmarshal)
bundle.RegisterUnmarshalFunc("yml", yamlUnmarshal)
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
files, err := ioutil.ReadDir(i18nDir)
if err != nil {
return nil, errors.ThrowNotFound(err, "HTML-MnXRie", "path not found")
}
for _, file := range files {
bundle.MustLoadMessageFile(path.Join(i18nDir, file.Name()))
}
return bundle, nil
}
func (t *Translator) LocalizeFromRequest(r *http.Request, id string, args map[string]interface{}) string {
s, err := t.localizerFromRequest(r).Localize(&i18n.LocalizeConfig{
MessageID: id,
TemplateData: args,
})
if err != nil {
logging.Log("HTML-MsF5sx").WithError(err).Warnf("missing translation")
return id
}
return s
}
func (t *Translator) Localize(id string, args map[string]interface{}) string {
s, _ := t.localizer().Localize(&i18n.LocalizeConfig{
MessageID: id,
TemplateData: args,
})
return s
}
func (t *Translator) Lang(r *http.Request) language.Tag {
matcher := language.NewMatcher(t.bundle.LanguageTags())
tag, _ := language.MatchStrings(matcher, t.langsFromRequest(r)...)
return tag
}
func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) {
t.cookieHandler.SetCookie(w, t.cookieName, lang.String())
}
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
return t.localizer(t.langsFromRequest(r)...)
}
func (t *Translator) localizer(langs ...string) *i18n.Localizer {
return i18n.NewLocalizer(t.bundle, langs...)
}
func (t *Translator) langsFromRequest(r *http.Request) []string {
langs := make([]string, 0)
if r == nil {
return langs
}
lang, err := t.cookieHandler.GetCookieValue(r, t.cookieName)
if err == nil {
langs = append(langs, lang)
}
return append(langs, r.Header.Get(api.AcceptLanguage))
}

View File

@ -0,0 +1,81 @@
package html
import (
"net/http"
"path"
"text/template"
"github.com/caos/logging"
"golang.org/x/text/language"
)
const (
TranslateFn = "t"
)
type Renderer struct {
Templates map[string]*template.Template
i18n *Translator
}
func NewRenderer(templatesDir string, tmplMapping map[string]string, funcs map[string]interface{}, translatorConfig TranslatorConfig) (*Renderer, error) {
var err error
r := new(Renderer)
r.i18n, err = NewTranslator(translatorConfig)
if err != nil {
return nil, err
}
r.loadTemplates(templatesDir, tmplMapping, funcs)
return r, nil
}
func (r *Renderer) RenderTemplate(w http.ResponseWriter, req *http.Request, tmpl *template.Template, data interface{}, reqFuncs map[string]interface{}) {
reqFuncs = r.registerTranslateFn(req, reqFuncs)
err := tmpl.Funcs(reqFuncs).Execute(w, data)
logging.LogWithFields("HTML-lF8F6w", "template", tmpl.Name).OnError(err).Error("error rendering template")
}
func (r *Renderer) Localize(id string, args map[string]interface{}) string {
return r.i18n.Localize(id, args)
}
func (r *Renderer) LocalizeFromRequest(req *http.Request, id string, args map[string]interface{}) string {
return r.i18n.LocalizeFromRequest(req, id, args)
}
func (r *Renderer) Lang(req *http.Request) language.Tag {
return r.i18n.Lang(req)
}
func (r *Renderer) loadTemplates(templatesDir string, tmplMapping map[string]string, funcs map[string]interface{}) {
funcs = r.registerTranslateFn(nil, funcs)
funcs[TranslateFn] = func(id string, args ...interface{}) string {
return id
}
tmpls := template.Must(template.New("").Funcs(funcs).ParseGlob(path.Join(templatesDir, "*.html")))
r.Templates = make(map[string]*template.Template, len(tmplMapping))
for name, file := range tmplMapping {
r.Templates[name] = tmpls.Lookup(file)
}
}
func (r *Renderer) registerTranslateFn(req *http.Request, funcs map[string]interface{}) map[string]interface{} {
if funcs == nil {
funcs = make(map[string]interface{})
}
funcs[TranslateFn] = func(id string, args ...interface{}) string {
m := map[string]interface{}{}
var key string
for i, arg := range args {
if i%2 == 0 {
key = arg.(string)
continue
}
m[key] = arg
}
if r == nil {
return r.Localize(id, m)
}
return r.LocalizeFromRequest(req, id, m)
}
return funcs
}

122
internal/api/http/cookie.go Normal file
View File

@ -0,0 +1,122 @@
package http
import (
"net/http"
"github.com/gorilla/securecookie"
"github.com/caos/zitadel/internal/errors"
)
type CookieHandler struct {
securecookie *securecookie.SecureCookie
secureOnly bool
sameSite http.SameSite
path string
maxAge int
domain string
}
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
c := &CookieHandler{
secureOnly: true,
sameSite: http.SameSiteLaxMode,
path: "/",
}
for _, opt := range opts {
opt(c)
}
return c
}
type CookieHandlerOpt func(*CookieHandler)
func WithEncryption(hashKey, encryptKey []byte) CookieHandlerOpt {
return func(c *CookieHandler) {
c.securecookie = securecookie.New(hashKey, encryptKey)
}
}
func WithUnsecure() CookieHandlerOpt {
return func(c *CookieHandler) {
c.secureOnly = false
}
}
func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
return func(c *CookieHandler) {
c.sameSite = sameSite
}
}
func WithPath(path string) CookieHandlerOpt {
return func(c *CookieHandler) {
c.path = path
}
}
func WithMaxAge(maxAge int) CookieHandlerOpt {
return func(c *CookieHandler) {
c.maxAge = maxAge
c.securecookie.MaxAge(maxAge)
}
}
func WithDomain(domain string) CookieHandlerOpt {
return func(c *CookieHandler) {
c.domain = domain
}
}
func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, error) {
cookie, err := r.Cookie(name)
if err != nil {
return "", err
}
return cookie.Value, nil
}
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
cookie, err := r.Cookie(name)
if err != nil {
return err
}
if c.securecookie == nil {
return errors.ThrowInternal(nil, "HTTP-X6XpnL", "securecookie not configured")
}
return c.securecookie.Decode(name, cookie.Value, value)
}
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) {
c.httpSet(w, name, value, c.maxAge)
}
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, value interface{}) error {
if c.securecookie == nil {
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
}
encoded, err := c.securecookie.Encode(name, value)
if err != nil {
return err
}
c.httpSet(w, name, encoded, c.maxAge)
return nil
}
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
c.httpSet(w, name, "", -1)
}
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
http.SetCookie(w, &http.Cookie{
Name: name,
Value: value,
Domain: c.domain,
Path: c.path,
MaxAge: maxage,
HttpOnly: true,
Secure: c.secureOnly,
SameSite: c.sameSite,
})
}

View File

@ -0,0 +1,21 @@
package http
import (
"net"
"strings"
"github.com/caos/logging"
)
func CreateListener(endpoint string) net.Listener {
l, err := net.Listen("tcp", listenerEndpoint(endpoint))
logging.Log("SERVE-6vasef").OnError(err).Fatal("creating listener failed")
return l
}
func listenerEndpoint(endpoint string) string {
if strings.Contains(endpoint, ":") {
return endpoint
}
return ":" + endpoint
}

View File

@ -0,0 +1,46 @@
package middleware
import (
"net/http"
"github.com/rs/cors"
"github.com/caos/zitadel/internal/api"
)
var (
DefaultCORSOptions = cors.Options{
AllowCredentials: true,
AllowedHeaders: []string{
api.Origin,
api.ContentType,
api.Accept,
api.AcceptLanguage,
api.Authorization,
api.ZitadelOrgID,
},
AllowedMethods: []string{
http.MethodOptions,
http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
ExposedHeaders: []string{
api.Location,
},
AllowedOrigins: []string{
"http://localhost:*",
},
}
)
func CORSInterceptorOpts(opts cors.Options, h http.Handler) http.Handler {
return cors.New(opts).Handler(h)
}
func CORSInterceptor(h http.Handler) http.Handler {
return CORSInterceptorOpts(DefaultCORSOptions, h)
}

View File

@ -0,0 +1,12 @@
package middleware
import (
"net/http"
"github.com/caos/zitadel/internal/api"
"github.com/caos/zitadel/internal/tracing"
)
func DefaultTraceHandler(handler http.Handler) http.Handler {
return tracing.TraceHandler(handler, api.Probes...)
}

View File

@ -0,0 +1,28 @@
package http
import (
"net/http"
"github.com/gorilla/schema"
"github.com/caos/zitadel/internal/errors"
)
type Parser struct {
decoder *schema.Decoder
}
func NewParser() *Parser {
d := schema.NewDecoder()
d.IgnoreUnknownKeys(true)
return &Parser{d}
}
func (p *Parser) Parse(r *http.Request, data interface{}) error {
err := r.ParseForm()
if err != nil {
return errors.ThrowInternal(err, "FORM-lCC9zI", "error parsing http form")
}
return p.decoder.Decode(data, r.Form)
}

11
internal/api/probes.go Normal file
View File

@ -0,0 +1,11 @@
package api
const (
Healthz = "/Healthz"
Readiness = "/Ready"
Validation = "/Validate"
)
var (
Probes = []string{Healthz, Readiness, Validation}
)

4
internal/auth/config.go Normal file
View File

@ -0,0 +1,4 @@
package auth
type Config struct {
}

View File

@ -0,0 +1,21 @@
package config
import (
"flag"
"strings"
)
var _ flag.Value = (*ArrayFlags)(nil)
//ArrayFlags implements the flag/Value interface
//allowing to set multiple string flags with the same name
type ArrayFlags []string
func (i *ArrayFlags) String() string {
return strings.Join(*i, ";")
}
func (i *ArrayFlags) Set(value string) error {
*i = append(*i, value)
return nil
}

View File

@ -12,26 +12,16 @@ import (
"github.com/caos/zitadel/internal/errors"
)
type Reader interface {
Unmarshal(data []byte, o interface{}) error
}
type ValidatableConfiguration interface {
Validate() error
}
type ReaderFunc func(data []byte, o interface{}) error
func (c ReaderFunc) Unmarshal(data []byte, o interface{}) error {
return c(data, o)
}
var (
JSONReader = ReaderFunc(json.Unmarshal)
TOMLReader = ReaderFunc(toml.Unmarshal)
YAMLReader = ReaderFunc(func(y []byte, o interface{}) error {
return yaml.Unmarshal(y, o)
})
JSONReader = json.Unmarshal
TOMLReader = toml.Unmarshal
YAMLReader = func(data []byte, o interface{}) error { return yaml.Unmarshal(data, o) }
)
// Read deserializes each config file to the target obj
@ -39,11 +29,11 @@ var (
// env vars are replaced in the config file as well as the file path
func Read(obj interface{}, configFiles ...string) error {
for _, cf := range configFiles {
configReader, err := configReaderForFile(cf)
readerFunc, err := readerFuncForFile(cf)
if err != nil {
return err
}
if err := readConfigFile(configReader, cf, obj); err != nil {
if err := readConfigFile(readerFunc, cf, obj); err != nil {
return err
}
}
@ -57,13 +47,9 @@ func Read(obj interface{}, configFiles ...string) error {
return nil
}
func readConfigFile(configReader Reader, configFile string, obj interface{}) error {
func readConfigFile(readerFunc ReaderFunc, configFile string, obj interface{}) error {
configFile = os.ExpandEnv(configFile)
if _, err := os.Stat(configFile); err != nil {
return errors.ThrowNotFoundf(err, "CONFI-Hs93M", "config file %s does not exist", configFile)
}
configStr, err := ioutil.ReadFile(configFile)
if err != nil {
return errors.ThrowInternalf(err, "CONFI-nJk2a", "failed to read config file %s", configFile)
@ -71,14 +57,14 @@ func readConfigFile(configReader Reader, configFile string, obj interface{}) err
configStr = []byte(os.ExpandEnv(string(configStr)))
if err := configReader.Unmarshal(configStr, obj); err != nil {
if err := readerFunc(configStr, obj); err != nil {
return errors.ThrowInternalf(err, "CONFI-2Mc3c", "error parse config file %s", configFile)
}
return nil
}
func configReaderForFile(configFile string) (Reader, error) {
func readerFuncForFile(configFile string) (ReaderFunc, error) {
ext := filepath.Ext(configFile)
switch ext {
case ".yaml", ".yml":

View File

@ -0,0 +1,231 @@
package config
import (
"errors"
"reflect"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
type test struct {
Test bool
}
type validatable struct {
Test bool
}
type multiple struct {
Test bool
MoreData string
}
func (v *validatable) Validate() error {
if v.Test {
return nil
}
return errors.New("invalid")
}
func TestRead(t *testing.T) {
type args struct {
obj interface{}
configFiles []string
}
tests := []struct {
name string
args args
wantErr bool
want interface{}
}{
{
"not supoorted config file error",
args{
configFiles: []string{"notsupported.unknown"},
obj: &test{},
},
true,
&test{},
},
{
"non existing config file error",
args{
configFiles: []string{"nonexisting.yaml"},
obj: &test{},
},
true,
&test{},
},
{
"non parsable config file error",
args{
configFiles: []string{"./testdata/non_parsable.json"},
obj: &test{},
},
true,
&test{},
},
{
"invalid parsable config file error",
args{
configFiles: []string{"./testdata/invalid.json"},
obj: &validatable{},
},
true,
&validatable{},
},
{
"multiple files, one non parsable error ",
args{
configFiles: []string{"./testdata/non_parsable.json", "./testdata/more_data.yaml"},
obj: &multiple{},
},
true,
&multiple{},
},
{
"parsable config file ok",
args{
configFiles: []string{"./testdata/valid.json"},
obj: &test{},
},
false,
&test{Test: true},
},
{
"multiple parsable config files ok",
args{
configFiles: []string{"./testdata/valid.json", "./testdata/more_data.yaml"},
obj: &multiple{},
},
false,
&multiple{Test: true, MoreData: "data"},
},
{
"valid parsable config file ok",
args{
configFiles: []string{"./testdata/valid.json"},
obj: &validatable{},
},
false,
&validatable{Test: true},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := Read(tt.args.obj, tt.args.configFiles...); (err != nil) != tt.wantErr {
t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(tt.args.obj, tt.want) {
t.Errorf("Read() got = %v, want = %v", tt.args.obj, tt.want)
}
})
}
}
func Test_readerFuncForFile(t *testing.T) {
type args struct {
configFile string
}
tests := []struct {
name string
args args
want ReaderFunc
wantErr bool
}{
{
"unknown extension error",
args{configFile: "test.unknown"},
nil,
true,
},
{
"toml",
args{configFile: "test.toml"},
TOMLReader,
false,
},
{
"json",
args{configFile: "test.json"},
JSONReader,
false,
},
{
"yaml",
args{configFile: "test.yaml"},
YAMLReader,
false,
},
{
"yml",
args{configFile: "test.yml"},
YAMLReader,
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := readerFuncForFile(tt.args.configFile)
if (err != nil) != tt.wantErr {
t.Errorf("configReaderForFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
funcName1 := runtime.FuncForPC(reflect.ValueOf(got).Pointer()).Name()
funcName2 := runtime.FuncForPC(reflect.ValueOf(tt.want).Pointer()).Name()
if !assert.Equal(t, funcName1, funcName2) {
t.Errorf("configReaderForFile() got = %v, want %v", funcName1, funcName2)
}
})
}
}
func Test_readConfigFile(t *testing.T) {
type args struct {
configReader ReaderFunc
configFile string
obj interface{}
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"non existing config file error",
args{
configReader: YAMLReader,
configFile: "nonexisting.json",
obj: nil,
},
true,
},
{
"non parsable config file error",
args{
configReader: YAMLReader,
configFile: "./testdata/non_parsable.json",
obj: &test{},
},
true,
},
{
"parsable config file no error",
args{
configReader: YAMLReader,
configFile: "./testdata/valid.json",
obj: &test{},
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := readConfigFile(tt.args.configReader, tt.args.configFile, tt.args.obj); (err != nil) != tt.wantErr {
t.Errorf("readConfigFile() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

3
internal/config/testdata/invalid.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"Test" : false
}

View File

@ -0,0 +1 @@
MoreData: data

View File

@ -0,0 +1 @@
Test

3
internal/config/testdata/valid.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"Test" : true
}

View File

@ -0,0 +1,15 @@
package types
import (
"time"
)
type Duration struct {
time.Duration
}
func (d *Duration) UnmarshalText(data []byte) error {
var err error
d.Duration, err = time.ParseDuration(string(data))
return err
}

View File

@ -0,0 +1,46 @@
package types
import (
"testing"
"time"
)
func TestDuration_UnmarshalText(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
wantErr bool
want time.Duration
}{
{
"ok",
args{
data: []byte("10s"),
},
false,
time.Duration(10 * time.Second),
},
{
"error",
args{
data: []byte("10"),
},
true,
time.Duration(0),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Duration{}
if err := d.UnmarshalText(tt.args.data); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
}
if d.Duration != tt.want {
t.Errorf("UnmarshalText() got = %v, want %v", d.Duration, tt.want)
}
})
}
}

136
internal/crypto/aes.go Normal file
View File

@ -0,0 +1,136 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"io"
"github.com/caos/zitadel/internal/errors"
)
var _ EncryptionAlgorithm = (*AESCrypto)(nil)
type AESCrypto struct {
keys map[string]string
encryptionKeyID string
keyIDs []string
}
func NewAESCrypto(config *KeyConfig) (*AESCrypto, error) {
keys, ids, err := LoadKeys(config)
if err != nil {
return nil, err
}
return &AESCrypto{
keys: keys,
encryptionKeyID: config.EncryptionKeyID,
keyIDs: ids,
}, nil
}
func (a *AESCrypto) Algorithm() string {
return "aes"
}
func (a *AESCrypto) Encrypt(value []byte) ([]byte, error) {
return EncryptAES(value, a.encryptionKey())
}
func (a *AESCrypto) Decrypt(value []byte, keyID string) ([]byte, error) {
key, err := a.decryptionKey(keyID)
if err != nil {
return nil, err
}
return DecryptAES(value, key)
}
func (a *AESCrypto) DecryptString(value []byte, keyID string) (string, error) {
key, err := a.decryptionKey(keyID)
if err != nil {
return "", err
}
b, err := DecryptAES(value, key)
if err != nil {
return "", err
}
return string(b), nil
}
func (a *AESCrypto) EncryptionKeyID() string {
return a.encryptionKeyID
}
func (a *AESCrypto) DecryptionKeyIDs() []string {
return a.keyIDs
}
func (a *AESCrypto) encryptionKey() string {
return a.keys[a.encryptionKeyID]
}
func (a *AESCrypto) decryptionKey(keyID string) (string, error) {
key, ok := a.keys[keyID]
if !ok {
return "", errors.ThrowNotFound(nil, "CRYPT-nkj1s", "unknown key id")
}
return key, nil
}
func EncryptAESString(data string, key string) (string, error) {
encrypted, err := EncryptAES([]byte(data), key)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(encrypted), nil
}
func EncryptAES(plainText []byte, key string) ([]byte, error) {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return nil, err
}
cipherText := make([]byte, aes.BlockSize+len(plainText))
iv := cipherText[:aes.BlockSize]
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(cipherText[aes.BlockSize:], plainText)
return cipherText, nil
}
func DecryptAESString(data string, key string) (string, error) {
text, err := base64.URLEncoding.DecodeString(data)
if err != nil {
return "", nil
}
decrypted, err := DecryptAES(text, key)
if err != nil {
return "", err
}
return string(decrypted), nil
}
func DecryptAES(cipherText []byte, key string) ([]byte, error) {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return nil, err
}
if len(cipherText) < aes.BlockSize {
err = errors.ThrowPreconditionFailed(nil, "CRYPT-23kH1", "cipher text block too short")
return nil, err
}
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(cipherText, cipherText)
return cipherText, err
}

View File

@ -0,0 +1,18 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
//TODO: refactor test style
func TestDecrypt_OK(t *testing.T) {
encryptedpw, err := EncryptAESString("ThisIsMySecretPw", "passphrasewhichneedstobe32bytes!")
assert.NoError(t, err)
decryptedpw, err := DecryptAESString(encryptedpw, "passphrasewhichneedstobe32bytes!")
assert.NoError(t, err)
assert.Equal(t, "ThisIsMySecretPw", decryptedpw)
}

27
internal/crypto/bcrypt.go Normal file
View File

@ -0,0 +1,27 @@
package crypto
import (
"golang.org/x/crypto/bcrypt"
)
var _ HashAlgorithm = (*BCrypt)(nil)
type BCrypt struct {
cost int
}
func NewBCrypt(cost int) *BCrypt {
return &BCrypt{cost: cost}
}
func (b *BCrypt) Algorithm() string {
return "bcrypt"
}
func (b *BCrypt) Hash(value []byte) ([]byte, error) {
return bcrypt.GenerateFromPassword(value, b.cost)
}
func (b *BCrypt) CompareHash(hashed, value []byte) error {
return bcrypt.CompareHashAndPassword(hashed, value)
}

159
internal/crypto/code.go Normal file
View File

@ -0,0 +1,159 @@
package crypto
import (
"crypto/rand"
"time"
"github.com/caos/zitadel/internal/errors"
)
var (
LowerLetters = []rune("abcdefghijklmnopqrstuvwxyz")
UpperLetters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
Digits = []rune("0123456789")
Symbols = []rune("~!@#$^&*()_+`-={}|[]:<>?,./")
)
type Generator interface {
Length() uint
Expiry() time.Duration
Alg() Crypto
Runes() []rune
}
type EncryptionGenerator struct {
length uint
expiry time.Duration
alg EncryptionAlgorithm
runes []rune
}
func (g *EncryptionGenerator) Length() uint {
return g.length
}
func (g *EncryptionGenerator) Expiry() time.Duration {
return g.expiry
}
func (g *EncryptionGenerator) Alg() Crypto {
return g.alg
}
func (g *EncryptionGenerator) Runes() []rune {
return g.runes
}
func NewEncryptionGenerator(length uint, expiry time.Duration, alg EncryptionAlgorithm, runes []rune) *EncryptionGenerator {
return &EncryptionGenerator{
length: length,
expiry: expiry,
alg: alg,
runes: runes,
}
}
type HashGenerator struct {
length uint
expiry time.Duration
alg HashAlgorithm
runes []rune
}
func (g *HashGenerator) Length() uint {
return g.length
}
func (g *HashGenerator) Expiry() time.Duration {
return g.expiry
}
func (g *HashGenerator) Alg() Crypto {
return g.alg
}
func (g *HashGenerator) Runes() []rune {
return g.runes
}
func NewHashGenerator(length uint, expiry time.Duration, alg HashAlgorithm, runes []rune) *HashGenerator {
return &HashGenerator{
length: length,
expiry: expiry,
alg: alg,
runes: runes,
}
}
func NewCode(g Generator) (*CryptoValue, string, error) {
code, err := generateRandomString(g.Length(), g.Runes())
if err != nil {
return nil, "", err
}
crypto, err := Crypt([]byte(code), g.Alg())
if err != nil {
return nil, "", err
}
return crypto, code, nil
}
func IsCodeExpired(creationDate time.Time, expiry time.Duration) bool {
return creationDate.Add(expiry).Before(time.Now().UTC())
}
func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, g Generator) error {
if IsCodeExpired(creationDate, expiry) {
return errors.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "verification code is expired")
}
switch alg := g.Alg().(type) {
case EncryptionAlgorithm:
return verifyEncryptedCode(cryptoCode, verificationCode, alg)
case HashAlgorithm:
return verifyHashedCode(cryptoCode, verificationCode, alg)
}
return errors.ThrowInvalidArgument(nil, "CODE-fW2gNa", "generator alg is not supported")
}
func generateRandomString(length uint, chars []rune) (string, error) {
if length == 0 {
return "", nil
}
max := len(chars) - 1
maxStr := int(length - 1)
str := make([]rune, length)
randBytes := make([]byte, length)
if _, err := rand.Read(randBytes); err != nil {
return "", err
}
for i, rb := range randBytes {
str[i] = chars[int(rb)%max]
if i == maxStr {
return string(str), nil
}
}
return "", nil
}
func verifyEncryptedCode(cryptoCode *CryptoValue, verificationCode string, alg EncryptionAlgorithm) error {
if cryptoCode == nil {
return errors.ThrowInvalidArgument(nil, "CRYPT-aqrFV", "cryptoCode must not be nil")
}
code, err := DecryptString(cryptoCode, alg)
if err != nil {
return err
}
if code != verificationCode {
return errors.ThrowInvalidArgument(nil, "CODE-woT0xc", "verification code is invalid")
}
return nil
}
func verifyHashedCode(cryptoCode *CryptoValue, verificationCode string, alg HashAlgorithm) error {
if cryptoCode == nil {
return errors.ThrowInvalidArgument(nil, "CRYPT-2q3r", "cryptoCode must not be nil")
}
return CompareHash(cryptoCode, []byte(verificationCode), alg)
}

View File

@ -0,0 +1,90 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: code.go
// Package crypto is a generated GoMock package.
package crypto
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
time "time"
)
// MockGenerator is a mock of Generator interface
type MockGenerator struct {
ctrl *gomock.Controller
recorder *MockGeneratorMockRecorder
}
// MockGeneratorMockRecorder is the mock recorder for MockGenerator
type MockGeneratorMockRecorder struct {
mock *MockGenerator
}
// NewMockGenerator creates a new mock instance
func NewMockGenerator(ctrl *gomock.Controller) *MockGenerator {
mock := &MockGenerator{ctrl: ctrl}
mock.recorder = &MockGeneratorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder {
return m.recorder
}
// Length mocks base method
func (m *MockGenerator) Length() uint {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Length")
ret0, _ := ret[0].(uint)
return ret0
}
// Length indicates an expected call of Length
func (mr *MockGeneratorMockRecorder) Length() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Length", reflect.TypeOf((*MockGenerator)(nil).Length))
}
// Expiry mocks base method
func (m *MockGenerator) Expiry() time.Duration {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Expiry")
ret0, _ := ret[0].(time.Duration)
return ret0
}
// Expiry indicates an expected call of Expiry
func (mr *MockGeneratorMockRecorder) Expiry() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Expiry", reflect.TypeOf((*MockGenerator)(nil).Expiry))
}
// Alg mocks base method
func (m *MockGenerator) Alg() Crypto {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Alg")
ret0, _ := ret[0].(Crypto)
return ret0
}
// Alg indicates an expected call of Alg
func (mr *MockGeneratorMockRecorder) Alg() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Alg", reflect.TypeOf((*MockGenerator)(nil).Alg))
}
// Runes mocks base method
func (m *MockGenerator) Runes() []rune {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Runes")
ret0, _ := ret[0].([]rune)
return ret0
}
// Runes indicates an expected call of Runes
func (mr *MockGeneratorMockRecorder) Runes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Runes", reflect.TypeOf((*MockGenerator)(nil).Runes))
}

View File

@ -0,0 +1,352 @@
package crypto
import (
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/caos/zitadel/internal/errors"
)
func createMockEncryptionAlg(t *testing.T) EncryptionAlgorithm {
mCrypto := NewMockEncryptionAlgorithm(gomock.NewController(t))
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
mCrypto.EXPECT().Encrypt(gomock.Any()).DoAndReturn(
func(code []byte) ([]byte, error) {
return code, nil
},
)
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).DoAndReturn(
func(code []byte, keyID string) (string, error) {
if keyID != "id" {
return "", errors.ThrowInternal(nil, "id", "invalid key id")
}
return string(code), nil
},
)
return mCrypto
}
func createMockHashAlg(t *testing.T) HashAlgorithm {
mCrypto := NewMockHashAlgorithm(gomock.NewController(t))
mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash")
mCrypto.EXPECT().Hash(gomock.Any()).DoAndReturn(
func(code []byte) ([]byte, error) {
return code, nil
},
)
mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).DoAndReturn(
func(hashed, comparer []byte) error {
if string(hashed) != string(comparer) {
return errors.ThrowInternal(nil, "id", "invalid")
}
return nil
},
)
return mCrypto
}
func createMockCrypto(t *testing.T) Crypto {
mCrypto := NewMockCrypto(gomock.NewController(t))
mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto")
return mCrypto
}
func createMockGenerator(t *testing.T, crypto Crypto) Generator {
mGenerator := NewMockGenerator(gomock.NewController(t))
mGenerator.EXPECT().Alg().AnyTimes().Return(crypto)
return mGenerator
}
func TestIsCodeExpired(t *testing.T) {
type args struct {
creationDate time.Time
expiry time.Duration
}
tests := []struct {
name string
args args
want bool
}{
{
"not expired",
args{
creationDate: time.Now(),
expiry: time.Duration(5 * time.Minute),
},
false,
},
{
"expired",
args{
creationDate: time.Now().Add(-5 * time.Minute),
expiry: time.Duration(5 * time.Minute),
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsCodeExpired(tt.args.creationDate, tt.args.expiry); got != tt.want {
t.Errorf("IsCodeExpired() = %v, want %v", got, tt.want)
}
})
}
}
func TestVerifyCode(t *testing.T) {
type args struct {
creationDate time.Time
expiry time.Duration
cryptoCode *CryptoValue
verificationCode string
g Generator
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"expired",
args{
creationDate: time.Now().Add(-5 * time.Minute),
expiry: 5 * time.Minute,
cryptoCode: nil,
verificationCode: "",
g: nil,
},
true,
},
{
"unsupported alg err",
args{
creationDate: time.Now(),
expiry: 5 * time.Minute,
cryptoCode: nil,
verificationCode: "code",
g: createMockGenerator(t, createMockCrypto(t)),
},
true,
},
{
"encryption alg ok",
args{
creationDate: time.Now(),
expiry: 5 * time.Minute,
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code"),
},
verificationCode: "code",
g: createMockGenerator(t, createMockEncryptionAlg(t)),
},
false,
},
{
"hash alg ok",
args{
creationDate: time.Now(),
expiry: 5 * time.Minute,
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash",
Crypted: []byte("code"),
},
verificationCode: "code",
g: createMockGenerator(t, createMockHashAlg(t)),
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g); (err != nil) != tt.wantErr {
t.Errorf("VerifyCode() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_verifyEncryptedCode(t *testing.T) {
type args struct {
cryptoCode *CryptoValue
verificationCode string
alg EncryptionAlgorithm
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"nil error",
args{
cryptoCode: nil,
verificationCode: "",
alg: createMockEncryptionAlg(t),
},
true,
},
{
"wrong cryptotype error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Crypted: nil,
},
verificationCode: "",
alg: createMockEncryptionAlg(t),
},
true,
},
{
"wrong algorithm error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Algorithm: "enc2",
Crypted: nil,
},
verificationCode: "",
alg: createMockEncryptionAlg(t),
},
true,
},
{
"wrong key id error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Algorithm: "enc",
Crypted: nil,
},
verificationCode: "wrong",
alg: createMockEncryptionAlg(t),
},
true,
},
{
"wrong verification code error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code"),
},
verificationCode: "wrong",
alg: createMockEncryptionAlg(t),
},
true,
},
{
"verification code ok",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code"),
},
verificationCode: "code",
alg: createMockEncryptionAlg(t),
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := verifyEncryptedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr {
t.Errorf("verifyEncryptedCode() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_verifyHashedCode(t *testing.T) {
type args struct {
cryptoCode *CryptoValue
verificationCode string
alg HashAlgorithm
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"nil error",
args{
cryptoCode: nil,
verificationCode: "",
alg: createMockHashAlg(t),
},
true,
},
{
"wrong cryptotype error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Crypted: nil,
},
verificationCode: "",
alg: createMockHashAlg(t),
},
true,
},
{
"wrong algorithm error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash2",
Crypted: nil,
},
verificationCode: "",
alg: createMockHashAlg(t),
},
true,
},
{
"wrong verification code error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash",
Crypted: []byte("code"),
},
verificationCode: "wrong",
alg: createMockHashAlg(t),
},
true,
},
{
"verification code ok",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash",
Crypted: []byte("code"),
},
verificationCode: "code",
alg: createMockHashAlg(t),
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := verifyHashedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr {
t.Errorf("verifyHashedCode() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

106
internal/crypto/crypto.go Normal file
View File

@ -0,0 +1,106 @@
package crypto
import (
"github.com/caos/zitadel/internal/errors"
)
const (
TypeEncryption CryptoType = iota
TypeHash
)
type Crypto interface {
Algorithm() string
}
type EncryptionAlgorithm interface {
Crypto
EncryptionKeyID() string
DecryptionKeyIDs() []string
Encrypt(value []byte) ([]byte, error)
Decrypt(hashed []byte, keyID string) ([]byte, error)
DecryptString(hashed []byte, keyID string) (string, error)
}
type HashAlgorithm interface {
Crypto
Hash(value []byte) ([]byte, error)
CompareHash(hashed, comparer []byte) error
}
type CryptoValue struct {
CryptoType CryptoType
Algorithm string
KeyID string
Crypted []byte
}
type CryptoType int
func Crypt(value []byte, c Crypto) (*CryptoValue, error) {
switch alg := c.(type) {
case EncryptionAlgorithm:
return Encrypt(value, alg)
case HashAlgorithm:
return Hash(value, alg)
}
return nil, errors.ThrowInternal(nil, "CRYPT-r4IaHZ", "algorithm not supported")
}
func Encrypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) {
encrypted, err := alg.Encrypt(value)
if err != nil {
return nil, errors.ThrowInternal(err, "CRYPT-qCD0JB", "error encrypting value")
}
return &CryptoValue{
CryptoType: TypeEncryption,
Algorithm: alg.Algorithm(),
KeyID: alg.EncryptionKeyID(),
Crypted: encrypted,
}, nil
}
func Decrypt(value *CryptoValue, alg EncryptionAlgorithm) ([]byte, error) {
if err := checkEncryptionAlgorithm(value, alg); err != nil {
return nil, err
}
return alg.Decrypt(value.Crypted, value.KeyID)
}
func DecryptString(value *CryptoValue, alg EncryptionAlgorithm) (string, error) {
if err := checkEncryptionAlgorithm(value, alg); err != nil {
return "", err
}
return alg.DecryptString(value.Crypted, value.KeyID)
}
func checkEncryptionAlgorithm(value *CryptoValue, alg EncryptionAlgorithm) error {
if value.Algorithm != alg.Algorithm() {
return errors.ThrowInvalidArgument(nil, "CRYPT-Nx7XlT", "value was encrypted with a different key")
}
for _, id := range alg.DecryptionKeyIDs() {
if id == value.KeyID {
return nil
}
}
return errors.ThrowInvalidArgument(nil, "CRYPT-Kq12vn", "value was encrypted with a different key")
}
func Hash(value []byte, alg HashAlgorithm) (*CryptoValue, error) {
hashed, err := alg.Hash(value)
if err != nil {
return nil, errors.ThrowInternal(err, "CRYPT-rBVaJU", "error hashing value")
}
return &CryptoValue{
CryptoType: TypeHash,
Algorithm: alg.Algorithm(),
Crypted: hashed,
}, nil
}
func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error {
if value.Algorithm != alg.Algorithm() {
return errors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hash with a different algorithm")
}
return alg.CompareHash(value.Crypted, comparer)
}

View File

@ -0,0 +1,223 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: crypto.go
// Package crypto is a generated GoMock package.
package crypto
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockCrypto is a mock of Crypto interface
type MockCrypto struct {
ctrl *gomock.Controller
recorder *MockCryptoMockRecorder
}
// MockCryptoMockRecorder is the mock recorder for MockCrypto
type MockCryptoMockRecorder struct {
mock *MockCrypto
}
// NewMockCrypto creates a new mock instance
func NewMockCrypto(ctrl *gomock.Controller) *MockCrypto {
mock := &MockCrypto{ctrl: ctrl}
mock.recorder = &MockCryptoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCrypto) EXPECT() *MockCryptoMockRecorder {
return m.recorder
}
// Algorithm mocks base method
func (m *MockCrypto) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm
func (mr *MockCryptoMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockCrypto)(nil).Algorithm))
}
// MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface
type MockEncryptionAlgorithm struct {
ctrl *gomock.Controller
recorder *MockEncryptionAlgorithmMockRecorder
}
// MockEncryptionAlgorithmMockRecorder is the mock recorder for MockEncryptionAlgorithm
type MockEncryptionAlgorithmMockRecorder struct {
mock *MockEncryptionAlgorithm
}
// NewMockEncryptionAlgorithm creates a new mock instance
func NewMockEncryptionAlgorithm(ctrl *gomock.Controller) *MockEncryptionAlgorithm {
mock := &MockEncryptionAlgorithm{ctrl: ctrl}
mock.recorder = &MockEncryptionAlgorithmMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockEncryptionAlgorithm) EXPECT() *MockEncryptionAlgorithmMockRecorder {
return m.recorder
}
// Algorithm mocks base method
func (m *MockEncryptionAlgorithm) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm
func (mr *MockEncryptionAlgorithmMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Algorithm))
}
// EncryptionKeyID mocks base method
func (m *MockEncryptionAlgorithm) EncryptionKeyID() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EncryptionKeyID")
ret0, _ := ret[0].(string)
return ret0
}
// EncryptionKeyID indicates an expected call of EncryptionKeyID
func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID))
}
// DecryptionKeyIDs mocks base method
func (m *MockEncryptionAlgorithm) DecryptionKeyIDs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptionKeyIDs")
ret0, _ := ret[0].([]string)
return ret0
}
// DecryptionKeyIDs indicates an expected call of DecryptionKeyIDs
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptionKeyIDs() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptionKeyIDs", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptionKeyIDs))
}
// Encrypt mocks base method
func (m *MockEncryptionAlgorithm) Encrypt(value []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Encrypt", value)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Encrypt indicates an expected call of Encrypt
func (mr *MockEncryptionAlgorithmMockRecorder) Encrypt(value interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Encrypt), value)
}
// Decrypt mocks base method
func (m *MockEncryptionAlgorithm) Decrypt(hashed []byte, keyID string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Decrypt", hashed, keyID)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Decrypt indicates an expected call of Decrypt
func (mr *MockEncryptionAlgorithmMockRecorder) Decrypt(hashed, keyID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Decrypt), hashed, keyID)
}
// DecryptString mocks base method
func (m *MockEncryptionAlgorithm) DecryptString(hashed []byte, keyID string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptString", hashed, keyID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecryptString indicates an expected call of DecryptString
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptString(hashed, keyID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptString", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptString), hashed, keyID)
}
// MockHashAlgorithm is a mock of HashAlgorithm interface
type MockHashAlgorithm struct {
ctrl *gomock.Controller
recorder *MockHashAlgorithmMockRecorder
}
// MockHashAlgorithmMockRecorder is the mock recorder for MockHashAlgorithm
type MockHashAlgorithmMockRecorder struct {
mock *MockHashAlgorithm
}
// NewMockHashAlgorithm creates a new mock instance
func NewMockHashAlgorithm(ctrl *gomock.Controller) *MockHashAlgorithm {
mock := &MockHashAlgorithm{ctrl: ctrl}
mock.recorder = &MockHashAlgorithmMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockHashAlgorithm) EXPECT() *MockHashAlgorithmMockRecorder {
return m.recorder
}
// Algorithm mocks base method
func (m *MockHashAlgorithm) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm
func (mr *MockHashAlgorithmMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockHashAlgorithm)(nil).Algorithm))
}
// Hash mocks base method
func (m *MockHashAlgorithm) Hash(value []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Hash", value)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Hash indicates an expected call of Hash
func (mr *MockHashAlgorithmMockRecorder) Hash(value interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHashAlgorithm)(nil).Hash), value)
}
// CompareHash mocks base method
func (m *MockHashAlgorithm) CompareHash(hashed, comparer []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CompareHash", hashed, comparer)
ret0, _ := ret[0].(error)
return ret0
}
// CompareHash indicates an expected call of CompareHash
func (mr *MockHashAlgorithmMockRecorder) CompareHash(hashed, comparer interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareHash", reflect.TypeOf((*MockHashAlgorithm)(nil).CompareHash), hashed, comparer)
}

View File

@ -0,0 +1,273 @@
package crypto
import (
"bytes"
"errors"
"reflect"
"testing"
)
type mockEncCrypto struct {
}
func (m *mockEncCrypto) Algorithm() string {
return "enc"
}
func (m *mockEncCrypto) Encrypt(value []byte) ([]byte, error) {
return value, nil
}
func (m *mockEncCrypto) Decrypt(value []byte, _ string) ([]byte, error) {
return value, nil
}
func (m *mockEncCrypto) DecryptString(value []byte, _ string) (string, error) {
return string(value), nil
}
func (m *mockEncCrypto) EncryptionKeyID() string {
return "keyID"
}
func (m *mockEncCrypto) DecryptionKeyIDs() []string {
return []string{"keyID"}
}
type mockHashCrypto struct {
}
func (m *mockHashCrypto) Algorithm() string {
return "hash"
}
func (m *mockHashCrypto) Hash(value []byte) ([]byte, error) {
return value, nil
}
func (m *mockHashCrypto) CompareHash(hashed, comparer []byte) error {
if !bytes.Equal(hashed, comparer) {
return errors.New("not equal")
}
return nil
}
type alg struct{}
func (a *alg) Algorithm() string {
return "alg"
}
func TestCrypt(t *testing.T) {
type args struct {
value []byte
c Crypto
}
tests := []struct {
name string
args args
want *CryptoValue
wantErr bool
}{
{
"encrypt",
args{[]byte("test"), &mockEncCrypto{}},
&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")},
false,
},
{
"hash",
args{[]byte("test"), &mockHashCrypto{}},
&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")},
false,
},
{
"wrong type",
args{[]byte("test"), &alg{}},
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Crypt(tt.args.value, tt.args.c)
if (err != nil) != tt.wantErr {
t.Errorf("Crypt() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Crypt() = %v, want %v", got, tt.want)
}
})
}
}
func TestEncrypt(t *testing.T) {
type args struct {
value []byte
c EncryptionAlgorithm
}
tests := []struct {
name string
args args
want *CryptoValue
wantErr bool
}{
{
"ok",
args{[]byte("test"), &mockEncCrypto{}},
&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Encrypt(tt.args.value, tt.args.c)
if (err != nil) != tt.wantErr {
t.Errorf("Encrypt() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Encrypt() = %v, want %v", got, tt.want)
}
})
}
}
func TestDecrypt(t *testing.T) {
type args struct {
value *CryptoValue
c EncryptionAlgorithm
}
tests := []struct {
name string
args args
want []byte
wantErr bool
}{
{
"ok",
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, &mockEncCrypto{}},
[]byte("test"),
false,
},
{
"wrong id",
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID2", Crypted: []byte("test")}, &mockEncCrypto{}},
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Decrypt(tt.args.value, tt.args.c)
if (err != nil) != tt.wantErr {
t.Errorf("Decrypt() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Decrypt() = %v, want %v", got, tt.want)
}
})
}
}
func TestDecryptString(t *testing.T) {
type args struct {
value *CryptoValue
c EncryptionAlgorithm
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
"ok",
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, &mockEncCrypto{}},
"test",
false,
},
{
"wrong id",
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID2", Crypted: []byte("test")}, &mockEncCrypto{}},
"",
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := DecryptString(tt.args.value, tt.args.c)
if (err != nil) != tt.wantErr {
t.Errorf("DecryptString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("DecryptString() = %v, want %v", got, tt.want)
}
})
}
}
func TestHash(t *testing.T) {
type args struct {
value []byte
c HashAlgorithm
}
tests := []struct {
name string
args args
want *CryptoValue
wantErr bool
}{
{
"ok",
args{[]byte("test"), &mockHashCrypto{}},
&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Hash(tt.args.value, tt.args.c)
if (err != nil) != tt.wantErr {
t.Errorf("Hash() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Hash() = %v, want %v", got, tt.want)
}
})
}
}
func TestCompareHash(t *testing.T) {
type args struct {
value *CryptoValue
comparer []byte
c HashAlgorithm
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"ok",
args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test"), &mockHashCrypto{}},
false,
},
{
"wrong",
args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test2"), &mockHashCrypto{}},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CompareHash(tt.args.value, tt.args.comparer, tt.args.c); (err != nil) != tt.wantErr {
t.Errorf("CompareHash() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -0,0 +1,4 @@
package crypto
//go:generate mockgen -source crypto.go -destination ./crypto_mock.go -package crypto
//go:generate mockgen -source code.go -destination ./code_mock.go -package crypto

64
internal/crypto/key.go Normal file
View File

@ -0,0 +1,64 @@
package crypto
import (
"os"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/config"
"github.com/caos/zitadel/internal/errors"
)
const (
ZitadelKeyPath = "ZITADEL_KEY_PATH"
)
type KeyConfig struct {
EncryptionKeyID string
DecryptionKeyIDs []string
Path string
}
type Keys map[string]string
func ReadKeys(path string) (Keys, error) {
if path == "" {
path = os.Getenv(ZitadelKeyPath)
if path == "" {
return nil, errors.ThrowInvalidArgument(nil, "CRYPT-56lka", "no path set")
}
}
keys := new(Keys)
err := config.Read(keys, path)
return *keys, err
}
func LoadKeys(config *KeyConfig) (map[string]string, []string, error) {
if config == nil {
return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be nil")
}
readKeys, err := ReadKeys(config.Path)
if err != nil {
return nil, nil, err
}
keys := make(map[string]string)
ids := make([]string, 0, len(config.DecryptionKeyIDs)+1)
if config.EncryptionKeyID != "" {
key, ok := readKeys[config.EncryptionKeyID]
if !ok {
return nil, nil, errors.ThrowInternalf(nil, "CRYPT-v2Kas", "encryption key not found")
}
keys[config.EncryptionKeyID] = key
ids = append(ids, config.EncryptionKeyID)
}
for _, id := range config.DecryptionKeyIDs {
key, ok := readKeys[id]
if !ok {
logging.Log("CRYPT-s23rf").Warnf("description key %s not found", id)
continue
}
keys[id] = key
ids = append(ids, id)
}
return keys, ids, nil
}

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestAlreadyExistsError(t *testing.T) {

View File

@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestErrorMethod(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestDeadlineExceededError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errors "github.com/caos/utils/errors"
caos_errors "github.com/caos/zitadel/internal/errors"
)
func TestContains(t *testing.T) {

View File

@ -32,7 +32,7 @@ func main() {
fmt.Print(`
!!!!!
Add status mapping in grpc/errors/caos_errors.go //TODO: fix path when pkg exists
Add status mapping in internal/api/grpc/caos_errors.go
!!!!!`)
}

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func Test{{.ErrorName}}Error(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestInternalError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestInvalidArgumentError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestNotFoundError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestPermissionDeniedError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestPreconditionFailedError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestUnauthenticatedError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestUnavailableError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestUnimplementedError(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/utils/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestUnknownError(t *testing.T) {

4
internal/login/config.go Normal file
View File

@ -0,0 +1,4 @@
package login
type Config struct {
}

View File

@ -0,0 +1,3 @@
package management
type Config struct{}

50
internal/proto/struct.go Normal file
View File

@ -0,0 +1,50 @@
package proto
import (
"bytes"
"encoding/json"
"github.com/golang/protobuf/jsonpb"
pb_struct "github.com/golang/protobuf/ptypes/struct"
"github.com/caos/logging"
)
var (
marshaller = new(jsonpb.Marshaler)
)
func MustToPBStruct(object interface{}) *pb_struct.Struct {
s, err := ToPBStruct(object)
logging.Log("PROTO-7Aa3t").OnError(err).Panic("unable to map object to pb-struct")
return s
}
func BytesToPBStruct(b []byte) (*pb_struct.Struct, error) {
fields := new(pb_struct.Struct)
err := jsonpb.Unmarshal(bytes.NewReader(b), fields)
return fields, err
}
func ToPBStruct(object interface{}) (*pb_struct.Struct, error) {
marshalled, err := json.Marshal(object)
if err != nil {
return nil, err
}
fields := new(pb_struct.Struct)
err = jsonpb.Unmarshal(bytes.NewReader(marshalled), fields)
return fields, err
}
func MustFromPBStruct(object interface{}, s *pb_struct.Struct) {
err := FromPBStruct(object, s)
logging.Log("PROTO-WeMYY").OnError(err).Panic("unable to map pb-struct into object")
}
func FromPBStruct(object interface{}, s *pb_struct.Struct) error {
jsonString, err := marshaller.MarshalToString(s)
if err != nil {
return err
}
return json.Unmarshal([]byte(jsonString), object)
}

View File

@ -0,0 +1,115 @@
package proto
import (
"testing"
pb_struct "github.com/golang/protobuf/ptypes/struct"
)
func Test_ToPBStruct(t *testing.T) {
type obj struct {
ID string
Seq uint64
}
type args struct {
obj obj
}
tests := []struct {
name string
args args
wantErr bool
length int
result obj
}{
{
name: "to pb stuct",
args: args{
obj: obj{ID: "ID", Seq: 12345},
},
wantErr: false,
length: 2,
result: obj{ID: "ID", Seq: 12345},
},
{
name: "empty struct",
args: args{
obj: obj{},
},
wantErr: false,
length: 2,
result: obj{ID: "", Seq: 0},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fields, err := ToPBStruct(tt.args.obj)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)
}
if !tt.wantErr && len(fields.Fields) != tt.length {
t.Errorf("got wrong result length, expecting: %v, actual: %v ", tt.length, len(fields.Fields))
}
if !tt.wantErr && tt.result.ID != fields.Fields["ID"].GetStringValue() {
t.Errorf("got wrong result, ID should be same: expecting: %v, actual: %v ", tt.result.ID, fields.Fields["ID"].GetStringValue())
}
if !tt.wantErr && int(tt.result.Seq) != int(fields.Fields["Seq"].GetNumberValue()) {
t.Errorf("got wrong result, Seq should be same: expecting: %v, actual: %v ", tt.result.Seq, fields.Fields["Seq"].GetStringValue())
}
})
}
}
func Test_FromPBStruct(t *testing.T) {
type obj struct {
ID string
Seq uint64
}
type args struct {
obj *obj
fields *pb_struct.Struct
}
tests := []struct {
name string
args args
wantErr bool
result obj
}{
{
name: "from pb stuct",
args: args{
obj: &obj{},
fields: &pb_struct.Struct{Fields: map[string]*pb_struct.Value{
"ID": &pb_struct.Value{Kind: &pb_struct.Value_StringValue{StringValue: "ID"}},
"Seq": &pb_struct.Value{Kind: &pb_struct.Value_NumberValue{NumberValue: 12345}},
},
},
},
wantErr: false,
result: obj{ID: "ID", Seq: 12345},
},
{
name: "no fields",
args: args{
obj: &obj{},
fields: &pb_struct.Struct{Fields: map[string]*pb_struct.Value{},
},
},
wantErr: false,
result: obj{ID: "", Seq: 0},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := FromPBStruct(tt.args.obj, tt.args.fields)
if tt.wantErr && err == nil {
t.Errorf("got wrong result, should get err: actual: %v ", err)
}
if !tt.wantErr && tt.result.ID != tt.args.obj.ID {
t.Errorf("got wrong result, ID should be same: expecting: %v, actual: %v ", tt.result.ID, tt.args.obj.ID)
}
if !tt.wantErr && int(tt.result.Seq) != int(tt.args.obj.Seq) {
t.Errorf("got wrong result, Seq should be same: expecting: %v, actual: %v ", tt.result.Seq, tt.args.obj.Seq)
}
})
}
}

View File

@ -0,0 +1,112 @@
package protocbase
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"text/template"
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
)
type GeneratorFunc func(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error)
type ProtocGenerator interface {
Generate(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error)
}
func (f GeneratorFunc) Generate(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error) {
return f(target, registry, file)
}
func parseReq(r io.Reader) (*plugin.CodeGeneratorRequest, error) {
glog.V(1).Info("Parsing code generator request")
input, err := ioutil.ReadAll(r)
if err != nil {
glog.Errorf("Failed to read code generator request: %v", err)
return nil, err
}
req := &plugin.CodeGeneratorRequest{}
if err = proto.Unmarshal(input, req); err != nil {
glog.Errorf("Failed to unmarshal code generator request: %v", err)
return nil, err
}
glog.V(1).Info("Parsed code generator request")
return req, nil
}
func RunWithBaseTemplate(targetFileNameFmt string, tmpl *template.Template) {
Run(GeneratorFunc(func(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error) {
fileName := fmt.Sprintf(targetFileNameFmt, strings.Split(target, ".")[0])
fContent, err := GenerateFromBaseTemplate(tmpl, registry, file)
return fileName, fContent, err
}))
}
func Run(generator ProtocGenerator) {
flag.Parse()
defer glog.Flush()
req, err := parseReq(os.Stdin)
if err != nil {
glog.Fatal(err)
}
registry := descriptor.NewRegistry()
if err = registry.Load(req); err != nil {
glog.Fatal(err)
}
var result []*plugin.CodeGeneratorResponse_File
for _, t := range req.FileToGenerate {
file, err := registry.LookupFile(t)
if err != nil {
EmitError(err)
return
}
fName, fContent, err := generator.Generate(t, registry, file)
if err != nil {
EmitError(err)
return
}
result = append(result, &plugin.CodeGeneratorResponse_File{
Name: &fName,
Content: &fContent,
})
}
EmitFiles(result)
}
func EmitFiles(out []*plugin.CodeGeneratorResponse_File) {
EmitResp(&plugin.CodeGeneratorResponse{File: out})
}
func EmitError(err error) {
EmitResp(&plugin.CodeGeneratorResponse{Error: proto.String(err.Error())})
}
func EmitResp(resp *plugin.CodeGeneratorResponse) {
buf, err := proto.Marshal(resp)
if err != nil {
glog.Fatal(err)
}
if _, err := os.Stdout.Write(buf); err != nil {
glog.Fatal(err)
}
}

View File

@ -0,0 +1,106 @@
package protocbase
import (
"bytes"
"fmt"
"text/template"
"time"
"github.com/Masterminds/sprig"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
"golang.org/x/tools/imports"
)
var extensions = map[string]*proto.ExtensionDesc{}
type BaseTemplateData struct {
Now time.Time
File *descriptor.File
registry *descriptor.Registry
}
var templateFuncs = map[string]interface{}{
"option": getOption,
}
func RegisterTmplFunc(name string, f interface{}) {
if _, existing := templateFuncs[name]; existing {
panic(fmt.Sprintf("func with name %v is already registered", name))
}
templateFuncs[name] = f
}
func RegisterExtension(ext *proto.ExtensionDesc) {
extensions[ext.Name] = ext
}
func GetBaseTemplateData(registry *descriptor.Registry, file *descriptor.File) *BaseTemplateData {
return &BaseTemplateData{
Now: time.Now().UTC(),
File: file,
registry: registry,
}
}
func getOption(opts proto.Message, extName string) interface{} {
extDesc := extensions[extName]
if !proto.HasExtension(opts, extDesc) {
return nil
}
ext, err := proto.GetExtension(opts, extDesc)
if err != nil {
panic(err)
}
return ext
}
func (data *BaseTemplateData) ResolveMsgType(msgType string) string {
msg, err := data.registry.LookupMsg(data.File.GetPackage(), msgType)
if err != nil {
panic(err)
}
return msg.GoType(data.File.GoPkg.Path)
}
func (data *BaseTemplateData) ResolveFile(fileName string) *descriptor.File {
file, err := data.registry.LookupFile(fileName)
if err != nil {
panic(err)
}
return file
}
func LoadTemplate(templateData []byte, err error) *template.Template {
if err != nil {
panic(err)
}
return template.Must(template.New("").
Funcs(sprig.TxtFuncMap()).
Funcs(templateFuncs).
Parse(string(templateData)))
}
func GenerateFromTemplate(tmpl *template.Template, data interface{}) (string, error) {
var tpl bytes.Buffer
err := tmpl.Execute(&tpl, data)
if err != nil {
return "", err
}
tmplResult := tpl.Bytes()
tmplResult, err = imports.Process(".", tmplResult, nil)
return string(tmplResult), err
}
func GenerateFromBaseTemplate(tmpl *template.Template, registry *descriptor.Registry, file *descriptor.File) (string, error) {
return GenerateFromTemplate(tmpl, GetBaseTemplateData(registry, file))
}

View File

@ -0,0 +1,37 @@
# protoc-gen-authoption
Proto options to annotate auth methods in protos
## Generate protos/templates
protos: `go generate authoption/generate.go`
templates/install: `go generate generate.go`
## Usage
```
// proto file
import "authoption/options.proto";
service MyService {
rpc Hello(Hello) returns (google.protobuf.Empty) {
option (google.api.http) = {
get: "/hello"
};
option (caos.zitadel.utils.v1.auth_option) = {
zitadel_permission: "hello.read"
zitadel_check_param: "id"
};
}
message Hello {
string id = 1;
}
}
```
Caos Auth Option is used for granting groups
On each zitadel role is specified which auth methods are allowed to call
Get protoc-get-authoption: ``go get github.com/caos/zitadel/internal/protoc/protoc-gen-authoption``
Protc-Flag: ``--authoption_out=.``

View File

@ -0,0 +1,3 @@
package authoption
//go:generate protoc -I. -I$GOPATH/src --go_out=plugins=grpc:$GOPATH/src options.proto

View File

@ -0,0 +1,105 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: options.proto
package authoption
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type AuthOption struct {
Permission string `protobuf:"bytes,1,opt,name=permission,proto3" json:"permission,omitempty"`
CheckFieldName string `protobuf:"bytes,2,opt,name=check_field_name,json=checkFieldName,proto3" json:"check_field_name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AuthOption) Reset() { *m = AuthOption{} }
func (m *AuthOption) String() string { return proto.CompactTextString(m) }
func (*AuthOption) ProtoMessage() {}
func (*AuthOption) Descriptor() ([]byte, []int) {
return fileDescriptor_110d40819f1994f9, []int{0}
}
func (m *AuthOption) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AuthOption.Unmarshal(m, b)
}
func (m *AuthOption) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_AuthOption.Marshal(b, m, deterministic)
}
func (m *AuthOption) XXX_Merge(src proto.Message) {
xxx_messageInfo_AuthOption.Merge(m, src)
}
func (m *AuthOption) XXX_Size() int {
return xxx_messageInfo_AuthOption.Size(m)
}
func (m *AuthOption) XXX_DiscardUnknown() {
xxx_messageInfo_AuthOption.DiscardUnknown(m)
}
var xxx_messageInfo_AuthOption proto.InternalMessageInfo
func (m *AuthOption) GetPermission() string {
if m != nil {
return m.Permission
}
return ""
}
func (m *AuthOption) GetCheckFieldName() string {
if m != nil {
return m.CheckFieldName
}
return ""
}
var E_AuthOption = &proto.ExtensionDesc{
ExtendedType: (*descriptor.MethodOptions)(nil),
ExtensionType: (*AuthOption)(nil),
Field: 50000,
Name: "caos.zitadel.utils.v1.auth_option",
Tag: "bytes,50000,opt,name=auth_option",
Filename: "options.proto",
}
func init() {
proto.RegisterType((*AuthOption)(nil), "caos.zitadel.utils.v1.AuthOption")
proto.RegisterExtension(E_AuthOption)
}
func init() { proto.RegisterFile("options.proto", fileDescriptor_110d40819f1994f9) }
var fileDescriptor_110d40819f1994f9 = []byte{
// 252 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x8f, 0x31, 0x4b, 0xc5, 0x30,
0x14, 0x85, 0x79, 0x0a, 0x82, 0x79, 0x28, 0x52, 0x10, 0x8a, 0x83, 0x54, 0xa7, 0x2e, 0xef, 0x06,
0x75, 0x73, 0xd3, 0x41, 0x44, 0x50, 0xe1, 0x0d, 0x0e, 0x2e, 0x25, 0x4d, 0xef, 0x6b, 0x83, 0x6d,
0x6e, 0x49, 0x6e, 0x1c, 0xfc, 0x01, 0xfe, 0x3e, 0x7f, 0x92, 0x34, 0xa9, 0x3e, 0x07, 0xa7, 0x5c,
0x0e, 0xe7, 0x9c, 0x7c, 0x47, 0x1c, 0xd0, 0xc8, 0x86, 0xac, 0x87, 0xd1, 0x11, 0x53, 0x76, 0xac,
0x15, 0x79, 0xf8, 0x30, 0xac, 0x1a, 0xec, 0x21, 0xb0, 0xe9, 0x3d, 0xbc, 0x5f, 0x9c, 0x14, 0x2d,
0x51, 0xdb, 0xa3, 0x8c, 0xa6, 0x3a, 0x6c, 0x64, 0x83, 0x5e, 0x3b, 0x33, 0x32, 0xb9, 0x14, 0x3c,
0x7f, 0x11, 0xe2, 0x26, 0x70, 0xf7, 0x1c, 0xdb, 0xb2, 0x53, 0x21, 0x46, 0x74, 0x83, 0xf1, 0xde,
0x90, 0xcd, 0x17, 0xc5, 0xa2, 0xdc, 0x5f, 0xff, 0x51, 0xb2, 0x52, 0x1c, 0xe9, 0x0e, 0xf5, 0x5b,
0xb5, 0x31, 0xd8, 0x37, 0x95, 0x55, 0x03, 0xe6, 0x3b, 0xd1, 0x75, 0x18, 0xf5, 0xbb, 0x49, 0x7e,
0x52, 0x03, 0x5e, 0x37, 0x62, 0xa9, 0x02, 0x77, 0x15, 0xcd, 0xc5, 0x90, 0x48, 0xe0, 0x87, 0x04,
0x1e, 0x91, 0x3b, 0x6a, 0xd2, 0xbf, 0x3e, 0xff, 0xfa, 0xdc, 0x2d, 0x16, 0xe5, 0xf2, 0xf2, 0x0c,
0xfe, 0x1d, 0x02, 0x5b, 0xc6, 0xb5, 0x50, 0xbf, 0xf7, 0xed, 0xc3, 0xeb, 0x7d, 0x6b, 0xb8, 0x0b,
0x35, 0x68, 0x1a, 0xe4, 0x14, 0x95, 0x73, 0x54, 0x1a, 0xcb, 0xe8, 0xac, 0xea, 0xd3, 0x76, 0x3d,
0x3f, 0xab, 0x16, 0xed, 0x6a, 0x2a, 0x48, 0x5c, 0x72, 0x7b, 0xd6, 0x7b, 0xd1, 0x71, 0xf5, 0x1d,
0x00, 0x00, 0xff, 0xff, 0xd2, 0xa7, 0xf7, 0xca, 0x5a, 0x01, 0x00, 0x00,
}

View File

@ -0,0 +1,17 @@
syntax = "proto3";
package caos.zitadel.utils.v1;
import "google/protobuf/descriptor.proto";
option go_package = "github.com/caos/zitadel/internal/protoc/protoc-gen-authoption/authoption";
extend google.protobuf.MethodOptions {
AuthOption auth_option = 50000;
}
message AuthOption {
string permission = 1;
string check_field_name = 2;
}

View File

@ -0,0 +1,4 @@
package main
//go:generate go-bindata -pkg main -o templates.go templates
//go:generate go install

View File

@ -0,0 +1,15 @@
package main
import (
base "github.com/caos/zitadel/internal/protoc/protoc-base"
"github.com/caos/zitadel/internal/protoc/protoc-gen-authoption/authoption"
)
const (
fileName = "%v.pb.authoptions.go"
)
func main() {
base.RegisterExtension(authoption.E_AuthOption)
base.RunWithBaseTemplate(fileName, base.LoadTemplate(templatesAuth_method_mappingGoTmplBytes()))
}

View File

@ -0,0 +1,237 @@
// Code generated by go-bindata.
// sources:
// templates/auth_method_mapping.go.tmpl
// DO NOT EDIT!
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _templatesAuth_method_mappingGoTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x52\xc1\x6a\xe3\x30\x10\x3d\xaf\xbe\x62\x30\x3e\xb4\x21\x95\xd8\x6b\xa0\x87\x25\xdd\x2e\x3d\xb4\x09\x6c\x76\xaf\x41\xb5\x27\xb2\x88\x2d\x19\x49\x0e\xb4\x42\xff\xbe\x8c\xed\xac\x9d\xb4\x0b\xeb\x93\xac\x99\x37\xef\xbd\xd1\x13\x02\xd6\xb6\x44\x50\x68\xd0\xc9\x80\x25\xbc\xbe\x41\xeb\x6c\xb0\xc5\x9d\x42\x73\x27\xbb\x50\x35\x18\x2a\x5b\x72\x78\xd8\xc0\xcb\x66\x07\xdf\x1f\x9e\x76\x9c\xb1\x56\x16\x47\xa9\x10\x62\xe4\x8f\xba\x46\xfe\xc3\x6e\x8f\x8a\xbf\xc8\x06\x53\x62\x8c\xe9\xa6\xb5\x2e\xc0\x0d\x03\x00\xc8\x94\xb5\xaa\x46\xae\x6c\x2d\x8d\xe2\xd6\x29\xa1\x5c\x5b\x64\x7d\x91\x7d\xe9\x82\xae\xfd\x9e\xa8\x20\x53\x3a\x54\xdd\x2b\x2f\x6c\x23\x0a\x69\xbd\x78\xd7\x41\x96\x58\x0b\x6d\x02\x3a\x23\x6b\x41\x6d\xd9\x19\x43\x53\xfe\x03\x33\x90\xdd\x32\x16\x23\x38\x69\x14\x42\xee\x61\x75\x0f\x83\xf2\x9f\xe8\x4e\xba\x40\x0f\x24\x5c\x2c\x16\x0c\x16\x10\x63\xee\xcf\x66\x60\x21\x18\x3b\x49\x37\xbf\xdc\x7f\xeb\x42\xf5\xdc\x2f\xc6\xc3\x3d\x4c\x0e\xf8\x54\x78\x96\x6d\xab\x8d\x82\xd8\xdb\x9c\xa8\x1b\xa2\xce\x3d\x1f\xba\x88\x60\xfc\x62\x84\xbc\x21\xfc\xa6\x0d\xd4\x63\xdb\xa0\xad\x81\xbc\xe1\x9b\xfe\xe4\x21\x23\x7f\x7c\xf4\xc7\x7b\x56\x7e\xfa\xca\x89\x78\x3f\x74\x67\x70\x39\x50\x1f\x40\x9a\x72\x36\xf7\xef\x89\x6f\xd1\x35\xda\x7b\xa2\x98\x61\xfa\xf7\x12\x31\xe6\xc3\x72\xb6\xc3\x33\xa7\xc4\x67\xee\x45\x8c\xe3\x29\x5b\x5d\x7b\x1f\xa4\xc6\x8b\x79\xf4\x4d\x6c\x2b\xc8\x62\xfc\x4c\x46\x4a\xd9\xf2\x03\x6e\x5d\x61\x71\xdc\x4a\x27\x9b\x2b\x5c\x5f\x78\xd4\x58\x97\xa3\x94\x4b\x6c\x5a\xce\xd6\x80\xe6\xbc\xe7\x18\x61\xf8\x49\x8c\x1d\x3a\x53\x7c\x78\x54\xeb\xf4\xbb\x24\x0b\xfb\x27\x8a\x4f\x81\x6d\xb0\xee\xe6\x84\x4e\x1f\x34\xba\xb9\xdb\x9d\x3d\xa2\xf9\x3d\x16\x96\x40\x77\x6b\x6b\x0e\xb0\xb8\xda\x08\x5d\x6a\x75\x0b\x94\x42\xfe\xcb\x48\xf7\x46\x89\x43\x37\x9b\x3f\x66\xc4\x61\xe8\x9c\x81\x29\xdb\xfc\x42\xd1\x67\x82\x26\xde\xe5\xbf\xf2\x79\x4b\x5e\x07\xdf\x90\xd2\x9f\x00\x00\x00\xff\xff\xbf\x91\xbb\x3b\xf2\x03\x00\x00")
func templatesAuth_method_mappingGoTmplBytes() ([]byte, error) {
return bindataRead(
_templatesAuth_method_mappingGoTmpl,
"templates/auth_method_mapping.go.tmpl",
)
}
func templatesAuth_method_mappingGoTmpl() (*asset, error) {
bytes, err := templatesAuth_method_mappingGoTmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/auth_method_mapping.go.tmpl", size: 1010, mode: os.FileMode(420), modTime: time.Unix(1584960713, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"templates/auth_method_mapping.go.tmpl": templatesAuth_method_mappingGoTmpl,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates": &bintree{nil, map[string]*bintree{
"auth_method_mapping.go.tmpl": &bintree{templatesAuth_method_mappingGoTmpl, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -0,0 +1,35 @@
// Code generated by protoc-gen-authmethod. DO NOT EDIT.
package {{.File.GoPkg.Name}}
import (
"google.golang.org/grpc"
utils_auth "github.com/caos/zitadel/internal/auth"
utils_grpc "github.com/caos/zitadel/internal/grpc"
)
{{ range $s := .File.Services }}
/**
* {{$s.Name}}
*/
var {{$s.Name}}_AuthMethods = utils_auth.AuthMethodMapping {
{{ range $m := $s.Method}}
{{ $mAuthOpt := option $m.Options "caos.zitadel.utils.v1.auth_option" }}
{{ if and $mAuthOpt $mAuthOpt.Permission }}
"/{{$.File.Package}}.{{$s.Name}}/{{.Name}}": utils_auth.AuthOption{
Permission: "{{$mAuthOpt.Permission}}",
CheckParam: "{{$mAuthOpt.CheckFieldName}}",
},
{{end}}
{{ end}}
}
func {{$s.Name}}_Authorization_Interceptor(verifier utils_auth.TokenVerifier, authConf *utils_auth.AuthConfig) grpc.UnaryServerInterceptor {
return utils_grpc.AuthorizationInterceptor(verifier, authConf, {{$s.Name}}_AuthMethods)
}
{{ end }}

View File

@ -0,0 +1,22 @@
package tracing
import (
"runtime"
"github.com/caos/logging"
)
func GetCaller() string {
fpcs := make([]uintptr, 1)
n := runtime.Callers(3, fpcs)
if n == 0 {
logging.Log("TRACE-rWjfC").Debug("no caller")
return ""
}
caller := runtime.FuncForPC(fpcs[0] - 1)
if caller == nil {
logging.Log("TRACE-25POw").Debug("caller was nil")
return ""
}
return caller.Name()
}

View File

@ -0,0 +1,59 @@
package config
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/tracing"
tracing_g "github.com/caos/zitadel/internal/tracing/google"
tracing_log "github.com/caos/zitadel/internal/tracing/log"
)
type TracingConfig struct {
Type string
Config tracing.Config
}
var tracer = map[string]func() tracing.Config{
"google": func() tracing.Config { return &tracing_g.Config{} },
"log": func() tracing.Config { return &tracing_log.Config{} },
}
func (c *TracingConfig) UnmarshalJSON(data []byte) error {
var rc struct {
Type string
Config json.RawMessage
}
if err := json.Unmarshal(data, &rc); err != nil {
return errors.ThrowInternal(err, "TRACE-vmjS", "error parsing config")
}
c.Type = rc.Type
var err error
c.Config, err = newTracingConfig(c.Type, rc.Config)
if err != nil {
return err
}
return c.Config.NewTracer()
}
func newTracingConfig(tracerType string, configData []byte) (tracing.Config, error) {
t, ok := tracer[tracerType]
if !ok {
return nil, errors.ThrowInternalf(nil, "TRACE-HMEJ", "config type %s not supported", tracerType)
}
tracingConfig := t()
if len(configData) == 0 {
return tracingConfig, nil
}
if err := json.Unmarshal(configData, tracingConfig); err != nil {
return nil, errors.ThrowInternal(err, "TRACE-1tSS", "Could not read config: %v")
}
return tracingConfig, nil
}

View File

@ -0,0 +1,3 @@
package tracing
//go:generate mockgen -package mock -destination mock/tracing_mock.go github.com/caos/zitadel/internal/tracing Tracer

View File

@ -0,0 +1,24 @@
package google
import (
"go.opencensus.io/trace"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/tracing"
)
type Config struct {
ProjectID string
MetricPrefix string
Fraction float64
}
func (c *Config) NewTracer() error {
if !envIsSet() {
return errors.ThrowInvalidArgument(nil, "GOOGL-sdh3a", "env not properly set, GOOGLE_APPLICATION_CREDENTIALS is misconfigured or missing")
}
tracing.T = &Tracer{projectID: c.ProjectID, metricPrefix: c.MetricPrefix, sampler: trace.ProbabilitySampler(c.Fraction)}
return tracing.T.Start()
}

View File

@ -0,0 +1,95 @@
package google
import (
"context"
"net/http"
"os"
"strings"
"contrib.go.opencensus.io/exporter/stackdriver"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/tracing"
)
type Tracer struct {
Exporter *stackdriver.Exporter
projectID string
metricPrefix string
sampler trace.Sampler
}
func (t *Tracer) Start() (err error) {
t.Exporter, err = stackdriver.NewExporter(stackdriver.Options{
ProjectID: t.projectID,
MetricPrefix: t.metricPrefix,
})
if err != nil {
return errors.ThrowInternal(err, "GOOGL-4dCnX", "unable to start exporter")
}
views := append(ocgrpc.DefaultServerViews, ocgrpc.DefaultClientViews...)
views = append(views, ochttp.DefaultClientViews...)
views = append(views, ochttp.DefaultServerViews...)
if err = view.Register(views...); err != nil {
return errors.ThrowInternal(err, "GOOGL-Q6L6w", "unable to register view")
}
trace.RegisterExporter(t.Exporter)
trace.ApplyConfig(trace.Config{DefaultSampler: t.sampler})
return nil
}
func (t *Tracer) Sampler() trace.Sampler {
return t.sampler
}
func (t *Tracer) NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) {
return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindServer))
}
func (t *Tracer) NewServerSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) {
return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindServer))
}
func (t *Tracer) NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) {
return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindClient))
}
func (t *Tracer) NewClientSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) {
return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindClient))
}
func (t *Tracer) NewSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) {
return t.newSpan(ctx, caller)
}
func (t *Tracer) newSpan(ctx context.Context, caller string, options ...trace.StartOption) (context.Context, *tracing.Span) {
return t.newSpanFromName(ctx, caller, options...)
}
func (t *Tracer) newSpanFromName(ctx context.Context, name string, options ...trace.StartOption) (context.Context, *tracing.Span) {
ctx, span := trace.StartSpan(ctx, name, options...)
return ctx, tracing.CreateSpan(span)
}
func (t *Tracer) NewSpanHTTP(r *http.Request, caller string) (*http.Request, *tracing.Span) {
ctx, span := t.NewSpan(r.Context(), caller)
r = r.WithContext(ctx)
return r, span
}
func envIsSet() bool {
gAuthCred := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
return strings.Contains(gAuthCred, ".json")
}
func (t *Tracer) SetErrStatus(span *trace.Span, code int32, err error, obj ...string) {
span.SetStatus(trace.Status{Code: code, Message: err.Error() + strings.Join(obj, ", ")})
}

View File

@ -0,0 +1,30 @@
package tracing
import (
"net/http"
"strings"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/trace"
)
func TraceHandler(handler http.Handler, ignoredMethods ...string) http.Handler {
healthEndpoints := strings.Join(ignoredMethods, ";;")
return &ochttp.Handler{
Handler: handler,
FormatSpanName: func(r *http.Request) string {
host := r.URL.Host
if host == "" {
host = r.Host
}
return host + r.URL.Path
},
StartOptions: trace.StartOptions{Sampler: Sampler()},
IsHealthEndpoint: func(r *http.Request) bool {
n := strings.Contains(healthEndpoints, r.URL.RequestURI())
return n
},
}
}

View File

@ -0,0 +1,21 @@
package log
import (
"go.opencensus.io/trace"
"github.com/caos/zitadel/internal/tracing"
)
type Config struct {
Fraction float64
}
func (c *Config) NewTracer() error {
if c.Fraction < 1 {
c.Fraction = 1
}
tracing.T = &Tracer{trace.ProbabilitySampler(c.Fraction)}
return tracing.T.Start()
}

View File

@ -0,0 +1,74 @@
package log
import (
"context"
"net/http"
"go.opencensus.io/examples/exporter"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/tracing"
)
type Tracer struct {
sampler trace.Sampler
}
func (t *Tracer) Start() error {
trace.RegisterExporter(&exporter.PrintExporter{})
views := append(ocgrpc.DefaultServerViews, ocgrpc.DefaultClientViews...)
views = append(views, ochttp.DefaultClientViews...)
views = append(views, ochttp.DefaultServerViews...)
if err := view.Register(views...); err != nil {
return errors.ThrowInternal(err, "LOG-PoFiB", "unable to register view")
}
trace.ApplyConfig(trace.Config{DefaultSampler: t.sampler})
return nil
}
func (t *Tracer) Sampler() trace.Sampler {
return t.sampler
}
func (t *Tracer) NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) {
return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindServer))
}
func (t *Tracer) NewServerSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) {
return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindServer))
}
func (t *Tracer) NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) {
return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindClient))
}
func (t *Tracer) NewClientSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) {
return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindClient))
}
func (t *Tracer) NewSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) {
return t.newSpan(ctx, caller)
}
func (t *Tracer) newSpan(ctx context.Context, caller string, options ...trace.StartOption) (context.Context, *tracing.Span) {
return t.newSpanFromName(ctx, caller, options...)
}
func (t *Tracer) newSpanFromName(ctx context.Context, name string, options ...trace.StartOption) (context.Context, *tracing.Span) {
ctx, span := trace.StartSpan(ctx, name, options...)
return ctx, tracing.CreateSpan(span)
}
func (t *Tracer) NewSpanHTTP(r *http.Request, caller string) (*http.Request, *tracing.Span) {
ctx, span := t.NewSpan(r.Context(), caller)
r = r.WithContext(ctx)
return r, span
}

View File

@ -0,0 +1,155 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/zitadel/internal/tracing (interfaces: Tracer)
// Package mock is a generated GoMock package.
package mock
import (
context "context"
tracing "github.com/caos/zitadel/internal/tracing"
gomock "github.com/golang/mock/gomock"
trace "go.opencensus.io/trace"
http "net/http"
reflect "reflect"
)
// MockTracer is a mock of Tracer interface
type MockTracer struct {
ctrl *gomock.Controller
recorder *MockTracerMockRecorder
}
// MockTracerMockRecorder is the mock recorder for MockTracer
type MockTracerMockRecorder struct {
mock *MockTracer
}
// NewMockTracer creates a new mock instance
func NewMockTracer(ctrl *gomock.Controller) *MockTracer {
mock := &MockTracer{ctrl: ctrl}
mock.recorder = &MockTracerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTracer) EXPECT() *MockTracerMockRecorder {
return m.recorder
}
// NewClientInterceptorSpan mocks base method
func (m *MockTracer) NewClientInterceptorSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewClientInterceptorSpan", arg0, arg1)
ret0, _ := ret[0].(context.Context)
ret1, _ := ret[1].(*tracing.Span)
return ret0, ret1
}
// NewClientInterceptorSpan indicates an expected call of NewClientInterceptorSpan
func (mr *MockTracerMockRecorder) NewClientInterceptorSpan(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientInterceptorSpan", reflect.TypeOf((*MockTracer)(nil).NewClientInterceptorSpan), arg0, arg1)
}
// NewClientSpan mocks base method
func (m *MockTracer) NewClientSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewClientSpan", arg0, arg1)
ret0, _ := ret[0].(context.Context)
ret1, _ := ret[1].(*tracing.Span)
return ret0, ret1
}
// NewClientSpan indicates an expected call of NewClientSpan
func (mr *MockTracerMockRecorder) NewClientSpan(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientSpan", reflect.TypeOf((*MockTracer)(nil).NewClientSpan), arg0, arg1)
}
// NewServerInterceptorSpan mocks base method
func (m *MockTracer) NewServerInterceptorSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewServerInterceptorSpan", arg0, arg1)
ret0, _ := ret[0].(context.Context)
ret1, _ := ret[1].(*tracing.Span)
return ret0, ret1
}
// NewServerInterceptorSpan indicates an expected call of NewServerInterceptorSpan
func (mr *MockTracerMockRecorder) NewServerInterceptorSpan(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewServerInterceptorSpan", reflect.TypeOf((*MockTracer)(nil).NewServerInterceptorSpan), arg0, arg1)
}
// NewServerSpan mocks base method
func (m *MockTracer) NewServerSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewServerSpan", arg0, arg1)
ret0, _ := ret[0].(context.Context)
ret1, _ := ret[1].(*tracing.Span)
return ret0, ret1
}
// NewServerSpan indicates an expected call of NewServerSpan
func (mr *MockTracerMockRecorder) NewServerSpan(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewServerSpan", reflect.TypeOf((*MockTracer)(nil).NewServerSpan), arg0, arg1)
}
// NewSpan mocks base method
func (m *MockTracer) NewSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewSpan", arg0, arg1)
ret0, _ := ret[0].(context.Context)
ret1, _ := ret[1].(*tracing.Span)
return ret0, ret1
}
// NewSpan indicates an expected call of NewSpan
func (mr *MockTracerMockRecorder) NewSpan(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSpan", reflect.TypeOf((*MockTracer)(nil).NewSpan), arg0, arg1)
}
// NewSpanHTTP mocks base method
func (m *MockTracer) NewSpanHTTP(arg0 *http.Request, arg1 string) (*http.Request, *tracing.Span) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewSpanHTTP", arg0, arg1)
ret0, _ := ret[0].(*http.Request)
ret1, _ := ret[1].(*tracing.Span)
return ret0, ret1
}
// NewSpanHTTP indicates an expected call of NewSpanHTTP
func (mr *MockTracerMockRecorder) NewSpanHTTP(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSpanHTTP", reflect.TypeOf((*MockTracer)(nil).NewSpanHTTP), arg0, arg1)
}
// Sampler mocks base method
func (m *MockTracer) Sampler() trace.Sampler {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Sampler")
ret0, _ := ret[0].(trace.Sampler)
return ret0
}
// Sampler indicates an expected call of Sampler
func (mr *MockTracerMockRecorder) Sampler() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sampler", reflect.TypeOf((*MockTracer)(nil).Sampler))
}
// Start mocks base method
func (m *MockTracer) Start() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Start")
ret0, _ := ret[0].(error)
return ret0
}
// Start indicates an expected call of Start
func (mr *MockTracerMockRecorder) Start() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockTracer)(nil).Start))
}

View File

@ -0,0 +1,20 @@
package mock
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/caos/zitadel/internal/tracing"
)
func NewSimpleMockTracer(t *testing.T) *MockTracer {
return NewMockTracer(gomock.NewController(t))
}
func ExpectServerSpan(ctx context.Context, mock interface{}) {
m := mock.(*MockTracer)
any := gomock.Any()
m.EXPECT().NewServerSpan(any, any).AnyTimes().Return(ctx, &tracing.Span{})
}

88
internal/tracing/span.go Normal file
View File

@ -0,0 +1,88 @@
package tracing
import (
"fmt"
"strconv"
"go.opencensus.io/trace"
"github.com/caos/zitadel/internal/api/grpc"
"github.com/caos/zitadel/internal/errors"
)
type Span struct {
span *trace.Span
attributes []trace.Attribute
}
func CreateSpan(span *trace.Span) *Span {
return &Span{span: span, attributes: []trace.Attribute{}}
}
func (s *Span) End() {
if s.span == nil {
return
}
s.span.AddAttributes(s.attributes...)
s.span.End()
}
func (s *Span) EndWithError(err error) {
s.SetStatusByError(err)
s.End()
}
func (s *Span) SetStatusByError(err error) {
if s.span == nil {
return
}
s.span.SetStatus(statusFromError(err))
}
func statusFromError(err error) trace.Status {
code, msg, _ := grpc.Extract(err)
return trace.Status{Code: int32(code), Message: msg}
}
// AddAnnotation creates an annotation. The annotation will not be added to the tracing use Annotate(msg) afterwards
func (s *Span) AddAnnotation(key string, value interface{}) *Span {
attribute, err := toTraceAttribute(key, value)
if err != nil {
return s
}
s.attributes = append(s.attributes, attribute)
return s
}
// Annotate creates an annotation in tracing. Before added annotations will be set
func (s *Span) Annotate(message string) *Span {
if s.span == nil {
return s
}
s.span.Annotate(s.attributes, message)
s.attributes = []trace.Attribute{}
return s
}
func (s *Span) Annotatef(format string, addiations ...interface{}) *Span {
s.Annotate(fmt.Sprintf(format, addiations...))
return s
}
func toTraceAttribute(key string, value interface{}) (attr trace.Attribute, err error) {
switch value := value.(type) {
case bool:
return trace.BoolAttribute(key, value), nil
case string:
return trace.StringAttribute(key, value), nil
}
if valueInt, err := convertToInt64(value); err == nil {
return trace.Int64Attribute(key, valueInt), nil
}
return attr, errors.ThrowInternal(nil, "TRACE-jlq3s", "Attribute is not of type bool, string or int64")
}
func convertToInt64(value interface{}) (int64, error) {
valueString := fmt.Sprintf("%v", value)
return strconv.ParseInt(valueString, 10, 64)
}

View File

@ -0,0 +1,74 @@
package tracing
import (
"context"
"net/http"
"go.opencensus.io/trace"
)
type Tracer interface {
Start() error
NewSpan(ctx context.Context, caller string) (context.Context, *Span)
NewClientSpan(ctx context.Context, caller string) (context.Context, *Span)
NewServerSpan(ctx context.Context, caller string) (context.Context, *Span)
NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *Span)
NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *Span)
NewSpanHTTP(r *http.Request, caller string) (*http.Request, *Span)
Sampler() trace.Sampler
}
type Config interface {
NewTracer() error
}
var T Tracer
func Sampler() trace.Sampler {
if T == nil {
return trace.NeverSample()
}
return T.Sampler()
}
func NewSpan(ctx context.Context) (context.Context, *Span) {
if T == nil {
return ctx, CreateSpan(nil)
}
return T.NewSpan(ctx, GetCaller())
}
func NewClientSpan(ctx context.Context) (context.Context, *Span) {
if T == nil {
return ctx, CreateSpan(nil)
}
return T.NewClientSpan(ctx, GetCaller())
}
func NewServerSpan(ctx context.Context) (context.Context, *Span) {
if T == nil {
return ctx, CreateSpan(nil)
}
return T.NewServerSpan(ctx, GetCaller())
}
func NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *Span) {
if T == nil {
return ctx, CreateSpan(nil)
}
return T.NewClientInterceptorSpan(ctx, name)
}
func NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *Span) {
if T == nil {
return ctx, CreateSpan(nil)
}
return T.NewServerInterceptorSpan(ctx, name)
}
func NewSpanHTTP(r *http.Request) (*http.Request, *Span) {
if T == nil {
return r, CreateSpan(nil)
}
return T.NewSpanHTTP(r, GetCaller())
}

View File

@ -3,12 +3,17 @@ package admin
import (
"context"
app "github.com/caos/zitadel/internal/admin"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/pkg/admin/api"
)
type Config struct {
App app.Config
API api.Config
}
func Start(ctx context.Context, config Config) error {
func Start(ctx context.Context, config Config, authZ auth.Config) error {
return errors.ThrowUnimplemented(nil, "ADMIN-n8vw5", "not implemented yet") //TODO: implement
}

7
pkg/admin/api/config.go Normal file
View File

@ -0,0 +1,7 @@
package api
import "github.com/caos/zitadel/internal/api/grpc"
type Config struct {
GRPC grpc.Config
}

7
pkg/auth/api/config.go Normal file
View File

@ -0,0 +1,7 @@
package api
import "github.com/caos/zitadel/internal/api/grpc"
type Config struct {
GRPC grpc.Config
}

View File

@ -3,12 +3,17 @@ package auth
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
app "github.com/caos/zitadel/internal/auth"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/pkg/auth/api"
)
type Config struct {
App app.Config
API api.Config
}
func Start(ctx context.Context, config Config) error {
func Start(ctx context.Context, config Config, authZ auth.Config) error {
return errors.ThrowUnimplemented(nil, "AUTH-l7Hdx", "not implemented yet") //TODO: implement
}

16
pkg/console/console.go Normal file
View File

@ -0,0 +1,16 @@
package console
import (
"context"
"github.com/caos/zitadel/internal/errors"
)
type Config struct {
Port string
StaticDir string
}
func Start(ctx context.Context, config Config) error {
return errors.ThrowUnimplemented(nil, "CONSO-4cT5D", "not implemented yet") //TODO: implement
}

Some files were not shown because too many files have changed in this diff Show More