Add the backchannel logout event

This commit is contained in:
Roman Perekhod
2024-06-25 10:59:00 +02:00
parent 721b32bd1e
commit eac5eaea8f
15 changed files with 194 additions and 56 deletions

View File

@@ -8,6 +8,9 @@ import (
"os"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/stream"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/store"
chimiddleware "github.com/go-chi/chi/v5/middleware"
@@ -132,22 +135,65 @@ func Server(cfg *config.Config) *cli.Command {
proxy.Logger(logger),
proxy.Config(cfg),
)
if err != nil {
return fmt.Errorf("failed to initialize reverse proxy: %w", err)
}
gatewaySelector, err := pool.GatewaySelector(
cfg.Reva.Address,
append(
cfg.Reva.GetRevaOptions(),
pool.WithRegistry(registry.GetRegistry()),
pool.WithTracerProvider(traceProvider),
)...)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to get gateway selector")
}
var userProvider backend.UserBackend
switch cfg.AccountBackend {
case "cs3":
userProvider = backend.NewCS3UserBackend(
backend.WithLogger(logger),
backend.WithRevaGatewaySelector(gatewaySelector),
backend.WithMachineAuthAPIKey(cfg.MachineAuthAPIKey),
backend.WithOIDCissuer(cfg.OIDC.Issuer),
backend.WithServiceAccount(cfg.ServiceAccount),
backend.WithAutoProvisionClaims(cfg.AutoProvisionClaims),
)
default:
logger.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend)
}
var publisher events.Stream
if cfg.Events.Endpoint != "" {
var err error
publisher, err = stream.NatsFromConfig(cfg.Service.Name, false, stream.NatsConfig(cfg.Events))
if err != nil {
logger.Error().
Err(err).
Msg("Error initializing events publisher")
return fmt.Errorf("could not initialize events publisher %w", err)
}
}
lh := staticroutes.StaticRouteHandler{
Prefix: cfg.HTTP.Root,
UserInfoCache: userInfoCache,
Logger: logger,
Config: *cfg,
OidcClient: oidcClient,
OidcHttpClient: oidcHTTPClient,
Proxy: rp,
Prefix: cfg.HTTP.Root,
UserInfoCache: userInfoCache,
Logger: logger,
Config: *cfg,
OidcClient: oidcClient,
OidcHttpClient: oidcHTTPClient,
Proxy: rp,
EventsPublisher: publisher,
UserProvider: userProvider,
}
if err != nil {
return fmt.Errorf("failed to initialize reverse proxy: %w", err)
}
{
middlewares := loadMiddlewares(ctx, logger, cfg, userInfoCache, signingKeyStore, traceProvider, *m)
middlewares := loadMiddlewares(logger, cfg, userInfoCache, signingKeyStore, traceProvider, *m, userProvider, gatewaySelector)
server, err := proxyHTTP.Server(
proxyHTTP.Handler(lh.Handler()),
proxyHTTP.Logger(logger),
@@ -200,37 +246,12 @@ func Server(cfg *config.Config) *cli.Command {
}
}
func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics) alice.Chain {
func loadMiddlewares(logger log.Logger, cfg *config.Config,
userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics,
userProvider backend.UserBackend, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) alice.Chain {
rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", cfg.GrpcClient)
policiesProviderClient := policiessvc.NewPoliciesProviderService("com.owncloud.api.policies", cfg.GrpcClient)
gatewaySelector, err := pool.GatewaySelector(
cfg.Reva.Address,
append(
cfg.Reva.GetRevaOptions(),
pool.WithRegistry(registry.GetRegistry()),
pool.WithTracerProvider(traceProvider),
)...)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to get gateway selector")
}
if err != nil {
logger.Fatal().Err(err).
Msg("Failed to create token manager")
}
var userProvider backend.UserBackend
switch cfg.AccountBackend {
case "cs3":
userProvider = backend.NewCS3UserBackend(
backend.WithLogger(logger),
backend.WithRevaGatewaySelector(gatewaySelector),
backend.WithMachineAuthAPIKey(cfg.MachineAuthAPIKey),
backend.WithOIDCissuer(cfg.OIDC.Issuer),
backend.WithServiceAccount(cfg.ServiceAccount),
backend.WithAutoProvisionClaims(cfg.AutoProvisionClaims),
)
default:
logger.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend)
}
var roleAssigner userroles.UserRoleAssigner
switch cfg.RoleAssignment.Driver {

View File

@@ -10,13 +10,13 @@ import (
// Config combines all available configuration parts.
type Config struct {
Commons *shared.Commons `mask:"struct" yaml:"-"` // don't use this directly as configuration for a service
Commons *shared.Commons `yaml:"-" mask:"struct"` // don't use this directly as configuration for a service
Service Service `yaml:"-"`
Tracing *Tracing `yaml:"tracing"`
Log *Log `yaml:"log"`
Debug Debug `mask:"struct" yaml:"debug"`
Debug Debug `yaml:"debug" mask:"struct"`
HTTP HTTP `yaml:"http"`
@@ -35,7 +35,7 @@ type Config struct {
AccountBackend string `yaml:"account_backend" env:"PROXY_ACCOUNT_BACKEND_TYPE" desc:"Account backend the PROXY service should use. Currently only 'cs3' is possible here." introductionVersion:"pre5.0"`
UserOIDCClaim string `yaml:"user_oidc_claim" env:"PROXY_USER_OIDC_CLAIM" desc:"The name of an OpenID Connect claim that is used for resolving users with the account backend. The value of the claim must hold a per user unique, stable and non re-assignable identifier. The availability of claims depends on your Identity Provider. There are common claims available for most Identity providers like 'email' or 'preferred_username' but you can also add your own claim." introductionVersion:"pre5.0"`
UserCS3Claim string `yaml:"user_cs3_claim" env:"PROXY_USER_CS3_CLAIM" desc:"The name of a CS3 user attribute (claim) that should be mapped to the 'user_oidc_claim'. Supported values are 'username', 'mail' and 'userid'." introductionVersion:"pre5.0"`
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;PROXY_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"pre5.0"`
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;PROXY_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"pre5.0" mask:"password"`
AutoprovisionAccounts bool `yaml:"auto_provision_accounts" env:"PROXY_AUTOPROVISION_ACCOUNTS" desc:"Set this to 'true' to automatically provision users that do not yet exist in the users service on-demand upon first sign-in. To use this a write-enabled libregraph user backend needs to be setup an running." introductionVersion:"pre5.0"`
AutoProvisionClaims AutoProvisionClaims `yaml:"auto_provision_claims"`
EnableBasicAuth bool `yaml:"enable_basic_auth" env:"PROXY_ENABLE_BASIC_AUTH" desc:"Set this to true to enable 'basic authentication' (username/password)." introductionVersion:"pre5.0"`
@@ -44,8 +44,9 @@ type Config struct {
AuthMiddleware AuthMiddleware `yaml:"auth_middleware"`
PoliciesMiddleware PoliciesMiddleware `yaml:"policies_middleware"`
CSPConfigFileLocation string `yaml:"csp_config_file_location" env:"PROXY_CSP_CONFIG_FILE_LOCATION" desc:"The location of the CSP configuration file." introductionVersion:"6.0.0"`
Events Events `yaml:"events"`
Context context.Context `yaml:"-" json:"-"`
Context context.Context `json:"-" yaml:"-"`
}
// Policy enables us to use multiple directors.
@@ -217,3 +218,14 @@ type ServiceAccount struct {
ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;PROXY_SERVICE_ACCOUNT_ID" desc:"The ID of the service account the service should use. See the 'auth-service' service description for more details." introductionVersion:"5.0"`
ServiceAccountSecret string `yaml:"service_account_secret" env:"OCIS_SERVICE_ACCOUNT_SECRET;PROXY_SERVICE_ACCOUNT_SECRET" desc:"The service account secret." introductionVersion:"5.0"`
}
// Events combines the configuration options for the event bus.
type Events struct {
Endpoint string `yaml:"endpoint" env:"OCIS_EVENTS_ENDPOINT;PROXY_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Set to a empty string to disable emitting events." introductionVersion:"%%NEXT%%"`
Cluster string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;PROXY_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture." introductionVersion:"%%NEXT%%"`
TLSInsecure bool `yaml:"tls_insecure" env:"OCIS_INSECURE;PROXY_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"%%NEXT%%"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;PROXY_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided PROXY_EVENTS_TLS_INSECURE will be seen as false." introductionVersion:"%%NEXT%%"`
EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;PROXY_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services." introductionVersion:"%%NEXT%%"`
AuthUsername string `yaml:"username" env:"OCIS_EVENTS_AUTH_USERNAME;PROXY_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services." introductionVersion:"%%NEXT%%"`
AuthPassword string `yaml:"password" env:"OCIS_EVENTS_AUTH_PASSWORD;PROXY_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the ocis service which receives and delivers events between the services." introductionVersion:"%%NEXT%%"`
}

View File

@@ -92,6 +92,11 @@ func DefaultConfig() *config.Config {
EnableBasicAuth: false,
InsecureBackends: false,
CSPConfigFileLocation: "",
Events: config.Events{
Endpoint: "127.0.0.1:9233",
Cluster: "ocis-cluster",
EnableTLS: false,
},
}
}

View File

@@ -1,10 +1,16 @@
package staticroutes
import (
"context"
"fmt"
"net/http"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/render"
"github.com/owncloud/ocis/v2/ocis-pkg/oidc"
"github.com/pkg/errors"
"github.com/shamaton/msgpack/v2"
microstore "go-micro.dev/v4/store"
)
@@ -33,7 +39,6 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
render.JSON(w, r, nil)
return
}
if err != nil {
logger.Error().Err(err).Msg("Error reading userinfo cache")
render.Status(r, http.StatusBadRequest)
@@ -42,6 +47,10 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
}
for _, record := range records {
err := s.publishBackchannelLogoutEvent(r.Context(), record, logoutToken)
if err != nil {
s.Logger.Warn().Err(err).Msg("could not publish backchannel logout event")
}
err = s.UserInfoCache.Delete(string(record.Value))
if err != nil && !errors.Is(err, microstore.ErrNotFound) {
// Spec requires us to return a 400 BadRequest when the session could not be destroyed
@@ -62,3 +71,43 @@ func (s *StaticRouteHandler) backchannelLogout(w http.ResponseWriter, r *http.Re
render.Status(r, http.StatusOK)
render.JSON(w, r, nil)
}
// publishBackchannelLogoutEvent publishes a backchannel logout event when the callback revived from the identity provider
func (s StaticRouteHandler) publishBackchannelLogoutEvent(ctx context.Context, record *microstore.Record, logoutToken *oidc.LogoutToken) error {
if s.EventsPublisher == nil {
return fmt.Errorf("the events publisher is not set")
}
urecords, err := s.UserInfoCache.Read(string(record.Value))
if err != nil {
return fmt.Errorf("reading userinfo cache: %w", err)
}
if len(urecords) == 0 {
return fmt.Errorf("userinfo not found")
}
var claims map[string]interface{}
if err = msgpack.UnmarshalAsMap(urecords[0].Value, &claims); err != nil {
return fmt.Errorf("could not unmarshal userinfo: %w", err)
}
oidcClaim, ok := claims[s.Config.UserOIDCClaim].(string)
if !ok {
return fmt.Errorf("could not get claim %w", err)
}
user, _, err := s.UserProvider.GetUserByClaims(ctx, s.Config.UserCS3Claim, oidcClaim)
if err != nil || user.GetId() == nil {
return fmt.Errorf("could not get user by claims: %w", err)
}
e := events.BackchannelLogout{
Executant: user.GetId(),
SessionId: logoutToken.SessionId,
Timestamp: utils.TSNow(),
}
if err := events.Publish(ctx, s.EventsPublisher, e); err != nil {
return fmt.Errorf("could not publish user created event %w", err)
}
return nil
}

View File

@@ -3,22 +3,26 @@ package staticroutes
import (
"net/http"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/go-chi/chi/v5"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/oidc"
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
"github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend"
microstore "go-micro.dev/v4/store"
)
// StaticRouteHandler defines a Route Handler for static routes
type StaticRouteHandler struct {
Prefix string
Proxy http.Handler
UserInfoCache microstore.Store
Logger log.Logger
Config config.Config
OidcClient oidc.OIDCClient
OidcHttpClient *http.Client
Prefix string
Proxy http.Handler
UserInfoCache microstore.Store
Logger log.Logger
Config config.Config
OidcClient oidc.OIDCClient
OidcHttpClient *http.Client
EventsPublisher events.Publisher
UserProvider backend.UserBackend
}
type jse struct {