mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-31 01:10:20 -06:00
Merge pull request #5617 from owncloud/role-quota
add config option to set default quota per role
This commit is contained in:
12
changelog/unreleased/role-quota.md
Normal file
12
changelog/unreleased/role-quota.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Enhancement: Added option to configure default quota per role
|
||||
|
||||
Admins can assign default quotas to users with certain roles by adding the following config to the `proxy.yaml`.
|
||||
E.g.:
|
||||
```
|
||||
role_quotas:
|
||||
d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11: 2300000
|
||||
```
|
||||
|
||||
It maps a role ID to the quota in bytes.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/5616
|
||||
3
go.mod
3
go.mod
@@ -11,7 +11,7 @@ require (
|
||||
github.com/blevesearch/bleve/v2 v2.3.5
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20221012090518-ef2996678965
|
||||
github.com/cs3org/reva/v2 v2.12.1-0.20230214085134-ec27f5f8feb3
|
||||
github.com/cs3org/reva/v2 v2.12.1-0.20230222151731-83c7b4d26b2b
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/ggwhite/go-masker v1.0.9
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
@@ -251,6 +251,7 @@ require (
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/prometheus/statsd_exporter v0.22.8 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/rs/cors v1.8.2 // indirect
|
||||
github.com/rs/xid v1.4.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.1.1 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -343,8 +343,8 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p
|
||||
github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A=
|
||||
github.com/crewjam/saml v0.4.9 h1:X2jDv4dv3IvfT9t+RhADavzNFAcq3fVxzTCIH3G605U=
|
||||
github.com/crewjam/saml v0.4.9/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4=
|
||||
github.com/cs3org/reva/v2 v2.12.1-0.20230214085134-ec27f5f8feb3 h1:KaFl1ZfjjKSlDsq/zvhBV9f+mXYqnLdK5IhAaZBoXDo=
|
||||
github.com/cs3org/reva/v2 v2.12.1-0.20230214085134-ec27f5f8feb3/go.mod h1:u73Df9JAZsDj43GIjQIb3DO1PLJuPutZXkRqQH0oGXA=
|
||||
github.com/cs3org/reva/v2 v2.12.1-0.20230222151731-83c7b4d26b2b h1:wIwnuSyH8tM4dbr16UYEoYF7ESlfxah2q99oz/FscU0=
|
||||
github.com/cs3org/reva/v2 v2.12.1-0.20230222151731-83c7b4d26b2b/go.mod h1:dbaNP2U3nGQA5BHLc5w/hqviq7b0F4eygNwC38jeaiU=
|
||||
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI=
|
||||
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
|
||||
@@ -28,6 +28,7 @@ type Config struct {
|
||||
EnableFavorites bool `yaml:"enable_favorites" env:"FRONTEND_ENABLE_FAVORITES" desc:"Enables the support for favorites in the clients."`
|
||||
EnableProjectSpaces bool `yaml:"enable_project_spaces" env:"FRONTEND_ENABLE_PROJECT_SPACES" desc:"Changing this value is NOT supported. Indicates to clients that project spaces are supposed to be made available."`
|
||||
EnableShareJail bool `yaml:"enable_share_jail" env:"FRONTEND_ENABLE_SHARE_JAIL" desc:"Changing this value is NOT supported. Indicates to clients that the share jail is supposed to be used."`
|
||||
MaxQuota uint64 `yaml:"max_quota" env:"OCIS_SPACES_MAX_QUOTA;FRONTEND_MAX_QUOTA" desc:"Set the global max quota value in the capabilities."`
|
||||
UploadMaxChunkSize int `yaml:"upload_max_chunk_size" env:"FRONTEND_UPLOAD_MAX_CHUNK_SIZE" desc:"Sets the max chunk sizes in bytes for uploads via the clients."`
|
||||
UploadHTTPMethodOverride string `yaml:"upload_http_method_override" env:"FRONTEND_UPLOAD_HTTP_METHOD_OVERRIDE" desc:"Advise TUS to replace PATCH requests by POST requests."`
|
||||
DefaultUploadProtocol string `yaml:"default_upload_protocol" env:"FRONTEND_DEFAULT_UPLOAD_PROTOCOL" desc:"The default upload protocol to use in the clients (e.g. tus)."`
|
||||
|
||||
@@ -265,6 +265,7 @@ func FrontendConfigFromStruct(cfg *config.Config) (map[string]interface{}, error
|
||||
"enabled": cfg.EnableProjectSpaces || cfg.EnableShareJail,
|
||||
"projects": cfg.EnableProjectSpaces,
|
||||
"share_jail": cfg.EnableShareJail,
|
||||
"max_quota": cfg.MaxQuota,
|
||||
},
|
||||
},
|
||||
"version": map[string]interface{}{
|
||||
|
||||
@@ -13,6 +13,19 @@ The following request authentication schemes are implemented:
|
||||
- Signed URL
|
||||
- Public Share Token
|
||||
|
||||
## Automatic quota assignments
|
||||
|
||||
It is possible to automatically assign a specific quota amount to new users depending on their role.
|
||||
To do this you need to add the following config snippet to the proxy.yaml config file.
|
||||
|
||||
```yaml
|
||||
role_quotas:
|
||||
<role ID1>: <quota1>
|
||||
<role ID2>: <quota2>
|
||||
```
|
||||
|
||||
There you need to configure the mapping between the roles by their ID and the quota in Bytes.
|
||||
|
||||
## Recommendations for Production Deployments
|
||||
|
||||
In a production deployment, you want to have basic authentication (`PROXY_ENABLE_BASIC_AUTH`) disabled which is the default state. You also want to setup a firewall to only allow requests to the proxy service or the reverse proxy if you have one. Requests to the other services should be blocked by the firewall.
|
||||
|
||||
@@ -249,6 +249,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config)
|
||||
middleware.Logger(logger),
|
||||
middleware.TokenManagerConfig(*cfg.TokenManager),
|
||||
middleware.RevaGatewayClient(revaClient),
|
||||
middleware.RoleQuotas(cfg.RoleQuotas),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,20 +21,21 @@ type Config struct {
|
||||
Reva *shared.Reva `yaml:"reva"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
|
||||
Policies []Policy `yaml:"policies"`
|
||||
OIDC OIDC `yaml:"oidc"`
|
||||
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
|
||||
PolicySelector *PolicySelector `yaml:"policy_selector"`
|
||||
PreSignedURL PreSignedURL `yaml:"pre_signed_url"`
|
||||
AccountBackend string `yaml:"account_backend" env:"PROXY_ACCOUNT_BACKEND_TYPE" desc:"Account backend the PROXY service should use. Currently only 'cs3' is possible here."`
|
||||
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_user' but you can also add your own claim."`
|
||||
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'."`
|
||||
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."`
|
||||
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."`
|
||||
EnableBasicAuth bool `yaml:"enable_basic_auth" env:"PROXY_ENABLE_BASIC_AUTH" desc:"Set this to true to enable 'basic authentication' (username/password)."`
|
||||
InsecureBackends bool `yaml:"insecure_backends" env:"PROXY_INSECURE_BACKENDS" desc:"Disable TLS certificate validation for all HTTP backend connections."`
|
||||
BackendHTTPSCACert string `yaml:"backend_https_cacert" env:"PROXY_HTTPS_CACERT" desc:"Path/File for the root CA certificate used to validate the server’s TLS certificate for https enabled backend services."`
|
||||
AuthMiddleware AuthMiddleware `yaml:"auth_middleware"`
|
||||
RoleQuotas map[string]uint64 `yaml:"role_quotas"`
|
||||
Policies []Policy `yaml:"policies"`
|
||||
OIDC OIDC `yaml:"oidc"`
|
||||
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
|
||||
PolicySelector *PolicySelector `yaml:"policy_selector"`
|
||||
PreSignedURL PreSignedURL `yaml:"pre_signed_url"`
|
||||
AccountBackend string `yaml:"account_backend" env:"PROXY_ACCOUNT_BACKEND_TYPE" desc:"Account backend the PROXY service should use. Currently only 'cs3' is possible here."`
|
||||
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_user' but you can also add your own claim."`
|
||||
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'."`
|
||||
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."`
|
||||
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."`
|
||||
EnableBasicAuth bool `yaml:"enable_basic_auth" env:"PROXY_ENABLE_BASIC_AUTH" desc:"Set this to true to enable 'basic authentication' (username/password)."`
|
||||
InsecureBackends bool `yaml:"insecure_backends" env:"PROXY_INSECURE_BACKENDS" desc:"Disable TLS certificate validation for all HTTP backend connections."`
|
||||
BackendHTTPSCACert string `yaml:"backend_https_cacert" env:"PROXY_HTTPS_CACERT" desc:"Path/File for the root CA certificate used to validate the server’s TLS certificate for https enabled backend services."`
|
||||
AuthMiddleware AuthMiddleware `yaml:"auth_middleware"`
|
||||
|
||||
Context context.Context `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
@@ -22,6 +26,7 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler {
|
||||
next: next,
|
||||
logger: logger,
|
||||
revaGatewayClient: options.RevaGatewayClient,
|
||||
roleQuotas: options.RoleQuotas,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +35,7 @@ type createHome struct {
|
||||
next http.Handler
|
||||
logger log.Logger
|
||||
revaGatewayClient gateway.GatewayAPIClient
|
||||
roleQuotas map[string]uint64
|
||||
}
|
||||
|
||||
func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -45,6 +51,19 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := metadata.AppendToOutgoingContext(req.Context(), revactx.TokenHeader, token)
|
||||
|
||||
createHomeReq := &provider.CreateHomeRequest{}
|
||||
u, ok := revactx.ContextGetUser(ctx)
|
||||
if ok {
|
||||
roleIDs, err := m.getUserRoles(u)
|
||||
if err != nil {
|
||||
m.logger.Error().Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user")
|
||||
errorcode.GeneralException.Render(w, req, http.StatusInternalServerError, "Unauthorized")
|
||||
return
|
||||
}
|
||||
if limit, hasLimit := m.checkRoleQuotaLimit(roleIDs); hasLimit {
|
||||
createHomeReq.Opaque = utils.AppendPlainToOpaque(nil, "quota", strconv.FormatUint(limit, 10))
|
||||
}
|
||||
}
|
||||
|
||||
createHomeRes, err := m.revaGatewayClient.CreateHome(ctx, createHomeReq)
|
||||
|
||||
if err != nil {
|
||||
@@ -62,3 +81,27 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
func (m createHome) shouldServe(req *http.Request) bool {
|
||||
return req.Header.Get("x-access-token") != ""
|
||||
}
|
||||
|
||||
func (m createHome) getUserRoles(user *userv1beta1.User) ([]string, error) {
|
||||
var roleIDs []string
|
||||
if err := utils.ReadJSONFromOpaque(user.Opaque, "roles", &roleIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmp := make(map[string]struct{})
|
||||
for _, id := range roleIDs {
|
||||
tmp[id] = struct{}{}
|
||||
}
|
||||
|
||||
dedup := make([]string, 0, len(tmp))
|
||||
for k := range tmp {
|
||||
dedup = append(dedup, k)
|
||||
}
|
||||
return dedup, nil
|
||||
}
|
||||
|
||||
func (m createHome) checkRoleQuotaLimit(roleIDs []string) (uint64, bool) {
|
||||
id := roleIDs[0] // At the moment a user can only have one role.
|
||||
quota, ok := m.roleQuotas[id]
|
||||
return quota, ok
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ type Options struct {
|
||||
AccessTokenVerifyMethod string
|
||||
// JWKS sets the options for fetching the JWKS from the IDP
|
||||
JWKS config.JWKS
|
||||
// RoleQuotas hold userid:quota mappings. These will be used when provisioning new users.
|
||||
// The users will get as much quota as is set for their role.
|
||||
RoleQuotas map[string]uint64
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -206,9 +209,16 @@ func AccessTokenVerifyMethod(method string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// JWKS sets the options for fetching the JWKS from the IDP
|
||||
// JWKSOptions sets the options for fetching the JWKS from the IDP
|
||||
func JWKSOptions(jo config.JWKS) Option {
|
||||
return func(o *Options) {
|
||||
o.JWKS = jo
|
||||
}
|
||||
}
|
||||
|
||||
// RoleQuotas sets the role quota mapping setting
|
||||
func RoleQuotas(roleQuotas map[string]uint64) Option {
|
||||
return func(o *Options) {
|
||||
o.RoleQuotas = roleQuotas
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ type OCISDriver struct {
|
||||
MaxAcquireLockCycles int `yaml:"max_acquire_lock_cycles" env:"STORAGE_USERS_OCIS_MAX_ACQUIRE_LOCK_CYCLES" desc:"When trying to lock files, ocis will try this amount of times to acquire the lock before failing. After each try it will wait for an increasing amount of time. Values of 0 or below will be ignored and the default value of 20 will be used."`
|
||||
LockCycleDurationFactor int `yaml:"lock_cycle_duration_factor" env:"STORAGE_USERS_OCIS_LOCK_CYCLE_DURATION_FACTOR" desc:"When trying to lock files, ocis will multiply the cycle with this factor and use it as a millisecond timeout. Values of 0 or below will be ignored and the default value of 30 will be used."`
|
||||
AsyncUploads bool `yaml:"async_uploads" env:"STORAGE_USERS_OCIS_ASYNC_UPLOADS" desc:"Enable asynchronous file uploads."`
|
||||
MaxQuota uint64 `yaml:"max_quota" env:"OCIS_SPACES_MAX_QUOTA;STORAGE_USERS_OCIS_MAX_QUOTA" desc:"Set a global max quota for spaces. If you are not setting OCIS_SPACES_MAX_QUOTA then don't forget to set FRONTEND_MAX_QUOTA."`
|
||||
}
|
||||
|
||||
type S3NGDriver struct {
|
||||
|
||||
@@ -123,6 +123,7 @@ func Ocis(cfg *config.Config) map[string]interface{} {
|
||||
"max_acquire_lock_cycles": cfg.Drivers.OCIS.MaxAcquireLockCycles,
|
||||
"lock_cycle_duration_factor": cfg.Drivers.OCIS.LockCycleDurationFactor,
|
||||
"asyncfileuploads": cfg.Drivers.OCIS.AsyncUploads,
|
||||
"max_quota": cfg.Drivers.OCIS.MaxQuota,
|
||||
"statcache": map[string]interface{}{
|
||||
"cache_store": cfg.Cache.Store,
|
||||
"cache_nodes": cfg.Cache.Nodes,
|
||||
@@ -158,6 +159,7 @@ func OcisNoEvents(cfg *config.Config) map[string]interface{} {
|
||||
"permissionssvc_tls_mode": cfg.Commons.GRPCClientTLS.Mode,
|
||||
"max_acquire_lock_cycles": cfg.Drivers.OCIS.MaxAcquireLockCycles,
|
||||
"lock_cycle_duration_factor": cfg.Drivers.OCIS.LockCycleDurationFactor,
|
||||
"max_quota": cfg.Drivers.OCIS.MaxQuota,
|
||||
"statcache": map[string]interface{}{
|
||||
"cache_store": cfg.Cache.Store,
|
||||
"cache_nodes": cfg.Cache.Nodes,
|
||||
|
||||
Reference in New Issue
Block a user