mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-03-06 08:17:42 -05:00
get rid of ldap and oidc, refactor error handling
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user