get rid of ldap and oidc, refactor error handling

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2021-07-09 22:20:30 +00:00
parent 5070941dc4
commit 2a54b647f1
11 changed files with 269 additions and 336 deletions

View File

@@ -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

View File

@@ -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=

View File

@@ -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

View File

@@ -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",

View File

@@ -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,

View File

@@ -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{

View File

@@ -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)

View File

@@ -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"`

View File

@@ -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?
}
}

View File

@@ -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,
},
},
}
}

View File

@@ -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))
}