Merge pull request #24 from butonic/provisinging-api-fixes

This commit is contained in:
Alex Unger
2020-07-29 15:01:21 +02:00
committed by GitHub
6 changed files with 197 additions and 16 deletions

View File

@@ -0,0 +1,6 @@
package data
// Groups holds group ids for the groups listing
type Groups struct {
Groups []string `json:"groups" xml:"groups>element"`
}

View File

@@ -9,9 +9,15 @@ type Meta struct {
ItemsPerPage string `json:"itemsperpage,omitempty" xml:"itemsperpage,omitempty"`
}
// MetaOK is the default ok response
// MetaOK is the default ok response with code 100
var MetaOK = Meta{Status: "ok", StatusCode: 100, Message: "OK"}
// MetaFailure is a failure response with code 101
var MetaFailure = Meta{Status: "", StatusCode: 101, Message: "Failure"}
// MetaInvalidInput is an error response with code 102
var MetaInvalidInput = Meta{Status: "", StatusCode: 102, Message: "Invalid Input"}
// MetaBadRequest is used for unknown errors
var MetaBadRequest = Meta{Status: "error", StatusCode: 400, Message: "Bad Request"}

View File

@@ -3,13 +3,18 @@ package data
// User holds the payload for a GetUser response
type User struct {
// TODO needs better naming, clarify if we need a userid, a username or both
UserID string `json:"userid" xml:"userid"`
UserID string `json:"id" xml:"id"`
Username string `json:"username" xml:"username"`
DisplayName string `json:"displayname" xml:"displayname"`
Email string `json:"email" xml:"email"`
Enabled bool `json:"enabled" xml:"enabled"`
}
// Users holds user ids for the user listing
type Users struct {
Users []string `json:"users" xml:"users>element"`
}
// SigningKey holds the Payload for a GetSigningKey response
type SigningKey struct {
User string `json:"user" xml:"user"`

66
pkg/service/v0/groups.go Normal file
View File

@@ -0,0 +1,66 @@
package svc
import (
"fmt"
"net/http"
"github.com/go-chi/render"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
"github.com/owncloud/ocis-ocs/pkg/service/v0/response"
)
// 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{}}))
}
// 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"))
}
// 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"))
}
// 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))
}
accSvc := o.getGroupsService()
res, err := accSvc.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)
}
render.Render(w, r, response.DataRender(&data.Groups{Groups: groups}))
}
// AddGroup adds a group
func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) {
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "not implemented"))
}
// 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"))
}
// 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"))
}

View File

