From 2a54b647f101f6e9bf249f5dff4d1b2ac0f03a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 9 Jul 2021 22:20:30 +0000 Subject: [PATCH] get rid of ldap and oidc, refactor error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- graph/go.mod | 2 +- graph/go.sum | 4 - graph/pkg/config/config.go | 39 ++--- graph/pkg/flagset/flagset.go | 73 +--------- graph/pkg/server/http/server.go | 1 + graph/pkg/service/v0/drives.go | 102 ++++++++----- graph/pkg/service/v0/errorcode/errorcode.go | 20 ++- graph/pkg/service/v0/graph.go | 6 +- graph/pkg/service/v0/groups.go | 110 ++++++++++---- graph/pkg/service/v0/ldap.go | 94 ------------ graph/pkg/service/v0/users.go | 154 ++++++++++++-------- 11 files changed, 269 insertions(+), 336 deletions(-) delete mode 100644 graph/pkg/service/v0/ldap.go diff --git a/graph/go.mod b/graph/go.mod index ac33ce4eb9..952ae74b9e 100644 --- a/graph/go.mod +++ b/graph/go.mod @@ -6,12 +6,12 @@ require ( contrib.go.opencensus.io/exporter/jaeger v0.2.1 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/zipkin v0.1.2 + github.com/ascarter/requestid v0.0.0-20170313220838-5b76ab3d4aee github.com/asim/go-micro/v3 v3.5.1-0.20210217182006-0f0ace1a44a9 github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f github.com/cs3org/reva v1.10.0 github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/render v1.0.1 - github.com/go-ldap/ldap/v3 v3.3.0 github.com/micro/cli/v2 v2.1.2 github.com/oklog/run v1.1.0 github.com/openzipkin/zipkin-go v0.2.5 diff --git a/graph/go.sum b/graph/go.sum index 1bb29729ac..c2f618890a 100644 --- a/graph/go.sum +++ b/graph/go.sum @@ -305,7 +305,6 @@ github.com/cs3org/reva v1.6.1-0.20210329145723-ed244aac4ddc/go.mod h1:exwJqEJ8lV github.com/cs3org/reva v1.10.0 h1:8sne7z4pe+9rkGP3ZX2i3Sx1FMQ8hhBs5QCb4VVvZJI= github.com/cs3org/reva v1.10.0/go.mod h1:4bpcovnx3EAetafPIp4Fia1GkFvjFDkztacmCWI7cN0= github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= -github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -917,7 +916,6 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= @@ -1540,7 +1538,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/thejerf/suture/v4 v4.0.0 h1:GX3X+1Qaewtj9flL2wgoTBfLA5NcmrCY39TJRpPbUrI= github.com/thejerf/suture/v4 v4.0.0/go.mod h1:g0e8vwskm9tI0jRjxrnA6lSr0q6OfPdWJVX7G5bVWRs= @@ -2199,7 +2196,6 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/grpc/examples v0.0.0-20210712234202-ebfe3be62a82/go.mod h1:bF8wuZSAZTcbF7ZPKrDI/qY52toTP/yxLpRRY4Eu9Js= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/graph/pkg/config/config.go b/graph/pkg/config/config.go index ca1a5f934c..e678250f54 100644 --- a/graph/pkg/config/config.go +++ b/graph/pkg/config/config.go @@ -40,24 +40,6 @@ type Tracing struct { Service string } -// Ldap defined the available LDAP configuration. -type Ldap struct { - Network string - Address string - UserName string - Password string - BaseDNUsers string - BaseDNGroups string -} - -// OpenIDConnect defined the available OpenID Connect configuration. -type OpenIDConnect struct { - Endpoint string - Realm string - SigningAlgs []string - Insecure bool -} - // Reva defines all available REVA configuration. type Reva struct { Address string @@ -74,18 +56,15 @@ type Spaces struct { // Config combines all available configuration parts. type Config struct { - File string - WebdavNamespace string - Log Log - Debug Debug - HTTP HTTP - Server Server - Tracing Tracing - Ldap Ldap - OpenIDConnect OpenIDConnect - Reva Reva - TokenManager TokenManager - Spaces Spaces + File string + Log Log + Debug Debug + HTTP HTTP + Server Server + Tracing Tracing + Reva Reva + TokenManager TokenManager + Spaces Spaces Context context.Context Supervised bool diff --git a/graph/pkg/flagset/flagset.go b/graph/pkg/flagset/flagset.go index 44028fd8b2..0481669eea 100644 --- a/graph/pkg/flagset/flagset.go +++ b/graph/pkg/flagset/flagset.go @@ -144,75 +144,11 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { &cli.StringFlag{ Name: "spaces-webdav-base", Value: flags.OverrideDefaultString(cfg.Spaces.WebDavBase, "https://localhost:9200/dav/spaces/"), - Usage: "spaces webdav base URL", + Usage: "spaces webdav base URL to use when rendering drive WabDAV URLs", EnvVars: []string{"GRAPH_SPACES_WEBDAV_BASE"}, Destination: &cfg.Spaces.WebDavBase, }, - &cli.StringFlag{ - Name: "ldap-network", - Value: flags.OverrideDefaultString(cfg.Ldap.Network, "tcp"), - Usage: "Network protocol to use to connect to the Ldap server", - EnvVars: []string{"GRAPH_LDAP_NETWORK"}, - Destination: &cfg.Ldap.Network, - }, - &cli.StringFlag{ - Name: "ldap-address", - Value: flags.OverrideDefaultString(cfg.Ldap.Address, "0.0.0.0:9125"), - Usage: "Address to connect to the Ldap server", - EnvVars: []string{"GRAPH_LDAP_ADDRESS"}, - Destination: &cfg.Ldap.Address, - }, - &cli.StringFlag{ - Name: "ldap-username", - Value: flags.OverrideDefaultString(cfg.Ldap.UserName, "cn=admin,dc=example,dc=org"), - Usage: "User to bind to the Ldap server", - EnvVars: []string{"GRAPH_LDAP_USERNAME"}, - Destination: &cfg.Ldap.UserName, - }, - &cli.StringFlag{ - Name: "ldap-password", - Value: flags.OverrideDefaultString(cfg.Ldap.Password, "admin"), - Usage: "Password to bind to the Ldap server", - EnvVars: []string{"GRAPH_LDAP_PASSWORD"}, - Destination: &cfg.Ldap.Password, - }, - &cli.StringFlag{ - Name: "ldap-basedn-users", - Value: flags.OverrideDefaultString(cfg.Ldap.BaseDNUsers, "ou=users,dc=example,dc=org"), - Usage: "BaseDN to look for users", - EnvVars: []string{"GRAPH_LDAP_BASEDN_USERS"}, - Destination: &cfg.Ldap.BaseDNUsers, - }, - &cli.StringFlag{ - Name: "ldap-basedn-groups", - Value: flags.OverrideDefaultString(cfg.Ldap.BaseDNGroups, "ou=groups,dc=example,dc=org"), - Usage: "BaseDN to look for users", - EnvVars: []string{"GRAPH_LDAP_BASEDN_GROUPS"}, - Destination: &cfg.Ldap.BaseDNGroups, - }, - - &cli.StringFlag{ - Name: "oidc-endpoint", - Value: flags.OverrideDefaultString(cfg.OpenIDConnect.Endpoint, "https://localhost:9200"), - Usage: "OpenIDConnect endpoint", - EnvVars: []string{"GRAPH_OIDC_ENDPOINT", "OCIS_URL"}, - Destination: &cfg.OpenIDConnect.Endpoint, - }, - &cli.BoolFlag{ - Name: "oidc-insecure", - Usage: "OpenIDConnect endpoint", - EnvVars: []string{"GRAPH_OIDC_INSECURE"}, - Destination: &cfg.OpenIDConnect.Insecure, - }, - &cli.StringFlag{ - Name: "oidc-realm", - Value: flags.OverrideDefaultString(cfg.OpenIDConnect.Realm, ""), - Usage: "OpenIDConnect realm", - EnvVars: []string{"GRAPH_OIDC_REALM"}, - Destination: &cfg.OpenIDConnect.Realm, - }, - &cli.StringFlag{ Name: "jwt-secret", Value: flags.OverrideDefaultString(cfg.TokenManager.JWTSecret, "Pive-Fumkiu4"), @@ -227,13 +163,6 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { EnvVars: []string{"REVA_GATEWAY_ADDR"}, Destination: &cfg.Reva.Address, }, - &cli.StringFlag{ - Name: "webdav-namespace", - Value: flags.OverrideDefaultString(cfg.WebdavNamespace, "/home"), - Usage: "Namespace prefix for the webdav endpoint", - EnvVars: []string{"STORAGE_WEBDAV_NAMESPACE"}, - Destination: &cfg.WebdavNamespace, - }, &cli.StringFlag{ Name: "extensions", Usage: "Run specific extensions during supervised mode. This flag is set by the runtime", diff --git a/graph/pkg/server/http/server.go b/graph/pkg/server/http/server.go index c9d733e655..0526ce0ef6 100644 --- a/graph/pkg/server/http/server.go +++ b/graph/pkg/server/http/server.go @@ -28,6 +28,7 @@ func Server(opts ...Option) (http.Service, error) { svc.Logger(options.Logger), svc.Config(options.Config), svc.Middleware( + middleware.RequestID, middleware.Version( "graph", version.String, diff --git a/graph/pkg/service/v0/drives.go b/graph/pkg/service/v0/drives.go index 3677da3475..9026796a80 100644 --- a/graph/pkg/service/v0/drives.go +++ b/graph/pkg/service/v0/drives.go @@ -4,15 +4,14 @@ import ( "math" "net/http" "net/url" - "strings" + "path" "time" - "github.com/go-chi/render" - cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - + "github.com/go-chi/render" + "github.com/owncloud/ocis/graph/pkg/service/v0/errorcode" msgraph "github.com/owncloud/open-graph-api-go" ) @@ -24,34 +23,40 @@ func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) { client, err := g.GetClient() if err != nil { g.logger.Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } - res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{}) - if err != nil { + res, err := client.ListStorageSpaces(ctx, &storageprovider.ListStorageSpacesRequest{ + // TODO add filters? + }) + switch { + case err != nil: g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request") - w.WriteHeader(http.StatusInternalServerError) - return - } - // TODO handle not found and other status codes - if res.Status.Code != cs3rpc.Code_CODE_OK { - g.logger.Error().Err(err).Interface("status", res.Status).Msg("error calling grpc list storage spaces") - w.WriteHeader(http.StatusInternalServerError) + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + // return an empty list + render.Status(r, http.StatusOK) + render.JSON(w, r, &listResponse{}) + return + } + g.logger.Error().Err(err).Msg("error sending list storage spaces grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) } wdu, err := url.Parse(g.config.Spaces.WebDavBase) if err != nil { g.logger.Error().Err(err).Msg("error parsing url") - w.WriteHeader(http.StatusInternalServerError) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } files, err := formatDrives(wdu, res.StorageSpaces) if err != nil { g.logger.Error().Err(err).Msg("error encoding response as json") - w.WriteHeader(http.StatusInternalServerError) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } @@ -64,39 +69,58 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { g.logger.Info().Msg("Calling GetRootDriveChildren") ctx := r.Context() - fn := g.config.WebdavNamespace - client, err := g.GetClient() if err != nil { - g.logger.Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) + g.logger.Error().Err(err).Msg("could not get client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) return } - ref := &storageprovider.Reference{ - Path: fn, - } - - req := &storageprovider.ListContainerRequest{ - Ref: ref, - } - res, err := client.ListContainer(ctx, req) - if err != nil { - g.logger.Error().Err(err).Str("path", fn).Msg("error sending list container grpc request") - w.WriteHeader(http.StatusInternalServerError) + res, err := client.GetHome(ctx, &storageprovider.GetHomeRequest{}) + switch { + case err != nil: + g.logger.Error().Err(err).Msg("error sending get home grpc request") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) return - } - // TODO handle not found and other status codes - if res.Status.Code != cs3rpc.Code_CODE_OK { - g.logger.Error().Err(err).Str("path", fn).Interface("status", res.Status).Msg("error calling grpc list container") - w.WriteHeader(http.StatusInternalServerError) + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) + return + } + g.logger.Error().Err(err).Msg("error sending get home grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) return } - files, err := formatDriveItems(res.Infos) + lRes, err := client.ListContainer(ctx, &storageprovider.ListContainerRequest{ + Ref: &storageprovider.Reference{ + Path: res.Path, + }, + }) + switch { + case err != nil: + g.logger.Error().Err(err).Msg("error sending list container grpc request") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) + return + } + if res.Status.Code == cs3rpc.Code_CODE_PERMISSION_DENIED { + // TODO check if we should return 404 to not disclose existing items + errorcode.AccessDenied.Render(w, r, http.StatusForbidden, res.Status.Message) + return + } + g.logger.Error().Err(err).Msg("error sending list container grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) + return + } + + files, err := formatDriveItems(lRes.Infos) if err != nil { g.logger.Error().Err(err).Msg("error encoding response as json") - w.WriteHeader(http.StatusInternalServerError) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } @@ -111,7 +135,7 @@ func cs3TimestampToTime(t *types.Timestamp) time.Time { func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*msgraph.DriveItem, error) { size := new(int64) *size = int64(res.Size) // uint64 -> int :boom: - name := strings.TrimPrefix(res.Path, "/home/") + name := path.Base(res.Path) driveItem := &msgraph.DriveItem{ BaseItem: msgraph.BaseItem{ diff --git a/graph/pkg/service/v0/errorcode/errorcode.go b/graph/pkg/service/v0/errorcode/errorcode.go index c2d8805bf9..068e684789 100644 --- a/graph/pkg/service/v0/errorcode/errorcode.go +++ b/graph/pkg/service/v0/errorcode/errorcode.go @@ -2,9 +2,11 @@ package errorcode import ( "net/http" + "time" "github.com/go-chi/render" - msgraph "github.com/yaegashi/msgraph.go/v1.0" + "github.com/ascarter/requestid" + msgraph "github.com/owncloud/open-graph-api-go" ) // ErrorCode defines code as used in MS Graph - see https://docs.microsoft.com/en-us/graph/errors?context=graph%2Fapi%2F1.0&view=graph-rest-1.0 @@ -66,9 +68,19 @@ var errorCodes = [...]string{ // Render writes an Graph ErrorObject to the response writer func (e ErrorCode) Render(w http.ResponseWriter, r *http.Request, status int, msg string) { - resp := &msgraph.ErrorObject{ - Code: e.String(), - Message: msg, + innererror := map[string]interface{}{ + "date": time.Now().UTC().Format(time.RFC3339), + // TODO return client-request-id? + } + if id, ok := requestid.FromContext(r.Context()); ok { + innererror["request-id"] = id + } + resp := &msgraph.OdataError{ + Error: msgraph.OdataErrorMain{ + Code: e.String(), + Message: msg, + Innererror: &innererror, + }, } render.Status(r, status) render.JSON(w, r, resp) diff --git a/graph/pkg/service/v0/graph.go b/graph/pkg/service/v0/graph.go index ad6d65816f..71bef85789 100644 --- a/graph/pkg/service/v0/graph.go +++ b/graph/pkg/service/v0/graph.go @@ -31,8 +31,10 @@ func (g Graph) GetClient() (gateway.GatewayAPIClient, error) { // other packages. type key int -const userIDKey key = 0 -const groupIDKey key = 1 +const ( + userKey key = iota + groupKey +) type listResponse struct { Value interface{} `json:"value,omitempty"` diff --git a/graph/pkg/service/v0/groups.go b/graph/pkg/service/v0/groups.go index 2d0277da50..c31ed3953b 100644 --- a/graph/pkg/service/v0/groups.go +++ b/graph/pkg/service/v0/groups.go @@ -2,14 +2,16 @@ package svc import ( "context" - "fmt" "net/http" + cs3 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/owncloud/ocis/graph/pkg/service/v0/errorcode" "github.com/go-chi/chi" "github.com/go-chi/render" - "github.com/go-ldap/ldap/v3" + + //msgraph "github.com/owncloud/open-graph-api-go" // FIXME add groups to open graph, needs OnPremisesSamAccountName and OnPremisesDomainName msgraph "github.com/yaegashi/msgraph.go/v1.0" ) @@ -20,50 +22,80 @@ 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, "groupID empty") - return - } - // TODO make filter configurable - filter := fmt.Sprintf("(&(objectClass=posixGroup)(ownCloudUUID=%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.StatusInternalServerError, "") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing group id") return } - ctx := context.WithValue(r.Context(), groupIDKey, group) + client, err := g.GetClient() + if err != nil { + g.logger.Error().Err(err).Msg("could not get client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + res, err := client.GetGroupByClaim(r.Context(), &cs3.GetGroupByClaimRequest{ + Claim: "groupid", // FIXME add consts to reva + Value: groupID, + }) + + switch { + case err != nil: + g.logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) + return + } + g.logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) + return + } + + ctx := context.WithValue(r.Context(), groupKey, res.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() + client, err := g.GetClient() if err != nil { - g.logger.Error().Err(err).Msg("Failed to initialize ldap") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "") + g.logger.Error().Err(err).Msg("could not get client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) return } - // TODO make filter configurable - result, err := g.ldapSearch(con, "(objectClass=posixGroup)", g.config.Ldap.BaseDNGroups) + search := r.URL.Query().Get("search") + if search == "" { + search = r.URL.Query().Get("$search") + } - if err != nil { - g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectClass=posixGroup)'") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "") + res, err := client.FindGroups(r.Context(), &cs3.FindGroupsRequest{ + // FIXME presence match is currently not implemented, an empty search currently leads to + // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented + Filter: search, + }) + switch { + case err != nil: + g.logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) + return + } + g.logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) return } - groups := make([]*msgraph.Group, 0, len(result.Entries)) + groups := make([]*msgraph.Group, 0, len(res.Groups)) - for _, group := range result.Entries { - groups = append( - groups, - createGroupModelFromLDAP( - group, - ), - ) + for _, group := range res.Groups { + groups = append(groups, createGroupModelFromCS3(group)) } render.Status(r, http.StatusOK) @@ -72,8 +104,26 @@ func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) { // GetGroup implements the Service interface. func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) { - group := r.Context().Value(groupIDKey).(*ldap.Entry) + group := r.Context().Value(groupKey).(*cs3.Group) render.Status(r, http.StatusOK) - render.JSON(w, r, createGroupModelFromLDAP(group)) + render.JSON(w, r, createGroupModelFromCS3(group)) +} + +func createGroupModelFromCS3(g *cs3.Group) *msgraph.Group { + if g.Id == nil { + g.Id = &cs3.GroupId{} + } + return &msgraph.Group{ + DirectoryObject: msgraph.DirectoryObject{ + Entity: msgraph.Entity{ + ID: &g.Id.OpaqueId, + }, + }, + OnPremisesDomainName: &g.Id.Idp, + OnPremisesSamAccountName: &g.GroupName, + DisplayName: &g.DisplayName, + Mail: &g.Mail, + // TODO when to fetch and expand memberof, usernames or ids? + } } diff --git a/graph/pkg/service/v0/ldap.go b/graph/pkg/service/v0/ldap.go deleted file mode 100644 index 10c8ec0cd6..0000000000 --- a/graph/pkg/service/v0/ldap.go +++ /dev/null @@ -1,94 +0,0 @@ -package svc - -import ( - "errors" - - "github.com/go-ldap/ldap/v3" - msgraph "github.com/yaegashi/msgraph.go/v1.0" -) - -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/graph/pkg/service/v0/users.go b/graph/pkg/service/v0/users.go index a18eec6ea4..4926f5a80d 100644 --- a/graph/pkg/service/v0/users.go +++ b/graph/pkg/service/v0/users.go @@ -2,15 +2,16 @@ package svc import ( "context" - "fmt" "net/http" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/pkg/user" "github.com/go-chi/chi" "github.com/go-chi/render" - "github.com/go-ldap/ldap/v3" "github.com/owncloud/ocis/graph/pkg/service/v0/errorcode" + + //msgraph "github.com/owncloud/open-graph-api-go" // FIXME needs OnPremisesSamAccountName, OnPremisesDomainName and AdditionalData msgraph "github.com/yaegashi/msgraph.go/v1.0" ) @@ -20,24 +21,41 @@ import ( // TODO use cs3 api to look up user 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 - } - // TODO make filter configurable - filter := fmt.Sprintf("(&(objectClass=posixAccount)(ownCloudUUID=%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, "") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing user id") return } - ctx := context.WithValue(r.Context(), userIDKey, user) + client, err := g.GetClient() + if err != nil { + g.logger.Error().Err(err).Msg("could not get client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + res, err := client.GetUserByClaim(r.Context(), &cs3.GetUserByClaimRequest{ + Claim: "userid", // FIXME add consts to reva + Value: userID, + }) + + switch { + case err != nil: + g.logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) + return + } + g.logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) + return + } + + ctx := context.WithValue(r.Context(), userKey, res.User) next.ServeHTTP(w, r.WithContext(ctx)) }) } @@ -47,7 +65,8 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { u, ok := user.ContextGetUser(r.Context()) if !ok { - errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "") + g.logger.Error().Msg("user not in context") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "user not in context") return } @@ -59,11 +78,68 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, me) } -func createUserModelFromCS3(u *userpb.User) *msgraph.User { +// GetUsers implements the Service interface. +// TODO use cs3 api to look up user +func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) { + + client, err := g.GetClient() + if err != nil { + g.logger.Error().Err(err).Msg("could not get client") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + } + + search := r.URL.Query().Get("search") + if search == "" { + search = r.URL.Query().Get("$search") + } + + res, err := client.FindUsers(r.Context(), &cs3.FindUsersRequest{ + // FIXME presence match is currently not implemented, an empty search currently leads to + // Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented + Filter: search, + }) + switch { + case err != nil: + g.logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request") + errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error()) + return + case res.Status.Code != cs3rpc.Code_CODE_OK: + if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { + errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, res.Status.Message) + return + } + g.logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request") + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) + return + } + + users := make([]*msgraph.User, 0, len(res.Users)) + + for _, user := range res.Users { + users = append(users, createUserModelFromCS3(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(userKey).(*cs3.User) + + render.Status(r, http.StatusOK) + render.JSON(w, r, createUserModelFromCS3(user)) +} + +func createUserModelFromCS3(u *cs3.User) *msgraph.User { + if u.Id == nil { + u.Id = &cs3.UserId{} + } return &msgraph.User{ DisplayName: &u.DisplayName, Mail: &u.Mail, - // TODO u.Groups + // TODO u.Groups are those ids or group names? OnPremisesSamAccountName: &u.Username, DirectoryObject: msgraph.DirectoryObject{ Entity: msgraph.Entity{ @@ -78,45 +154,3 @@ func createUserModelFromCS3(u *userpb.User) *msgraph.User { }, } } - -// GetUsers implements the Service interface. -// TODO use cs3 api to look up user -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 - } - - // TODO make filter configurable - result, err := g.ldapSearch(con, "(objectClass=posixAccount)", g.config.Ldap.BaseDNUsers) - - if err != nil { - g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectClass=posixAccount)'") - errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "") - return - } - - users := make([]*msgraph.User, 0, len(result.Entries)) - - 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)) -}