From a21ff4592bd09a4c90341e89cc654dc1202c59a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 29 Jul 2020 16:26:02 +0200 Subject: [PATCH] implement more group management endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/groups-provisioning.md | 5 + pkg/service/v0/groups.go | 119 ++++++++++++++++++-- pkg/service/v0/users.go | 38 +++++-- 3 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 changelog/unreleased/groups-provisioning.md diff --git a/changelog/unreleased/groups-provisioning.md b/changelog/unreleased/groups-provisioning.md new file mode 100644 index 0000000..b33b9d0 --- /dev/null +++ b/changelog/unreleased/groups-provisioning.md @@ -0,0 +1,5 @@ +Enhancement: Add Group management for OCS Povisioning API + +We added support for the group management related set of API calls of the provisioning API. [Reference](https://doc.owncloud.com/server/admin_manual/configuration/user/user_provisioning_api.html) + +https://github.com/owncloud/ocis-ocs/pull/25 diff --git a/pkg/service/v0/groups.go b/pkg/service/v0/groups.go index 4f89a81..edb73cf 100644 --- a/pkg/service/v0/groups.go +++ b/pkg/service/v0/groups.go @@ -4,7 +4,9 @@ import ( "fmt" "net/http" + "github.com/go-chi/chi" "github.com/go-chi/render" + merrors "github.com/micro/go-micro/v2/errors" accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0" "github.com/owncloud/ocis-ocs/pkg/service/v0/data" @@ -13,17 +15,78 @@ import ( // ListUserGroups lists a users groups func (o Ocs) ListUserGroups(w http.ResponseWriter, r *http.Request) { - render.Render(w, r, response.DataRender(&data.Groups{Groups: []string{}})) + userid := chi.URLParam(r, "userid") + + account, err := o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{Id: userid}) + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found")) + } else { + render.Render(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 := []string{} + for i := range account.MemberOf { + groups = append(groups, account.MemberOf[i].Id) + } + + o.logger.Error().Err(err).Int("count", len(groups)).Str("userid", userid).Msg("listing groups for user") + render.Render(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) { - render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "not implemented")) + userid := chi.URLParam(r, "userid") + groupid := r.URL.Query().Get("groupid") + + _, err := o.getGroupsService().AddMember(r.Context(), &accounts.AddMemberRequest{ + AccountId: userid, + GroupId: groupid, + }) + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found")) + } else { + render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + o.logger.Error().Err(err).Str("userid", userid).Str("groupid", groupid).Msg("could not add user to group") + return + } + + o.logger.Debug().Str("userid", userid).Str("groupid", groupid).Msg("added user to group") + render.Render(w, r, response.DataRender(struct{}{})) } // RemoveFromGroup removes a user from a group func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) { - render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "not implemented")) + userid := chi.URLParam(r, "userid") + groupid := r.URL.Query().Get("groupid") + + _, err := o.getGroupsService().RemoveMember(r.Context(), &accounts.RemoveMemberRequest{ + AccountId: userid, + GroupId: groupid, + }) + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found")) + } else { + render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + o.logger.Error().Err(err).Str("userid", userid).Str("groupid", groupid).Msg("could not remove user from group") + return + } + + o.logger.Debug().Str("userid", userid).Str("groupid", groupid).Msg("removed user from group") + render.Render(w, r, response.DataRender(struct{}{})) } // ListGroups lists all groups @@ -33,15 +96,17 @@ func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) { if search != "" { query = fmt.Sprintf("id eq '%s' or on_premises_sam_account_name eq '%s'", escapeValue(search), escapeValue(search)) } - accSvc := o.getGroupsService() - res, err := accSvc.ListGroups(r.Context(), &accounts.ListGroupsRequest{ + + res, err := o.getGroupsService().ListGroups(r.Context(), &accounts.ListGroupsRequest{ Query: query, }) + if err != nil { o.logger.Err(err).Msg("could not list users") render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not list users")) return } + groups := []string{} for i := range res.Groups { groups = append(groups, res.Groups[i].Id) @@ -57,10 +122,50 @@ func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) { // DeleteGroup deletes a group func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) { - render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "not implemented")) + groupid := chi.URLParam(r, "groupid") + + _, err := o.getGroupsService().DeleteGroup(r.Context(), &accounts.DeleteGroupRequest{ + Id: groupid, + }) + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested group could not be found")) + } else { + render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + o.logger.Error().Err(err).Str("groupid", groupid).Msg("could not remove group") + return + } + + o.logger.Debug().Str("groupid", groupid).Msg("removed group") + render.Render(w, r, response.DataRender(struct{}{})) } // GetGroupMembers lists all members of a group func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) { - render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "not implemented")) + + groupid := chi.URLParam(r, "groupid") + + res, err := o.getGroupsService().ListMembers(r.Context(), &accounts.ListMembersRequest{Id: groupid}) + + if err != nil { + merr := merrors.FromError(err) + if merr.Code == http.StatusNotFound { + render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested group could not be found")) + } else { + render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error())) + } + o.logger.Error().Err(err).Str("groupid", groupid).Msg("could not get list of members") + return + } + + members := []string{} + for i := range res.Members { + members = append(members, res.Members[i].Id) + } + + o.logger.Error().Err(err).Int("count", len(members)).Str("groupid", groupid).Msg("listing group members") + render.Render(w, r, response.DataRender(&data.Users{Users: members})) } diff --git a/pkg/service/v0/users.go b/pkg/service/v0/users.go index 4348474..b12d553 100644 --- a/pkg/service/v0/users.go +++ b/pkg/service/v0/users.go @@ -22,7 +22,7 @@ import ( // GetUser returns the currently logged in user func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { - // TODO this endpoint needs authentication + // TODO this endpoint needs authentication using the roles and permissions userid := chi.URLParam(r, "userid") if userid == "" { @@ -35,8 +35,7 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { userid = u.Id.OpaqueId } - accSvc := o.getAccountService() - account, err := accSvc.GetAccount(r.Context(), &accounts.GetAccountRequest{ + account, err := o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{ Id: userid, }) if err != nil { @@ -67,15 +66,26 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) { // AddUser creates a new user account func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) { - // TODO this endpoint needs authentication + // TODO this endpoint needs authentication using the roles and permissions userid := r.PostFormValue("userid") password := r.PostFormValue("password") username := r.PostFormValue("username") displayname := r.PostFormValue("displayname") email := r.PostFormValue("email") - accSvc := o.getAccountService() - account, err := accSvc.CreateAccount(r.Context(), &accounts.CreateAccountRequest{ + // fallbacks + /* TODO decide if we want to make these fallbacks. Keep in mind: + - ocis requires a username and email + - the username should really be different from the userid + if username == "" { + username = userid + } + if displayname == "" { + displayname = username + } + */ + + account, err := o.getAccountService().CreateAccount(r.Context(), &accounts.CreateAccountRequest{ Account: &accounts.Account{ DisplayName: displayname, PreferredName: username, @@ -148,8 +158,7 @@ func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { return } - accSvc := o.getAccountService() - account, err := accSvc.UpdateAccount(r.Context(), &req) + account, err := o.getAccountService().UpdateAccount(r.Context(), &req) if err != nil { merr := merrors.FromError(err) switch merr.Code { @@ -168,6 +177,7 @@ func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) { if account.PasswordProfile != nil { account.PasswordProfile.Password = "" } + o.logger.Debug().Interface("account", account).Msg("updated user") render.Render(w, r, response.DataRender(struct{}{})) } @@ -177,8 +187,8 @@ func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { req := accounts.DeleteAccountRequest{ Id: chi.URLParam(r, "userid"), } - accSvc := o.getAccountService() - _, err := accSvc.DeleteAccount(r.Context(), &req) + + _, err := o.getAccountService().DeleteAccount(r.Context(), &req) if err != nil { merr := merrors.FromError(err) if merr.Code == http.StatusNotFound { @@ -189,6 +199,7 @@ func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) { o.logger.Error().Err(err).Str("userid", req.Id).Msg("could not delete user") return } + o.logger.Debug().Str("userid", req.Id).Msg("deleted user") render.Render(w, r, response.DataRender(struct{}{})) } @@ -203,6 +214,7 @@ func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) { render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context")) return } + c := storepb.NewStoreService("com.owncloud.api.store", grpc.NewClient()) res, err := c.Read(r.Context(), &storepb.ReadRequest{ Options: &storepb.ReadOptions{ @@ -251,6 +263,7 @@ func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) { // TODO Expiry? }, }) + if err != nil { //o.logger.Error().Err(err).Msg("error writing key") render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not persist signing key")) @@ -270,8 +283,8 @@ func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) { if search != "" { query = fmt.Sprintf("id eq '%s' or on_premises_sam_account_name eq '%s'", escapeValue(search), escapeValue(search)) } - accSvc := o.getAccountService() - res, err := accSvc.ListAccounts(r.Context(), &accounts.ListAccountsRequest{ + + res, err := o.getAccountService().ListAccounts(r.Context(), &accounts.ListAccountsRequest{ Query: query, }) if err != nil { @@ -279,6 +292,7 @@ func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) { render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not list users")) return } + users := []string{} for i := range res.Accounts { users = append(users, res.Accounts[i].Id)