mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-05 19:59:37 -06:00
1046 lines
38 KiB
Go
1046 lines
38 KiB
Go
package svc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"regexp"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/CiscoM31/godata"
|
|
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
|
|
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
|
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/render"
|
|
"github.com/google/uuid"
|
|
settingsmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/settings/v0"
|
|
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity"
|
|
ocsettingssvc "github.com/opencloud-eu/opencloud/services/settings/pkg/service/v0"
|
|
"github.com/opencloud-eu/opencloud/services/settings/pkg/store/defaults"
|
|
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
|
"github.com/opencloud-eu/reva/v2/pkg/events"
|
|
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
|
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
|
libregraph "github.com/owncloud/libre-graph-api-go"
|
|
)
|
|
|
|
// GetMe implements the Service interface.
|
|
func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Msg("calling get user in /me")
|
|
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
|
|
|
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: query error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
u, ok := revactx.ContextGetUser(r.Context())
|
|
if !ok {
|
|
logger.Debug().Msg("could not get user: user not in context")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context")
|
|
return
|
|
}
|
|
|
|
exp, err := identity.GetExpandValues(odataReq.Query)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
var me *libregraph.User
|
|
// We can just return the user from context unless we need to expand the group memberships
|
|
if !slices.Contains(exp, "memberOf") {
|
|
me = identity.CreateUserModelFromCS3(u)
|
|
} else {
|
|
var err error
|
|
logger.Debug().Msg("calling get user on backend")
|
|
me, err = g.identityBackend.GetUser(r.Context(), u.GetId().GetOpaqueId(), odataReq)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("user", u).Msg("could not get user from backend")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
if me.MemberOf == nil {
|
|
me.MemberOf = []libregraph.Group{}
|
|
}
|
|
}
|
|
|
|
// expand appRoleAssignments if requested
|
|
if slices.Contains(exp, appRoleAssignments) {
|
|
var err error
|
|
me.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), me.GetId())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("userid", me.GetId()).Msg("could not get appRoleAssignments for self")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
preferedLanguage, _, err := getUserLanguage(r.Context(), g.valueService, me.GetId())
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not get user language")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get user language")
|
|
return
|
|
}
|
|
|
|
me.PreferredLanguage = &preferedLanguage
|
|
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, me)
|
|
}
|
|
|
|
func (g Graph) fetchAppRoleAssignments(ctx context.Context, accountuuid string) ([]libregraph.AppRoleAssignment, error) {
|
|
lrar, err := g.roleService.ListRoleAssignments(ctx, &settingssvc.ListRoleAssignmentsRequest{
|
|
AccountUuid: accountuuid,
|
|
})
|
|
if err != nil {
|
|
return []libregraph.AppRoleAssignment{}, err
|
|
}
|
|
|
|
values := make([]libregraph.AppRoleAssignment, 0, len(lrar.Assignments))
|
|
for _, assignment := range lrar.GetAssignments() {
|
|
values = append(values, g.assignmentToAppRoleAssignment(assignment))
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// GetUserDrive implements the Service interface.
|
|
func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Interface("query", r.URL.Query()).Msg("calling get user drive")
|
|
|
|
userID, err := url.PathUnescape(chi.URLParam(r, "userID"))
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("userID", chi.URLParam(r, "userID")).Msg("could not get drive: unescaping drive id failed")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
|
return
|
|
}
|
|
|
|
if userID == "" {
|
|
u, ok := revactx.ContextGetUser(r.Context())
|
|
if !ok {
|
|
logger.Debug().Msg("could not get user: user not in context")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context")
|
|
return
|
|
}
|
|
userID = u.GetId().GetOpaqueId()
|
|
}
|
|
|
|
logger.Debug().Str("userID", userID).Msg("calling list storage spaces with user and personal filter")
|
|
ctx := r.Context()
|
|
|
|
filters := []*storageprovider.ListStorageSpacesRequest_Filter{listStorageSpacesTypeFilter("personal"), listStorageSpacesUserFilter(userID)}
|
|
res, err := g.ListStorageSpacesWithFilters(ctx, filters, true)
|
|
switch {
|
|
case err != nil:
|
|
logger.Error().Err(err).Msg("could not get drive: transport error")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
case res.Status.Code != cs3rpc.Code_CODE_OK:
|
|
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
|
|
// the client is doing a lookup for a specific space, therefore we need to return
|
|
// not found to the caller
|
|
logger.Debug().Str("userID", userID).Msg("could not get personal drive for user: not found")
|
|
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
|
|
return
|
|
}
|
|
logger.Debug().
|
|
Str("userID", userID).
|
|
Str("grpcmessage", res.GetStatus().GetMessage()).
|
|
Msg("could not get personal drive for user: grpc error")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
|
|
return
|
|
}
|
|
|
|
webDavBaseURL, err := g.getWebDavBaseURL()
|
|
if err != nil {
|
|
logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get personal drive: error parsing webdav base url")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
switch num := len(spaces); {
|
|
case num == 0:
|
|
logger.Debug().Str("userID", userID).Msg("could not get personal drive: no drive returned from storage")
|
|
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "no drive returned from storage")
|
|
return
|
|
case num == 1:
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, spaces[0])
|
|
default:
|
|
logger.Debug().Int("number", num).Msg("could not get personal drive: expected to find a single drive but fetched more")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get personal drive: expected to find a single drive but fetched more")
|
|
return
|
|
}
|
|
}
|
|
|
|
func (g Graph) contextUserHasFullAccountPerms(reqctx context.Context) bool {
|
|
// mostly copied from the canCreateSpace method
|
|
pr, err := g.permissionsService.GetPermissionByID(reqctx, &settingssvc.GetPermissionByIDRequest{
|
|
PermissionId: defaults.AccountManagementPermission(0).Id,
|
|
})
|
|
if err != nil || pr.Permission == nil {
|
|
return false
|
|
}
|
|
|
|
if pr.Permission.Constraint != defaults.All {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GetUsers implements the Service interface.
|
|
func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Interface("query", r.URL.Query()).Msg("calling get users")
|
|
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
|
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: query error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
ctxHasFullPerms := g.contextUserHasFullAccountPerms(r.Context())
|
|
searchHasAcceptableLength := false
|
|
if odataReq.Query != nil && odataReq.Query.Search != nil {
|
|
minSearchLength := g.config.API.IdentitySearchMinLength
|
|
if strings.HasPrefix(odataReq.Query.Search.RawValue, "\"") {
|
|
// if search starts with double quotes then it must finish with double quotes
|
|
// add +2 to the minimum search length in this case
|
|
minSearchLength += 2
|
|
}
|
|
searchHasAcceptableLength = len(odataReq.Query.Search.RawValue) >= minSearchLength
|
|
}
|
|
if !ctxHasFullPerms && !searchHasAcceptableLength {
|
|
// for regular user the search term must have a minimum length
|
|
logger.Debug().Interface("query", r.URL.Query()).Msgf("search with less than %d chars for a regular user", g.config.API.IdentitySearchMinLength)
|
|
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "search term too short")
|
|
return
|
|
}
|
|
|
|
if !ctxHasFullPerms && odataReq.Query.Filter != nil {
|
|
// regular users are allowed to filter only by userType
|
|
filter := odataReq.Query.Filter
|
|
switch {
|
|
case filter.Tree.Token.Type != godata.ExpressionTokenLogical:
|
|
fallthrough
|
|
case filter.Tree.Token.Value != "eq":
|
|
fallthrough
|
|
case filter.Tree.Children[0].Token.Value != "userType":
|
|
logger.Debug().Interface("query", r.URL.Query()).Msg("forbidden filter for a regular user")
|
|
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "filter has forbidden elements for regular users")
|
|
return
|
|
}
|
|
}
|
|
if !ctxHasFullPerms && (odataReq.Query.Apply != nil || odataReq.Query.Expand != nil || odataReq.Query.Compute != nil) {
|
|
// regular users can't use filter, apply, expand and compute
|
|
logger.Debug().Interface("query", r.URL.Query()).Msg("forbidden query elements for a regular user")
|
|
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "query has forbidden elements for regular users")
|
|
return
|
|
}
|
|
|
|
logger.Debug().Interface("query", r.URL.Query()).Msg("calling get users on backend")
|
|
|
|
var users []*libregraph.User
|
|
|
|
if odataReq.Query.Filter != nil {
|
|
users, err = g.applyUserFilter(r.Context(), odataReq, nil)
|
|
} else {
|
|
users, err = g.identityBackend.GetUsers(r.Context(), odataReq)
|
|
}
|
|
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users from backend")
|
|
var errcode errorcode.Error
|
|
var godataerr *godata.GoDataError
|
|
switch {
|
|
case errors.As(err, &errcode):
|
|
errcode.Render(w, r)
|
|
case errors.As(err, &godataerr):
|
|
errorcode.GeneralException.Render(w, r, godataerr.ResponseCode, err.Error())
|
|
default:
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
// If the user isn't admin, we'll show just the minimum user attibutes
|
|
if !ctxHasFullPerms {
|
|
finalUsers := make([]*libregraph.User, len(users))
|
|
for i, u := range users {
|
|
finalUsers[i] = &libregraph.User{
|
|
Id: u.Id,
|
|
DisplayName: u.DisplayName,
|
|
UserType: u.UserType,
|
|
Identities: u.Identities,
|
|
}
|
|
|
|
if g.config.API.ShowUserEmailInResults {
|
|
finalUsers[i].Mail = u.Mail
|
|
}
|
|
}
|
|
users = finalUsers
|
|
}
|
|
|
|
exp, err := identity.GetExpandValues(odataReq.Query)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
expandAppRoleAssignments := slices.Contains(exp, appRoleAssignments)
|
|
expandMemberOf := slices.Contains(exp, "memberOf")
|
|
for _, u := range users {
|
|
if expandAppRoleAssignments && u.AppRoleAssignments == nil {
|
|
u.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), u.GetId())
|
|
if err != nil {
|
|
// TODO I think we should not continue here, see http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand
|
|
// > The $expand system query option indicates the related entities and stream values that MUST be represented inline. The service MUST return the specified content, and MAY choose to return additional information.
|
|
logger.Debug().Err(err).Str("userid", u.GetId()).Msg("could not get appRoleAssignments when listing user")
|
|
}
|
|
}
|
|
if expandMemberOf {
|
|
if u.MemberOf == nil {
|
|
u.MemberOf = []libregraph.Group{}
|
|
}
|
|
}
|
|
}
|
|
|
|
users, err = sortUsers(odataReq, users)
|
|
if err != nil {
|
|
logger.Debug().Interface("query", odataReq).Msg("error while sorting users according to query")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, &ListResponse{Value: users})
|
|
}
|
|
|
|
// PostUser implements the Service interface.
|
|
func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Msg("calling create user")
|
|
u := libregraph.NewUser("", "")
|
|
err := StrictJSONUnmarshal(r.Body, u)
|
|
if err != nil {
|
|
logger.Info().Err(err).Msg("could not create user: invalid request body")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %v", err.Error()))
|
|
return
|
|
}
|
|
|
|
if u.GetDisplayName() == "" {
|
|
logger.Info().Err(err).Interface("user", u).Msg("could not create user: empty displayname")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Empty displayname")
|
|
return
|
|
}
|
|
if username := u.GetOnPremisesSamAccountName(); !g.isValidUsername(username) {
|
|
logger.Info().Str("username", username).Msg("could not create user: invalid username")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Invalid username")
|
|
return
|
|
}
|
|
if mail, ok := u.GetMailOk(); ok {
|
|
if !isValidEmail(*mail) {
|
|
logger.Info().Str("mail", *u.Mail).Msg("could not create user: invalid email address")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid email address")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Disallow user-supplied IDs. It's supposed to be readonly. We're either
|
|
// generating them in the backend ourselves or rely on the Backend's
|
|
// storage (e.g. LDAP) to provide a unique ID.
|
|
if u.HasId() {
|
|
logger.Info().Interface("user", u).Msg("could not create user: user id is a read-only attribute")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user id is a read-only attribute")
|
|
return
|
|
}
|
|
|
|
// treat memberOf as a read-only attribute.
|
|
if u.HasMemberOf() {
|
|
logger.Info().Interface("user", u).Msg("could not create user: memberOf id is a read-only attribute")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "memberOf id is a read-only attribute")
|
|
return
|
|
}
|
|
|
|
// treat userType as a read-only attribute.
|
|
if u.HasUserType() {
|
|
logger.Info().Interface("user", u).Msg("could not create user: userType is a read-only attribute")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "userType is a read-only attribute")
|
|
return
|
|
}
|
|
u.SetUserType("Member")
|
|
|
|
logger.Debug().Interface("user", u).Msg("calling create user on backend")
|
|
if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil {
|
|
logger.Error().Err(err).Msg("could not create user: backend error")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
|
|
// assign roles if possible
|
|
if g.roleService != nil && g.config.API.AssignDefaultUserRole {
|
|
// All users get the user role by default currently.
|
|
// to all new users for now, as create Account request does not have any role field
|
|
if _, err = g.roleService.AssignRoleToUser(r.Context(), &settingssvc.AssignRoleToUserRequest{
|
|
AccountUuid: *u.Id,
|
|
RoleId: ocsettingssvc.BundleUUIDRoleUser,
|
|
}); err != nil {
|
|
// log as error, admin eventually needs to do something
|
|
logger.Error().Err(err).Str("id", *u.Id).Str("role", ocsettingssvc.BundleUUIDRoleUser).Msg("could not create user: role assignment failed")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "role assignment failed")
|
|
return
|
|
}
|
|
}
|
|
|
|
e := events.UserCreated{UserID: *u.Id}
|
|
if currentUser, ok := revactx.ContextGetUser(r.Context()); ok {
|
|
e.Executant = currentUser.GetId()
|
|
}
|
|
g.publishEvent(r.Context(), e)
|
|
|
|
render.Status(r, http.StatusCreated)
|
|
render.JSON(w, r, u)
|
|
}
|
|
|
|
// GetUser implements the Service interface.
|
|
func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Msg("calling get user")
|
|
|
|
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
|
|
|
userID := chi.URLParam(r, "userID")
|
|
userID, err := url.PathUnescape(userID)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("id", userID).Msg("could not get user: unescaping user id failed")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
|
return
|
|
}
|
|
|
|
if userID == "" {
|
|
logger.Debug().Msg("could not get user: missing user id")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id")
|
|
return
|
|
}
|
|
|
|
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: query error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
exp, err := identity.GetExpandValues(odataReq.Query)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
logger.Debug().Str("id", userID).Msg("calling get user from backend")
|
|
user, err := g.identityBackend.GetUser(r.Context(), userID, odataReq)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not get user: error fetching user from backend")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
|
|
listDrives := slices.Contains(exp, "drives")
|
|
listDrive := slices.Contains(exp, "drive")
|
|
|
|
// do we need to list all or only the personal drive
|
|
filters := []*storageprovider.ListStorageSpacesRequest_Filter{}
|
|
if listDrives || listDrive {
|
|
filters = append(filters, listStorageSpacesUserFilter(user.GetId()))
|
|
if !listDrives {
|
|
// TODO filter by owner when decomposedfs supports the OWNER filter
|
|
filters = append(filters, listStorageSpacesTypeFilter("personal"))
|
|
}
|
|
}
|
|
|
|
if len(filters) > 0 {
|
|
wdu, err := g.getWebDavBaseURL()
|
|
if err != nil {
|
|
// log error, wrong configuration
|
|
logger.Error().
|
|
Err(err).
|
|
Str("webdav_base", g.config.Spaces.WebDavBase).
|
|
Str("webdav_path", g.config.Spaces.WebDavPath).
|
|
Msg("error parsing webdav URL")
|
|
render.Status(r, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
logger.Debug().Str("id", user.GetId()).Msg("calling list storage spaces with filter")
|
|
|
|
// use the unrestricted flag to get all possible spaces
|
|
// users with the canListAllSpaces permission should see all spaces
|
|
|
|
client, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("error selecting next gateway client")
|
|
render.Status(r, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
opaque := utils.AppendPlainToOpaque(nil, "unrestricted", "T")
|
|
lspr, err := client.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{
|
|
Opaque: opaque,
|
|
Filters: filters,
|
|
})
|
|
if err != nil {
|
|
// transport error, needs to be fixed by admin
|
|
logger.Error().Err(err).Interface("query", r.URL.Query()).Msg("error getting storages: transport error")
|
|
render.Status(r, http.StatusInternalServerError)
|
|
render.JSON(w, r, user)
|
|
return
|
|
}
|
|
if lspr.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
|
|
logger.Debug().Str("grpc", lspr.GetStatus().GetMessage()).Msg("could not list drives for user")
|
|
// in case of NOT_OK, we can just return the user object with empty drives
|
|
render.Status(r, status.HTTPStatusFromCode(http.StatusOK))
|
|
render.JSON(w, r, user)
|
|
return
|
|
}
|
|
if listDrives {
|
|
user.Drives = make([]libregraph.Drive, 0, len(lspr.GetStorageSpaces()))
|
|
}
|
|
if listDrive {
|
|
user.Drive = &libregraph.Drive{}
|
|
}
|
|
for _, sp := range lspr.GetStorageSpaces() {
|
|
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("id", sp.Id).Msg("error converting space to drive")
|
|
continue
|
|
}
|
|
quota, err := g.getDriveQuota(r.Context(), sp)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("id", sp.Id).Msg("error calling get quota on drive")
|
|
}
|
|
d.Quota = "a
|
|
if d.GetDriveType() == "personal" && sp.GetOwner().GetId().GetOpaqueId() == user.GetId() {
|
|
if listDrive {
|
|
user.Drive = d
|
|
}
|
|
} else {
|
|
if listDrives {
|
|
user.Drives = append(user.Drives, *d)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// expand appRoleAssignments if requested
|
|
if slices.Contains(exp, appRoleAssignments) {
|
|
user.AppRoleAssignments, err = g.fetchAppRoleAssignments(r.Context(), user.GetId())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("userid", user.GetId()).Msg("could not get appRoleAssignments for user")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if !g.config.API.ShowUserEmailInResults {
|
|
user.Mail = nil
|
|
}
|
|
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, user)
|
|
}
|
|
|
|
// getUserLanguage returns the language of the user in the context.
|
|
func getUserLanguage(ctx context.Context, valueService settingssvc.ValueService, userID string) (string, string, error) {
|
|
gvr, err := valueService.GetValueByUniqueIdentifiers(ctx, &settingssvc.GetValueByUniqueIdentifiersRequest{
|
|
AccountUuid: userID,
|
|
SettingId: defaults.SettingUUIDProfileLanguage,
|
|
})
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
langVal := gvr.GetValue().GetValue().GetListValue().GetValues()
|
|
if len(langVal) > 0 && langVal[0] != nil {
|
|
return langVal[0].GetStringValue(), gvr.GetValue().GetValue().GetId(), nil
|
|
}
|
|
return "", "", errors.New("no language value found")
|
|
}
|
|
|
|
// DeleteUser implements the Service interface.
|
|
func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Msg("calling delete user")
|
|
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
|
userID := chi.URLParam(r, "userID")
|
|
userID, err := url.PathUnescape(userID)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("id", userID).Msg("could not delete user: unescaping user id failed")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
|
return
|
|
}
|
|
|
|
if userID == "" {
|
|
logger.Debug().Msg("could not delete user: missing user id")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id")
|
|
return
|
|
}
|
|
|
|
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: query error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
logger.Debug().Str("id", userID).Msg("calling get user on user backend")
|
|
user, err := g.identityBackend.GetUser(r.Context(), userID, odataReq)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("userID", userID).Msg("failed to get user from backend")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
|
|
e := events.UserDeleted{UserID: user.GetId()}
|
|
if currentUser, ok := revactx.ContextGetUser(r.Context()); ok {
|
|
if currentUser.GetId().GetOpaqueId() == user.GetId() {
|
|
logger.Debug().Msg("could not delete user: self deletion forbidden")
|
|
errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "self deletion forbidden")
|
|
return
|
|
}
|
|
e.Executant = currentUser.GetId()
|
|
}
|
|
|
|
if g.gatewaySelector != nil {
|
|
logger.Debug().
|
|
Str("user", user.GetId()).
|
|
Msg("calling list spaces with user filter to fetch the personal space for deletion")
|
|
opaque := utils.AppendPlainToOpaque(nil, "unrestricted", "T")
|
|
f := listStorageSpacesUserFilter(user.GetId())
|
|
client, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("error selecting next gateway client")
|
|
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "error selecting next gateway client, aborting")
|
|
return
|
|
}
|
|
lspr, err := client.ListStorageSpaces(r.Context(), &storageprovider.ListStorageSpacesRequest{
|
|
Opaque: opaque,
|
|
Filters: []*storageprovider.ListStorageSpacesRequest_Filter{f},
|
|
})
|
|
if err != nil {
|
|
// transport error, log as error
|
|
logger.Error().Err(err).Msg("could not fetch spaces: transport error")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not fetch spaces for deletion, aborting")
|
|
return
|
|
}
|
|
for _, sp := range lspr.GetStorageSpaces() {
|
|
if !(sp.SpaceType == _spaceTypePersonal && sp.Owner.Id.OpaqueId == user.GetId()) {
|
|
continue
|
|
}
|
|
// TODO: check if request contains a homespace and if, check if requesting user has the privilege to
|
|
// delete it and make sure it is not deleting its own homespace
|
|
// needs modification of the cs3api
|
|
|
|
// Deleting a space a two step process (1. disabling/trashing, 2. purging)
|
|
// Do the "disable/trash" step only if the space is not marked as trashed yet:
|
|
if _, ok := sp.Opaque.Map[_spaceStateTrashed]; !ok {
|
|
_, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{
|
|
Id: &storageprovider.StorageSpaceId{
|
|
OpaqueId: sp.Id.OpaqueId,
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not disable homespace: transport error")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not disable homespace, aborting")
|
|
return
|
|
}
|
|
}
|
|
purgeFlag := utils.AppendPlainToOpaque(nil, "purge", "")
|
|
_, err := client.DeleteStorageSpace(r.Context(), &storageprovider.DeleteStorageSpaceRequest{
|
|
Opaque: purgeFlag,
|
|
Id: &storageprovider.StorageSpaceId{
|
|
OpaqueId: sp.Id.OpaqueId,
|
|
},
|
|
})
|
|
if err != nil {
|
|
// transport error, log as error
|
|
logger.Error().Err(err).Msg("could not delete homespace: transport error")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not delete homespace, aborting")
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
logger.Debug().Str("id", user.GetId()).Msg("calling delete user on backend")
|
|
err = g.identityBackend.DeleteUser(r.Context(), user.GetId())
|
|
|
|
if err != nil {
|
|
logger.Debug().Err(err).Msg("could not delete user: backend error")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
|
|
g.publishEvent(r.Context(), e)
|
|
|
|
render.Status(r, http.StatusNoContent)
|
|
render.NoContent(w, r)
|
|
}
|
|
|
|
// PatchMe implements the Service Interface. Updates the specified attributes of the current user
|
|
func (g Graph) PatchMe(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Msg("calling patch me")
|
|
userID := revactx.ContextMustGetUser(r.Context()).GetId().GetOpaqueId()
|
|
if userID == "" {
|
|
logger.Debug().Msg("could not update user: missing user id")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id")
|
|
return
|
|
}
|
|
changes := libregraph.NewUserUpdate()
|
|
err := StrictJSONUnmarshal(r.Body, changes)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update user: invalid request body")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest,
|
|
fmt.Sprintf("invalid request body: %s", err.Error()))
|
|
return
|
|
}
|
|
if _, ok := changes.GetDisplayNameOk(); ok {
|
|
logger.Info().Interface("user", changes).Msg("could not update user: user is not allowed to change own displayname")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is not allowed to change own displayname")
|
|
return
|
|
}
|
|
if _, ok := changes.GetMailOk(); ok {
|
|
logger.Info().Interface("user", changes).Msg("could not update user: user is not allowed to change own mail")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is not allowed to change own mail")
|
|
return
|
|
}
|
|
g.patchUser(w, r, userID, changes)
|
|
}
|
|
|
|
// PatchUser implements the Service Interface. Updates the specified attributes of an
|
|
// ExistingUser
|
|
func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
nameOrID := chi.URLParam(r, "userID")
|
|
nameOrID, err := url.PathUnescape(nameOrID)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("id", nameOrID).Msg("could not update user: unescaping user id failed")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
|
return
|
|
}
|
|
changes := libregraph.NewUserUpdate()
|
|
err = StrictJSONUnmarshal(r.Body, changes)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update user: invalid request body")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest,
|
|
fmt.Sprintf("invalid request body: %s", err.Error()))
|
|
return
|
|
}
|
|
if _, ok := changes.GetPreferredLanguageOk(); ok {
|
|
logger.Info().Interface("user", changes).Msg("could not update user: user is not allowed to change other users language")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is not allowed to change other users language")
|
|
return
|
|
}
|
|
|
|
g.patchUser(w, r, nameOrID, changes)
|
|
}
|
|
|
|
func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string, changes *libregraph.UserUpdate) {
|
|
logger := g.logger.SubloggerWithRequestID(r.Context())
|
|
logger.Debug().Msg("calling patch user")
|
|
|
|
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
|
|
|
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
|
if err != nil {
|
|
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: query error")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
oldUserValues, err := g.identityBackend.GetUser(r.Context(), nameOrID, odataReq)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("id", nameOrID).Msg("could not get user: backend error")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
|
|
if nameOrID == "" {
|
|
logger.Debug().Msg("could not update user: missing user id")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id")
|
|
return
|
|
}
|
|
|
|
if reflect.ValueOf(*changes).IsZero() {
|
|
logger.Debug().Interface("body", r.Body).Msg("ignoring empty request body")
|
|
render.Status(r, http.StatusNoContent)
|
|
render.NoContent(w, r)
|
|
return
|
|
}
|
|
|
|
if changes.HasId() {
|
|
logger.Debug().Msg("could not update user: user id is a read-only attribute")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user id is a read-only attribute")
|
|
return
|
|
}
|
|
|
|
if changes.HasMemberOf() {
|
|
logger.Debug().Msg("could not update user: memberOf id is a read-only attribute")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "memberOf id is a read-only attribute")
|
|
return
|
|
}
|
|
|
|
if changes.HasUserType() {
|
|
logger.Debug().Msg("could not update user: userType is a read-only attribute")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "userType is a read-only attribute")
|
|
return
|
|
}
|
|
|
|
if accountName, ok := changes.GetOnPremisesSamAccountNameOk(); ok {
|
|
if !g.isValidUsername(*accountName) {
|
|
logger.Info().Str("username", *accountName).Msg("could not update user: invalid username")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Invalid username")
|
|
return
|
|
}
|
|
}
|
|
|
|
var features []events.UserFeature
|
|
addfeature := func(name string, value string, oldvalue *string) {
|
|
features = append(features, events.UserFeature{
|
|
Name: name,
|
|
Value: value,
|
|
OldValue: oldvalue,
|
|
})
|
|
}
|
|
|
|
preferredLanguage, ok := changes.GetPreferredLanguageOk()
|
|
if ok {
|
|
_, vID, err := getUserLanguage(r.Context(), g.valueService, oldUserValues.GetId())
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not get user language")
|
|
tvID, err := uuid.NewUUID()
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not create user: error generating uuid")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "error generating uuid")
|
|
return
|
|
}
|
|
vID = tvID.String()
|
|
}
|
|
_, err = g.valueService.SaveValue(r.Context(), &settingssvc.SaveValueRequest{
|
|
Value: &settingsmsg.Value{
|
|
Id: vID,
|
|
BundleId: defaults.BundleUUIDProfile,
|
|
SettingId: defaults.SettingUUIDProfileLanguage,
|
|
AccountUuid: oldUserValues.GetId(),
|
|
Resource: &settingsmsg.Resource{
|
|
Type: settingsmsg.Resource_TYPE_USER,
|
|
},
|
|
Value: &settingsmsg.Value_ListValue{
|
|
ListValue: &settingsmsg.ListValue{Values: []*settingsmsg.ListOptionValue{
|
|
{
|
|
Option: &settingsmsg.ListOptionValue_StringValue{
|
|
StringValue: *preferredLanguage,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("could not update user: error saving language setting")
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "error saving language setting")
|
|
return
|
|
}
|
|
|
|
addfeature("preferredLanguage", *preferredLanguage, nil) // do we need to get the old value?
|
|
|
|
// if there are no more changes we can exit here
|
|
m, _ := changes.ToMap() // always returns nil error
|
|
if len(m) == 1 {
|
|
oldUserValues.PreferredLanguage = preferredLanguage
|
|
g.patchUserResponse(w, r, oldUserValues, features)
|
|
return
|
|
}
|
|
}
|
|
|
|
if mail, ok := changes.GetMailOk(); ok {
|
|
if !isValidEmail(*mail) {
|
|
logger.Debug().Str("mail", *mail).Msg("could not update user: email is not a valid email address")
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest,
|
|
fmt.Sprintf("'%s' is not a valid email address", *mail))
|
|
return
|
|
}
|
|
addfeature("email", *mail, oldUserValues.Mail)
|
|
}
|
|
|
|
if name, ok := changes.GetDisplayNameOk(); ok {
|
|
addfeature("displayname", *name, &oldUserValues.DisplayName)
|
|
}
|
|
|
|
if accEnabled, ok := changes.GetAccountEnabledOk(); ok {
|
|
oldAccVal := strconv.FormatBool(oldUserValues.GetAccountEnabled())
|
|
addfeature("accountEnabled", strconv.FormatBool(*accEnabled), &oldAccVal)
|
|
}
|
|
|
|
if changes.HasPasswordProfile() {
|
|
addfeature("passwordChanged", "", nil)
|
|
}
|
|
|
|
logger.Debug().Str("nameid", nameOrID).Interface("changes", *changes).Msg("calling update user on backend")
|
|
u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes)
|
|
if err != nil {
|
|
logger.Debug().Err(err).Str("id", nameOrID).Msg("could not update user: backend error")
|
|
errorcode.RenderError(w, r, err)
|
|
return
|
|
}
|
|
|
|
u.PreferredLanguage = preferredLanguage
|
|
g.patchUserResponse(w, r, u, features)
|
|
}
|
|
|
|
func (g *Graph) patchUserResponse(w http.ResponseWriter, r *http.Request, user *libregraph.User, features []events.UserFeature) {
|
|
e := events.UserFeatureChanged{
|
|
UserID: *user.Id,
|
|
Features: features,
|
|
Timestamp: utils.TSNow(),
|
|
}
|
|
|
|
if currentUser, ok := revactx.ContextGetUser(r.Context()); ok {
|
|
e.Executant = currentUser.GetId()
|
|
}
|
|
|
|
g.publishEvent(r.Context(), e)
|
|
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, user)
|
|
}
|
|
|
|
const (
|
|
usernameMatchDefault = "default"
|
|
usernameMatchNone = "none"
|
|
)
|
|
|
|
var usernameRegexes = map[string]*regexp.Regexp{
|
|
// We want to allow email addresses as usernames so they show up when using them in ACLs on storages that allow integration with our glauth LDAP service
|
|
// so we are adding a few restrictions from https://stackoverflow.com/questions/6949667/what-are-the-real-rules-for-linux-usernames-on-centos-6-and-rhel-6
|
|
// names should not start with numbers
|
|
usernameMatchDefault: regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]*(@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)*$"),
|
|
|
|
// In some cases users will be provisioned from an existing system, which may or may not have strange usernames. Because of this we want to "trust" the
|
|
// upstream system and allow weird usernames, so relying on the used identity provider to complain if a username is violating its restrictions.
|
|
usernameMatchNone: regexp.MustCompile(".*"),
|
|
}
|
|
|
|
func (g Graph) isValidUsername(e string) bool {
|
|
if len(e) < 1 && len(e) > 254 {
|
|
return false
|
|
}
|
|
|
|
return usernameRegexes[g.config.API.UsernameMatch].MatchString(e)
|
|
}
|
|
|
|
// regex from https://www.w3.org/TR/2016/REC-html51-20161101/sec-forms.html#valid-e-mail-address
|
|
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
|
|
|
func isValidEmail(e string) bool {
|
|
if len(e) < 3 && len(e) > 254 {
|
|
return false
|
|
}
|
|
return emailRegex.MatchString(e)
|
|
}
|
|
|
|
func sortUsers(req *godata.GoDataRequest, users []*libregraph.User) ([]*libregraph.User, error) {
|
|
if req.Query.OrderBy == nil || len(req.Query.OrderBy.OrderByItems) != 1 {
|
|
return users, nil
|
|
}
|
|
var less func(i, j int) bool
|
|
|
|
switch req.Query.OrderBy.OrderByItems[0].Field.Value {
|
|
case displayNameAttr:
|
|
less = func(i, j int) bool {
|
|
return strings.ToLower(users[i].GetDisplayName()) < strings.ToLower(users[j].GetDisplayName())
|
|
}
|
|
case "mail":
|
|
less = func(i, j int) bool {
|
|
return strings.ToLower(users[i].GetMail()) < strings.ToLower(users[j].GetMail())
|
|
}
|
|
case "onPremisesSamAccountName":
|
|
less = func(i, j int) bool {
|
|
return strings.ToLower(users[i].GetOnPremisesSamAccountName()) < strings.ToLower(users[j].GetOnPremisesSamAccountName())
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("we do not support <%s> as a order parameter", req.Query.OrderBy.OrderByItems[0].Field.Value)
|
|
}
|
|
|
|
if req.Query.OrderBy.OrderByItems[0].Order == _sortDescending {
|
|
sort.Slice(users, reverse(less))
|
|
} else {
|
|
sort.Slice(users, less)
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
func isValidUserType(userType string) bool {
|
|
userType = strings.ToLower(userType)
|
|
|
|
for _, value := range []string{"member", "guest"} {
|
|
if userType == value {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (g Graph) searchOCMAcceptedUsers(ctx context.Context, odataReq *godata.GoDataRequest) ([]*libregraph.User, error) {
|
|
logger := g.logger.SubloggerWithRequestID(ctx)
|
|
users := []*libregraph.User{}
|
|
|
|
if !g.config.IncludeOCMSharees {
|
|
logger.Debug().Msg("OCM sharing is disabled")
|
|
return users, nil
|
|
}
|
|
|
|
gwc, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
term, err := identity.GetSearchValues(odataReq.Query)
|
|
if err != nil {
|
|
return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
|
|
}
|
|
|
|
remoteUsersRes, err := gwc.FindAcceptedUsers(ctx, &invitepb.FindAcceptedUsersRequest{Filter: term})
|
|
if err != nil {
|
|
logger.Error().Err(err).Msg("FindAcceptedUsers call failed")
|
|
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
if remoteUsersRes.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
|
|
return nil, errorcode.FromCS3Status(remoteUsersRes.Status, nil)
|
|
}
|
|
|
|
cs3Users := remoteUsersRes.GetAcceptedUsers()
|
|
users = make([]*libregraph.User, 0, len(cs3Users))
|
|
for _, user := range cs3Users {
|
|
users = append(users, identity.CreateUserModelFromCS3(user))
|
|
}
|
|
return users, nil
|
|
}
|