From b3d878792193631e8b213a4b900a939d095aa575 Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Tue, 11 Apr 2023 15:37:42 +0200 Subject: [PATCH] feat: add new api services (#5619) * feat: add new services * improve demos and comments * remove unused field * add comment to demo proto calls * Apply suggestions from code review Co-authored-by: Silvan --------- Co-authored-by: Silvan --- build/zitadel/generate-grpc.sh | 32 ++++++++ cmd/start/start.go | 32 +++++--- internal/api/api.go | 79 +++++++++++++++---- internal/api/grpc/admin/server.go | 4 +- internal/api/grpc/auth/server.go | 4 +- internal/api/grpc/management/server.go | 4 +- internal/api/grpc/server/gateway.go | 72 +++++++++++++++-- .../server/middleware/service_interceptor.go | 8 +- internal/api/grpc/server/server.go | 14 +++- internal/api/grpc/session/v2/server.go | 51 ++++++++++++ internal/api/grpc/session/v2/session.go | 18 +++++ internal/api/grpc/system/server.go | 4 +- internal/api/grpc/user/v2/server.go | 51 ++++++++++++ internal/api/grpc/user/v2/test.go | 55 +++++++++++++ proto/zitadel/session/v2alpha/session.proto | 12 +++ .../session/v2alpha/session_service.proto | 33 ++++++++ proto/zitadel/user/v2alpha/user.proto | 9 +++ proto/zitadel/user/v2alpha/user_service.proto | 78 ++++++++++++++++++ 18 files changed, 516 insertions(+), 44 deletions(-) create mode 100644 internal/api/grpc/session/v2/server.go create mode 100644 internal/api/grpc/session/v2/session.go create mode 100644 internal/api/grpc/user/v2/server.go create mode 100644 internal/api/grpc/user/v2/test.go create mode 100644 proto/zitadel/session/v2alpha/session.proto create mode 100644 proto/zitadel/session/v2alpha/session_service.proto create mode 100644 proto/zitadel/user/v2alpha/user.proto create mode 100644 proto/zitadel/user/v2alpha/user_service.proto diff --git a/build/zitadel/generate-grpc.sh b/build/zitadel/generate-grpc.sh index 93053eedb5..02d1fa86b1 100755 --- a/build/zitadel/generate-grpc.sh +++ b/build/zitadel/generate-grpc.sh @@ -93,4 +93,36 @@ protoc \ mv ${ZITADEL_PATH}/pkg/grpc/auth/zitadel/* ${ZITADEL_PATH}/pkg/grpc/auth rm -r ${ZITADEL_PATH}/pkg/grpc/auth/zitadel +protoc \ + -I=/proto/include \ + --grpc-gateway_out ${GOPATH}/src \ + --grpc-gateway_opt logtostderr=true \ + --grpc-gateway_opt allow_delete_body=true \ + --openapiv2_out ${OPENAPI_PATH} \ + --openapiv2_opt logtostderr=true \ + --openapiv2_opt allow_delete_body=true \ + --authoption_out=${GRPC_PATH}/user \ + --validate_out=lang=go:${GOPATH}/src \ + ${PROTO_PATH}/user/v2alpha/user_service.proto + +# authoptions are generated into the wrong folder +cp -r ${ZITADEL_PATH}/pkg/grpc/user/zitadel/* ${ZITADEL_PATH}/pkg/grpc +rm -r ${ZITADEL_PATH}/pkg/grpc/user/zitadel + +protoc \ + -I=/proto/include \ + --grpc-gateway_out ${GOPATH}/src \ + --grpc-gateway_opt logtostderr=true \ + --grpc-gateway_opt allow_delete_body=true \ + --openapiv2_out ${OPENAPI_PATH} \ + --openapiv2_opt logtostderr=true \ + --openapiv2_opt allow_delete_body=true \ + --authoption_out=${GRPC_PATH}/session \ + --validate_out=lang=go:${GOPATH}/src \ + ${PROTO_PATH}/session/v2alpha/session_service.proto + +# authoptions are generated into the wrong folder +cp -r ${ZITADEL_PATH}/pkg/grpc/session/zitadel/* ${ZITADEL_PATH}/pkg/grpc +rm -r ${ZITADEL_PATH}/pkg/grpc/session/zitadel + echo "done generating grpc" diff --git a/cmd/start/start.go b/cmd/start/start.go index 61d35540e5..f80fc6fef1 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -33,7 +33,9 @@ import ( "github.com/zitadel/zitadel/internal/api/grpc/admin" "github.com/zitadel/zitadel/internal/api/grpc/auth" "github.com/zitadel/zitadel/internal/api/grpc/management" + "github.com/zitadel/zitadel/internal/api/grpc/session/v2" "github.com/zitadel/zitadel/internal/api/grpc/system" + "github.com/zitadel/zitadel/internal/api/grpc/user/v2" http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/api/oidc" @@ -223,7 +225,10 @@ func startAPIs( logging.Warn("access logs are currently in beta") } accessInterceptor := middleware.NewAccessInterceptor(accessSvc, config.Quotas.Access) - apis := api.New(config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc) + apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc) + if err != nil { + return fmt.Errorf("error creating api %w", err) + } authRepo, err := auth_es.Start(ctx, config.Auth, config.SystemDefaults, commands, queries, dbClient, eventstore, keys.OIDC, keys.User) if err != nil { return fmt.Errorf("error starting auth repo: %w", err) @@ -244,9 +249,15 @@ func startAPIs( if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil { return err } + if err := apis.RegisterService(ctx, user.CreateServer(commands, queries)); err != nil { + return err + } + if err := apis.RegisterService(ctx, session.CreateServer(commands, queries)); err != nil { + return err + } instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...) assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge) - apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle)) + apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle)) userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources) if err != nil { @@ -258,37 +269,34 @@ func startAPIs( if err != nil { return fmt.Errorf("unable to start openapi handler: %w", err) } - apis.RegisterHandler(openapi.HandlerPrefix, openAPIHandler) + apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler) oidcProvider, err := oidc.NewProvider(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, accessInterceptor.Handle) if err != nil { return fmt.Errorf("unable to start oidc provider: %w", err) } + apis.RegisterHandlerPrefixes(oidcProvider.HttpHandler(), "/.well-known/openid-configuration", "/oidc/v1", "/oauth/v2") samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, accessInterceptor.Handle) if err != nil { return fmt.Errorf("unable to start saml provider: %w", err) } - apis.RegisterHandler(saml.HandlerPrefix, samlProvider.HttpHandler()) + apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler()) c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, accessInterceptor.Handle, config.CustomerPortal) if err != nil { return fmt.Errorf("unable to start console: %w", err) } - apis.RegisterHandler(console.HandlerPrefix, c) + apis.RegisterHandlerOnPrefix(console.HandlerPrefix, c) l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), provider.AuthCallbackURL(samlProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, provider.NewIssuerInterceptor(samlProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, assetsCache.Handler, accessInterceptor.Handle, keys.User, keys.IDPConfig, keys.CSRFCookieKey) if err != nil { return fmt.Errorf("unable to start login: %w", err) } - apis.RegisterHandler(login.HandlerPrefix, l.Handler()) + apis.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler()) - //handle oidc at last, to be able to handle the root - //we might want to change that in the future - //esp. if we want to have multiple well-known endpoints - //it might make sense to handle the discovery endpoint and oauth and oidc prefixes individually - //but this will require a change in the oidc lib - apis.RegisterHandler("", oidcProvider.HttpHandler()) + // handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes + apis.RouteGRPC() return nil } diff --git a/internal/api/api.go b/internal/api/api.go index 78044e5b6b..3cb851e3d0 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -10,6 +10,8 @@ import ( "github.com/improbable-eng/grpc-web/go/grpcweb" "github.com/zitadel/logging" "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" internal_authz "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/server" @@ -26,17 +28,19 @@ type API struct { port uint16 grpcServer *grpc.Server verifier *internal_authz.TokenVerifier - health health + health healthCheck router *mux.Router http1HostName string + grpcGateway *server.Gateway + healthServer *health.Server } -type health interface { +type healthCheck interface { Health(ctx context.Context) error - Instance(ctx context.Context, shouldTriggerBulk bool) (*query.Instance, error) } func New( + ctx context.Context, port uint16, router *mux.Router, queries *query.Queries, @@ -44,7 +48,7 @@ func New( authZ internal_authz.Config, tlsConfig *tls.Config, http2HostName, http1HostName string, accessSvc *logstore.Service, -) *API { +) (_ *API, err error) { api := &API{ port: port, verifier: verifier, @@ -54,43 +58,89 @@ func New( } api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessSvc) - api.routeGRPC() + api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName) + if err != nil { + return nil, err + } + api.registerHealthServer() - api.RegisterHandler("/debug", api.healthHandler()) + api.RegisterHandlerOnPrefix("/debug", api.healthHandler()) api.router.Handle("/", http.RedirectHandler(login.HandlerPrefix, http.StatusFound)) - return api + return api, nil } -func (a *API) RegisterServer(ctx context.Context, grpcServer server.Server) error { +// RegisterServer registers a grpc service on the grpc server, +// creates a new grpc gateway and registers it as a separate http handler +// +// used for v1 api (system, admin, mgmt, auth) +func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix) error { grpcServer.RegisterServer(a.grpcServer) - handler, prefix, err := server.CreateGateway(ctx, grpcServer, a.port, a.http1HostName) + handler, prefix, err := server.CreateGatewayWithPrefix(ctx, grpcServer, a.port, a.http1HostName) if err != nil { return err } - a.RegisterHandler(prefix, handler) + a.RegisterHandlerOnPrefix(prefix, handler) a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods()) + a.healthServer.SetServingStatus(grpcServer.MethodPrefix(), healthpb.HealthCheckResponse_SERVING) return nil } -func (a *API) RegisterHandler(prefix string, handler http.Handler) { +// RegisterService registers a grpc service on the grpc server, +// and its gateway on the gateway handler +// +// used for >= v2 api (e.g. user, session, ...) +func (a *API) RegisterService(ctx context.Context, grpcServer server.Server) error { + grpcServer.RegisterServer(a.grpcServer) + err := server.RegisterGateway(ctx, a.grpcGateway, grpcServer) + if err != nil { + return err + } + a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods()) + a.healthServer.SetServingStatus(grpcServer.MethodPrefix(), healthpb.HealthCheckResponse_SERVING) + return nil +} + +// RegisterHandlerOnPrefix registers a http handler on a path prefix +// the prefix will not be passed to the actual handler +func (a *API) RegisterHandlerOnPrefix(prefix string, handler http.Handler) { prefix = strings.TrimSuffix(prefix, "/") subRouter := a.router.PathPrefix(prefix).Name(prefix).Subrouter() subRouter.PathPrefix("").Handler(http.StripPrefix(prefix, handler)) } -func (a *API) routeGRPC() { +// RegisterHandlerPrefixes registers a http handler on a multiple path prefixes +// the prefix will remain when calling the actual handler +func (a *API) RegisterHandlerPrefixes(handler http.Handler, prefixes ...string) { + for _, prefix := range prefixes { + prefix = strings.TrimSuffix(prefix, "/") + subRouter := a.router.PathPrefix(prefix).Name(prefix).Subrouter() + subRouter.PathPrefix("").Handler(handler) + } +} + +func (a *API) registerHealthServer() { + healthServer := health.NewServer() + healthpb.RegisterHealthServer(a.grpcServer, healthServer) + a.healthServer = healthServer +} + +func (a *API) RouteGRPC() { http2Route := a.router. MatcherFunc(func(r *http.Request, _ *mux.RouteMatch) bool { return r.ProtoMajor == 2 }). - Subrouter() + Subrouter(). + Name("grpc") http2Route. Methods(http.MethodPost). Headers("Content-Type", "application/grpc"). Handler(a.grpcServer) a.routeGRPCWeb() + a.router.NewRoute(). + Handler(a.grpcGateway.Handler()). + Name("grpc-gateway") } func (a *API) routeGRPCWeb() { @@ -117,7 +167,8 @@ func (a *API) routeGRPCWeb() { func(r *http.Request, _ *mux.RouteMatch) bool { return grpcWebServer.IsGrpcWebRequest(r) || grpcWebServer.IsAcceptableGrpcCorsRequest(r) }). - Handler(grpcWebServer) + Handler(grpcWebServer). + Name("grpc-web") } func (a *API) healthHandler() http.Handler { diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go index 42429cb124..267fad2f45 100644 --- a/internal/api/grpc/admin/server.go +++ b/internal/api/grpc/admin/server.go @@ -78,8 +78,8 @@ func (s *Server) AuthMethods() authz.MethodMapping { return admin.AdminService_AuthMethods } -func (s *Server) RegisterGateway() server.GatewayFunc { - return admin.RegisterAdminServiceHandlerFromEndpoint +func (s *Server) RegisterGateway() server.RegisterGatewayFunc { + return admin.RegisterAdminServiceHandler } func (s *Server) GatewayPathPrefix() string { diff --git a/internal/api/grpc/auth/server.go b/internal/api/grpc/auth/server.go index e30ab12892..ff7d7ae7b6 100644 --- a/internal/api/grpc/auth/server.go +++ b/internal/api/grpc/auth/server.go @@ -76,8 +76,8 @@ func (s *Server) AuthMethods() authz.MethodMapping { return auth.AuthService_AuthMethods } -func (s *Server) RegisterGateway() server.GatewayFunc { - return auth.RegisterAuthServiceHandlerFromEndpoint +func (s *Server) RegisterGateway() server.RegisterGatewayFunc { + return auth.RegisterAuthServiceHandler } func (s *Server) GatewayPathPrefix() string { diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index 30d177a5e8..732f44ea29 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -70,8 +70,8 @@ func (s *Server) AuthMethods() authz.MethodMapping { return management.ManagementService_AuthMethods } -func (s *Server) RegisterGateway() server.GatewayFunc { - return management.RegisterManagementServiceHandlerFromEndpoint +func (s *Server) RegisterGateway() server.RegisterGatewayFunc { + return management.RegisterManagementServiceHandler } func (s *Server) GatewayPathPrefix() string { diff --git a/internal/api/grpc/server/gateway.go b/internal/api/grpc/server/gateway.go index 6c3b2fbdea..7fae522d95 100644 --- a/internal/api/grpc/server/gateway.go +++ b/internal/api/grpc/server/gateway.go @@ -7,8 +7,10 @@ import ( "strings" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/zitadel/logging" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/protobuf/encoding/protojson" client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware" @@ -50,26 +52,84 @@ var ( ) ) -type Gateway interface { - RegisterGateway() GatewayFunc - GatewayPathPrefix() string +type Gateway struct { + mux *runtime.ServeMux + http1HostName string + connection *grpc.ClientConn } -type GatewayFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error +func (g *Gateway) Handler() http.Handler { + return addInterceptors(g.mux, g.http1HostName) +} -func CreateGateway(ctx context.Context, g Gateway, port uint16, http1HostName string) (http.Handler, string, error) { +type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error + +func CreateGatewayWithPrefix(ctx context.Context, g WithGatewayPrefix, port uint16, http1HostName string) (http.Handler, string, error) { runtimeMux := runtime.NewServeMux(serveMuxOptions...) opts := []grpc.DialOption{ grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()), } - err := g.RegisterGateway()(ctx, runtimeMux, fmt.Sprintf("localhost:%d", port), opts) + connection, err := dial(ctx, port, opts) + if err != nil { + return nil, "", err + } + err = g.RegisterGateway()(ctx, runtimeMux, connection) if err != nil { return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err) } return addInterceptors(runtimeMux, http1HostName), g.GatewayPathPrefix(), nil } +func CreateGateway(ctx context.Context, port uint16, http1HostName string) (*Gateway, error) { + connection, err := dial(ctx, + port, + []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()), + }) + if err != nil { + return nil, err + } + runtimeMux := runtime.NewServeMux(append(serveMuxOptions, runtime.WithHealthzEndpoint(healthpb.NewHealthClient(connection)))...) + return &Gateway{ + mux: runtimeMux, + http1HostName: http1HostName, + connection: connection, + }, nil +} + +func RegisterGateway(ctx context.Context, gateway *Gateway, server Server) error { + err := server.RegisterGateway()(ctx, gateway.mux, gateway.connection) + if err != nil { + return fmt.Errorf("failed to register grpc gateway: %w", err) + } + return nil +} + +func dial(ctx context.Context, port uint16, opts []grpc.DialOption) (*grpc.ClientConn, error) { + endpoint := fmt.Sprintf("localhost:%d", port) + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + logging.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + logging.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return conn, nil +} + func addInterceptors(handler http.Handler, http1HostName string) http.Handler { handler = http_mw.CallDurationHandler(handler) handler = http1Host(handler, http1HostName) diff --git a/internal/api/grpc/server/middleware/service_interceptor.go b/internal/api/grpc/server/middleware/service_interceptor.go index 99291e936b..a32fcfa989 100644 --- a/internal/api/grpc/server/middleware/service_interceptor.go +++ b/internal/api/grpc/server/middleware/service_interceptor.go @@ -3,14 +3,18 @@ package middleware import ( "context" + "google.golang.org/grpc" + "github.com/zitadel/zitadel/internal/api/service" _ "github.com/zitadel/zitadel/internal/statik" - "google.golang.org/grpc" ) func ServiceHandler() grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - namer := info.Server.(interface{ AppName() string }) + namer, ok := info.Server.(interface{ AppName() string }) + if !ok { + return handler(ctx, req) + } ctx = service.WithService(ctx, namer.AppName()) return handler(ctx, req) } diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go index 86fb625928..71eef0afa6 100644 --- a/internal/api/grpc/server/server.go +++ b/internal/api/grpc/server/server.go @@ -2,9 +2,11 @@ package server import ( "crypto/tls" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + healthpb "google.golang.org/grpc/health/grpc_health_v1" "github.com/zitadel/zitadel/internal/api/authz" grpc_api "github.com/zitadel/zitadel/internal/api/grpc" @@ -16,13 +18,21 @@ import ( ) type Server interface { - Gateway RegisterServer(*grpc.Server) + RegisterGateway() RegisterGatewayFunc AppName() string MethodPrefix() string AuthMethods() authz.MethodMapping } +// WithGatewayPrefix extends the server interface with a prefix for the grpc gateway +// +// it's used for the System, Admin, Mgmt and Auth API +type WithGatewayPrefix interface { + Server + GatewayPathPrefix() string +} + func CreateServer( verifier *authz.TokenVerifier, authConfig authz.Config, @@ -40,7 +50,7 @@ func CreateServer( middleware.MetricsHandler(metricTypes, grpc_api.Probes...), middleware.NoCacheInterceptor(), middleware.ErrorHandler(), - middleware.InstanceInterceptor(queries, hostHeaderName, system_pb.SystemService_MethodPrefix), + middleware.InstanceInterceptor(queries, hostHeaderName, system_pb.SystemService_MethodPrefix, healthpb.Health_ServiceDesc.ServiceName), middleware.AccessStorageInterceptor(accessSvc), middleware.AuthorizationInterceptor(verifier, authConfig), middleware.TranslationHandler(), diff --git a/internal/api/grpc/session/v2/server.go b/internal/api/grpc/session/v2/server.go new file mode 100644 index 0000000000..22919fdb7d --- /dev/null +++ b/internal/api/grpc/session/v2/server.go @@ -0,0 +1,51 @@ +package session + +import ( + "google.golang.org/grpc" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/server" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/pkg/grpc/session/v2alpha" +) + +var _ session.SessionServiceServer = (*Server)(nil) + +type Server struct { + session.UnimplementedSessionServiceServer + command *command.Commands + query *query.Queries +} + +type Config struct{} + +func CreateServer( + command *command.Commands, + query *query.Queries, +) *Server { + return &Server{ + command: command, + query: query, + } +} + +func (s *Server) RegisterServer(grpcServer *grpc.Server) { + session.RegisterSessionServiceServer(grpcServer, s) +} + +func (s *Server) AppName() string { + return session.SessionService_ServiceDesc.ServiceName +} + +func (s *Server) MethodPrefix() string { + return session.SessionService_ServiceDesc.ServiceName +} + +func (s *Server) AuthMethods() authz.MethodMapping { + return session.SessionService_AuthMethods +} + +func (s *Server) RegisterGateway() server.RegisterGatewayFunc { + return session.RegisterSessionServiceHandler +} diff --git a/internal/api/grpc/session/v2/session.go b/internal/api/grpc/session/v2/session.go new file mode 100644 index 0000000000..3e90e76a99 --- /dev/null +++ b/internal/api/grpc/session/v2/session.go @@ -0,0 +1,18 @@ +package session + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/pkg/grpc/session/v2alpha" + "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha" +) + +func (s *Server) GetSession(ctx context.Context, req *session.GetSessionRequest) (*session.GetSessionResponse, error) { + return &session.GetSessionResponse{ + Session: &session.Session{ + Id: req.Id, + User: &user.User{Id: authz.GetCtxData(ctx).UserID}, + }, + }, nil +} diff --git a/internal/api/grpc/system/server.go b/internal/api/grpc/system/server.go index 2d9745181a..a954cfce43 100644 --- a/internal/api/grpc/system/server.go +++ b/internal/api/grpc/system/server.go @@ -67,8 +67,8 @@ func (s *Server) AuthMethods() authz.MethodMapping { return system.SystemService_AuthMethods } -func (s *Server) RegisterGateway() server.GatewayFunc { - return system.RegisterSystemServiceHandlerFromEndpoint +func (s *Server) RegisterGateway() server.RegisterGatewayFunc { + return system.RegisterSystemServiceHandler } func (s *Server) GatewayPathPrefix() string { diff --git a/internal/api/grpc/user/v2/server.go b/internal/api/grpc/user/v2/server.go new file mode 100644 index 0000000000..6a7a96d31d --- /dev/null +++ b/internal/api/grpc/user/v2/server.go @@ -0,0 +1,51 @@ +package user + +import ( + "google.golang.org/grpc" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/server" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha" +) + +var _ user.UserServiceServer = (*Server)(nil) + +type Server struct { + user.UnimplementedUserServiceServer + command *command.Commands + query *query.Queries +} + +type Config struct{} + +func CreateServer( + command *command.Commands, + query *query.Queries, +) *Server { + return &Server{ + command: command, + query: query, + } +} + +func (s *Server) RegisterServer(grpcServer *grpc.Server) { + user.RegisterUserServiceServer(grpcServer, s) +} + +func (s *Server) AppName() string { + return user.UserService_ServiceDesc.ServiceName +} + +func (s *Server) MethodPrefix() string { + return user.UserService_ServiceDesc.ServiceName +} + +func (s *Server) AuthMethods() authz.MethodMapping { + return user.UserService_AuthMethods +} + +func (s *Server) RegisterGateway() server.RegisterGatewayFunc { + return user.RegisterUserServiceHandler +} diff --git a/internal/api/grpc/user/v2/test.go b/internal/api/grpc/user/v2/test.go new file mode 100644 index 0000000000..8b5bc38654 --- /dev/null +++ b/internal/api/grpc/user/v2/test.go @@ -0,0 +1,55 @@ +package user + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha" +) + +func (s *Server) TestGet(ctx context.Context, req *user.TestGetRequest) (*user.TestGetResponse, error) { + return &user.TestGetResponse{ + Ctx: req.Ctx.String(), + }, nil +} + +func (s *Server) TestPost(ctx context.Context, req *user.TestPostRequest) (*user.TestPostResponse, error) { + return &user.TestPostResponse{ + Ctx: req.Ctx.String(), + }, nil +} + +func (s *Server) TestAuth(ctx context.Context, req *user.TestAuthRequest) (*user.TestAuthResponse, error) { + reqCtx, err := authDemo(ctx, req.Ctx) + if err != nil { + return nil, err + } + return &user.TestAuthResponse{ + User: &user.User{Id: authz.GetCtxData(ctx).UserID}, + Ctx: reqCtx, + }, nil +} + +func authDemo(ctx context.Context, reqCtx *user.Context) (*user.Context, error) { + ro := authz.GetCtxData(ctx).ResourceOwner + if reqCtx == nil { + return &user.Context{Ctx: &user.Context_OrgId{OrgId: ro}}, nil + } + switch c := reqCtx.Ctx.(type) { + case *user.Context_OrgId: + if c.OrgId == ro { + return reqCtx, nil + } + return nil, errors.ThrowPermissionDenied(nil, "USER-dg4g", "Errors.User.NotAllowedOrg") + case *user.Context_OrgDomain: + if c.OrgDomain == "forbidden.com" { + return nil, errors.ThrowPermissionDenied(nil, "USER-SDg4g", "Errors.User.NotAllowedOrg") + } + return reqCtx, nil + case *user.Context_Instance: + return reqCtx, nil + default: + return reqCtx, nil + } +} diff --git a/proto/zitadel/session/v2alpha/session.proto b/proto/zitadel/session/v2alpha/session.proto new file mode 100644 index 0000000000..8d4c168b17 --- /dev/null +++ b/proto/zitadel/session/v2alpha/session.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package zitadel.session.v2alpha; + +import "zitadel/user/v2alpha/user.proto"; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/session/v2alpha;session"; + +message Session { + string id = 1; + zitadel.user.v2alpha.User user = 2; +} diff --git a/proto/zitadel/session/v2alpha/session_service.proto b/proto/zitadel/session/v2alpha/session_service.proto new file mode 100644 index 0000000000..aefd47e21b --- /dev/null +++ b/proto/zitadel/session/v2alpha/session_service.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package zitadel.session.v2alpha; + +import "zitadel/options.proto"; +import "zitadel/session/v2alpha/session.proto"; +import "google/api/annotations.proto"; +import "validate/validate.proto"; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/session/v2alpha;session"; + +service SessionService { + + // GetSession is to demonstrate an authenticated request, where the authenticated user (usage of another grpc package) is returned + // + // this request is subject to change and currently used for demonstration only + rpc GetSession (GetSessionRequest) returns (GetSessionResponse) { + option (google.api.http) = { + get: "/v2alpha/sessions/{id}" + }; + + option (zitadel.v1.auth_option) = { + permission: "authenticated" + }; + } +} + +message GetSessionRequest{ + string id = 1; +} +message GetSessionResponse{ + Session session = 1; +} diff --git a/proto/zitadel/user/v2alpha/user.proto b/proto/zitadel/user/v2alpha/user.proto new file mode 100644 index 0000000000..9b8426f517 --- /dev/null +++ b/proto/zitadel/user/v2alpha/user.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package zitadel.user.v2alpha; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha;user"; + +message User { + string id = 1; +} diff --git a/proto/zitadel/user/v2alpha/user_service.proto b/proto/zitadel/user/v2alpha/user_service.proto new file mode 100644 index 0000000000..f16bb8ecfa --- /dev/null +++ b/proto/zitadel/user/v2alpha/user_service.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +package zitadel.user.v2alpha; + +import "zitadel/options.proto"; +import "zitadel/user/v2alpha/user.proto"; +import "google/api/annotations.proto"; +import "validate/validate.proto"; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha;user"; + +service UserService { + + // TestGet simply demonstrates how the context (org, instance) could be handled in a GET request // + // + // this request is subject to change and currently used for demonstration only + rpc TestGet (TestGetRequest) returns (TestGetResponse) { + option (google.api.http) = { + get: "/v2alpha/users/test" + }; + } + + // TestPOST simply demonstrates how the context (org, instance) could be handled in a POST request + // + // this request is subject to change and currently used for demonstration only + rpc TestPost (TestPostRequest) returns (TestPostResponse) { + option (google.api.http) = { + post: "/v2alpha/users/test" + body: "*" + }; + } + + // TestAuth demonstrates how the context (org, instance) could be handled in combination of the authorized context + // + // this request is subject to change and currently used for demonstration only + rpc TestAuth (TestAuthRequest) returns (TestAuthResponse) { + option (google.api.http) = { + get: "/v2alpha/users/test_auth" + }; + + option (zitadel.v1.auth_option) = { + permission: "authenticated" + }; + } +} + +message TestGetRequest{ + Context ctx = 1; +} + +message TestGetResponse{ + string ctx = 1; +} + +message TestPostRequest{ + Context ctx = 1; +} + +message TestPostResponse{ + string ctx = 1; +} + +message TestAuthRequest{ + Context ctx = 1; +} + +message TestAuthResponse{ + User user = 1; + Context ctx = 2; +} + +message Context { + oneof ctx { + bool instance = 1; + string org_id = 2; + string org_domain = 3; + } +}