mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-17 18:38:18 -05:00
Adding multiple members at once is done via PATCH on
`groups/{group-oid}` with a body containing a list of refernces to the
members.
321 lines
9.4 KiB
Go
321 lines
9.4 KiB
Go
package svc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
libregraph "github.com/owncloud/libre-graph-api-go"
|
|
"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/render"
|
|
)
|
|
|
|
const memberRefsLimit = 20
|
|
|
|
// GetGroups implements the Service interface.
|
|
func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) {
|
|
groups, err := g.identityBackend.GetGroups(r.Context(), r.URL.Query())
|
|
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
}
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, &listResponse{Value: groups})
|
|
}
|
|
|
|
// PostGroup implements the Service interface.
|
|
func (g Graph) PostGroup(w http.ResponseWriter, r *http.Request) {
|
|
grp := libregraph.NewGroup()
|
|
err := json.NewDecoder(r.Body).Decode(grp)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
if isNilOrEmpty(grp.DisplayName) {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute")
|
|
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 !isNilOrEmpty(grp.Id) {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "group id is a read-only attribute")
|
|
return
|
|
}
|
|
|
|
if grp, err = g.identityBackend.CreateGroup(r.Context(), *grp); err != nil {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, grp)
|
|
}
|
|
|
|
// PatchGroup implements the Service interface.
|
|
func (g Graph) PatchGroup(w http.ResponseWriter, r *http.Request) {
|
|
g.logger.Debug().Msg("Calling PatchGroup")
|
|
groupID := chi.URLParam(r, "groupID")
|
|
groupID, err := url.PathUnescape(groupID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
return
|
|
}
|
|
|
|
if groupID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
changes := libregraph.NewGroup()
|
|
err = json.NewDecoder(r.Body).Decode(changes)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
if memberRefs, ok := changes.GetMembersodataBindOk(); ok {
|
|
// The spec defines a limit of 20 members maxium per Request
|
|
if len(memberRefs) > memberRefsLimit {
|
|
errorcode.NotAllowed.Render(w, r, http.StatusInternalServerError,
|
|
fmt.Sprintf("Request is limited to %d members", memberRefsLimit))
|
|
return
|
|
}
|
|
memberIDs := make([]string, 0, len(memberRefs))
|
|
for _, memberRef := range memberRefs {
|
|
memberType, id, err := g.parseMemberRef(memberRef)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing member@odata.bind values")
|
|
return
|
|
}
|
|
g.logger.Debug().Str("memberType", memberType).Str("memberid", id).Msg("Add Member")
|
|
// The MS Graph spec allows "directoryObject", "user", "group" and "organizational Contact"
|
|
// we restrict this to users for now. Might add Groups as members later
|
|
if memberType != "users" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Only user are allowed as group members")
|
|
return
|
|
}
|
|
memberIDs = append(memberIDs, id)
|
|
}
|
|
err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, memberIDs)
|
|
}
|
|
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
render.Status(r, http.StatusNoContent)
|
|
render.NoContent(w, r)
|
|
}
|
|
|
|
// GetGroup implements the Service interface.
|
|
func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) {
|
|
groupID := chi.URLParam(r, "groupID")
|
|
groupID, err := url.PathUnescape(groupID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
}
|
|
|
|
if groupID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
|
|
group, err := g.identityBackend.GetGroup(r.Context(), groupID)
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
}
|
|
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, group)
|
|
}
|
|
|
|
// DeleteGroup implements the Service interface.
|
|
func (g Graph) DeleteGroup(w http.ResponseWriter, r *http.Request) {
|
|
groupID := chi.URLParam(r, "groupID")
|
|
groupID, err := url.PathUnescape(groupID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
return
|
|
}
|
|
|
|
if groupID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
|
|
err = g.identityBackend.DeleteGroup(r.Context(), groupID)
|
|
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
render.Status(r, http.StatusNoContent)
|
|
render.NoContent(w, r)
|
|
}
|
|
|
|
func (g Graph) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
|
|
groupID := chi.URLParam(r, "groupID")
|
|
groupID, err := url.PathUnescape(groupID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
return
|
|
}
|
|
|
|
if groupID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
|
|
members, err := g.identityBackend.GetGroupMembers(r.Context(), groupID)
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
render.Status(r, http.StatusOK)
|
|
render.JSON(w, r, members)
|
|
}
|
|
|
|
// PostGroupMember implements the Service interface.
|
|
func (g Graph) PostGroupMember(w http.ResponseWriter, r *http.Request) {
|
|
g.logger.Info().Msg("Calling PostGroupMember")
|
|
|
|
groupID := chi.URLParam(r, "groupID")
|
|
groupID, err := url.PathUnescape(groupID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
return
|
|
}
|
|
|
|
if groupID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
memberRef := libregraph.NewMemberReference()
|
|
err = json.NewDecoder(r.Body).Decode(memberRef)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
memberRefURL, ok := memberRef.GetOdataIdOk()
|
|
if !ok {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "@odata.id refernce is missing")
|
|
return
|
|
}
|
|
memberType, id, err := g.parseMemberRef(*memberRefURL)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing @odata.id url")
|
|
return
|
|
}
|
|
// The MS Graph spec allows "directoryObject", "user", "group" and "organizational Contact"
|
|
// we restrict this to users for now. Might add Groups as members later
|
|
if memberType != "users" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Only user are allowed as group members")
|
|
return
|
|
}
|
|
|
|
g.logger.Debug().Str("memberType", memberType).Str("id", id).Msg("Add Member")
|
|
err = g.identityBackend.AddMembersToGroup(r.Context(), groupID, []string{id})
|
|
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
render.Status(r, http.StatusNoContent)
|
|
render.NoContent(w, r)
|
|
}
|
|
|
|
// DeleteGroupMember implements the Service interface.
|
|
func (g Graph) DeleteGroupMember(w http.ResponseWriter, r *http.Request) {
|
|
g.logger.Info().Msg("Calling DeleteGroupMember")
|
|
|
|
groupID := chi.URLParam(r, "groupID")
|
|
groupID, err := url.PathUnescape(groupID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
return
|
|
}
|
|
|
|
if groupID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
|
|
memberID := chi.URLParam(r, "memberID")
|
|
memberID, err = url.PathUnescape(memberID)
|
|
if err != nil {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping group id failed")
|
|
return
|
|
}
|
|
|
|
if memberID == "" {
|
|
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id")
|
|
return
|
|
}
|
|
g.logger.Debug().Str("groupID", groupID).Str("memberID", memberID).Msg("DeleteGroupMember")
|
|
err = g.identityBackend.RemoveMemberFromGroup(r.Context(), groupID, memberID)
|
|
|
|
if err != nil {
|
|
var errcode errorcode.Error
|
|
if errors.As(err, &errcode) {
|
|
errcode.Render(w, r)
|
|
} else {
|
|
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
render.Status(r, http.StatusNoContent)
|
|
render.NoContent(w, r)
|
|
}
|
|
|
|
func (g Graph) parseMemberRef(ref string) (string, string, error) {
|
|
memberURL, err := url.ParseRequestURI(ref)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
segments := strings.Split(memberURL.Path, "/")
|
|
if len(segments) < 2 {
|
|
return "", "", errors.New("invalid member reference")
|
|
}
|
|
id := segments[len(segments)-1]
|
|
memberType := segments[len(segments)-2]
|
|
return memberType, id, nil
|
|
}
|