diff --git a/pkg/service/v0/cs3-client.go b/pkg/cs3/client.go similarity index 62% rename from pkg/service/v0/cs3-client.go rename to pkg/cs3/client.go index 056b10e942..9eed1a3610 100644 --- a/pkg/service/v0/cs3-client.go +++ b/pkg/cs3/client.go @@ -1,4 +1,4 @@ -package svc +package cs3 import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -15,7 +15,8 @@ func newConn(endpoint string) (*grpc.ClientConn, error) { return conn, nil } -func getGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) { +// GetGatewayServiceClient returns a new cs3 gateway client +func GetGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) { conn, err := newConn(endpoint) if err != nil { return nil, err @@ -23,8 +24,3 @@ func getGatewayServiceClient(endpoint string) (gateway.GatewayAPIClient, error) return gateway.NewGatewayAPIClient(conn), nil } - -// GetClient returns a gateway client to talk to reva -func (g Graph) GetClient() (gateway.GatewayAPIClient, error) { - return getGatewayServiceClient(g.config.Reva.Address) -} diff --git a/pkg/service/v0/drive.go b/pkg/service/v0/drives.go similarity index 100% rename from pkg/service/v0/drive.go rename to pkg/service/v0/drives.go diff --git a/pkg/service/v0/graph.go b/pkg/service/v0/graph.go index e6ddf894b2..35ea17513d 100644 --- a/pkg/service/v0/graph.go +++ b/pkg/service/v0/graph.go @@ -1,20 +1,13 @@ package svc import ( - "context" - "errors" - "fmt" "net/http" - "github.com/owncloud/ocis-graph/pkg/service/v0/errorcode" - + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/go-chi/chi" - "github.com/go-chi/render" "github.com/owncloud/ocis-graph/pkg/config" + "github.com/owncloud/ocis-graph/pkg/cs3" "github.com/owncloud/ocis-pkg/v2/log" - "github.com/owncloud/ocis-pkg/v2/oidc" - msgraph "github.com/yaegashi/msgraph.go/v1.0" - "gopkg.in/ldap.v3" ) // Graph defines implements the business logic for Service. @@ -29,238 +22,9 @@ func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) { g.mux.ServeHTTP(w, r) } -// UserCtx middleware is used to load an User object from -// the URL parameters passed through as the request. In case -// the User could not be found, we stop here and return a 404. -func (g Graph) UserCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var user *ldap.Entry - var err error - - userID := chi.URLParam(r, "userID") - if userID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest) - return - } - filter := fmt.Sprintf("(entryuuid=%s)", userID) - user, err = g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter) - if err != nil { - g.logger.Info().Err(err).Msgf("Failed to read user %s", userID) - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound) - return - } - - ctx := context.WithValue(r.Context(), userIDKey, user) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -// GroupCtx middleware is used to load an User object from -// the URL parameters passed through as the request. In case -// the User could not be found, we stop here and return a 404. -func (g Graph) GroupCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - groupID := chi.URLParam(r, "groupID") - if groupID == "" { - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest) - return - } - filter := fmt.Sprintf("(entryuuid=%s)", groupID) - group, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNGroups, filter) - if err != nil { - g.logger.Info().Err(err).Msgf("Failed to read group %s", groupID) - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound) - return - } - - ctx := context.WithValue(r.Context(), groupIDKey, group) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -// GetMe implements the Service interface. -func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { - claims := oidc.FromContext(r.Context()) - g.logger.Info().Interface("Claims", claims).Msg("Claims in /me") - - filter := fmt.Sprintf("(uid=%s)", claims.PreferredUsername) - user, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter) - if err != nil { - g.logger.Info().Err(err).Msgf("Failed to read user %s", claims.PreferredUsername) - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound) - return - } - - me := createUserModelFromLDAP(user) - - render.Status(r, http.StatusOK) - render.JSON(w, r, me) -} - -// GetUsers implements the Service interface. -func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { - con, err := g.initLdap() - if err != nil { - g.logger.Error().Err(err).Msg("Failed to initialize ldap") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) - return - } - - result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNUsers) - - if err != nil { - g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) - return - } - - var users []*msgraph.User - - for _, user := range result.Entries { - users = append( - users, - createUserModelFromLDAP( - user, - ), - ) - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, &listResponse{Value: users}) -} - -// GetUser implements the Service interface. -func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { - user := r.Context().Value(userIDKey).(*ldap.Entry) - - render.Status(r, http.StatusOK) - render.JSON(w, r, createUserModelFromLDAP(user)) -} - -// GetGroups implements the Service interface. -func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { - con, err := g.initLdap() - if err != nil { - g.logger.Error().Err(err).Msg("Failed to initialize ldap") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) - return - } - - result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNGroups) - - if err != nil { - g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) - return - } - - var groups []*msgraph.Group - - for _, group := range result.Entries { - groups = append( - groups, - createGroupModelFromLDAP( - group, - ), - ) - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, &listResponse{Value: groups}) -} - -// GetGroup implements the Service interface. -func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { - group := r.Context().Value(groupIDKey).(*ldap.Entry) - - render.Status(r, http.StatusOK) - render.JSON(w, r, createGroupModelFromLDAP(group)) -} - -func (g Graph) ldapGetSingleEntry(baseDn string, filter string) (*ldap.Entry, error) { - conn, err := g.initLdap() - if err != nil { - return nil, err - } - result, err := g.ldapSearch(conn, filter, baseDn) - if err != nil { - return nil, err - } - if len(result.Entries) == 0 { - return nil, errors.New("resource not found") - } - return result.Entries[0], nil -} - -func (g Graph) initLdap() (*ldap.Conn, error) { - g.logger.Info().Msgf("Dialing ldap %s://%s", g.config.Ldap.Network, g.config.Ldap.Address) - con, err := ldap.Dial(g.config.Ldap.Network, g.config.Ldap.Address) - - if err != nil { - return nil, err - } - - if err := con.Bind(g.config.Ldap.UserName, g.config.Ldap.Password); err != nil { - return nil, err - } - return con, nil -} - -func (g Graph) ldapSearch(con *ldap.Conn, filter string, baseDN string) (*ldap.SearchResult, error) { - search := ldap.NewSearchRequest( - baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - filter, - []string{"dn", - "uid", - "givenname", - "mail", - "displayname", - "entryuuid", - "sn", - "cn", - }, - nil, - ) - - return con.Search(search) -} - -func createUserModelFromLDAP(entry *ldap.Entry) *msgraph.User { - displayName := entry.GetAttributeValue("displayname") - givenName := entry.GetAttributeValue("givenname") - mail := entry.GetAttributeValue("mail") - surName := entry.GetAttributeValue("sn") - id := entry.GetAttributeValue("entryuuid") - return &msgraph.User{ - DisplayName: &displayName, - GivenName: &givenName, - Surname: &surName, - Mail: &mail, - DirectoryObject: msgraph.DirectoryObject{ - Entity: msgraph.Entity{ - ID: &id, - }, - }, - } -} - -func createGroupModelFromLDAP(entry *ldap.Entry) *msgraph.Group { - id := entry.GetAttributeValue("entryuuid") - displayName := entry.GetAttributeValue("cn") - - return &msgraph.Group{ - DisplayName: &displayName, - DirectoryObject: msgraph.DirectoryObject{ - Entity: msgraph.Entity{ - ID: &id, - }, - }, - } +// GetClient returns a gateway client to talk to reva +func (g Graph) GetClient() (gateway.GatewayAPIClient, error) { + return cs3.GetGatewayServiceClient(g.config.Reva.Address) } // The key type is unexported to prevent collisions with context keys defined in diff --git a/pkg/service/v0/groups.go b/pkg/service/v0/groups.go new file mode 100644 index 0000000000..1cbdd4abad --- /dev/null +++ b/pkg/service/v0/groups.go @@ -0,0 +1,77 @@ +package svc + +import ( + "context" + "fmt" + "net/http" + + "github.com/owncloud/ocis-graph/pkg/service/v0/errorcode" + + "github.com/go-chi/chi" + "github.com/go-chi/render" + msgraph "github.com/yaegashi/msgraph.go/v1.0" + "gopkg.in/ldap.v3" +) + +// GroupCtx middleware is used to load an User object from +// the URL parameters passed through as the request. In case +// the User could not be found, we stop here and return a 404. +func (g Graph) GroupCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + groupID := chi.URLParam(r, "groupID") + if groupID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest) + return + } + filter := fmt.Sprintf("(entryuuid=%s)", groupID) + group, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNGroups, filter) + if err != nil { + g.logger.Info().Err(err).Msgf("Failed to read group %s", groupID) + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound) + return + } + + ctx := context.WithValue(r.Context(), groupIDKey, group) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// GetGroups implements the Service interface. +func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { + con, err := g.initLdap() + if err != nil { + g.logger.Error().Err(err).Msg("Failed to initialize ldap") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) + return + } + + result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNGroups) + + if err != nil { + g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) + return + } + + var groups []*msgraph.Group + + for _, group := range result.Entries { + groups = append( + groups, + createGroupModelFromLDAP( + group, + ), + ) + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, &listResponse{Value: groups}) +} + +// GetGroup implements the Service interface. +func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { + group := r.Context().Value(groupIDKey).(*ldap.Entry) + + render.Status(r, http.StatusOK) + render.JSON(w, r, createGroupModelFromLDAP(group)) +} diff --git a/pkg/service/v0/ldap.go b/pkg/service/v0/ldap.go new file mode 100644 index 0000000000..90c2145027 --- /dev/null +++ b/pkg/service/v0/ldap.go @@ -0,0 +1,94 @@ +package svc + +import ( + "errors" + + msgraph "github.com/yaegashi/msgraph.go/v1.0" + "gopkg.in/ldap.v3" +) + +func (g Graph) ldapGetSingleEntry(baseDn string, filter string) (*ldap.Entry, error) { + conn, err := g.initLdap() + if err != nil { + return nil, err + } + result, err := g.ldapSearch(conn, filter, baseDn) + if err != nil { + return nil, err + } + if len(result.Entries) == 0 { + return nil, errors.New("resource not found") + } + return result.Entries[0], nil +} + +func (g Graph) initLdap() (*ldap.Conn, error) { + g.logger.Info().Msgf("Dialing ldap %s://%s", g.config.Ldap.Network, g.config.Ldap.Address) + con, err := ldap.Dial(g.config.Ldap.Network, g.config.Ldap.Address) + + if err != nil { + return nil, err + } + + if err := con.Bind(g.config.Ldap.UserName, g.config.Ldap.Password); err != nil { + return nil, err + } + return con, nil +} + +func (g Graph) ldapSearch(con *ldap.Conn, filter string, baseDN string) (*ldap.SearchResult, error) { + search := ldap.NewSearchRequest( + baseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + filter, + []string{"dn", + "uid", + "givenname", + "mail", + "displayname", + "entryuuid", + "sn", + "cn", + }, + nil, + ) + + return con.Search(search) +} + +func createUserModelFromLDAP(entry *ldap.Entry) *msgraph.User { + displayName := entry.GetAttributeValue("displayname") + givenName := entry.GetAttributeValue("givenname") + mail := entry.GetAttributeValue("mail") + surName := entry.GetAttributeValue("sn") + id := entry.GetAttributeValue("entryuuid") + return &msgraph.User{ + DisplayName: &displayName, + GivenName: &givenName, + Surname: &surName, + Mail: &mail, + DirectoryObject: msgraph.DirectoryObject{ + Entity: msgraph.Entity{ + ID: &id, + }, + }, + } +} + +func createGroupModelFromLDAP(entry *ldap.Entry) *msgraph.Group { + id := entry.GetAttributeValue("entryuuid") + displayName := entry.GetAttributeValue("cn") + + return &msgraph.Group{ + DisplayName: &displayName, + DirectoryObject: msgraph.DirectoryObject{ + Entity: msgraph.Entity{ + ID: &id, + }, + }, + } +} diff --git a/pkg/service/v0/users.go b/pkg/service/v0/users.go new file mode 100644 index 0000000000..d0259250b9 --- /dev/null +++ b/pkg/service/v0/users.go @@ -0,0 +1,100 @@ +package svc + +import ( + "context" + "fmt" + "net/http" + + "github.com/owncloud/ocis-graph/pkg/service/v0/errorcode" + + "github.com/go-chi/chi" + "github.com/go-chi/render" + "github.com/owncloud/ocis-pkg/v2/oidc" + msgraph "github.com/yaegashi/msgraph.go/v1.0" + "gopkg.in/ldap.v3" +) + +// UserCtx middleware is used to load an User object from +// the URL parameters passed through as the request. In case +// the User could not be found, we stop here and return a 404. +func (g Graph) UserCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var user *ldap.Entry + var err error + + userID := chi.URLParam(r, "userID") + if userID == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest) + return + } + filter := fmt.Sprintf("(entryuuid=%s)", userID) + user, err = g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter) + if err != nil { + g.logger.Info().Err(err).Msgf("Failed to read user %s", userID) + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound) + return + } + + ctx := context.WithValue(r.Context(), userIDKey, user) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// GetMe implements the Service interface. +func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { + claims := oidc.FromContext(r.Context()) + g.logger.Info().Interface("Claims", claims).Msg("Claims in /me") + + filter := fmt.Sprintf("(uid=%s)", claims.PreferredUsername) + user, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter) + if err != nil { + g.logger.Info().Err(err).Msgf("Failed to read user %s", claims.PreferredUsername) + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound) + return + } + + me := createUserModelFromLDAP(user) + + render.Status(r, http.StatusOK) + render.JSON(w, r, me) +} + +// GetUsers implements the Service interface. +func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { + con, err := g.initLdap() + if err != nil { + g.logger.Error().Err(err).Msg("Failed to initialize ldap") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) + return + } + + result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNUsers) + + if err != nil { + g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError) + return + } + + var users []*msgraph.User + + for _, user := range result.Entries { + users = append( + users, + createUserModelFromLDAP( + user, + ), + ) + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, &listResponse{Value: users}) +} + +// GetUser implements the Service interface. +func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(userIDKey).(*ldap.Entry) + + render.Status(r, http.StatusOK) + render.JSON(w, r, createUserModelFromLDAP(user)) +}