mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-03-06 08:17:42 -05:00
Up to now when using the CS3 backend (e.g. to use an external LDAP
server) queries to /users/{userid}/groups just errored out. This add a
simple stub to just return and empty group list for now.
This allows using and external LDAP server without having to fiddle with
the proxy configuration to redirect to the reva ocs implementation.
(Which also is just returning an empty group list currently)
456 lines
15 KiB
Go
456 lines
15 KiB
Go
package svc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
accountsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/accounts/v0"
|
|
accountssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/accounts/v0"
|
|
|
|
revactx "github.com/cs3org/reva/pkg/ctx"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/owncloud/ocis/ocs/pkg/service/v0/data"
|
|
"github.com/owncloud/ocis/ocs/pkg/service/v0/response"
|
|
ocstracing "github.com/owncloud/ocis/ocs/pkg/tracing"
|
|
merrors "go-micro.dev/v4/errors"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
)
|
|
|
|
// ListUserGroups lists a users groups
|
|
func (o Ocs) ListUserGroups(w http.ResponseWriter, r *http.Request) {
|
|
userid := chi.URLParam(r, "userid")
|
|
userid, err := url.PathUnescape(userid)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
var account *accountsmsg.Account
|
|
|
|
if o.config.AccountBackend == "cs3" {
|
|
o.mustRender(w, r, response.DataRender(&data.Groups{}))
|
|
return
|
|
}
|
|
|
|
// short circuit if there is a user already in the context
|
|
if u, ok := revactx.ContextGetUser(r.Context()); ok {
|
|
// we are not sure whether the current user in the context is the admin or the authenticated user.
|
|
if u.Username == userid {
|
|
// the OCS API is a REST API and it uses the username to look for groups. If the id from the user in the context
|
|
// differs from that of the url we can assume we are an admin because we are past the selfOrAdmin middleware.
|
|
|
|
_, span := ocstracing.TraceProvider.
|
|
Tracer("ocs").
|
|
Start(r.Context(), "ListUserGroups")
|
|
defer span.End()
|
|
|
|
span.SetAttributes(attribute.StringSlice("groups", u.Groups))
|
|
|
|
if len(u.Groups) > 0 {
|
|
o.mustRender(w, r, response.DataRender(&data.Groups{Groups: u.Groups}))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if isValidUUID(userid) {
|
|
account, err = o.getAccountService().GetAccount(r.Context(), &accountssvc.GetAccountRequest{
|
|
Id: userid,
|
|
})
|
|
} else {
|
|
// despite the confusion, if we make it here we got ourselves a username
|
|
account, err = o.fetchAccountByUsername(r.Context(), userid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageUserNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("userid", userid).Msg("could not get list of user groups")
|
|
return
|
|
}
|
|
}
|
|
|
|
groups := make([]string, 0, len(account.MemberOf))
|
|
for i := range account.MemberOf {
|
|
if account.MemberOf[i].OnPremisesSamAccountName == "" {
|
|
o.logger.Warn().Str("groupid", account.MemberOf[i].Id).Msg("group on_premises_sam_account_name is empty, trying to lookup by id")
|
|
// we can try to look up the name
|
|
group, err := o.getGroupsService().GetGroup(r.Context(), &accountssvc.GetGroupRequest{
|
|
Id: account.MemberOf[i].Id,
|
|
})
|
|
|
|
if err != nil {
|
|
o.logger.Error().Err(err).Str("groupid", account.MemberOf[i].Id).Msg("could not get group")
|
|
continue
|
|
}
|
|
if group.OnPremisesSamAccountName == "" {
|
|
o.logger.Error().Err(err).Str("groupid", account.MemberOf[i].Id).Msg("group on_premises_sam_account_name is empty")
|
|
continue
|
|
}
|
|
groups = append(groups, group.OnPremisesSamAccountName)
|
|
} else {
|
|
groups = append(groups, account.MemberOf[i].OnPremisesSamAccountName)
|
|
}
|
|
}
|
|
|
|
o.logger.Error().Err(err).Int("count", len(groups)).Str("userid", account.Id).Msg("listing groups for user")
|
|
|
|
_, span := ocstracing.TraceProvider.
|
|
Tracer("ocs").
|
|
Start(r.Context(), "ListUserGroups")
|
|
defer span.End()
|
|
|
|
span.SetAttributes(attribute.StringSlice("groups", groups))
|
|
|
|
o.mustRender(w, r, response.DataRender(&data.Groups{Groups: groups}))
|
|
}
|
|
|
|
// AddToGroup adds a user to a group
|
|
func (o Ocs) AddToGroup(w http.ResponseWriter, r *http.Request) {
|
|
groupid := r.PostFormValue("groupid")
|
|
userid := chi.URLParam(r, "userid")
|
|
userid, err := url.PathUnescape(userid)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
|
|
if groupid == "" {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "empty group assignment: unspecified group"))
|
|
return
|
|
}
|
|
account, err := o.fetchAccountByUsername(r.Context(), userid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageUserNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
// ocs only knows about names so we have to look up the internal id
|
|
group, err := o.fetchGroupByName(r.Context(), groupid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
_, err = o.getGroupsService().AddMember(r.Context(), &accountssvc.AddMemberRequest{
|
|
AccountId: account.Id,
|
|
GroupId: group.Id,
|
|
})
|
|
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("userid", account.Id).Str("groupid", group.Id).Msg("could not add user to group")
|
|
return
|
|
}
|
|
|
|
o.logger.Debug().Str("userid", account.Id).Str("groupid", group.Id).Msg("added user to group")
|
|
o.mustRender(w, r, response.DataRender(struct{}{}))
|
|
}
|
|
|
|
// RemoveFromGroup removes a user from a group
|
|
func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) {
|
|
userid := chi.URLParam(r, "userid")
|
|
userid, err := url.PathUnescape(userid)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
|
|
// Really? a DELETE with form encoded body?!?
|
|
// but it is not encoded as mime, so we cannot just call r.ParseForm()
|
|
// read it manually
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, err.Error()))
|
|
return
|
|
}
|
|
if err = r.Body.Close(); err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
return
|
|
}
|
|
|
|
values, err := url.ParseQuery(string(body))
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, err.Error()))
|
|
return
|
|
}
|
|
|
|
groupid := values.Get("groupid")
|
|
if groupid == "" {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "no group id"))
|
|
return
|
|
}
|
|
|
|
var account *accountsmsg.Account
|
|
|
|
if isValidUUID(userid) {
|
|
account, _ = o.getAccountService().GetAccount(r.Context(), &accountssvc.GetAccountRequest{
|
|
Id: userid,
|
|
})
|
|
} else {
|
|
// despite the confusion, if we make it here we got ourselves a username
|
|
account, err = o.fetchAccountByUsername(r.Context(), userid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, data.MessageUserNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("userid", userid).Msg("could not get list of user groups")
|
|
return
|
|
}
|
|
}
|
|
|
|
// ocs only knows about names so we have to look up the internal id
|
|
group, err := o.fetchGroupByName(r.Context(), groupid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
_, err = o.getGroupsService().RemoveMember(r.Context(), &accountssvc.RemoveMemberRequest{
|
|
AccountId: account.Id,
|
|
GroupId: group.Id,
|
|
})
|
|
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("userid", account.Id).Str("groupid", group.Id).Msg("could not remove user from group")
|
|
return
|
|
}
|
|
|
|
o.logger.Debug().Str("userid", account.Id).Str("groupid", group.Id).Msg("removed user from group")
|
|
o.mustRender(w, r, response.DataRender(struct{}{}))
|
|
}
|
|
|
|
// ListGroups lists all groups
|
|
func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) {
|
|
search := r.URL.Query().Get("search")
|
|
query := ""
|
|
if search != "" {
|
|
query = fmt.Sprintf("id eq '%s' or on_premises_sam_account_name eq '%s'", escapeValue(search), escapeValue(search))
|
|
}
|
|
|
|
res, err := o.getGroupsService().ListGroups(r.Context(), &accountssvc.ListGroupsRequest{
|
|
Query: query,
|
|
})
|
|
|
|
if err != nil {
|
|
o.logger.Err(err).Msg("could not list users")
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not list users"))
|
|
return
|
|
}
|
|
|
|
groups := make([]string, 0, len(res.Groups))
|
|
for i := range res.Groups {
|
|
groups = append(groups, res.Groups[i].OnPremisesSamAccountName)
|
|
}
|
|
|
|
_, span := ocstracing.TraceProvider.
|
|
Tracer("ocs").
|
|
Start(r.Context(), "ListGroups")
|
|
defer span.End()
|
|
|
|
span.SetAttributes(attribute.StringSlice("groups", groups))
|
|
|
|
o.mustRender(w, r, response.DataRender(&data.Groups{Groups: groups}))
|
|
}
|
|
|
|
// AddGroup adds a group
|
|
// oC10 implementation: https://github.com/owncloud/core/blob/762780a23c9eadda4fb5fa8db99eba66a5100b6e/apps/provisioning_api/lib/Groups.php#L126-L154
|
|
func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) {
|
|
groupid := r.PostFormValue("groupid")
|
|
displayname := r.PostFormValue("displayname")
|
|
gid := r.PostFormValue("gidnumber")
|
|
|
|
if displayname == "" && groupid == "" {
|
|
code := data.MetaFailure.StatusCode // v1
|
|
if response.APIVersion(r.Context()) == "2" {
|
|
code = data.MetaBadRequest.StatusCode
|
|
}
|
|
o.mustRender(w, r, response.ErrRender(code, "No groupid or display name provided"))
|
|
return
|
|
}
|
|
|
|
if displayname == "" {
|
|
// oC10 OCS does not know about a group displayname
|
|
// therefore we fall back to the oC10 parameter groupid (which is the groupname in the oC10 world)
|
|
displayname = groupid
|
|
}
|
|
|
|
var gidNumber int64
|
|
var err error
|
|
|
|
if gid != "" {
|
|
gidNumber, err = strconv.ParseInt(gid, 10, 64)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "Cannot use the gidnumber provided"))
|
|
o.logger.Error().Err(err).Str("gid", gid).Str("groupid", groupid).Msg("Cannot use the gidnumber provided")
|
|
return
|
|
}
|
|
}
|
|
|
|
newGroup := &accountsmsg.Group{
|
|
Id: groupid,
|
|
DisplayName: displayname,
|
|
OnPremisesSamAccountName: groupid,
|
|
GidNumber: gidNumber,
|
|
}
|
|
group, err := o.getGroupsService().CreateGroup(r.Context(), &accountssvc.CreateGroupRequest{
|
|
Group: newGroup,
|
|
})
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
switch merr.Code {
|
|
case http.StatusBadRequest:
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
|
|
case http.StatusConflict:
|
|
if response.APIVersion(r.Context()) == "2" {
|
|
// it seems the application framework sets the ocs status code to the httpstatus code, which affects the provisioning api
|
|
// see https://github.com/owncloud/core/blob/b9ff4c93e051c94adfb301545098ae627e52ef76/lib/public/AppFramework/OCSController.php#L142-L150
|
|
o.mustRender(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaInvalidInput.StatusCode, merr.Detail))
|
|
}
|
|
default:
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("groupid", groupid).Msg("could not add group")
|
|
// TODO check error if group already existed
|
|
return
|
|
}
|
|
o.logger.Debug().Interface("group", group).Msg("added group")
|
|
|
|
o.mustRender(w, r, response.DataRender(struct{}{}))
|
|
}
|
|
|
|
// DeleteGroup deletes a group
|
|
func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) {
|
|
groupid := chi.URLParam(r, "groupid")
|
|
groupid, err := url.PathUnescape(groupid)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
|
|
// ocs only knows about names so we have to look up the internal id
|
|
group, err := o.fetchGroupByName(r.Context(), groupid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
_, err = o.getGroupsService().DeleteGroup(r.Context(), &accountssvc.DeleteGroupRequest{
|
|
Id: group.Id,
|
|
})
|
|
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("groupid", group.Id).Msg("could not remove group")
|
|
return
|
|
}
|
|
|
|
o.logger.Debug().Str("groupid", group.Id).Msg("removed group")
|
|
o.mustRender(w, r, response.DataRender(struct{}{}))
|
|
}
|
|
|
|
// GetGroupMembers lists all members of a group
|
|
func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
|
|
|
|
groupid := chi.URLParam(r, "groupid")
|
|
groupid, err := url.PathUnescape(groupid)
|
|
if err != nil {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
|
|
// ocs only knows about names so we have to look up the internal id
|
|
group, err := o.fetchGroupByName(r.Context(), groupid)
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
return
|
|
}
|
|
|
|
res, err := o.getGroupsService().ListMembers(r.Context(), &accountssvc.ListMembersRequest{Id: group.Id})
|
|
|
|
if err != nil {
|
|
merr := merrors.FromError(err)
|
|
if merr.Code == http.StatusNotFound {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaNotFound.StatusCode, data.MessageGroupNotFound))
|
|
} else {
|
|
o.mustRender(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
|
}
|
|
o.logger.Error().Err(err).Str("groupid", group.Id).Msg("could not get list of members")
|
|
return
|
|
}
|
|
|
|
members := make([]string, 0, len(res.Members))
|
|
for i := range res.Members {
|
|
members = append(members, res.Members[i].OnPremisesSamAccountName)
|
|
}
|
|
|
|
o.logger.Error().Err(err).Int("count", len(members)).Str("groupid", groupid).Msg("listing group members")
|
|
o.mustRender(w, r, response.DataRender(&data.Users{Users: members}))
|
|
}
|
|
|
|
func isValidUUID(uuid string) bool {
|
|
r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
|
|
return r.MatchString(uuid)
|
|
}
|
|
|
|
func (o Ocs) fetchGroupByName(ctx context.Context, name string) (*accountsmsg.Group, error) {
|
|
var res *accountssvc.ListGroupsResponse
|
|
res, err := o.getGroupsService().ListGroups(ctx, &accountssvc.ListGroupsRequest{
|
|
Query: fmt.Sprintf("on_premises_sam_account_name eq '%v'", escapeValue(name)),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res != nil && len(res.Groups) == 1 {
|
|
return res.Groups[0], nil
|
|
}
|
|
return nil, merrors.NotFound("", data.MessageGroupNotFound)
|
|
}
|