diff --git a/pkg/service/v0/graph.go b/pkg/service/v0/graph.go new file mode 100644 index 000000000..a7f871f8c --- /dev/null +++ b/pkg/service/v0/graph.go @@ -0,0 +1,203 @@ +package svc + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/go-chi/render" + "github.com/owncloud/ocis-graph/pkg/config" + "github.com/owncloud/ocis-pkg/log" + msgraph "github.com/yaegashi/msgraph.go/v1.0" + ldap "gopkg.in/ldap.v3" +) + +// Graph defines implements the business logic for Service. +type Graph struct { + config *config.Config + mux *chi.Mux + logger *log.Logger +} + +// ServeHTTP implements the Service interface. +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 + + if userID := chi.URLParam(r, "userID"); userID != "" { + user, err = g.ldapGetUser(userID) + } else { + // TODO: we should not give this error out to users + // http.Error(w, err.Error(), http.StatusInternalServerError) + render.Status(r, http.StatusNotFound) + return + } + if err != nil { + g.logger.Info().Msgf("error reading user: %s", err.Error()) + render.Status(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) { + me := createUserModel( + "Alice", + "1234-5678-9000-000", + ) + + resp, err := json.Marshal(me) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, resp) +} + +// GetUsers implements the Service interface. +func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { + con, err := g.initLdap() + if err != nil { + // TODO: we should not give this error out to users + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + result, err := g.ldapSearch(con, "(objectclass=*)") + + if err != nil { + // TODO: we should not give this error out to users + http.Error(w, err.Error(), 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, 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)) +} + +func (g Graph) ldapGetUser(userID string) (*ldap.Entry, error) { + conn, err := g.initLdap() + if err != nil { + return nil, err + } + filter := fmt.Sprintf("(entryuuid=%s)", userID) + result, err := g.ldapSearch(conn, filter) + if err != nil { + return nil, err + } + if len(result.Entries) == 0 { + return nil, errors.New("user not found") + } + return result.Entries[0], nil +} + +func (g Graph) initLdap() (*ldap.Conn, error) { + g.logger.Info().Msg("Dailing ldap.... ") + con, err := ldap.Dial("tcp", "localhost:10389") + + if err != nil { + return nil, err + } + + if err := con.Bind("cn=admin,dc=example,dc=org", "admin"); err != nil { + return nil, err + } + return con, nil +} + +func (g Graph) ldapSearch(con *ldap.Conn, filter string) (*ldap.SearchResult, error) { + search := ldap.NewSearchRequest( + "ou=users,dc=example,dc=org", + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + filter, + []string{"dn", + "uid", + "givenname", + "mail", + "displayname", + "entryuuid", + "sn", + }, + nil, + ) + + return con.Search(search) +} + +func createUserModel(displayName string, id string) *msgraph.User { + return &msgraph.User{ + DisplayName: &displayName, + GivenName: &displayName, + DirectoryObject: msgraph.DirectoryObject{ + Entity: msgraph.Entity{ + ID: &id, + }, + }, + } +} + +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, + }, + }, + } +} + +// The key type is unexported to prevent collisions with context keys defined in +// other packages. +type key int + +const userIDKey key = 0 diff --git a/pkg/service/v0/instrument.go b/pkg/service/v0/instrument.go index 2e2488c5f..b427a0c51 100644 --- a/pkg/service/v0/instrument.go +++ b/pkg/service/v0/instrument.go @@ -24,12 +24,17 @@ func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { i.next.ServeHTTP(w, r) } -// Me implements the Service interface. -func (i instrument) Me(w http.ResponseWriter, r *http.Request) { - i.next.Me(w, r) +// GetMe implements the Service interface. +func (i instrument) GetMe(w http.ResponseWriter, r *http.Request) { + i.next.GetMe(w, r) } -// Users implements the Service interface. -func (i instrument) Users(w http.ResponseWriter, r *http.Request) { - i.next.Users(w, r) +// GetUsers implements the Service interface. +func (i instrument) GetUsers(w http.ResponseWriter, r *http.Request) { + i.next.GetUsers(w, r) +} + +// GetUsers implements the Service interface. +func (i instrument) GetUser(w http.ResponseWriter, r *http.Request) { + i.next.GetUser(w, r) } diff --git a/pkg/service/v0/logging.go b/pkg/service/v0/logging.go index 7b7944395..a44cb9552 100644 --- a/pkg/service/v0/logging.go +++ b/pkg/service/v0/logging.go @@ -24,12 +24,17 @@ func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) { l.next.ServeHTTP(w, r) } -// Me implements the Service interface. -func (l logging) Me(w http.ResponseWriter, r *http.Request) { - l.next.Me(w, r) +// GetMe implements the Service interface. +func (l logging) GetMe(w http.ResponseWriter, r *http.Request) { + l.next.GetMe(w, r) } -// Users implements the Service interface. -func (l logging) Users(w http.ResponseWriter, r *http.Request) { - l.next.Users(w, r) +// GetUsers implements the Service interface. +func (l logging) GetUsers(w http.ResponseWriter, r *http.Request) { + l.next.GetUsers(w, r) +} + +// GetUser implements the Service interface. +func (l logging) GetUser(w http.ResponseWriter, r *http.Request) { + l.next.GetUser(w, r) } diff --git a/pkg/service/v0/service.go b/pkg/service/v0/service.go index 97adfa3a9..a2137ff6e 100644 --- a/pkg/service/v0/service.go +++ b/pkg/service/v0/service.go @@ -1,21 +1,17 @@ package svc import ( - "encoding/json" "net/http" "github.com/go-chi/chi" - "github.com/go-chi/render" - "github.com/owncloud/ocis-graph/pkg/config" - msgraph "github.com/yaegashi/msgraph.go/v1.0" - ldap "gopkg.in/ldap.v3" ) // Service defines the extension handlers. type Service interface { ServeHTTP(http.ResponseWriter, *http.Request) - Me(http.ResponseWriter, *http.Request) - Users(http.ResponseWriter, *http.Request) + GetMe(http.ResponseWriter, *http.Request) + GetUsers(http.ResponseWriter, *http.Request) + GetUser(http.ResponseWriter, *http.Request) } // NewService returns a service implementation for Service. @@ -28,109 +24,19 @@ func NewService(opts ...Option) Service { svc := Graph{ config: options.Config, mux: m, + logger: &options.Logger, } - m.HandleFunc("/v1.0/me", svc.Me) - m.HandleFunc("/v1.0/users", svc.Users) + m.Route("/v1.0", func(r chi.Router) { + r.Get("/me", svc.GetMe) + r.Route("/users", func(r chi.Router) { + r.Get("/", svc.GetUsers) + r.Route("/{userID}", func(r chi.Router) { + r.Use(svc.UserCtx) + r.Get("/", svc.GetUser) + }) + }) + }) return svc } - -// Graph defines implements the business logic for Service. -type Graph struct { - config *config.Config - mux *chi.Mux -} - -// ServeHTTP implements the Service interface. -func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) { - g.mux.ServeHTTP(w, r) -} - -// Me implements the Service interface. -func (g Graph) Me(w http.ResponseWriter, r *http.Request) { - me := createUserModel( - "Alice", - "1234-5678-9000-000", - ) - - resp, err := json.Marshal(me) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, resp) -} - -// Users implements the Service interface. -func (g Graph) Users(w http.ResponseWriter, r *http.Request) { - con, err := ldap.Dial("tcp", "localhost:10389") - - if err != nil { - // TODO: we should not give this error out to users - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := con.Bind("cn=admin,dc=example,dc=org", "admin"); err != nil { - // TODO: we should not give this error out to users - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - search := ldap.NewSearchRequest( - "ou=groups,dc=example,dc=org", - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(objectclass=*)", - []string{ - "dn", - "uuid", - "uid", - "givenName", - "mail", - }, - nil, - ) - - result, err := con.Search(search) - - if err != nil { - // TODO: we should not give this error out to users - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - users := make([]*msgraph.User, len(result.Entries)) - - for _, user := range result.Entries { - users = append( - users, - createUserModel( - user.DN, - "1234-5678-9000-000", - ), - ) - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, users) -} - -func createUserModel(displayName string, id string) *msgraph.User { - return &msgraph.User{ - DisplayName: &displayName, - GivenName: &displayName, - DirectoryObject: msgraph.DirectoryObject{ - Entity: msgraph.Entity{ - ID: &id, - }, - }, - } -} diff --git a/pkg/service/v0/tracing.go b/pkg/service/v0/tracing.go index 423c492b7..64cbc9708 100644 --- a/pkg/service/v0/tracing.go +++ b/pkg/service/v0/tracing.go @@ -20,12 +20,17 @@ func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.next.ServeHTTP(w, r) } -// Me implements the Service interface. -func (t tracing) Me(w http.ResponseWriter, r *http.Request) { - t.next.Me(w, r) +// GetMe implements the Service interface. +func (t tracing) GetMe(w http.ResponseWriter, r *http.Request) { + t.next.GetMe(w, r) } -// Users implements the Service interface. -func (t tracing) Users(w http.ResponseWriter, r *http.Request) { - t.next.Users(w, r) +// GetUsers implements the Service interface. +func (t tracing) GetUsers(w http.ResponseWriter, r *http.Request) { + t.next.GetUsers(w, r) +} + +// GetUser implements the Service interface. +func (t tracing) GetUser(w http.ResponseWriter, r *http.Request) { + t.next.GetUser(w, r) }