mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-04 10:00:10 -05:00
Merge branch 'master' into graph-reads-space-yaml
This commit is contained in:
@@ -36,9 +36,11 @@ type Spaces struct {
|
||||
}
|
||||
|
||||
type LDAP struct {
|
||||
URI string `ocisConfig:"uri" env:"GRAPH_LDAP_URI"`
|
||||
BindDN string `ocisConfig:"bind_dn" env:"GRAPH_LDAP_BIND_DN"`
|
||||
BindPassword string `ocisConfig:"bind_password" env:"GRAPH_LDAP_BIND_PASSWORD"`
|
||||
URI string `ocisConfig:"uri" env:"GRAPH_LDAP_URI"`
|
||||
BindDN string `ocisConfig:"bind_dn" env:"GRAPH_LDAP_BIND_DN"`
|
||||
BindPassword string `ocisConfig:"bind_password" env:"GRAPH_LDAP_BIND_PASSWORD"`
|
||||
UseServerUUID bool `ocisConfig:"use_server_uuid" env:"GRAPH_LDAP_SERVER_UUID"`
|
||||
WriteEnabled bool `ocisConfig:"write_enabled" env:"GRAPH_LDAP_SERVER_WRITE_ENABLED"`
|
||||
|
||||
UserBaseDN string `ocisConfig:"user_base_dn" env:"GRAPH_LDAP_USER_BASE_DN"`
|
||||
UserSearchScope string `ocisConfig:"user_search_scope" env:"GRAPH_LDAP_USER_SCOPE"`
|
||||
|
||||
@@ -32,20 +32,22 @@ func DefaultConfig() *Config {
|
||||
URI: "ldap://localhost:9125",
|
||||
BindDN: "",
|
||||
BindPassword: "",
|
||||
UseServerUUID: false,
|
||||
WriteEnabled: false,
|
||||
UserBaseDN: "ou=users,dc=ocis,dc=test",
|
||||
UserSearchScope: "sub",
|
||||
UserFilter: "(objectClass=posixaccount)",
|
||||
UserFilter: "(objectClass=inetOrgPerson)",
|
||||
UserEmailAttribute: "mail",
|
||||
UserDisplayNameAttribute: "displayName",
|
||||
UserNameAttribute: "uid",
|
||||
// FIXME: switch this to some more widely available attribute by default
|
||||
// ideally this needs to be constant for the lifetime of a users
|
||||
UserIDAttribute: "ownclouduuid",
|
||||
UserIDAttribute: "owncloudUUID",
|
||||
GroupBaseDN: "ou=groups,dc=ocis,dc=test",
|
||||
GroupSearchScope: "sub",
|
||||
GroupFilter: "(objectclass=groupOfNames)",
|
||||
GroupNameAttribute: "cn",
|
||||
GroupIDAttribute: "cn",
|
||||
GroupIDAttribute: "owncloudUUID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@ import (
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
// CreateUser creates a given user in the identity backend.
|
||||
CreateUser(ctx context.Context, user libregraph.User) (*libregraph.User, error)
|
||||
|
||||
// DeleteUser deletes a given user, identified by username or id, from the backend
|
||||
DeleteUser(ctx context.Context, nameOrId string) error
|
||||
|
||||
// UpdateUser applies changes to given user, identified by username or id
|
||||
UpdateUser(ctx context.Context, nameOrId string, user libregraph.User) (*libregraph.User, error)
|
||||
|
||||
GetUser(ctx context.Context, nameOrId string) (*libregraph.User, error)
|
||||
GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error)
|
||||
|
||||
|
||||
@@ -20,6 +20,21 @@ type CS3 struct {
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
// CreateUser implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) CreateUser(ctx context.Context, user libregraph.User) (*libregraph.User, error) {
|
||||
return nil, errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) DeleteUser(ctx context.Context, nameOrID string) error {
|
||||
return errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
}
|
||||
|
||||
// UpdateUser implements the Backend Interface. It's currently not suported for the CS3 backend
|
||||
func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) {
|
||||
return nil, errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
}
|
||||
|
||||
func (i *CS3) GetUser(ctx context.Context, userID string) (*libregraph.User, error) {
|
||||
client, err := pool.GetGatewayServiceClient(i.Config.Address)
|
||||
if err != nil {
|
||||
|
||||
+187
-7
@@ -6,6 +6,7 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gofrs/uuid"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
|
||||
"github.com/owncloud/ocis/graph/pkg/config"
|
||||
@@ -14,6 +15,9 @@ import (
|
||||
)
|
||||
|
||||
type LDAP struct {
|
||||
useServerUUID bool
|
||||
writeEnabled bool
|
||||
|
||||
userBaseDN string
|
||||
userFilter string
|
||||
userScope int
|
||||
@@ -71,6 +75,7 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
|
||||
}
|
||||
|
||||
return &LDAP{
|
||||
useServerUUID: config.UseServerUUID,
|
||||
userBaseDN: config.UserBaseDN,
|
||||
userFilter: config.UserFilter,
|
||||
userScope: userScope,
|
||||
@@ -81,15 +86,180 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
|
||||
groupAttributeMap: gam,
|
||||
logger: logger,
|
||||
conn: lc,
|
||||
writeEnabled: config.WriteEnabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *LDAP) GetUser(ctx context.Context, userID string) (*libregraph.User, error) {
|
||||
i.logger.Debug().Str("backend", "ldap").Msg("GetUser")
|
||||
userID = ldap.EscapeFilter(userID)
|
||||
// CreateUser implements the Backend Interface. It converts the libregraph.User into an
|
||||
// LDAP User Entry (using the inetOrgPerson LDAP Objectclass) add adds that to the
|
||||
// configured LDAP server
|
||||
func (i *LDAP) CreateUser(ctx context.Context, user libregraph.User) (*libregraph.User, error) {
|
||||
if !i.writeEnabled {
|
||||
return nil, errorcode.New(errorcode.NotAllowed, "server is configured read-only")
|
||||
}
|
||||
ar := ldap.AddRequest{
|
||||
DN: fmt.Sprintf("uid=%s,%s", *user.OnPremisesSamAccountName, i.userBaseDN),
|
||||
Attributes: []ldap.Attribute{
|
||||
// inetOrgPerson requires "cn"
|
||||
{
|
||||
Type: "cn",
|
||||
Vals: []string{*user.OnPremisesSamAccountName},
|
||||
},
|
||||
{
|
||||
Type: i.userAttributeMap.mail,
|
||||
Vals: []string{*user.Mail},
|
||||
},
|
||||
{
|
||||
Type: i.userAttributeMap.userName,
|
||||
Vals: []string{*user.OnPremisesSamAccountName},
|
||||
},
|
||||
{
|
||||
Type: i.userAttributeMap.displayName,
|
||||
Vals: []string{*user.DisplayName},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
objectClasses := []string{"inetOrgPerson", "organizationalPerson", "person", "top"}
|
||||
|
||||
if user.PasswordProfile != nil && user.PasswordProfile.Password != nil {
|
||||
// TODO? This relies to the LDAP server to properly hash the password.
|
||||
// We might want to add support for the Password Modify LDAP Extended
|
||||
// Operation for servers that implement it. (Or implement client-side
|
||||
// hashing here.
|
||||
ar.Attribute("userPassword", []string{*user.PasswordProfile.Password})
|
||||
}
|
||||
if !i.useServerUUID {
|
||||
ar.Attribute("owncloudUUID", []string{uuid.Must(uuid.NewV4()).String()})
|
||||
objectClasses = append(objectClasses, "owncloud")
|
||||
}
|
||||
ar.Attribute("objectClass", objectClasses)
|
||||
|
||||
// inetOrgPerson requires "sn" to be set. Set it to the Username if
|
||||
// Surname is not set in the Request
|
||||
var sn string
|
||||
if user.Surname != nil && *user.Surname != "" {
|
||||
sn = *user.Surname
|
||||
} else {
|
||||
sn = *user.OnPremisesSamAccountName
|
||||
}
|
||||
ar.Attribute("sn", []string{sn})
|
||||
|
||||
if err := i.conn.Add(&ar); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read back user from LDAP to get the generated UUID
|
||||
e, err := i.getUserByDN(ar.DN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.createUserModelFromLDAP(e), nil
|
||||
}
|
||||
|
||||
// DeleteUser implements the Backend Interface. It permanently deletes a User identified
|
||||
// by name or id from the LDAP server
|
||||
func (i *LDAP) DeleteUser(ctx context.Context, nameOrID string) error {
|
||||
if !i.writeEnabled {
|
||||
return errorcode.New(errorcode.NotAllowed, "server is configured read-only")
|
||||
}
|
||||
e, err := i.getLDAPUserByNameOrID(nameOrID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dr := ldap.DelRequest{DN: e.DN}
|
||||
if err = i.conn.Del(&dr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUser implements the Backend Interface. It's currently not suported for the CS3 backedn
|
||||
func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) {
|
||||
if !i.writeEnabled {
|
||||
return nil, errorcode.New(errorcode.NotAllowed, "server is configured read-only")
|
||||
}
|
||||
e, err := i.getLDAPUserByNameOrID(nameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Don't allow updates of the ID
|
||||
if user.Id != nil && *user.Id != "" {
|
||||
if e.GetEqualFoldAttributeValue(i.userAttributeMap.id) != *user.Id {
|
||||
return nil, errorcode.New(errorcode.NotAllowed, "changing the UserId is not allowed")
|
||||
}
|
||||
}
|
||||
// TODO: In order to allow updating the user name we'd need to issue a ModRDN operation
|
||||
// As we currently using uid as the naming Attribute for the user entries. (Do we even
|
||||
// want to allow changing the user name?). For now just disallow it.
|
||||
if user.OnPremisesSamAccountName != nil && *user.OnPremisesSamAccountName != "" {
|
||||
if e.GetEqualFoldAttributeValue(i.userAttributeMap.userName) != *user.OnPremisesSamAccountName {
|
||||
return nil, errorcode.New(errorcode.NotSupported, "changing the user name is currently not supported")
|
||||
}
|
||||
}
|
||||
|
||||
mr := ldap.ModifyRequest{DN: e.DN}
|
||||
if user.DisplayName != nil && *user.DisplayName != "" {
|
||||
if e.GetEqualFoldAttributeValue(i.userAttributeMap.displayName) != *user.DisplayName {
|
||||
mr.Replace(i.userAttributeMap.displayName, []string{*user.DisplayName})
|
||||
}
|
||||
}
|
||||
if user.Mail != nil && *user.Mail != "" {
|
||||
if e.GetEqualFoldAttributeValue(i.userAttributeMap.mail) != *user.Mail {
|
||||
mr.Replace(i.userAttributeMap.mail, []string{*user.Mail})
|
||||
}
|
||||
}
|
||||
if user.PasswordProfile != nil && user.PasswordProfile.Password != nil && *user.PasswordProfile.Password != "" {
|
||||
// password are hashed server side there is no need to check if the new password
|
||||
// is actually different from the old one.
|
||||
mr.Replace("userPassword", []string{*user.PasswordProfile.Password})
|
||||
}
|
||||
|
||||
if err := i.conn.Modify(&mr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read back user from LDAP to get the generated UUID
|
||||
e, err = i.getUserByDN(e.DN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.createUserModelFromLDAP(e), nil
|
||||
}
|
||||
|
||||
func (i *LDAP) getUserByDN(dn string) (*ldap.Entry, error) {
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
dn, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false,
|
||||
"(objectclass=*)",
|
||||
[]string{
|
||||
i.userAttributeMap.displayName,
|
||||
i.userAttributeMap.id,
|
||||
i.userAttributeMap.mail,
|
||||
i.userAttributeMap.userName,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
i.logger.Debug().Str("backend", "ldap").Str("dn", dn).Msg("Search user by DN")
|
||||
res, err := i.conn.Search(searchRequest)
|
||||
|
||||
if err != nil {
|
||||
i.logger.Error().Err(err).Str("backend", "ldap").Str("dn", dn).Msg("Search user by DN failed")
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, err.Error())
|
||||
}
|
||||
if len(res.Entries) == 0 {
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, "not found")
|
||||
}
|
||||
|
||||
return res.Entries[0], nil
|
||||
}
|
||||
|
||||
func (i *LDAP) getLDAPUserByNameOrID(nameOrID string) (*ldap.Entry, error) {
|
||||
nameOrID = ldap.EscapeFilter(nameOrID)
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
i.userBaseDN, i.userScope, ldap.NeverDerefAliases, 1, 0, false,
|
||||
fmt.Sprintf("(&%s(|(%s=%s)(%s=%s)))", i.userFilter, i.userAttributeMap.userName, userID, i.userAttributeMap.id, userID),
|
||||
fmt.Sprintf("(&%s(|(%s=%s)(%s=%s)))", i.userFilter, i.userAttributeMap.userName, nameOrID, i.userAttributeMap.id, nameOrID),
|
||||
[]string{
|
||||
i.userAttributeMap.displayName,
|
||||
i.userAttributeMap.id,
|
||||
@@ -105,8 +275,9 @@ func (i *LDAP) GetUser(ctx context.Context, userID string) (*libregraph.User, er
|
||||
var errmsg string
|
||||
if lerr, ok := err.(*ldap.Error); ok {
|
||||
if lerr.ResultCode == ldap.LDAPResultSizeLimitExceeded {
|
||||
errmsg = fmt.Sprintf("too many results searching for user '%s'", userID)
|
||||
i.logger.Debug().Str("backend", "ldap").Err(lerr).Msg(errmsg)
|
||||
errmsg = fmt.Sprintf("too many results searching for user '%s'", nameOrID)
|
||||
i.logger.Debug().Str("backend", "ldap").Err(lerr).
|
||||
Str("user", nameOrID).Msg("too many results searching for user")
|
||||
}
|
||||
}
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, errmsg)
|
||||
@@ -115,7 +286,16 @@ func (i *LDAP) GetUser(ctx context.Context, userID string) (*libregraph.User, er
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, "not found")
|
||||
}
|
||||
|
||||
return i.createUserModelFromLDAP(res.Entries[0]), nil
|
||||
return res.Entries[0], nil
|
||||
}
|
||||
|
||||
func (i *LDAP) GetUser(ctx context.Context, nameOrID string) (*libregraph.User, error) {
|
||||
i.logger.Debug().Str("backend", "ldap").Msg("GetUser")
|
||||
e, err := i.getLDAPUserByNameOrID(nameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.createUserModelFromLDAP(e), nil
|
||||
}
|
||||
|
||||
func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) {
|
||||
|
||||
@@ -162,24 +162,49 @@ func (c ConnWithReconnect) ExternalBind() error {
|
||||
return ldap.NewError(ldap.LDAPResultNotSupported, fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
||||
func (c ConnWithReconnect) Add(*ldap.AddRequest) error {
|
||||
return ldap.NewError(ldap.LDAPResultNotSupported, fmt.Errorf("not implemented"))
|
||||
func (c ConnWithReconnect) Add(a *ldap.AddRequest) error {
|
||||
conn, err := c.GetConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.Add(a)
|
||||
}
|
||||
|
||||
func (c ConnWithReconnect) Del(*ldap.DelRequest) error {
|
||||
return ldap.NewError(ldap.LDAPResultNotSupported, fmt.Errorf("not implemented"))
|
||||
func (c ConnWithReconnect) Del(d *ldap.DelRequest) error {
|
||||
conn, err := c.GetConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.Del(d)
|
||||
}
|
||||
|
||||
func (c ConnWithReconnect) Modify(*ldap.ModifyRequest) error {
|
||||
return ldap.NewError(ldap.LDAPResultNotSupported, fmt.Errorf("not implemented"))
|
||||
func (c ConnWithReconnect) Modify(m *ldap.ModifyRequest) error {
|
||||
conn, err := c.GetConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.Modify(m)
|
||||
}
|
||||
|
||||
func (c ConnWithReconnect) ModifyDN(*ldap.ModifyDNRequest) error {
|
||||
return ldap.NewError(ldap.LDAPResultNotSupported, fmt.Errorf("not implemented"))
|
||||
func (c ConnWithReconnect) ModifyDN(m *ldap.ModifyDNRequest) error {
|
||||
conn, err := c.GetConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.ModifyDN(m)
|
||||
}
|
||||
|
||||
func (c ConnWithReconnect) ModifyWithResult(*ldap.ModifyRequest) (*ldap.ModifyResult, error) {
|
||||
return nil, ldap.NewError(ldap.LDAPResultNotSupported, fmt.Errorf("not implemented"))
|
||||
func (c ConnWithReconnect) ModifyWithResult(m *ldap.ModifyRequest) (*ldap.ModifyResult, error) {
|
||||
conn, err := c.GetConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn.ModifyWithResult(m)
|
||||
}
|
||||
|
||||
func (c ConnWithReconnect) Compare(dn, attribute, value string) (bool, error) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/CiscoM31/godata"
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
@@ -35,6 +36,14 @@ import (
|
||||
|
||||
// GetDrives implements the Service interface.
|
||||
func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
||||
// Parse the request with odata parser
|
||||
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
||||
if err != nil {
|
||||
g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
g.logger.Info().Msg("Calling GetDrives")
|
||||
ctx := r.Context()
|
||||
|
||||
@@ -61,6 +70,12 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
filters, err := generateCs3Filters(odataReq)
|
||||
if err != nil {
|
||||
g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error")
|
||||
errorcode.NotSupported.Render(w, r, http.StatusNotImplemented, err.Error())
|
||||
return
|
||||
}
|
||||
res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{
|
||||
Opaque: &types.Opaque{Map: map[string]*types.OpaqueEntry{
|
||||
"permissions": {
|
||||
@@ -68,7 +83,7 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) {
|
||||
Value: value,
|
||||
},
|
||||
}},
|
||||
// TODO add filters?
|
||||
Filters: filters,
|
||||
})
|
||||
switch {
|
||||
case err != nil:
|
||||
@@ -618,3 +633,35 @@ func canSetSpaceQuota(ctx context.Context, user *userv1beta1.User) (bool, error)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func generateCs3Filters(request *godata.GoDataRequest) ([]*storageprovider.ListStorageSpacesRequest_Filter, error) {
|
||||
var filters []*storageprovider.ListStorageSpacesRequest_Filter
|
||||
if request.Query.Filter != nil {
|
||||
if request.Query.Filter.Tree.Token.Value == "eq" {
|
||||
switch request.Query.Filter.Tree.Children[0].Token.Value {
|
||||
case "driveType":
|
||||
filter1 := &storageprovider.ListStorageSpacesRequest_Filter{
|
||||
Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
|
||||
Term: &storageprovider.ListStorageSpacesRequest_Filter_SpaceType{
|
||||
SpaceType: strings.Trim(request.Query.Filter.Tree.Children[1].Token.Value, "'"),
|
||||
},
|
||||
}
|
||||
filters = append(filters, filter1)
|
||||
case "id":
|
||||
filter2 := &storageprovider.ListStorageSpacesRequest_Filter{
|
||||
Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID,
|
||||
Term: &storageprovider.ListStorageSpacesRequest_Filter_Id{
|
||||
Id: &storageprovider.StorageSpaceId{
|
||||
OpaqueId: strings.Trim(request.Query.Filter.Tree.Children[1].Token.Value, "'"),
|
||||
},
|
||||
},
|
||||
}
|
||||
filters = append(filters, filter2)
|
||||
}
|
||||
} else {
|
||||
err := fmt.Errorf("unsupported filter operand: %s", request.Query.Filter.Tree.Token.Value)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
@@ -34,7 +34,22 @@ func (i instrument) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetUsers(w, r)
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
// GetUser implements the Service interface.
|
||||
func (i instrument) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetUser(w, r)
|
||||
}
|
||||
|
||||
// PostUser implements the Service interface.
|
||||
func (i instrument) PostUser(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.PostUser(w, r)
|
||||
}
|
||||
|
||||
// DeleteUser implements the Service interface.
|
||||
func (i instrument) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.DeleteUser(w, r)
|
||||
}
|
||||
|
||||
// PatchUser implements the Service interface.
|
||||
func (i instrument) PatchUser(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.PatchUser(w, r)
|
||||
}
|
||||
|
||||
@@ -38,3 +38,18 @@ func (l logging) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
func (l logging) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetUser(w, r)
|
||||
}
|
||||
|
||||
// PostUser implements the Service interface.
|
||||
func (l logging) PostUser(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.PostUser(w, r)
|
||||
}
|
||||
|
||||
// DeleteUser implements the Service interface.
|
||||
func (l logging) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.DeleteUser(w, r)
|
||||
}
|
||||
|
||||
// PatchUser implements the Service interface.
|
||||
func (l logging) PatchUser(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.PatchUser(w, r)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ type Service interface {
|
||||
GetMe(http.ResponseWriter, *http.Request)
|
||||
GetUsers(http.ResponseWriter, *http.Request)
|
||||
GetUser(http.ResponseWriter, *http.Request)
|
||||
PostUser(http.ResponseWriter, *http.Request)
|
||||
DeleteUser(http.ResponseWriter, *http.Request)
|
||||
PatchUser(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// NewService returns a service implementation for Service.
|
||||
@@ -69,8 +72,11 @@ func NewService(opts ...Option) Service {
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", svc.GetUsers)
|
||||
r.Post("/", svc.PostUser)
|
||||
r.Route("/{userID}", func(r chi.Router) {
|
||||
r.Get("/", svc.GetUser)
|
||||
r.Delete("/", svc.DeleteUser)
|
||||
r.Patch("/", svc.PatchUser)
|
||||
})
|
||||
})
|
||||
r.Route("/groups", func(r chi.Router) {
|
||||
|
||||
@@ -34,3 +34,18 @@ func (t tracing) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
func (t tracing) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetUser(w, r)
|
||||
}
|
||||
|
||||
// PostUser implements the Service interface.
|
||||
func (t tracing) PostUser(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.PostUser(w, r)
|
||||
}
|
||||
|
||||
// DeleteUser implements the Service interface.
|
||||
func (t tracing) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.DeleteUser(w, r)
|
||||
}
|
||||
|
||||
// PatchUser implements the Service interface.
|
||||
func (t tracing) PatchUser(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.PatchUser(w, r)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -8,9 +9,9 @@ import (
|
||||
revactx "github.com/cs3org/reva/pkg/ctx"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/owncloud/ocis/graph/pkg/identity"
|
||||
"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
|
||||
//msgraph "github.com/owncloud/open-graph-api-go" // FIXME needs OnPremisesSamAccountName, OnPremisesDomainName and AdditionalData
|
||||
)
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
@@ -32,7 +33,6 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
// TODO use cs3 api to look up user
|
||||
func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := g.identityBackend.GetUsers(r.Context(), r.URL.Query())
|
||||
if err != nil {
|
||||
@@ -47,6 +47,36 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, &listResponse{Value: users})
|
||||
}
|
||||
|
||||
func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) {
|
||||
u := libregraph.NewUser()
|
||||
err := json.NewDecoder(r.Body).Decode(u)
|
||||
if err != nil {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if isNilOrEmpty(u.DisplayName) || isNilOrEmpty(u.OnPremisesSamAccountName) || isNilOrEmpty(u.Mail) {
|
||||
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(u.Id) {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user id is a read-only attribute")
|
||||
return
|
||||
}
|
||||
|
||||
if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, u)
|
||||
}
|
||||
|
||||
// GetUser implements the Service interface.
|
||||
func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "userID")
|
||||
@@ -74,3 +104,68 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, user)
|
||||
}
|
||||
|
||||
func (g Graph) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "userID")
|
||||
userID, err := url.PathUnescape(userID)
|
||||
if err != nil {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
||||
}
|
||||
|
||||
if userID == "" {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id")
|
||||
return
|
||||
}
|
||||
|
||||
err = g.identityBackend.DeleteUser(r.Context(), userID)
|
||||
|
||||
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.StatusNoContent)
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
// PatchUser implements the Service Interface. Updates the specified attributes of an
|
||||
// ExistingUser
|
||||
func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) {
|
||||
nameOrID := chi.URLParam(r, "userID")
|
||||
nameOrID, err := url.PathUnescape(nameOrID)
|
||||
if err != nil {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
||||
}
|
||||
|
||||
if nameOrID == "" {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id")
|
||||
return
|
||||
}
|
||||
changes := libregraph.NewUser()
|
||||
err = json.NewDecoder(r.Body).Decode(changes)
|
||||
if err != nil {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes)
|
||||
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, u)
|
||||
|
||||
}
|
||||
|
||||
func isNilOrEmpty(s *string) bool {
|
||||
return s == nil || *s == ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user