@@ -6,7 +6,9 @@ import (
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/render"
"github.com/micro/go-micro/v2/client/grpc"
accounts "github.com/owncloud/ocis-accounts/pkg/proto/v0"
"github.com/owncloud/ocis-ocs/pkg/config"
ocsm "github.com/owncloud/ocis-ocs/pkg/middleware"
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
@@ -57,6 +59,18 @@ func NewService(opts ...Option) Service {
r.Get("/{userid}", svc.GetUser)
r.Put("/{userid}", svc.EditUser)
r.Delete("/{userid}", svc.DeleteUser)
r.Route("/{userid}/groups", func(r chi.Router) {
r.Get("/", svc.ListUserGroups)
r.Post("/", svc.AddToGroup)
r.Delete("/", svc.RemoveFromGroup)
})
})
r.Route("/groups", func(r chi.Router) {
r.Get("/", svc.ListGroups)
r.Post("/", svc.AddGroup)
r.Delete("/{groupid}", svc.DeleteGroup)
r.Get("/{groupid}", svc.GetGroupMembers)
})
})
r.Route("/config", func(r chi.Router) {
@@ -82,5 +96,13 @@ func (o Ocs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// NotFound uses ErrRender to always return a proper OCS payload
func (o Ocs) NotFound(w http.ResponseWriter, r *http.Request) {
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services"))
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "not found"))
}
func (o Ocs) getAccountService() accounts.AccountsService {
return accounts.NewAccountsService("com.owncloud.api.accounts", grpc.NewClient())
}
func (o Ocs) getGroupsService() accounts.GroupsService {
return accounts.NewGroupsService("com.owncloud.api.accounts", grpc.NewClient())
}

View File

@@ -3,7 +3,9 @@ package svc
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"strings"
"github.com/cs3org/reva/pkg/user"
"github.com/go-chi/chi"
@@ -33,15 +35,27 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
userid = u.Id.OpaqueId
}
accSvc := accounts.NewAccountsService("com.owncloud.api.accounts", grpc.NewClient())
accSvc := o.getAccountService()
account, err := accSvc.GetAccount(r.Context(), &accounts.GetAccountRequest{
Id: userid,
})
if err != nil {
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
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 user")
return
}
// remove password from log if it is set
if account.PasswordProfile != nil {
account.PasswordProfile.Password = ""
}
o.logger.Debug().Interface("account", account).Msg("got user")
render.Render(w, r, response.DataRender(&data.User{
UserID: account.Id, // TODO userid vs username! implications for clients if we return the userid here? -> implement graph ASAP?
Username: account.PreferredName,
@@ -60,7 +74,7 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) {
displayname := r.PostFormValue("displayname")
email := r.PostFormValue("email")
accSvc := accounts.NewAccountsService("com.owncloud.api.accounts", grpc.NewClient())
accSvc := o.getAccountService()
account, err := accSvc.CreateAccount(r.Context(), &accounts.CreateAccountRequest{
Account: &accounts.Account{
DisplayName: displayname,
@@ -75,11 +89,22 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) {
},
})
if err != nil {
render.Render(w, r, response.DataRender(data.MetaServerError))
merr := merrors.FromError(err)
if merr.Code == http.StatusBadRequest {
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
} else {
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
}
o.logger.Error().Err(err).Str("userid", userid).Msg("could not add user")
// TODO check error if account already existed
return
}
o.logger.Debug().Interface("account", account).Msg("add user: account info")
// remove password from log if it is set
if account.PasswordProfile != nil {
account.PasswordProfile.Password = ""
}
o.logger.Debug().Interface("account", account).Msg("added user")
render.Render(w, r, response.DataRender(&data.User{
UserID: account.Id,
@@ -118,26 +143,54 @@ func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) {
req.Account.DisplayName = value
req.UpdateMask = &fieldmaskpb.FieldMask{Paths: []string{"DisplayName"}}
default:
render.Render(w, r, response.DataRender(data.MetaServerError))
// https://github.com/owncloud/core/blob/24b7fa1d2604a208582055309a5638dbd9bda1d1/apps/provisioning_api/lib/Users.php#L321
render.Render(w, r, response.ErrRender(103, "unknown key '"+key+"'"))
return
}
accSvc := accounts.NewAccountsService("com.owncloud.api.accounts", grpc.NewClient())
accSvc := o.getAccountService()
account, err := accSvc.UpdateAccount(r.Context(), &req)
if err != nil {
// TODO to be compliant with the spec, check whether the user exists or not
// https://doc.owncloud.com/server/admin_manual/configuration/user/user_provisioning_api.html#edit-user
render.Render(w, r, response.DataRender(data.MetaServerError))
merr := merrors.FromError(err)
switch merr.Code {
case http.StatusNotFound:
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found"))
case http.StatusBadRequest:
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
default:
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
}
o.logger.Error().Err(err).Str("userid", req.Account.Id).Msg("could not edit user")
return
}
o.logger.Debug().Interface("account", account).Msg("update user: account info")
// remove password from log if it is set
if account.PasswordProfile != nil {
account.PasswordProfile.Password = ""
}
o.logger.Debug().Interface("account", account).Msg("updated user")
render.Render(w, r, response.DataRender(struct{}{}))
}
// DeleteUser deletes a user
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)
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", 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{}{}))
}
// GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist
@@ -212,6 +265,29 @@ func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) {
// ListUsers lists the users
func (o Ocs) ListUsers(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))
}
accSvc := o.getAccountService()
res, err := accSvc.ListAccounts(r.Context(), &accounts.ListAccountsRequest{
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
}
users := []string{}
for i := range res.Accounts {
users = append(users, res.Accounts[i].Id)
}
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services"))
render.Render(w, r, response.DataRender(&data.Users{Users: users}))
}
// escapeValue escapes all special characters in the value
func escapeValue(value string) string {
return strings.ReplaceAll(value, "'", "''")
}