|
|
|
|
@@ -1,10 +1,8 @@
|
|
|
|
|
package middleware
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
|
|
|
|
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
|
|
|
@@ -16,26 +14,20 @@ import (
|
|
|
|
|
"github.com/owncloud/reva/v2/pkg/rgrpc/status"
|
|
|
|
|
"github.com/owncloud/reva/v2/pkg/rgrpc/todo/pool"
|
|
|
|
|
"github.com/owncloud/reva/v2/pkg/utils"
|
|
|
|
|
"golang.org/x/sync/singleflight"
|
|
|
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// CreateHome provides a middleware which sends a CreateHome request to the reva gateway.
|
|
|
|
|
// It deduplicates concurrent requests for the same user and caches successful results
|
|
|
|
|
// for the lifetime of the process.
|
|
|
|
|
// CreateHome provides a middleware which sends a CreateHome request to the reva gateway
|
|
|
|
|
func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler {
|
|
|
|
|
options := newOptions(optionSetters...)
|
|
|
|
|
logger := options.Logger
|
|
|
|
|
|
|
|
|
|
cacheDisabled := options.CreateHomeCacheDisabled
|
|
|
|
|
|
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
|
return &createHome{
|
|
|
|
|
next: next,
|
|
|
|
|
logger: logger,
|
|
|
|
|
revaGatewaySelector: options.RevaGatewaySelector,
|
|
|
|
|
roleQuotas: options.RoleQuotas,
|
|
|
|
|
cacheDisabled: cacheDisabled,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -45,96 +37,57 @@ type createHome struct {
|
|
|
|
|
logger log.Logger
|
|
|
|
|
revaGatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
|
|
|
|
roleQuotas map[string]uint64
|
|
|
|
|
cacheDisabled bool
|
|
|
|
|
|
|
|
|
|
knownHomes sync.Map // map[userID]struct{} — users whose home is confirmed
|
|
|
|
|
flight singleflight.Group // collapses concurrent CreateHome calls per user
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
if !m.shouldServe(req) {
|
|
|
|
|
m.next.ServeHTTP(w, req)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token := req.Header.Get("X-Access-Token")
|
|
|
|
|
token := req.Header.Get("x-access-token")
|
|
|
|
|
|
|
|
|
|
// we need to pass the token to authenticate the CreateHome request.
|
|
|
|
|
//ctx := tokenpkg.ContextSetToken(r.Context(), token)
|
|
|
|
|
ctx := metadata.AppendToOutgoingContext(req.Context(), revactx.TokenHeader, token)
|
|
|
|
|
|
|
|
|
|
u, ok := revactx.ContextGetUser(ctx)
|
|
|
|
|
if !ok {
|
|
|
|
|
m.next.ServeHTTP(w, req)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userID := u.Id.OpaqueId
|
|
|
|
|
|
|
|
|
|
// Fast path: home already known to exist for this user.
|
|
|
|
|
if m.homeKnown(userID) {
|
|
|
|
|
m.next.ServeHTTP(w, req)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createHomeReq := &provider.CreateHomeRequest{}
|
|
|
|
|
roleIDs, err := m.getUserRoles(u)
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client, err := m.revaGatewaySelector.Next()
|
|
|
|
|
if err != nil {
|
|
|
|
|
m.logger.Error().Err(err).Str("userid", userID).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))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deduplicate concurrent CreateHome calls for the same user.
|
|
|
|
|
_, err, _ = m.flight.Do(userID, func() (interface{}, error) {
|
|
|
|
|
return m.createHome(ctx, createHomeReq)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Only cache on success — if CreateHome failed, retry on next request.
|
|
|
|
|
if err == nil && !m.cacheDisabled {
|
|
|
|
|
m.knownHomes.Store(userID, struct{}{})
|
|
|
|
|
m.logger.Err(err).Msg("error selecting next gateway client")
|
|
|
|
|
} else {
|
|
|
|
|
createHomeRes, err := client.CreateHome(ctx, createHomeReq)
|
|
|
|
|
if err != nil {
|
|
|
|
|
m.logger.Err(err).Msg("error calling CreateHome")
|
|
|
|
|
} else if createHomeRes.Status.Code != rpc.Code_CODE_OK {
|
|
|
|
|
err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
|
|
|
|
|
if createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS {
|
|
|
|
|
m.logger.Err(err).Msg("error when calling Createhome")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m.next.ServeHTTP(w, req)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *createHome) homeKnown(userID string) bool {
|
|
|
|
|
if m.cacheDisabled {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
_, ok := m.knownHomes.Load(userID)
|
|
|
|
|
return ok
|
|
|
|
|
func (m createHome) shouldServe(req *http.Request) bool {
|
|
|
|
|
return req.Header.Get("x-access-token") != ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *createHome) createHome(ctx context.Context, req *provider.CreateHomeRequest) (interface{}, error) {
|
|
|
|
|
client, err := m.revaGatewaySelector.Next()
|
|
|
|
|
if err != nil {
|
|
|
|
|
m.logger.Err(err).Msg("error selecting next gateway client")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createHomeRes, err := client.CreateHome(ctx, req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
m.logger.Err(err).Msg("error calling CreateHome")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if createHomeRes.Status.Code != rpc.Code_CODE_OK && createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS {
|
|
|
|
|
err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
|
|
|
|
|
m.logger.Err(err).Msg("error when calling CreateHome")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *createHome) shouldServe(req *http.Request) bool {
|
|
|
|
|
return req.Header.Get("X-Access-Token") != ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *createHome) getUserRoles(user *userv1beta1.User) ([]string, error) {
|
|
|
|
|
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
|
|
|
|
|
@@ -152,7 +105,7 @@ func (m *createHome) getUserRoles(user *userv1beta1.User) ([]string, error) {
|
|
|
|
|
return dedup, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *createHome) checkRoleQuotaLimit(roleIDs []string) (uint64, bool) {
|
|
|
|
|
func (m createHome) checkRoleQuotaLimit(roleIDs []string) (uint64, bool) {
|
|
|
|
|
if len(roleIDs) == 0 {
|
|
|
|
|
return 0, false
|
|
|
|
|
}
|
|
|
|
|
|