Implement adding a member to a Group

This implements POST request to the graph/v1.0/groups/{groupid}/members/$ref
endpoint. Allowing to add members to a group.
This commit is contained in:
Ralf Haferkamp
2022-01-20 17:29:08 +01:00
parent 2ffd2d51f4
commit c0d486f3a5
10 changed files with 163 additions and 5 deletions
+2
View File
@@ -26,6 +26,8 @@ type Backend interface {
GetGroup(ctx context.Context, nameOrID string) (*libregraph.Group, error)
GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error)
GetGroupMembers(ctx context.Context, id string) ([]*libregraph.User, error)
// AddMemberToGroup adds a new member (reference by ID) to supplied group in the identity backend.
AddMemberToGroup(ctx context.Context, groupID string, memberID string) error
}
func CreateUserModelFromCS3(u *cs3.User) *libregraph.User {
+5
View File
@@ -179,6 +179,11 @@ func (i *CS3) GetGroupMembers(ctx context.Context, groupID string) ([]*libregrap
return nil, errorcode.New(errorcode.NotSupported, "not implemented")
}
// AddMemberToGroup implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) AddMemberToGroup(ctx context.Context, groupID string, memberID string) error {
return errorcode.New(errorcode.NotSupported, "not implemented")
}
func createGroupModelFromCS3(g *cs3group.Group) *libregraph.Group {
if g.Id == nil {
g.Id = &cs3group.GroupId{}
+49
View File
@@ -8,6 +8,7 @@ import (
"github.com/go-ldap/ldap/v3"
"github.com/gofrs/uuid"
ldapdn "github.com/libregraph/idm/pkg/ldapdn"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/graph/pkg/config"
@@ -551,6 +552,53 @@ func (i *LDAP) CreateGroup(ctx context.Context, group libregraph.Group) (*libreg
return i.createGroupModelFromLDAP(e), nil
}
// AddMemberToGroup implements the Backend Interface for the LDAP backend.
// Currently it is limited to adding Users as Group members. Adding other groups
// as members is not yet implemented
func (i *LDAP) AddMemberToGroup(ctx context.Context, groupID string, memberID string) error {
ge, err := i.getLDAPGroupByID(groupID, true)
if err != nil {
return err
}
me, err := i.getLDAPUserByID(memberID)
if err != nil {
return err
}
i.logger.Debug().Str("backend", "ldap").Str("groupdn", ge.DN).Str("member", me.DN).Msg("Add Member")
mr := ldap.ModifyRequest{DN: ge.DN}
// Handle empty groups (using the empty member attribute)
current := ge.GetEqualFoldAttributeValues(i.groupAttributeMap.member)
if len(current) == 1 && current[0] == "" {
mr.Delete(i.groupAttributeMap.member, []string{""})
}
nUserDN, err := ldapdn.ParseNormalize(me.DN)
for _, member := range current {
if member == "" {
continue
}
if nMember, err := ldapdn.ParseNormalize(member); err != nil {
// We couldn't parse the member value as a DN. Let's keep it
// as it is but log a warning
i.logger.Warn().Str("memberDN", member).Err(err).Msg("Couldn't parse DN")
continue
} else {
if nMember == nUserDN {
i.logger.Info().Str("memberDN", member).Msg("User already present. Nothing to do")
return nil
}
}
}
mr.Add(i.groupAttributeMap.member, []string{me.DN})
if err := i.conn.Modify(&mr); err != nil {
return err
}
return nil
}
func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *libregraph.User {
if e == nil {
return nil
@@ -569,6 +617,7 @@ func (i *LDAP) createGroupModelFromLDAP(e *ldap.Entry) *libregraph.Group {
Id: pointerOrNil(e.GetEqualFoldAttributeValue(i.groupAttributeMap.id)),
}
}
func pointerOrNil(val string) *string {
if val == "" {
return nil
+62 -1
View File
@@ -5,13 +5,13 @@ import (
"errors"
"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"
//msgraph "github.com/owncloud/open-graph-api-go" // FIXME add groups to open graph, needs OnPremisesSamAccountName and OnPremisesDomainName
)
// GetGroups implements the Service interface.
@@ -115,3 +115,64 @@ func (g Graph) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
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
}
memberURL, err := url.ParseRequestURI(*memberRefURL)
if err != nil {
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing @odata.id url")
return
}
segments := strings.Split(memberURL.Path, "/")
if len(segments) < 2 {
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "Error parsing @odata.id url path")
return
}
id := segments[len(segments)-1]
memberType := segments[len(segments)-2]
// 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.AddMemberToGroup(r.Context(), groupID, 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)
}
+5
View File
@@ -74,6 +74,11 @@ func (i instrument) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
i.next.GetGroupMembers(w, r)
}
// PostGroupMember implements the Service interface.
func (i instrument) PostGroupMember(w http.ResponseWriter, r *http.Request) {
i.next.PostGroupMember(w, r)
}
// GetDrives implements the Service interface.
func (i instrument) GetDrives(w http.ResponseWriter, r *http.Request) {
i.next.GetDrives(w, r)
+5
View File
@@ -74,6 +74,11 @@ func (l logging) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
l.next.GetGroupMembers(w, r)
}
// PostGroupMember implements the Service interface.
func (l logging) PostGroupMember(w http.ResponseWriter, r *http.Request) {
l.next.PostGroupMember(w, r)
}
// GetDrives implements the Service interface.
func (l logging) GetDrives(w http.ResponseWriter, r *http.Request) {
l.next.GetDrives(w, r)
+5 -1
View File
@@ -35,6 +35,7 @@ type Service interface {
GetGroup(http.ResponseWriter, *http.Request)
PostGroup(http.ResponseWriter, *http.Request)
GetGroupMembers(http.ResponseWriter, *http.Request)
PostGroupMember(http.ResponseWriter, *http.Request)
GetDrives(w http.ResponseWriter, r *http.Request)
}
@@ -117,7 +118,10 @@ func NewService(opts ...Option) Service {
r.Post("/", svc.PostGroup)
r.Route("/{groupID}", func(r chi.Router) {
r.Get("/", svc.GetGroup)
r.Get("/members", svc.GetGroupMembers)
r.Route("/members", func(r chi.Router) {
r.Get("/", svc.GetGroupMembers)
r.Post("/$ref", svc.PostGroupMember)
})
})
})
r.Group(func(r chi.Router) {
+5
View File
@@ -70,6 +70,11 @@ func (t tracing) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
t.next.GetGroupMembers(w, r)
}
// PostGroupMember implements the Service interface.
func (t tracing) PostGroupMember(w http.ResponseWriter, r *http.Request) {
t.next.PostGroupMember(w, r)
}
// GetDrives implements the Service interface.
func (t tracing) GetDrives(w http.ResponseWriter, r *http.Request) {
t.next.GetDrives(w, r)