Repository: owncloud/ocis
Pull request #6137 (2023-04-25 10:32:56)

Disable Email Notifications
Adds new setting to disable email  notifications

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=6137)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6137&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6137&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=6137&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=6137&resolved=false&types=CODE_SMELL)

[![53.8%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/50-16px.png '53.8%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6137&metric=new_coverage&view=list) [53.8% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6137&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=6137&metric=new_duplicated_lines_density&view=list)


ownclouders: E2E tests failed: https://drone.owncloud.com/owncloud/ocis/21745/68/1
<details><summary>:boom: To see the trace, please open the link in the console ...</summary>

<p>


#### npx playwright show-trace https://cache.owncloud.com/public/owncloud/ocis/21745/tracing/space-members-can-be-invited-with-an-expiration-date-alice-2023-4-25-10-57-58.zip 


</p></details>

<!-- id: pr-6137 -->

mmattel: Needs webui documentation.
Will appear as item (when impemented by the web team) in the users personal settings.
Same location where the GDPR report request.

Patch:
diff --git a/.drone.env b/.drone.env
index ac3bbd51162..872eb6c05fa 100644
--- a/.drone.env
+++ b/.drone.env
@@ -1,4 +1,4 @@
 # The test runner source for UI tests
 
-WEB_COMMITID=5610aa9df4413d89f5517479879bb28946be2077
+WEB_COMMITID=ca6eefbf300e2014a1264d11b982db8cd87cdd04
 WEB_BRANCH=master
diff --git a/.drone.star b/.drone.star
index 3289e26b109..a1797f3300c 100644
--- a/.drone.star
+++ b/.drone.star
@@ -1162,7 +1162,7 @@ def e2eTests(ctx):
         },
         "commands": [
             "cd %s" % dirs["web"],
-            "sleep 10 && pnpm test:e2e:cucumber tests/e2e/cucumber/**/*[!.oc10].feature",
+            "sleep 10 && pnpm test:e2e:cucumber tests/e2e/cucumber/**/*[!.oc10].feature --tags ~@skip",
         ],
     }]
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e3ed750c4f..9e8239b9bcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,15 +1,3 @@
-# Changelog for [unreleased] (UNRELEASED)
-
-The following sections list the changes for unreleased.
-
-[unreleased]: https://github.com/owncloud/ocis/compare/v3.0.0...master
-
-## Summary
-
-* Enhancement - Add debug server to idm: [#6153](https://github.com/owncloud/ocis/pull/6153)
-* Enhancement - Open Debug endpoint for Notifications: [#5002](https://github.com/owncloud/ocis/issues/5002)
-* Enhancement - Open Debug endpoint for Nats: [#5002](https://github.com/owncloud/ocis/issues/5002)
-
 # Changelog for [3.0.0] (2023-04-24)
 
 The following sections list the changes for 3.0.0.
diff --git a/changelog/unreleased/add-debug-to-idm.md b/changelog/unreleased/add-debug-to-idm.md
deleted file mode 100644
index c0213554f11..00000000000
--- a/changelog/unreleased/add-debug-to-idm.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Enhancement: Add debug server to idm
-
-We added a debug server to idm.
-
-https://github.com/owncloud/ocis/pull/6153
-https://github.com/owncloud/ocis/issues/5003
\ No newline at end of file
diff --git a/changelog/unreleased/add-notifications-debug-server.md b/changelog/unreleased/add-notifications-debug-server.md
deleted file mode 100644
index b87ce43c584..00000000000
--- a/changelog/unreleased/add-notifications-debug-server.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Enhancement: Open Debug endpoint for Notifications
-
-We added a debug server to the notifications service
-
-https://github.com/owncloud/ocis/issues/5002
-https://github.com/owncloud/ocis/pull/6155
\ No newline at end of file
diff --git a/changelog/unreleased/disable-notifications.md b/changelog/unreleased/disable-notifications.md
new file mode 100644
index 00000000000..671a106163d
--- /dev/null
+++ b/changelog/unreleased/disable-notifications.md
@@ -0,0 +1,5 @@
+Enhancement: Disable Notifications
+
+Introduce new setting to disable notifications
+
+https://github.com/owncloud/ocis/pull/6136
diff --git a/changelog/unreleased/nats-debug.md b/changelog/unreleased/nats-debug.md
deleted file mode 100644
index 6b0a7606c0e..00000000000
--- a/changelog/unreleased/nats-debug.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Enhancement: Open Debug endpoint for Nats
-
-We added a debug server to nats
-
-https://github.com/owncloud/ocis/issues/5002
-https://github.com/owncloud/ocis/pull/6139
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 8dffa474162..a21d5264359 100644
--- a/go.mod
+++ b/go.mod
@@ -42,7 +42,7 @@ require (
 	github.com/gofrs/uuid v4.4.0+incompatible
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/golang/protobuf v1.5.3
-	github.com/google/go-tika v0.3.0
+	github.com/google/go-tika v0.2.0
 	github.com/google/uuid v1.3.0
 	github.com/gookit/config/v2 v2.1.8
 	github.com/gorilla/mux v1.8.0
diff --git a/go.sum b/go.sum
index 690987bf62a..56dea46bdc5 100644
--- a/go.sum
+++ b/go.sum
@@ -966,8 +966,8 @@ github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
-github.com/google/go-tika v0.3.0 h1:JncwikDcIJrSwwoTjWg6NE7g3IW7XTrI6ZeeOGp03Y8=
-github.com/google/go-tika v0.3.0/go.mod h1:k/Afo/0kDgo9g/9gjIIUBoDXgyGgYO3JAISqowoJdVE=
+github.com/google/go-tika v0.2.0 h1:+1dnOoJ/pJrko2XH/3Rm5ssG9+ixOgjmPEz94ikUsxI=
+github.com/google/go-tika v0.2.0/go.mod h1:vnMADwNG1A2AJx+ycQgTNMGe3ZG4CZUowEhK2FykumQ=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -1786,6 +1786,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
diff --git a/ocis-pkg/oidc/client.go b/ocis-pkg/oidc/client.go
index f4fab46ee67..7ecdebb36a9 100644
--- a/ocis-pkg/oidc/client.go
+++ b/ocis-pkg/oidc/client.go
@@ -55,6 +55,8 @@ type oidcClient struct {
 	// Logger to use for logging, must be set
 	Logger log.Logger
 
+	clientID                string
+	skipClientIDCheck       bool
 	issuer                  string
 	provider                *ProviderMetadata
 	providerLock            *sync.Mutex
@@ -97,6 +99,8 @@ func NewOIDCClient(opts ...Option) OIDCClient {
 		JWKSOptions:             options.JWKSOptions, // TODO I don't like that we pass down config options ...
 		providerLock:            &sync.Mutex{},
 		jwksLock:                &sync.Mutex{},
+		clientID:                options.ClientID,
+		skipClientIDCheck:       options.SkipClientIDCheck,
 		remoteKeySet:            options.KeySet,
 		provider:                options.ProviderMetadata,
 	}
@@ -359,6 +363,19 @@ func (c *oidcClient) VerifyLogoutToken(ctx context.Context, rawToken string) (*L
 		return nil, fmt.Errorf("oidc: logout token issued by a different provider, expected %q got %q", c.issuer, token.Issuer)
 	}
 
+	// If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
+	//
+	// This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
+	if !c.skipClientIDCheck {
+		if c.clientID != "" {
+			if !contains(token.Audience, c.clientID) {
+				return nil, fmt.Errorf("oidc: expected audience %q got %q", c.clientID, token.Audience)
+			}
+		} else {
+			return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
+		}
+	}
+
 	switch len(jws.Signatures) {
 	case 0:
 		return nil, fmt.Errorf("oidc: logout token not signed")
diff --git a/ocis-pkg/oidc/options.go b/ocis-pkg/oidc/options.go
index 149433ebaff..86439532aef 100644
--- a/ocis-pkg/oidc/options.go
+++ b/ocis-pkg/oidc/options.go
@@ -27,6 +27,11 @@ type Options struct {
 	// AccessTokenVerifyMethod to use when verifying access tokens
 	// TODO pass a function or interface to verify? an AccessTokenVerifier?
 	AccessTokenVerifyMethod string
+	// ClientID the client id to expect in tokens. If not set SkipClientIDCheck must be true
+	// TODO also check in access token
+	ClientID string
+	// SkipClientIDCheck must be true if ClientID is empty
+	SkipClientIDCheck bool
 	// Config to use
 	Config *goidc.Config
 
@@ -87,6 +92,20 @@ func WithKeySet(val KeySet) Option {
 	}
 }
 
+// WithClientID provides a function to set the clientID option.
+func WithClientID(val string) Option {
+	return func(o *Options) {
+		o.ClientID = val
+	}
+}
+
+// WithSkipClientIDCheck provides a function to set the skipClientIDCheck option.
+func WithSkipClientIDCheck(val bool) Option {
+	return func(o *Options) {
+		o.SkipClientIDCheck = val
+	}
+}
+
 // WithConfig provides a function to set the Config option.
 func WithConfig(val *goidc.Config) Option {
 	return func(o *Options) {
diff --git a/services/idm/pkg/command/server.go b/services/idm/pkg/command/server.go
index 4ba21b2a8ed..9982847ba7c 100644
--- a/services/idm/pkg/command/server.go
+++ b/services/idm/pkg/command/server.go
@@ -13,13 +13,9 @@ import (
 	"github.com/libregraph/idm/pkg/ldappassword"
 	"github.com/libregraph/idm/pkg/ldbbolt"
 	"github.com/libregraph/idm/server"
-	"github.com/oklog/run"
 	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
 	pkgcrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto"
-	"github.com/owncloud/ocis/v2/ocis-pkg/handlers"
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
-	"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
-	"github.com/owncloud/ocis/v2/ocis-pkg/version"
 	"github.com/owncloud/ocis/v2/services/idm"
 	"github.com/owncloud/ocis/v2/services/idm/pkg/config"
 	"github.com/owncloud/ocis/v2/services/idm/pkg/config/parser"
@@ -37,90 +33,51 @@ func Server(cfg *config.Config) *cli.Command {
 			return configlog.ReturnFatal(parser.ParseConfig(cfg))
 		},
 		Action: func(c *cli.Context) error {
-			var (
-				gr          = run.Group{}
-				logger      = logging.Configure(cfg.Service.Name, cfg.Log)
-				ctx, cancel = func() (context.Context, context.CancelFunc) {
-					if cfg.Context == nil {
-						return context.WithCancel(context.Background())
-					}
-					return context.WithCancel(cfg.Context)
-				}()
-			)
-
-			defer cancel()
-
-			{
-				servercfg := server.Config{
-					Logger:          log.LogrusWrap(logger.Logger),
-					LDAPHandler:     "boltdb",
-					LDAPSListenAddr: cfg.IDM.LDAPSAddr,
-					TLSCertFile:     cfg.IDM.Cert,
-					TLSKeyFile:      cfg.IDM.Key,
-					LDAPBaseDN:      "o=libregraph-idm",
-					LDAPAdminDN:     "uid=libregraph,ou=sysusers,o=libregraph-idm",
-
-					BoltDBFile: cfg.IDM.DatabasePath,
-				}
-
-				if cfg.IDM.LDAPSAddr != "" {
-					// Generate a self-signing cert if no certificate is present
-					if err := pkgcrypto.GenCert(cfg.IDM.Cert, cfg.IDM.Key, logger); err != nil {
-						logger.Fatal().Err(err).Msgf("Could not generate test-certificate")
-					}
-				}
-				if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) {
-					logger.Debug().Msg("Bootstrapping IDM database")
-					if err = bootstrap(logger, cfg, servercfg); err != nil {
-						logger.Error().Err(err).Msg("failed to bootstrap idm database")
-					}
+			logger := logging.Configure(cfg.Service.Name, cfg.Log)
+			ctx, cancel := func() (context.Context, context.CancelFunc) {
+				if cfg.Context == nil {
+					return context.WithCancel(context.Background())
 				}
+				return context.WithCancel(cfg.Context)
+			}()
 
-				svc, err := server.NewServer(&servercfg)
-				if err != nil {
-					return err
-				}
+			defer cancel()
+			return start(ctx, logger, cfg)
+		},
+	}
+}
 
-				gr.Add(func() error {
-					err := make(chan error)
-					select {
-					case <-ctx.Done():
-						return nil
-
-					case err <- svc.Serve(ctx):
-						return <-err
-					}
-				}, func(err error) {
-					logger.Error().
-						Err(err).
-						Msg("Shutting down server")
-					cancel()
-				})
-			}
+func start(ctx context.Context, logger log.Logger, cfg *config.Config) error {
+	servercfg := server.Config{
+		Logger:          log.LogrusWrap(logger.Logger),
+		LDAPHandler:     "boltdb",
+		LDAPSListenAddr: cfg.IDM.LDAPSAddr,
+		TLSCertFile:     cfg.IDM.Cert,
+		TLSKeyFile:      cfg.IDM.Key,
+		LDAPBaseDN:      "o=libregraph-idm",
+		LDAPAdminDN:     "uid=libregraph,ou=sysusers,o=libregraph-idm",
+
+		BoltDBFile: cfg.IDM.DatabasePath,
+	}
 
-			{
-				server := debug.NewService(
-					debug.Logger(logger),
-					debug.Name(cfg.Service.Name),
-					debug.Version(version.GetString()),
-					debug.Address(cfg.Debug.Addr),
-					debug.Token(cfg.Debug.Token),
-					debug.Pprof(cfg.Debug.Pprof),
-					debug.Zpages(cfg.Debug.Zpages),
-					debug.Health(handlers.Health),
-					debug.Ready(handlers.Ready),
-				)
-
-				gr.Add(server.ListenAndServe, func(_ error) {
-					_ = server.Shutdown(ctx)
-					cancel()
-				})
-			}
+	if cfg.IDM.LDAPSAddr != "" {
+		// Generate a self-signing cert if no certificate is present
+		if err := pkgcrypto.GenCert(cfg.IDM.Cert, cfg.IDM.Key, logger); err != nil {
+			logger.Fatal().Err(err).Msgf("Could not generate test-certificate")
+		}
+	}
+	if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) {
+		logger.Debug().Msg("Bootstrapping IDM database")
+		if err = bootstrap(logger, cfg, servercfg); err != nil {
+			logger.Error().Err(err).Msg("failed to bootstrap idm database")
+		}
+	}
 
-			return gr.Run()
-			//return start(ctx, logger, cfg)
-		},
+	svc, err := server.NewServer(&servercfg)
+	if err != nil {
+		return err
 	}
+	return svc.Serve(ctx)
 }
 
 func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) error {
diff --git a/services/idm/pkg/config/defaults/defaultconfig.go b/services/idm/pkg/config/defaults/defaultconfig.go
index 93e77da153f..2a977b354e2 100644
--- a/services/idm/pkg/config/defaults/defaultconfig.go
+++ b/services/idm/pkg/config/defaults/defaultconfig.go
@@ -19,10 +19,7 @@ func FullDefaultConfig() *config.Config {
 func DefaultConfig() *config.Config {
 	return &config.Config{
 		Debug: config.Debug{
-			Addr:   "127.0.0.1:9239",
-			Token:  "",
-			Pprof:  false,
-			Zpages: false,
+			Addr: "127.0.0.1:9239",
 		},
 		Service: config.Service{
 			Name: "idm",
diff --git a/services/nats/pkg/command/server.go b/services/nats/pkg/command/server.go
index 09dc6cb54c5..c8de4e2eee8 100644
--- a/services/nats/pkg/command/server.go
+++ b/services/nats/pkg/command/server.go
@@ -9,9 +9,6 @@ import (
 
 	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
 	pkgcrypto "github.com/owncloud/ocis/v2/ocis-pkg/crypto"
-	"github.com/owncloud/ocis/v2/ocis-pkg/handlers"
-	"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
-	"github.com/owncloud/ocis/v2/ocis-pkg/version"
 	"github.com/owncloud/ocis/v2/services/nats/pkg/config"
 	"github.com/owncloud/ocis/v2/services/nats/pkg/config/parser"
 	"github.com/owncloud/ocis/v2/services/nats/pkg/logging"
@@ -41,25 +38,6 @@ func Server(cfg *config.Config) *cli.Command {
 
 			defer cancel()
 
-			{
-				server := debug.NewService(
-					debug.Logger(logger),
-					debug.Name(cfg.Service.Name),
-					debug.Version(version.GetString()),
-					debug.Address(cfg.Debug.Addr),
-					debug.Token(cfg.Debug.Token),
-					debug.Pprof(cfg.Debug.Pprof),
-					debug.Zpages(cfg.Debug.Zpages),
-					debug.Health(handlers.Health),
-					debug.Ready(handlers.Ready),
-				)
-
-				gr.Add(server.ListenAndServe, func(_ error) {
-					_ = server.Shutdown(ctx)
-					cancel()
-				})
-			}
-
 			var tlsConf *tls.Config
 			if cfg.Nats.EnableTLS {
 				// Generate a self-signing cert if no certificate is present
diff --git a/services/nats/pkg/config/config.go b/services/nats/pkg/config/config.go
index b54b1daa8b2..020c8d473c8 100644
--- a/services/nats/pkg/config/config.go
+++ b/services/nats/pkg/config/config.go
@@ -9,10 +9,11 @@ import (
 // Config combines all available configuration parts.
 type Config struct {
 	Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
-	Service Service         `yaml:"-"`
-	Tracing *Tracing        `yaml:"tracing"`
-	Log     *Log            `yaml:"log"`
-	Debug   Debug           `yaml:"debug"`
+
+	Service Service `yaml:"-"`
+
+	Log   *Log  `yaml:"log"`
+	Debug Debug `yaml:"debug"`
 
 	Nats Nats `ociConfig:"nats"`
 
@@ -30,11 +31,3 @@ type Nats struct {
 	TLSSkipVerifyClientCert bool   `yaml:"tls_skip_verify_client_cert" env:"OCIS_INSECURE;NATS_TLS_SKIP_VERIFY_CLIENT_CERT" desc:"Whether the NATS server should skip the client certificate verification during the TLS handshake."`
 	EnableTLS               bool   `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NATS_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.."`
 }
-
-// Tracing is the tracing config
-type Tracing struct {
-	Enabled   bool   `yaml:"enabled" env:"OCIS_TRACING_ENABLED;NATS_TRACING_ENABLED" desc:"Activates tracing."`
-	Type      string `yaml:"type" env:"OCIS_TRACING_TYPE;NATS_TRACING_TYPE" desc:"The type of tracing. Defaults to \"\", which is the same as \"jaeger\". Allowed tracing types are \"jaeger\" and \"\" as of now."`
-	Endpoint  string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;NATS_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."`
-	Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;NATS_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."`
-}
diff --git a/services/nats/pkg/config/defaults/defaultconfig.go b/services/nats/pkg/config/defaults/defaultconfig.go
index 9f2add03144..f4bf4f64e22 100644
--- a/services/nats/pkg/config/defaults/defaultconfig.go
+++ b/services/nats/pkg/config/defaults/defaultconfig.go
@@ -22,10 +22,7 @@ func FullDefaultConfig() *config.Config {
 func DefaultConfig() *config.Config {
 	return &config.Config{
 		Debug: config.Debug{
-			Addr:   "127.0.0.1:9234",
-			Token:  "",
-			Pprof:  false,
-			Zpages: false,
+			Addr: "127.0.0.1:9234",
 		},
 		Service: config.Service{
 			Name: "nats",
@@ -55,18 +52,6 @@ func EnsureDefaults(cfg *config.Config) {
 	} else if cfg.Log == nil {
 		cfg.Log = &config.Log{}
 	}
-
-	// provide with defaults for shared tracing, since we need a valid destination address for "envdecode".
-	if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil {
-		cfg.Tracing = &config.Tracing{
-			Enabled:   cfg.Commons.Tracing.Enabled,
-			Type:      cfg.Commons.Tracing.Type,
-			Endpoint:  cfg.Commons.Tracing.Endpoint,
-			Collector: cfg.Commons.Tracing.Collector,
-		}
-	} else if cfg.Tracing == nil {
-		cfg.Tracing = &config.Tracing{}
-	}
 }
 
 // Sanitize sanitizes the configuration
diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go
index f1b623c2b9c..2bacb06619d 100644
--- a/services/notifications/pkg/command/server.go
+++ b/services/notifications/pkg/command/server.go
@@ -1,7 +1,6 @@
 package command
 
 import (
-	"context"
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
@@ -11,13 +10,9 @@ import (
 	"github.com/cs3org/reva/v2/pkg/events/stream"
 	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
 	"github.com/go-micro/plugins/v4/events/natsjs"
-	"github.com/oklog/run"
 	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
 	"github.com/owncloud/ocis/v2/ocis-pkg/crypto"
-	"github.com/owncloud/ocis/v2/ocis-pkg/handlers"
-	"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
 	"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
-	"github.com/owncloud/ocis/v2/ocis-pkg/version"
 	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	"github.com/owncloud/ocis/v2/services/notifications/pkg/channels"
 	"github.com/owncloud/ocis/v2/services/notifications/pkg/config"
@@ -39,36 +34,6 @@ func Server(cfg *config.Config) *cli.Command {
 		Action: func(c *cli.Context) error {
 			logger := logging.Configure(cfg.Service.Name, cfg.Log)
 
-			gr := run.Group{}
-
-			ctx, cancel := func() (context.Context, context.CancelFunc) {
-				if cfg.Context == nil {
-					return context.WithCancel(context.Background())
-				}
-				return context.WithCancel(cfg.Context)
-			}()
-
-			defer cancel()
-
-			{
-				server := debug.NewService(
-					debug.Logger(logger),
-					debug.Name(cfg.Service.Name),
-					debug.Version(version.GetString()),
-					debug.Address(cfg.Debug.Addr),
-					debug.Token(cfg.Debug.Token),
-					debug.Pprof(cfg.Debug.Pprof),
-					debug.Zpages(cfg.Debug.Zpages),
-					debug.Health(handlers.Health),
-					debug.Ready(handlers.Ready),
-				)
-
-				gr.Add(server.ListenAndServe, func(_ error) {
-					_ = server.Shutdown(ctx)
-					cancel()
-				})
-			}
-
 			// evs defines a list of events to subscribe to
 			evs := []events.Unmarshaller{
 				events.ShareCreated{},
@@ -132,12 +97,7 @@ func Server(cfg *config.Config) *cli.Command {
 			}
 			valueService := settingssvc.NewValueService("com.owncloud.api.settings", grpc.DefaultClient())
 			svc := service.NewEventsNotifier(evts, channel, logger, gwclient, valueService, cfg.Notifications.MachineAuthAPIKey, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL)
-
-			gr.Add(svc.Run, func(error) {
-				cancel()
-			})
-
-			return gr.Run()
+			return svc.Run()
 		},
 	}
 }
diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go
index 4e4fb269f4c..34ce4a26aa1 100644
--- a/services/notifications/pkg/config/config.go
+++ b/services/notifications/pkg/config/config.go
@@ -12,9 +12,8 @@ type Config struct {
 
 	Service Service `yaml:"-"`
 
-	Tracing *Tracing `yaml:"tracing"`
-	Log     *Log     `yaml:"log"`
-	Debug   Debug    `yaml:"debug"`
+	Log   *Log  `yaml:"log"`
+	Debug Debug `yaml:"debug"`
 
 	WebUIURL string `yaml:"ocis_url" env:"OCIS_URL;NOTIFICATIONS_WEB_UI_URL" desc:"The public facing URL of the oCIS Web UI, used e.g. when sending notification eMails"`
 
@@ -55,11 +54,3 @@ type Events struct {
 	TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"NOTIFICATIONS_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."`
 	EnableTLS            bool   `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NOTIFICATIONS_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.."`
 }
-
-// Tracing defines the available tracing configuration.
-type Tracing struct {
-	Enabled   bool   `yaml:"enabled" env:"OCIS_TRACING_ENABLED;IDM_TRACING_ENABLED" desc:"Activates tracing."`
-	Type      string `yaml:"type" env:"OCIS_TRACING_TYPE;IDM_TRACING_TYPE" desc:"The type of tracing. Defaults to \"\", which is the same as \"jaeger\". Allowed tracing types are \"jaeger\" and \"\" as of now."`
-	Endpoint  string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;IDM_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."`
-	Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;IDM_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."`
-}
diff --git a/services/notifications/pkg/config/defaults/defaultconfig.go b/services/notifications/pkg/config/defaults/defaultconfig.go
index 5f3e1961c11..20cd4733042 100644
--- a/services/notifications/pkg/config/defaults/defaultconfig.go
+++ b/services/notifications/pkg/config/defaults/defaultconfig.go
@@ -21,9 +21,7 @@ func FullDefaultConfig() *config.Config {
 func DefaultConfig() *config.Config {
 	return &config.Config{
 		Debug: config.Debug{
-			Addr:   "127.0.0.1:9174",
-			Zpages: false,
-			Pprof:  false,
+			Addr: "127.0.0.1:9174",
 		},
 		Service: config.Service{
 			Name: "notifications",
@@ -62,18 +60,6 @@ func EnsureDefaults(cfg *config.Config) {
 		cfg.Log = &config.Log{}
 	}
 
-	// provide with defaults for shared tracing, since we need a valid destination address for "envdecode".
-	if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil {
-		cfg.Tracing = &config.Tracing{
-			Enabled:   cfg.Commons.Tracing.Enabled,
-			Type:      cfg.Commons.Tracing.Type,
-			Endpoint:  cfg.Commons.Tracing.Endpoint,
-			Collector: cfg.Commons.Tracing.Collector,
-		}
-	} else if cfg.Tracing == nil {
-		cfg.Tracing = &config.Tracing{}
-	}
-
 	if cfg.Notifications.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" {
 		cfg.Notifications.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey
 	}
diff --git a/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po
deleted file mode 100644
index a395e8b5865..00000000000
--- a/services/notifications/pkg/email/l10n/locale/pt_BR/LC_MESSAGES/notifications.po
+++ /dev/null
@@ -1,119 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-# 
-# Translators:
-# Flávio Veras <flaviove@gmail.com>, 2023
-# 
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2023-04-27 00:19+0000\n"
-"PO-Revision-Date: 2023-04-19 11:11+0000\n"
-"Last-Translator: Flávio Veras <flaviove@gmail.com>, 2023\n"
-"Language-Team: Portuguese (Brazil) (https://app.transifex.com/owncloud-org/teams/6149/pt_BR/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: pt_BR\n"
-"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
-
-#. UnsharedSpace email template, resolves via {{ .CallToAction }}
-#: pkg/email/templates.go:57
-msgid "Click here to check it: {ShareLink}"
-msgstr "Clique aqui para conferir: {ShareLink}"
-
-#. ShareCreated email template, resolves via {{ .CallToAction }}
-#. SharedSpace email template, resolves via {{ .CallToAction }}
-#: pkg/email/templates.go:18 pkg/email/templates.go:43
-msgid "Click here to view it: {ShareLink}"
-msgstr "Clique aqui para visualizá-lo: {ShareLink}"
-
-#. ShareCreated email template, resolves via {{ .Greeting }}
-#: pkg/email/templates.go:14
-msgid "Hello {ShareGrantee}"
-msgstr "Olá {ShareGrantee}"
-
-#. ShareExpired email template, resolves via {{ .Greeting }}
-#: pkg/email/templates.go:26
-msgid "Hello {ShareGrantee},"
-msgstr "Olá {ShareGrantee},"
-
-#. SharedSpace email template, resolves via {{ .Greeting }}
-#. UnsharedSpace email template, resolves via {{ .Greeting }}
-#. MembershipExpired email template, resolves via {{ .Greeting }}
-#: pkg/email/templates.go:39 pkg/email/templates.go:51
-#: pkg/email/templates.go:65
-msgid "Hello {SpaceGrantee},"
-msgstr "Olá {SpaceGrantee},"
-
-#. MembershipExpired email template, Subject field (resolves directly)
-#: pkg/email/templates.go:63
-msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
-msgstr "A assinatura de '{SpaceName}' expirou em {ExpiredAt}"
-
-#. ShareExpired email template, Subject field (resolves directly)
-#: pkg/email/templates.go:24
-msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
-msgstr "Compartilhar em '{ShareFolder}' expirou em {ExpiredAt}"
-
-#. MembershipExpired email template, resolves via {{ .MessageBody }}
-#: pkg/email/templates.go:67
-msgid ""
-"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
-"\n"
-"Even though this membership has expired you still might have access through other shares and/or space memberships"
-msgstr ""
-"Sua assinatura do espaço {SpaceName} expirou em {ExpiredAt}\n"
-"\n"
-"Mesmo que esta assinatura tenha expirado, você ainda pode ter acesso por meio de outros compartilhamentos e/ou assinaturas de espaço"
-
-#. ShareExpired email template, resolves via {{ .MessageBody }}
-#: pkg/email/templates.go:28
-msgid ""
-"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
-"\n"
-"Even though this share has been revoked you still might have access through other shares and/or space memberships."
-msgstr ""
-"Seu compartilhamento para {ShareFolder} expirou em {ExpiredAt}\n"
-"\n"
-"Mesmo que este compartilhamento tenha sido revogado, você ainda pode ter acesso por meio de outros compartilhamentos e/ou associações de espaço."
-
-#. ShareCreated email template, resolves via {{ .MessageBody }}
-#: pkg/email/templates.go:16
-msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
-msgstr "{ShareSharer} compartilhou \"{ShareFolder}\" com você."
-
-#. ShareCreated email template, Subject field (resolves directly)
-#: pkg/email/templates.go:12
-msgid "{ShareSharer} shared '{ShareFolder}' with you"
-msgstr "{ShareSharer} compartilhou '{ShareFolder}' com você"
-
-#. SharedSpace email template, resolves via {{ .MessageBody }}
-#: pkg/email/templates.go:41
-msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
-msgstr "{SpaceSharer} convidou você para participar de \"{SpaceName}\"."
-
-#. UnsharedSpace email template, resolves via {{ .MessageBody }}
-#: pkg/email/templates.go:53
-msgid ""
-"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
-"\n"
-"You might still have access through your other groups or direct membership."
-msgstr ""
-"{SpaceSharer} removeu você de \"{SpaceName}\".\n"
-"\n"
-"Você ainda pode ter acesso por meio de outros grupos ou associação direta."
-
-#. SharedSpace email template, Subject field (resolves directly)
-#: pkg/email/templates.go:37
-msgid "{SpaceSharer} invited you to join {SpaceName}"
-msgstr "{SpaceSharer} convidou você para participar de {SpaceName}"
-
-#. UnsharedSpace email template, Subject field (resolves directly)
-#: pkg/email/templates.go:49
-msgid "{SpaceSharer} removed you from {SpaceName}"
-msgstr "{SpaceSharer} removeu você de {SpaceName}"
diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go
index e20b917c283..df17067f984 100644
--- a/services/notifications/pkg/service/service.go
+++ b/services/notifications/pkg/service/service.go
@@ -109,10 +109,7 @@ func (s eventsNotifier) render(ctx context.Context, template email.MessageTempla
 	// Render the Email Template for each user
 	recipientList := make([]recipient, len(granteeList))
 	for i, userID := range granteeList {
-		locale, err := s.getUserLang(ctx, userID)
-		if err != nil {
-			return nil, err
-		}
+		locale := s.getUserLang(ctx, userID)
 		grantee, err := s.getUserName(ctx, userID)
 		if err != nil {
 			return nil, err
@@ -137,9 +134,12 @@ func (s eventsNotifier) send(ctx context.Context, recipientList []recipient, sen
 	}
 }
 
-func (s eventsNotifier) getGranteeList(ctx context.Context, owner, u *user.UserId, g *group.GroupId) ([]*user.UserId, error) {
+func (s eventsNotifier) getGranteeList(ctx context.Context, executant, u *user.UserId, g *group.GroupId) ([]*user.UserId, error) {
 	switch {
 	case u != nil:
+		if s.disableEmails(ctx, u) {
+			return []*user.UserId{}, nil
+		}
 		return []*user.UserId{u}, nil
 	case g != nil:
 		res, err := s.gwClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: g})
@@ -149,14 +149,22 @@ func (s eventsNotifier) getGranteeList(ctx context.Context, owner, u *user.UserI
 		if res.Status.Code != rpc.Code_CODE_OK {
 			return nil, errors.New("could not get group")
 		}
-		for i, userID := range res.GetGroup().GetMembers() {
-			// remove an executant from a list
-			if userID.GetOpaqueId() == owner.GetOpaqueId() {
-				res.Group.Members[i] = res.Group.Members[len(res.Group.Members)-1]
-				return res.Group.Members[:len(res.Group.Members)-1], nil
+
+		var grantees []*user.UserId
+		for _, userID := range res.GetGroup().GetMembers() {
+			// don't add the executant
+			if userID.GetOpaqueId() == executant.GetOpaqueId() {
+				continue
 			}
+
+			// don't add users who opted out
+			if s.disableEmails(ctx, userID) {
+				continue
+			}
+
+			grantees = append(grantees, userID)
 		}
-		return res.Group.Members, nil
+		return grantees, nil
 	default:
 		return nil, errors.New("need at least one non-nil grantee")
 	}
@@ -176,22 +184,34 @@ func (s eventsNotifier) getUserName(ctx context.Context, u *user.UserId) (string
 	return r.GetUser().GetDisplayName(), nil
 }
 
-func (s eventsNotifier) getUserLang(ctx context.Context, u *user.UserId) (string, error) {
+func (s eventsNotifier) getUserLang(ctx context.Context, u *user.UserId) string {
 	granteeCtx := metadata.Set(ctx, middleware.AccountID, u.OpaqueId)
 	if resp, err := s.valueService.GetValueByUniqueIdentifiers(granteeCtx,
 		&settingssvc.GetValueByUniqueIdentifiersRequest{
 			AccountUuid: u.OpaqueId,
 			SettingId:   defaults.SettingUUIDProfileLanguage,
-		}); err == nil {
-		if resp == nil {
-			return _defaultLocale, nil
-		}
-		val := resp.Value.GetValue().GetListValue().GetValues()
+		},
+	); err == nil {
+		val := resp.GetValue().GetValue().GetListValue().GetValues()
 		if len(val) > 0 && val[0] != nil {
-			return val[0].GetStringValue(), nil
+			return val[0].GetStringValue()
 		}
 	}
-	return _defaultLocale, nil
+	return _defaultLocale
+}
+
+func (s eventsNotifier) disableEmails(ctx context.Context, u *user.UserId) bool {
+	granteeCtx := metadata.Set(ctx, middleware.AccountID, u.OpaqueId)
+	if resp, err := s.valueService.GetValueByUniqueIdentifiers(granteeCtx,
+		&settingssvc.GetValueByUniqueIdentifiersRequest{
+			AccountUuid: u.OpaqueId,
+			SettingId:   defaults.SettingUUIDProfileDisableNotifications,
+		},
+	); err == nil {
+		return resp.GetValue().GetValue().GetBoolValue()
+
+	}
+	return false
 }
 
 func (s eventsNotifier) getResourceInfo(ctx context.Context, resourceID *provider.ResourceId, fieldmask *fieldmaskpb.FieldMask) (*provider.ResourceInfo, error) {
diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go
index 8e4cbbef3a6..3ed00b1050e 100644
--- a/services/proxy/pkg/command/server.go
+++ b/services/proxy/pkg/command/server.go
@@ -87,6 +87,8 @@ func Server(cfg *config.Config) *cli.Command {
 				oidc.WithHTTPClient(oidcHTTPClient),
 				oidc.WithOidcIssuer(cfg.OIDC.Issuer),
 				oidc.WithJWKSOptions(cfg.OIDC.JWKS),
+				oidc.WithClientID(cfg.OIDC.ClientID),
+				oidc.WithSkipClientIDCheck(cfg.OIDC.SkipClientIDCheck),
 			)
 
 			var (
diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go
index 9bf50d2ee59..4ed7220e536 100644
--- a/services/proxy/pkg/config/config.go
+++ b/services/proxy/pkg/config/config.go
@@ -108,6 +108,8 @@ type OIDC struct {
 	UserinfoCache           *Cache `yaml:"user_info_cache"`
 	JWKS                    JWKS   `yaml:"jwks"`
 	RewriteWellKnown        bool   `yaml:"rewrite_well_known" env:"PROXY_OIDC_REWRITE_WELLKNOWN" desc:"Enables rewriting the /.well-known/openid-configuration to the configured OIDC issuer. Needed by the Desktop Client, Android Client and iOS Client to discover the OIDC provider."`
+	ClientID                string `yaml:"client_id" env:"OCIS_OIDC_CLIENT_ID;PROXY_OIDC_CLIENT_ID" desc:"OIDC client ID, which ownCloud Web uses. This client needs to be set up in your external IDP (has no effect when using the builtin IDP)."`
+	SkipClientIDCheck       bool   `yaml:"skip_client_id_check" env:"PROXY_OIDC_SKIP_CLIENT_ID_CHECK" desc:"If true will skip checking the configured client ID is present in audience claims. See following chapter for more details: https://openid.net/specs/openid-connect-core-1_0.html#IDToken"`
 }
 
 type JWKS struct {
diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go
index 16ee92d710d..c8ac0e43167 100644
--- a/services/proxy/pkg/config/defaults/defaultconfig.go
+++ b/services/proxy/pkg/config/defaults/defaultconfig.go
@@ -53,6 +53,7 @@ func DefaultConfig() *config.Config {
 				RefreshTimeout:    10, // seconds
 				RefreshUnknownKID: true,
 			},
+			ClientID: "web",
 		},
 		PolicySelector: nil,
 		RoleAssignment: config.RoleAssignment{
diff --git a/services/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go
index 6e7b463f6b5..241bc8bd6e7 100644
--- a/services/settings/pkg/store/defaults/defaults.go
+++ b/services/settings/pkg/store/defaults/defaults.go
@@ -33,6 +33,13 @@ const (
 	// LanguageReadWriteName is the hardcoded setting name for the language read write permission
 	LanguageReadWriteName string = "language-readwrite"
 
+	// DisableEmailNotificationsPermissionID is the hardcoded setting UUID for the disable email notifications permission
+	DisableEmailNotificationsPermissionID string = "ad5bb5e5-dc13-4cd3-9304-09a424564ea8"
+	// DisableEmailNotificationsPermissionName is the hardcoded setting name for the disable email notifications permission
+	DisableEmailNotificationsPermissionName string = "EmailNotifications.ReadWriteDisabled"
+	// DisableEmailNotificationsPermissionDisplayName is the hardcoded setting name for the disable email notifications permission
+	DisableEmailNotificationsPermissionDisplayName string = "Disable Email Notifications"
+
 	// SetPersonalSpaceQuotaPermissionID is the hardcoded setting UUID for the set personal space quota permission
 	SetPersonalSpaceQuotaPermissionID string = "4e6f9709-f9e7-44f1-95d4-b762d27b7896"
 	// SetPersonalSpaceQuotaPermissionName is the hardcoded setting name for the set personal space quota permission
@@ -75,6 +82,8 @@ const (
 
 	// SettingUUIDProfileLanguage is the hardcoded setting UUID for the user profile language
 	SettingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f"
+	// SettingUUIDProfileDisableNotifications is the hardcoded setting UUID for the disable notifications setting
+	SettingUUIDProfileDisableNotifications = "33ffb5d6-cd07-4dc0-afb0-84f7559ae438"
 
 	// AccountManagementPermissionID is the hardcoded setting UUID for the account management permission
 	AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73"
@@ -169,6 +178,21 @@ func generateBundleAdminRole() *settingsmsg.Bundle {
 					},
 				},
 			},
+			{
+				Id:          DisableEmailNotificationsPermissionID,
+				Name:        DisableEmailNotificationsPermissionName,
+				DisplayName: DisableEmailNotificationsPermissionDisplayName,
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_SETTING,
+					Id:   SettingUUIDProfileDisableNotifications,
+				},
+				Value: &settingsmsg.Setting_PermissionValue{
+					PermissionValue: &settingsmsg.Permission{
+						Operation:  settingsmsg.Permission_OPERATION_READWRITE,
+						Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
+					},
+				},
+			},
 			{
 				Id:          AccountManagementPermissionID,
 				Name:        AccountManagementPermissionName,
@@ -426,6 +450,21 @@ func generateBundleSpaceAdminRole() *settingsmsg.Bundle {
 					},
 				},
 			},
+			{
+				Id:          DisableEmailNotificationsPermissionID,
+				Name:        DisableEmailNotificationsPermissionName,
+				DisplayName: DisableEmailNotificationsPermissionDisplayName,
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_SETTING,
+					Id:   SettingUUIDProfileDisableNotifications,
+				},
+				Value: &settingsmsg.Setting_PermissionValue{
+					PermissionValue: &settingsmsg.Permission{
+						Operation:  settingsmsg.Permission_OPERATION_READWRITE,
+						Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
+					},
+				},
+			},
 			{
 				Id:          SelfManagementPermissionID,
 				Name:        SelfManagementPermissionName,
@@ -502,6 +541,21 @@ func generateBundleUserRole() *settingsmsg.Bundle {
 					},
 				},
 			},
+			{
+				Id:          DisableEmailNotificationsPermissionID,
+				Name:        DisableEmailNotificationsPermissionName,
+				DisplayName: DisableEmailNotificationsPermissionDisplayName,
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_SETTING,
+					Id:   SettingUUIDProfileDisableNotifications,
+				},
+				Value: &settingsmsg.Setting_PermissionValue{
+					PermissionValue: &settingsmsg.Permission{
+						Operation:  settingsmsg.Permission_OPERATION_READWRITE,
+						Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
+					},
+				},
+			},
 			{
 				Id:          SelfManagementPermissionID,
 				Name:        SelfManagementPermissionName,
@@ -578,6 +632,21 @@ func generateBundleGuestRole() *settingsmsg.Bundle {
 					},
 				},
 			},
+			{
+				Id:          DisableEmailNotificationsPermissionID,
+				Name:        DisableEmailNotificationsPermissionName,
+				DisplayName: DisableEmailNotificationsPermissionDisplayName,
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_SETTING,
+					Id:   SettingUUIDProfileDisableNotifications,
+				},
+				Value: &settingsmsg.Setting_PermissionValue{
+					PermissionValue: &settingsmsg.Permission{
+						Operation:  settingsmsg.Permission_OPERATION_READWRITE,
+						Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
+					},
+				},
+			},
 		},
 	}
 }
@@ -603,6 +672,16 @@ func generateBundleProfileRequest() *settingsmsg.Bundle {
 				},
 				Value: &languageSetting,
 			},
+			{
+				Id:          SettingUUIDProfileDisableNotifications,
+				Name:        "disable-email-notifications",
+				DisplayName: "Disable Email Notifications",
+				Description: "Disable email notifications",
+				Resource: &settingsmsg.Resource{
+					Type: settingsmsg.Resource_TYPE_USER,
+				},
+				Value: &settingsmsg.Setting_BoolValue{BoolValue: &settingsmsg.Bool{Default: false, Label: "disable notifications"}},
+			},
 		},
 	}
 }
diff --git a/vendor/github.com/google/go-tika/tika/doc.go b/vendor/github.com/google/go-tika/tika/doc.go
index f9a1af6cb4b..cae42735bba 100644
--- a/vendor/github.com/google/go-tika/tika/doc.go
+++ b/vendor/github.com/google/go-tika/tika/doc.go
@@ -21,15 +21,11 @@ Start with basic imports:
 	import "github.com/google/go-tika/tika"
 
 You will need a running Server to make API calls to. So, if you don't
-have a server that is already running, and you don't have the Server
+have a server that is already running and you don't have the Server
 JAR already downloaded, you can download one. The caller is responsible
 for removing the file when no longer needed.
 
-Version is a custom type, and should be passed as such. There are constants in the code for these.
-The following example downloads version 1.21 to the named JAR in the
-current working directory.
-
-	err := tika.DownloadServer(context.Background(), tika.Version121, "tika-server-1.21.jar")
+	err := tika.DownloadServer(context.Background(), "1.16", "tika-server-1.16.jar")
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -37,7 +33,7 @@ current working directory.
 If you don't have a running Tika Server, you can start one.
 
 	// Optionally pass a port as the second argument.
-	s, err := tika.NewServer("tika-server-1.21.jar", "")
+	s, err := tika.NewServer("tika-server-1.16.jar", "")
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/vendor/github.com/google/go-tika/tika/server.go b/vendor/github.com/google/go-tika/tika/server.go
index 758d77ccb76..37d33c70792 100644
--- a/vendor/github.com/google/go-tika/tika/server.go
+++ b/vendor/github.com/google/go-tika/tika/server.go
@@ -37,6 +37,7 @@ import (
 // since you can pass its URL directly to a Client.
 // Additional Java system properties can be added to a Taka Server before
 // startup by adding to the JavaProps map
+
 type Server struct {
 	jar       string
 	url       string // url is derived from port.
@@ -90,12 +91,6 @@ func NewServer(jar, port string) (*Server, error) {
 	if jar == "" {
 		return nil, fmt.Errorf("no jar file specified")
 	}
-
-	// Check if the jar file exists.
-	if _, err := os.Stat(jar); os.IsNotExist(err) {
-		return nil, fmt.Errorf("jar file %q does not exist", jar)
-	}
-
 	if port == "" {
 		port = "9998"
 	}
diff --git a/vendor/github.com/google/go-tika/tika/tika.go b/vendor/github.com/google/go-tika/tika/tika.go
index e040508d348..6877ea5f1e9 100644
--- a/vendor/github.com/google/go-tika/tika/tika.go
+++ b/vendor/github.com/google/go-tika/tika/tika.go
@@ -24,6 +24,8 @@ import (
 	"net/http"
 	"reflect"
 	"strings"
+
+	"golang.org/x/net/context/ctxhttp"
 )
 
 // ClientError is returned by Client's various parse methods and
@@ -95,7 +97,7 @@ type Detector struct {
 // Translator represents the Java package of a Tika Translator.
 type Translator string
 
-// Translators available by default in Tika. You must configure all required
+// Translators available by defult in Tika. You must configure all required
 // authentication details in Tika Server (for example, an API key).
 const (
 	Lingo24Translator   Translator = "org.apache.tika.language.translate.Lingo24Translator"
@@ -117,13 +119,13 @@ func (c *Client) call(ctx context.Context, input io.Reader, method, path string,
 		c.httpClient = http.DefaultClient
 	}
 
-	req, err := http.NewRequestWithContext(ctx, method, c.url+path, input)
+	req, err := http.NewRequest(method, c.url+path, input)
 	if err != nil {
 		return nil, err
 	}
 	req.Header = header
 
-	resp, err := c.httpClient.Do(req)
+	resp, err := ctxhttp.Do(ctx, c.httpClient, req)
 	if err != nil {
 		return nil, err
 	}
@@ -136,8 +138,8 @@ func (c *Client) call(ctx context.Context, input io.Reader, method, path string,
 
 // callString makes the given request to c and returns the result as a string
 // and error. callString returns an error if the response code is not 200 StatusOK.
-func (c *Client) callString(ctx context.Context, input io.Reader, method, path string, header http.Header) (string, error) {
-	body, err := c.call(ctx, input, method, path, header)
+func (c *Client) callString(ctx context.Context, input io.Reader, method, path string) (string, error) {
+	body, err := c.call(ctx, input, method, path, nil)
 	if err != nil {
 		return "", err
 	}
@@ -154,27 +156,13 @@ func (c *Client) callString(ctx context.Context, input io.Reader, method, path s
 // Parse parses the given input, returning the body of the input as a string and an error.
 // If the error is not nil, the body is undefined.
 func (c *Client) Parse(ctx context.Context, input io.Reader) (string, error) {
-	return c.ParseWithHeader(ctx, input, nil)
+	return c.callString(ctx, input, "PUT", "/tika")
 }
 
 // ParseReader parses the given input, returning the body of the input as a reader and an error.
 // If the error is nil, the returned reader must be closed, else, the reader is nil.
 func (c *Client) ParseReader(ctx context.Context, input io.Reader) (io.ReadCloser, error) {
-	return c.ParseReaderWithHeader(ctx, input, nil)
-}
-
-// ParseWithHeader parses the given input, returning the body of the input as a string and an error.
-// If the error is not nil. the body is undefined.
-// This function also accepts a header so the caller can specify things like `Accept`
-func (c *Client) ParseWithHeader(ctx context.Context, input io.Reader, header http.Header) (string, error) {
-	return c.callString(ctx, input, "PUT", "/tika", header)
-}
-
-// ParseReaderWithHeader parses the given input, returning the body of the input as a reader and an error.
-// If the error is nil, the returned reader must be closed, else, the reader is nil.
-// This function also accepts a header so the caller can specify things like `Accept`
-func (c *Client) ParseReaderWithHeader(ctx context.Context, input io.Reader, header http.Header) (io.ReadCloser, error) {
-	return c.call(ctx, input, "PUT", "/tika", header)
+	return c.call(ctx, input, "PUT", "/tika", nil)
 }
 
 // ParseRecursive parses the given input and all embedded documents, returning a
@@ -198,40 +186,26 @@ func (c *Client) ParseRecursive(ctx context.Context, input io.Reader) ([]string,
 // Meta parses the metadata from the given input, returning the metadata and an
 // error. If the error is not nil, the metadata is undefined.
 func (c *Client) Meta(ctx context.Context, input io.Reader) (string, error) {
-	return c.MetaWithHeader(ctx, input, nil)
-}
-
-// MetaWithHeader parses the metadata from the given input, returning the metadata and an
-// error. If the error is not nil, the metadata is undefined.
-// This function also accepts a header so the caller can specify things like `Accept`
-func (c *Client) MetaWithHeader(ctx context.Context, input io.Reader, header http.Header) (string, error) {
-	return c.callString(ctx, input, "PUT", "/meta", header)
+	return c.callString(ctx, input, "PUT", "/meta")
 }
 
 // MetaField parses the metadata from the given input and returns the given
 // field. If the error is not nil, the result string is undefined.
 func (c *Client) MetaField(ctx context.Context, input io.Reader, field string) (string, error) {
-	return c.MetaFieldWithHeader(ctx, input, field, nil)
-}
-
-// MetaFieldWithHeader parses the metadata from the given input and returns the given
-// field. If the error is not nil, the result string is undefined.
-// This function also accepts a header so the caller can specify things like `Accept`
-func (c *Client) MetaFieldWithHeader(ctx context.Context, input io.Reader, field string, header http.Header) (string, error) {
-	return c.callString(ctx, input, "PUT", fmt.Sprintf("/meta/%v", field), header)
+	return c.callString(ctx, input, "PUT", fmt.Sprintf("/meta/%v", field))
 }
 
 // Detect gets the mimetype of the given input, returning the mimetype and an
 // error. If the error is not nil, the mimetype is undefined.
 func (c *Client) Detect(ctx context.Context, input io.Reader) (string, error) {
-	return c.callString(ctx, input, "PUT", "/detect/stream", nil)
+	return c.callString(ctx, input, "PUT", "/detect/stream")
 }
 
 // Language detects the language of the given input, returning the two letter
 // language code and an error. If the error is not nil, the language is
 // undefined.
 func (c *Client) Language(ctx context.Context, input io.Reader) (string, error) {
-	return c.callString(ctx, input, "PUT", "/language/stream", nil)
+	return c.callString(ctx, input, "PUT", "/language/stream")
 }
 
 // LanguageString detects the language of the given string, returning the two letter
@@ -239,7 +213,7 @@ func (c *Client) Language(ctx context.Context, input io.Reader) (string, error)
 // undefined.
 func (c *Client) LanguageString(ctx context.Context, input string) (string, error) {
 	r := strings.NewReader(input)
-	return c.callString(ctx, r, "PUT", "/language/string", nil)
+	return c.callString(ctx, r, "PUT", "/language/string")
 }
 
 // MetaRecursive parses the given input and all embedded documents. The result
@@ -298,7 +272,7 @@ func (c *Client) MetaRecursiveType(ctx context.Context, input io.Reader, content
 // Translate returns an error and the translated input from src language to
 // dst language using t. If the error is not nil, the translation is undefined.
 func (c *Client) Translate(ctx context.Context, input io.Reader, t Translator, src, dst string) (string, error) {
-	return c.callString(ctx, input, "POST", fmt.Sprintf("/translate/all/%s/%s/%s", t, src, dst), nil)
+	return c.callString(ctx, input, "POST", fmt.Sprintf("/translate/all/%s/%s/%s", t, src, dst))
 }
 
 // TranslateReader translates the given input from src language to dst language using t.
@@ -310,7 +284,7 @@ func (c *Client) TranslateReader(ctx context.Context, input io.Reader, t Transla
 
 // Version returns the default hello message from Tika server.
 func (c *Client) Version(ctx context.Context) (string, error) {
-	return c.callString(ctx, nil, "GET", "/version", nil)
+	return c.callString(ctx, nil, "GET", "/version")
 }
 
 var jsonHeader = http.Header{"Accept": []string{"application/json"}}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 8466d4b30a0..f6f2aeb6cc4 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1024,7 +1024,7 @@ github.com/google/go-cmp/cmp/internal/value
 # github.com/google/go-querystring v1.1.0
 ## explicit; go 1.10
 github.com/google/go-querystring/query
-# github.com/google/go-tika v0.3.0
+# github.com/google/go-tika v0.2.0
 ## explicit; go 1.11
 github.com/google/go-tika/tika
 # github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1


Test patch:
diff --git a/ocis-pkg/oidc/client_test.go b/ocis-pkg/oidc/client_test.go
index 93a2fb46800..e33dbf0d7a6 100644
--- a/ocis-pkg/oidc/client_test.go
+++ b/ocis-pkg/oidc/client_test.go
@@ -60,6 +60,9 @@ func TestLogoutVerify(t *testing.T) {
 								 "http://schemas.openid.net/event/backchannel-logout": {}
 								 }
 							  }`,
+			config: goidc.Config{
+				SkipClientIDCheck: true,
+			},
 			signKey: newRSAKey(t),
 		},
 		{
@@ -67,7 +70,8 @@ func TestLogoutVerify(t *testing.T) {
 			issuer:      "https://bar",
 			logoutToken: `{"iss":"https://foo"}`,
 			config: goidc.Config{
-				SkipExpiryCheck: true,
+				SkipClientIDCheck: true,
+				SkipExpiryCheck:   true,
 			},
 			signKey: newRSAKey(t),
 			wantErr: true,
@@ -86,7 +90,8 @@ func TestLogoutVerify(t *testing.T) {
 								 }
 							  }`,
 			config: goidc.Config{
-				SkipExpiryCheck: true,
+				SkipClientIDCheck: true,
+				SkipExpiryCheck:   true,
 			},
 			signKey:         newRSAKey(t),
 			verificationKey: newRSAKey(t),
@@ -103,6 +108,9 @@ func TestLogoutVerify(t *testing.T) {
 								 "http://schemas.openid.net/event/backchannel-logout": {}
 								 }
 							  }`,
+			config: goidc.Config{
+				SkipClientIDCheck: true,
+			},
 			signKey: newRSAKey(t),
 			wantErr: true,
 		},
@@ -119,6 +127,9 @@ func TestLogoutVerify(t *testing.T) {
 								 "http://schemas.openid.net/event/backchannel-logout": {}
 								 }
 							  }`,
+			config: goidc.Config{
+				SkipClientIDCheck: true,
+			},
 			signKey: newRSAKey(t),
 			wantErr: true,
 		},
@@ -135,6 +146,9 @@ func TestLogoutVerify(t *testing.T) {
 								 "not a logout event": {}
 								 }
 							  }`,
+			config: goidc.Config{
+				SkipClientIDCheck: true,
+			},
 			signKey: newRSAKey(t),
 			wantErr: true,
 		},
@@ -148,6 +162,9 @@ func TestLogoutVerify(t *testing.T) {
 							   "jti": "bWJq",
 							   "sid": "08a5019c-17e1-4977-8f42-65a12843ea02",
 							  }`,
+			config: goidc.Config{
+				SkipClientIDCheck: true,
+			},
 			signKey: newRSAKey(t),
 			wantErr: true,
 		},
@@ -157,6 +174,49 @@ func TestLogoutVerify(t *testing.T) {
 	}
 }
 
+func TestVerifyAudienceLogout(t *testing.T) {
+	tests := []logoutVerificationTest{
+		{
+			name: "good audience",
+			logoutToken: `{"iss":"https://foo","aud":"client1","sub":"subject","events": {
+								 "http://schemas.openid.net/event/backchannel-logout": {}
+								 }
+							}`,
+			config: goidc.Config{
+				ClientID:        "client1",
+				SkipExpiryCheck: true,
+			},
+			signKey: newRSAKey(t),
+		},
+		{
+			name: "mismatched audience",
+			logoutToken: `{"iss":"https://foo","aud":"client2","sub":"subject","events": {
+								 "http://schemas.openid.net/event/backchannel-logout": {}
+								 }}`,
+			config: goidc.Config{
+				ClientID:        "client1",
+				SkipExpiryCheck: true,
+			},
+			signKey: newRSAKey(t),
+			wantErr: true,
+		},
+		{
+			name: "multiple audiences, one matches",
+			logoutToken: `{"iss":"https://foo","aud":["client1","client2"],"sub":"subject","events": {
+								 "http://schemas.openid.net/event/backchannel-logout": {}
+								 }}`,
+			config: goidc.Config{
+				ClientID:        "client2",
+				SkipExpiryCheck: true,
+			},
+			signKey: newRSAKey(t),
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, test.run)
+	}
+}
+
 type logoutVerificationTest struct {
 	// Name of the subtest.
 	name string
@@ -203,11 +263,19 @@ func (v logoutVerificationTest) runGetToken(t *testing.T) (*oidc.LogoutToken, er
 	}
 
 	pm := oidc.ProviderMetadata{}
+	var clientID string
+	switch t.Name() {
+	case "TestLogoutVerify/good_token":
+		clientID = "s6BhdRkqt3"
+	default:
+		clientID = "client1"
+	}
 	verifier := oidc.NewOIDCClient(
 		oidc.WithOidcIssuer(issuer),
 		oidc.WithKeySet(ks),
 		oidc.WithConfig(&v.config),
 		oidc.WithProviderMetadata(&pm),
+		oidc.WithClientID(clientID),
 	)
 
 	return verifier.VerifyLogoutToken(ctx, token)