mirror of
https://github.com/owncloud/ocis
synced 2026-04-25 17:25:21 +02:00
feat(proxy): claim managed spaces
Signed-off-by: Julian Koberg <jkoberg@MBP-Julian-Koberg.local>
This commit is contained in:
5
changelog/unreleased/claim-managed-spaces.md
Normal file
5
changelog/unreleased/claim-managed-spaces.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Claim managed spaces
|
||||
|
||||
Allow managing spaces from oidc claims
|
||||
|
||||
https://github.com/owncloud/ocis/pull/11280
|
||||
65
ocis-pkg/claimsmapper/claimsmapper.go
Normal file
65
ocis-pkg/claimsmapper/claimsmapper.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package claimsmapper
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ClaimsMapper is a configurable mapper to map oidc claims to ocis spaceIDs and roles
|
||||
type ClaimsMapper struct {
|
||||
claimRegexp *regexp.Regexp
|
||||
roleMapping map[string]string
|
||||
}
|
||||
|
||||
// NewClaimsMapper parses the config to create a new ClaimsMapper. It expects
|
||||
// - a regexp extracting the spaceID and the (unmapped) role from the claim.
|
||||
// - a roleMapping string of the form "oidcRole1:manager","oidcRole2:editor","oidcRole3:viewer"
|
||||
// unused roles can be omitted. Second part of the mapping must be a valid ocis role.
|
||||
// can be omitted if roles already match ocis roles
|
||||
//
|
||||
// Panics if regexp is not compilable
|
||||
func NewClaimsMapper(reg string, roleMapping []string) ClaimsMapper {
|
||||
em := ClaimsMapper{
|
||||
claimRegexp: regexp.MustCompile(reg),
|
||||
}
|
||||
|
||||
if len(roleMapping) == 0 {
|
||||
return em
|
||||
}
|
||||
|
||||
em.roleMapping = make(map[string]string)
|
||||
for _, ms := range roleMapping {
|
||||
s := strings.Split(ms, ":")
|
||||
if len(s) != 2 {
|
||||
continue
|
||||
}
|
||||
em.roleMapping[s[0]] = s[1]
|
||||
}
|
||||
return em
|
||||
}
|
||||
|
||||
// Exec extracts the spaceID and the role from a entitlement
|
||||
func (em ClaimsMapper) Exec(e string) (match bool, spaceID string, role string) {
|
||||
s := em.claimRegexp.FindStringSubmatch(e)
|
||||
if len(s) != 3 {
|
||||
return
|
||||
}
|
||||
|
||||
spaceID = s[1]
|
||||
if spaceID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
role = s[2]
|
||||
if em.roleMapping == nil {
|
||||
match = true
|
||||
return
|
||||
}
|
||||
|
||||
role = em.roleMapping[role]
|
||||
if role != "" {
|
||||
match = true
|
||||
return
|
||||
}
|
||||
return false, "", ""
|
||||
}
|
||||
76
ocis-pkg/claimsmapper/claimsmapper_test.go
Normal file
76
ocis-pkg/claimsmapper/claimsmapper_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package claimsmapper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/test-go/testify/require"
|
||||
)
|
||||
|
||||
func TestMapClaims(t *testing.T) {
|
||||
type innercase struct {
|
||||
input string
|
||||
expectedNoMatch bool
|
||||
expectedSpaceID string
|
||||
expectedRole string
|
||||
}
|
||||
var testCases = []struct {
|
||||
regexp string
|
||||
mapping []string
|
||||
cases []innercase
|
||||
}{
|
||||
{
|
||||
regexp: "some-string:moreinfo:([a-zA-Z0-9-]+):and-the-role-is-(.*)",
|
||||
cases: []innercase{
|
||||
{
|
||||
input: "some-string:moreinfo:here-is-my-uuid:and-the-role-is-here",
|
||||
expectedSpaceID: "here-is-my-uuid",
|
||||
expectedRole: "here",
|
||||
},
|
||||
{
|
||||
input: "some-otherthing:moreinfo:here-is-my-uuid:and-the-role-is-not-here",
|
||||
expectedNoMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
regexp: "spaceid=([a-zA-Z0-9-]+),roleid=(.*)",
|
||||
mapping: []string{"overseer:manager", "worker:editor", "ghoul:viewer"},
|
||||
cases: []innercase{
|
||||
{
|
||||
input: "spaceid=vault36,roleid=overseer",
|
||||
expectedSpaceID: "vault36",
|
||||
expectedRole: "manager",
|
||||
},
|
||||
{
|
||||
input: "spaceid=vault36,roleid=worker",
|
||||
expectedSpaceID: "vault36",
|
||||
expectedRole: "editor",
|
||||
},
|
||||
{
|
||||
input: "spaceid=vault36,roleid=ghoul",
|
||||
expectedSpaceID: "vault36",
|
||||
expectedRole: "viewer",
|
||||
},
|
||||
{
|
||||
input: "spaceid=vault36,roleid=radroach",
|
||||
expectedNoMatch: true,
|
||||
},
|
||||
{
|
||||
input: "differentid=vault36,roleid=overseer",
|
||||
expectedNoMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cm := NewClaimsMapper(tc.regexp, tc.mapping)
|
||||
for _, c := range tc.cases {
|
||||
match, spaceID, role := cm.Exec(c.input)
|
||||
require.Equal(t, !c.expectedNoMatch, match, c.input)
|
||||
require.Equal(t, c.expectedSpaceID, spaceID, c.input)
|
||||
require.Equal(t, c.expectedRole, role, c.input)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -378,11 +378,18 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
|
||||
middleware.WithRevaGatewaySelector(gatewaySelector),
|
||||
middleware.PoliciesProviderService(policiesProviderClient),
|
||||
),
|
||||
// finally, trigger home creation when a user logs in
|
||||
// trigger home creation when a user logs in
|
||||
middleware.CreateHome(
|
||||
middleware.Logger(logger),
|
||||
middleware.WithRevaGatewaySelector(gatewaySelector),
|
||||
middleware.RoleQuotas(cfg.RoleQuotas),
|
||||
),
|
||||
// trigger space assignment when a user logs in
|
||||
middleware.SpaceManager(
|
||||
cfg.ClaimSpaceManagement,
|
||||
middleware.Logger(logger),
|
||||
middleware.WithRevaGatewaySelector(gatewaySelector),
|
||||
middleware.ServiceAccount(cfg.ServiceAccount.ServiceAccountID, cfg.ServiceAccount.ServiceAccountSecret),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,27 +24,28 @@ type Config struct {
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GrpcClient client.Client `yaml:"-"`
|
||||
|
||||
RoleQuotas map[string]uint64 `yaml:"role_quotas"`
|
||||
Policies []Policy `yaml:"policies"`
|
||||
AdditionalPolicies []Policy `yaml:"additional_policies"`
|
||||
OIDC OIDC `yaml:"oidc"`
|
||||
ServiceAccount ServiceAccount `yaml:"service_account"`
|
||||
RoleAssignment RoleAssignment `yaml:"role_assignment"`
|
||||
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." 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 `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"`
|
||||
InsecureBackends bool `yaml:"insecure_backends" env:"PROXY_INSECURE_BACKENDS" desc:"Disable TLS certificate validation for all HTTP backend connections." introductionVersion:"pre5.0"`
|
||||
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." introductionVersion:"pre5.0"`
|
||||
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"`
|
||||
RoleQuotas map[string]uint64 `yaml:"role_quotas"`
|
||||
Policies []Policy `yaml:"policies"`
|
||||
AdditionalPolicies []Policy `yaml:"additional_policies"`
|
||||
OIDC OIDC `yaml:"oidc"`
|
||||
ServiceAccount ServiceAccount `yaml:"service_account"`
|
||||
RoleAssignment RoleAssignment `yaml:"role_assignment"`
|
||||
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." 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 `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"`
|
||||
InsecureBackends bool `yaml:"insecure_backends" env:"PROXY_INSECURE_BACKENDS" desc:"Disable TLS certificate validation for all HTTP backend connections." introductionVersion:"pre5.0"`
|
||||
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." introductionVersion:"pre5.0"`
|
||||
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"`
|
||||
ClaimSpaceManagement ClaimSpaceManagement `yaml:"claim_space_management"`
|
||||
|
||||
Context context.Context `json:"-" yaml:"-"`
|
||||
}
|
||||
@@ -230,3 +231,11 @@ type Events struct {
|
||||
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:"7.0.0"`
|
||||
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:"7.0.0"`
|
||||
}
|
||||
|
||||
// ClaimSpaceManagement holds the configuration for claim managed spaces
|
||||
type ClaimSpaceManagement struct {
|
||||
Enabled bool `yaml:"enabled" env:"OCIS_CLAIMMANAGEDSPACES_ENABLED" desc:"Enables space management through OIDC claims" introductionVersion:"%%NEXT%%"`
|
||||
Claim string `yaml:"claim" env:"OCIS_CLAIMMANAGEDSPACES_CLAIMNAME" desc:"The name of the claim used for space management" introductionVersion:"%%NEXT%%"`
|
||||
Regexp string `yaml:"regexp" env:"OCIS_CLAIMMANAGEDSPACES_REGEXP" desc:"The regular expression that extracts spaceid and role from a claim" introductionVersion:"%%NEXT%%"`
|
||||
Mapping []string `yaml:"mapping" env:"OCIS_CLAIMMANAGEDSPACES_MAPPING" desc:"(Optional) Mapping of oidc roles to ocis space roles. Example: 'oidcroleA:viewer,oidcroleB:manager'" introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ type Options struct {
|
||||
// SkipUserInfo prevents the oidc middleware from querying the userinfo endpoint and read any claims directly from the access token instead
|
||||
SkipUserInfo bool
|
||||
EventsPublisher events.Publisher
|
||||
// Service Accounts
|
||||
ServiceAccountID string
|
||||
ServiceAccountSecret string
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -254,3 +257,11 @@ func EventsPublisher(ep events.Publisher) Option {
|
||||
o.EventsPublisher = ep
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceAccount sets the service account user
|
||||
func ServiceAccount(id string, secret string) Option {
|
||||
return func(o *Options) {
|
||||
o.ServiceAccountID = id
|
||||
o.ServiceAccountSecret = secret
|
||||
}
|
||||
}
|
||||
|
||||
279
services/proxy/pkg/middleware/space_manager.go
Normal file
279
services/proxy/pkg/middleware/space_manager.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/claimsmapper"
|
||||
"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/reva/v2/pkg/conversions"
|
||||
revactx "github.com/owncloud/reva/v2/pkg/ctx"
|
||||
"github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/owncloud/reva/v2/pkg/utils"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
// SpaceManager return a middleware that manages space memberships
|
||||
func SpaceManager(cfg config.ClaimSpaceManagement, opts ...Option) func(next http.Handler) http.Handler {
|
||||
options := newOptions(opts...)
|
||||
logger := options.Logger
|
||||
|
||||
var cm claimsmapper.ClaimsMapper
|
||||
if cfg.Enabled {
|
||||
cm = claimsmapper.NewClaimsMapper(cfg.Regexp, cfg.Mapping)
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return &claimSpaceManager{
|
||||
next: next,
|
||||
logger: logger,
|
||||
gws: options.RevaGatewaySelector,
|
||||
mapper: cm,
|
||||
serviceAccountID: options.ServiceAccountID,
|
||||
serviceAccountSecret: options.ServiceAccountSecret,
|
||||
claimName: cfg.Claim,
|
||||
enabled: cfg.Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type claimSpaceManager struct {
|
||||
next http.Handler
|
||||
logger log.Logger
|
||||
gws pool.Selectable[gateway.GatewayAPIClient]
|
||||
mapper claimsmapper.ClaimsMapper
|
||||
serviceAccountID string
|
||||
serviceAccountSecret string
|
||||
claimName string
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func (csm claimSpaceManager) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
defer csm.next.ServeHTTP(w, req)
|
||||
|
||||
if !csm.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
userid, spaceAssignments := csm.evaluateContext(req.Context())
|
||||
if userid == "" {
|
||||
// no user in context, we omit this request
|
||||
return
|
||||
}
|
||||
|
||||
ctx, gwc, err := csm.getCtx()
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("could not get service user context")
|
||||
return
|
||||
}
|
||||
|
||||
// get all project spaces
|
||||
res, err := gwc.ListStorageSpaces(ctx, listStorageSpaceRequest())
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("error doing grpc request")
|
||||
return
|
||||
}
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
csm.logger.Error().Str("message", res.GetStatus().GetMessage()).Msg("unexpected status code doing listspaces request")
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range res.GetStorageSpaces() {
|
||||
hasAccess, actualPerms, err := getSpaceMemberStatus(s, userid)
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("error extracting space member")
|
||||
continue
|
||||
}
|
||||
|
||||
desiredRole := conversions.RoleFromName(spaceAssignments[s.GetRoot().GetOpaqueId()])
|
||||
shouldHaveAccess := desiredRole.Name != conversions.RoleUnknown
|
||||
|
||||
switch {
|
||||
case shouldHaveAccess && !hasAccess:
|
||||
// add user to space
|
||||
res, err := gwc.CreateShare(ctx, createShareRequest(userid, s, desiredRole.CS3ResourcePermissions()))
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("error adding space member")
|
||||
continue
|
||||
}
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
csm.logger.Error().Str("message", res.GetStatus().GetMessage()).Msg("unexpected status code doing createshare request")
|
||||
continue
|
||||
}
|
||||
|
||||
case !shouldHaveAccess && hasAccess:
|
||||
// remove user from space
|
||||
res, err := gwc.RemoveShare(ctx, removeShareRequest(userid, s))
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("error removing space member")
|
||||
continue
|
||||
}
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
csm.logger.Error().Str("message", res.GetStatus().GetMessage()).Msg("unexpected status code doing removeshare request")
|
||||
continue
|
||||
}
|
||||
|
||||
case shouldHaveAccess && hasAccess && !permissionsEqual(actualPerms, desiredRole.CS3ResourcePermissions()):
|
||||
// update user permissions
|
||||
res, err := gwc.UpdateShare(ctx, updateShareRequest(userid, s, desiredRole.CS3ResourcePermissions()))
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("error updating space member")
|
||||
continue
|
||||
}
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
csm.logger.Error().Str("message", res.GetStatus().GetMessage()).Msg("unexpected status code doing updateshare request")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns the service user context and the gateway client
|
||||
func (csm claimSpaceManager) getCtx() (context.Context, gateway.GatewayAPIClient, error) {
|
||||
gwc, err := csm.gws.Next()
|
||||
if err != nil {
|
||||
csm.logger.Error().Err(err).Msg("could not get gateway client")
|
||||
return nil, nil, err
|
||||
}
|
||||
ctx, err := utils.GetServiceUserContext(csm.serviceAccountID, gwc, csm.serviceAccountSecret)
|
||||
return ctx, gwc, err
|
||||
}
|
||||
|
||||
// returns the userid and the space assignments from the context
|
||||
func (csm claimSpaceManager) evaluateContext(ctx context.Context) (string, map[string]string) {
|
||||
u, _ := revactx.ContextGetUser(ctx)
|
||||
return u.GetId().GetOpaqueId(), csm.getSpaceAssignments(ctx)
|
||||
}
|
||||
|
||||
// returns a map[spaceID]role
|
||||
func (csm claimSpaceManager) getSpaceAssignments(ctx context.Context) map[string]string {
|
||||
claims := oidc.FromContext(ctx)
|
||||
values, ok := claims[csm.claimName].([]any)
|
||||
if !ok {
|
||||
csm.logger.Error().Interface("entitlements", claims["entitlements"]).Msg("entitlements claims are not a []string")
|
||||
}
|
||||
|
||||
assignments := make(map[string]string)
|
||||
for _, ent := range values {
|
||||
e, ok := ent.(string)
|
||||
if !ok {
|
||||
csm.logger.Error().Interface("entitlement", ent).Msg("entitlement is not a sting")
|
||||
continue
|
||||
}
|
||||
|
||||
match, spaceid, role := csm.mapper.Exec(e)
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
assignments[spaceid] = role
|
||||
}
|
||||
|
||||
return assignments
|
||||
}
|
||||
|
||||
func getSpaceMemberStatus(space *storageprovider.StorageSpace, userid string) (bool, *storageprovider.ResourcePermissions, error) {
|
||||
var permissionsMap map[string]*storageprovider.ResourcePermissions
|
||||
if err := utils.ReadJSONFromOpaque(space.GetOpaque(), "grants", &permissionsMap); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
for id, perm := range permissionsMap {
|
||||
if id == userid {
|
||||
return true, perm, nil
|
||||
}
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
func permissionsEqual(p1, p2 *storageprovider.ResourcePermissions) bool {
|
||||
if !conversions.SufficientCS3Permissions(p1, p2) {
|
||||
return false
|
||||
}
|
||||
if !conversions.SufficientCS3Permissions(p2, p1) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func listStorageSpaceRequest() *storageprovider.ListStorageSpacesRequest {
|
||||
return &storageprovider.ListStorageSpacesRequest{
|
||||
Opaque: utils.AppendPlainToOpaque(nil, "unrestricted", "true"),
|
||||
Filters: []*storageprovider.ListStorageSpacesRequest_Filter{
|
||||
{
|
||||
Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
|
||||
Term: &storageprovider.ListStorageSpacesRequest_Filter_SpaceType{
|
||||
SpaceType: "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createShareRequest(userid string, space *storageprovider.StorageSpace, perms *storageprovider.ResourcePermissions) *collaboration.CreateShareRequest {
|
||||
return &collaboration.CreateShareRequest{
|
||||
ResourceInfo: space.GetRootInfo(),
|
||||
Grant: &collaboration.ShareGrant{
|
||||
Grantee: &storageprovider.Grantee{
|
||||
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
|
||||
OpaqueId: userid,
|
||||
}},
|
||||
},
|
||||
Permissions: &collaboration.SharePermissions{
|
||||
Permissions: perms,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func removeShareRequest(userid string, space *storageprovider.StorageSpace) *collaboration.RemoveShareRequest {
|
||||
return &collaboration.RemoveShareRequest{
|
||||
Ref: &collaboration.ShareReference{
|
||||
Spec: &collaboration.ShareReference_Key{
|
||||
Key: &collaboration.ShareKey{
|
||||
ResourceId: space.GetRoot(),
|
||||
Grantee: buildGrantee(userid)},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func updateShareRequest(userid string, s *storageprovider.StorageSpace, perms *storageprovider.ResourcePermissions) *collaboration.UpdateShareRequest {
|
||||
o := &types.Opaque{
|
||||
Map: map[string]*types.OpaqueEntry{
|
||||
"spacegrant": {},
|
||||
},
|
||||
}
|
||||
o = utils.AppendPlainToOpaque(o, "spacetype", "project")
|
||||
return &collaboration.UpdateShareRequest{
|
||||
Share: &collaboration.Share{
|
||||
ResourceId: s.GetRoot(),
|
||||
Grantee: buildGrantee(userid),
|
||||
Permissions: &collaboration.SharePermissions{Permissions: perms},
|
||||
},
|
||||
UpdateMask: &fieldmaskpb.FieldMask{
|
||||
Paths: []string{"permissions"},
|
||||
},
|
||||
Opaque: o,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func buildGrantee(userid string) *storageprovider.Grantee {
|
||||
return &storageprovider.Grantee{
|
||||
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &storageprovider.Grantee_UserId{
|
||||
UserId: &userpb.UserId{
|
||||
OpaqueId: userid,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user