Add 'graph/' from commit 'f1efaddc27c74c9de4509cd864fc673ffdf52e19'
git-subtree-dir: graph git-subtree-mainline:396d5d3a2fgit-subtree-split:f1efaddc27
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"github.com/go-chi/render"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/pkg/token"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
func getToken(r *http.Request) string {
|
||||
// 1. check Authorization header
|
||||
hdr := r.Header.Get("Authorization")
|
||||
t := strings.TrimPrefix(hdr, "Bearer ")
|
||||
if t != "" {
|
||||
return t
|
||||
}
|
||||
// TODO 2. check form encoded body parameter for POST requests, see https://tools.ietf.org/html/rfc6750#section-2.2
|
||||
|
||||
// 3. check uri query parameter, see https://tools.ietf.org/html/rfc6750#section-2.3
|
||||
tokens, ok := r.URL.Query()["access_token"]
|
||||
if !ok || len(tokens[0]) < 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return tokens[0]
|
||||
}
|
||||
|
||||
// GetRootDriveChildren implements the Service interface.
|
||||
func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
|
||||
g.logger.Info().Msgf("Calling GetRootDriveChildren")
|
||||
accessToken := getToken(r)
|
||||
if accessToken == "" {
|
||||
g.logger.Error().Msg("no access token provided in request")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
|
||||
fn := "/home"
|
||||
|
||||
client, err := g.GetClient()
|
||||
if err != nil {
|
||||
g.logger.Err(err).Msg("error getting grpc client")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// get reva token
|
||||
authReq := &gateway.AuthenticateRequest{
|
||||
Type: "bearer",
|
||||
ClientSecret: accessToken,
|
||||
}
|
||||
|
||||
authRes, _ := client.Authenticate(ctx, authReq);
|
||||
ctx = token.ContextSetToken(ctx, authRes.Token)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "x-access-token", authRes.Token)
|
||||
|
||||
g.logger.Info().Msgf("provides access token %v", ctx)
|
||||
|
||||
ref := &storageprovider.Reference{
|
||||
Spec: &storageprovider.Reference_Path{Path: fn},
|
||||
}
|
||||
|
||||
req := &storageprovider.ListContainerRequest{
|
||||
Ref: ref,
|
||||
}
|
||||
res, err := client.ListContainer(ctx, req)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msgf("error sending list container grpc request %s", fn)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if res.Status.Code != cs3rpc.Code_CODE_OK {
|
||||
g.logger.Error().Err(err).Msgf("error calling grpc list container %s", fn)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := formatDriveItems(res.Infos)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msgf("error encoding response as json %s", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &listResponse{Value: files})
|
||||
}
|
||||
|
||||
|
||||
func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*msgraph.DriveItem, error) {
|
||||
size := new(int)
|
||||
*size = int(res.Size) // uint64 -> int :boom:
|
||||
name := strings.TrimPrefix(res.Path, "/home/")
|
||||
lastModified := new(time.Time)
|
||||
*lastModified = time.Unix(int64(res.Mtime.Seconds), int64(res.Mtime.Nanos))
|
||||
|
||||
driveItem := &msgraph.DriveItem{
|
||||
BaseItem: msgraph.BaseItem{
|
||||
Entity: msgraph.Entity{
|
||||
Object: msgraph.Object{},
|
||||
ID: &res.Id.OpaqueId,
|
||||
},
|
||||
Name: &name,
|
||||
LastModifiedDateTime: lastModified,
|
||||
ETag: &res.Etag,
|
||||
},
|
||||
Size: size,
|
||||
}
|
||||
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_FILE {
|
||||
driveItem.File = &msgraph.File{
|
||||
MimeType: &res.MimeType,
|
||||
}
|
||||
}
|
||||
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
|
||||
driveItem.Folder = &msgraph.Folder{
|
||||
}
|
||||
}
|
||||
return driveItem, nil
|
||||
}
|
||||
|
||||
func formatDriveItems(mds []*storageprovider.ResourceInfo) ([]*msgraph.DriveItem, error) {
|
||||
responses := make([]*msgraph.DriveItem, 0, len(mds))
|
||||
for i := range mds {
|
||||
res, err := cs3ResourceToDriveItem(mds[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
responses = append(responses, res)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package errorcode
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
// 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
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
// AccessDenied defines the error if the caller doesn't have permission to perform the action.
|
||||
AccessDenied ErrorCode = iota
|
||||
// ActivityLimitReached defines the error if the app or user has been throttled.
|
||||
ActivityLimitReached
|
||||
// GeneralException defines the error if an unspecified error has occurred.
|
||||
GeneralException
|
||||
// InvalidRange defines the error if the specified byte range is invalid or unavailable.
|
||||
InvalidRange
|
||||
// InvalidRequest defines the error if the request is malformed or incorrect.
|
||||
InvalidRequest
|
||||
// ItemNotFound defines the error if the resource could not be found.
|
||||
ItemNotFound
|
||||
// MalwareDetected defines the error if malware was detected in the requested resource.
|
||||
MalwareDetected
|
||||
// NameAlreadyExists defines the error if the specified item name already exists.
|
||||
NameAlreadyExists
|
||||
// NotAllowed defines the error if the action is not allowed by the system.
|
||||
NotAllowed
|
||||
// NotSupported defines the error if the request is not supported by the system.
|
||||
NotSupported
|
||||
// ResourceModified defines the error if the resource being updated has changed since the caller last read it, usually an eTag mismatch.
|
||||
ResourceModified
|
||||
// ResyncRequired defines the error if the delta token is no longer valid, and the app must reset the sync state.
|
||||
ResyncRequired
|
||||
// ServiceNotAvailable defines the error if the service is not available. Try the request again after a delay. There may be a Retry-After header.
|
||||
ServiceNotAvailable
|
||||
// QuotaLimitReached the user has reached their quota limit.
|
||||
QuotaLimitReached
|
||||
// Unauthenticated the caller is not authenticated.
|
||||
Unauthenticated
|
||||
)
|
||||
|
||||
var errorCodes = [...]string{
|
||||
"accessDenied",
|
||||
"activityLimitReached",
|
||||
"generalException",
|
||||
"invalidRange",
|
||||
"invalidRequest",
|
||||
"itemNotFound",
|
||||
"malwareDetected",
|
||||
"nameAlreadyExists",
|
||||
"notAllowed",
|
||||
"notSupported",
|
||||
"resourceModified",
|
||||
"resyncRequired",
|
||||
"serviceNotAvailable",
|
||||
"quotaLimitReached",
|
||||
"unauthenticated",
|
||||
}
|
||||
|
||||
// Render writes an Graph ErrorObject to the response writer
|
||||
func (e ErrorCode) Render(w http.ResponseWriter, r *http.Request, status int) {
|
||||
resp := &msgraph.ErrorObject{
|
||||
Code: e.String(),
|
||||
}
|
||||
render.Status(r, status)
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
func (e ErrorCode) String() string {
|
||||
return errorCodes[e]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/owncloud/ocis-graph/pkg/config"
|
||||
"github.com/owncloud/ocis-graph/pkg/cs3"
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
)
|
||||
|
||||
// Graph defines implements the business logic for Service.
|
||||
type Graph struct {
|
||||
config *config.Config
|
||||
mux *chi.Mux
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
g.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetClient returns a gateway client to talk to reva
|
||||
func (g Graph) GetClient() (gateway.GatewayAPIClient, error) {
|
||||
return cs3.GetGatewayServiceClient(g.config.Reva.Address)
|
||||
}
|
||||
|
||||
// The key type is unexported to prevent collisions with context keys defined in
|
||||
// other packages.
|
||||
type key int
|
||||
|
||||
const userIDKey key = 0
|
||||
const groupIDKey key = 1
|
||||
|
||||
type listResponse struct {
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis-graph/pkg/service/v0/errorcode"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
// GroupCtx middleware is used to load an User object from
|
||||
// the URL parameters passed through as the request. In case
|
||||
// the User could not be found, we stop here and return a 404.
|
||||
func (g Graph) GroupCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
groupID := chi.URLParam(r, "groupID")
|
||||
if groupID == "" {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
filter := fmt.Sprintf("(entryuuid=%s)", groupID)
|
||||
group, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNGroups, filter)
|
||||
if err != nil {
|
||||
g.logger.Info().Err(err).Msgf("Failed to read group %s", groupID)
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), groupIDKey, group)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// GetGroups implements the Service interface.
|
||||
func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) {
|
||||
con, err := g.initLdap()
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("Failed to initialize ldap")
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNGroups)
|
||||
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'")
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var groups []*msgraph.Group
|
||||
|
||||
for _, group := range result.Entries {
|
||||
groups = append(
|
||||
groups,
|
||||
createGroupModelFromLDAP(
|
||||
group,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &listResponse{Value: groups})
|
||||
}
|
||||
|
||||
// GetGroup implements the Service interface.
|
||||
func (g Graph) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
group := r.Context().Value(groupIDKey).(*ldap.Entry)
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, createGroupModelFromLDAP(group))
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis-graph/pkg/metrics"
|
||||
)
|
||||
|
||||
// NewInstrument returns a service that instruments metrics.
|
||||
func NewInstrument(next Service, metrics *metrics.Metrics) Service {
|
||||
return instrument{
|
||||
next: next,
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
type instrument struct {
|
||||
next Service
|
||||
metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (i instrument) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetMe(w, r)
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
func (i instrument) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetUsers(w, r)
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
func (i instrument) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetUser(w, r)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
)
|
||||
|
||||
// NewLogging returns a service that logs messages.
|
||||
func NewLogging(next Service, logger log.Logger) Service {
|
||||
return logging{
|
||||
next: next,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type logging struct {
|
||||
next Service
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (l logging) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetMe(w, r)
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
func (l logging) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetUsers(w, r)
|
||||
}
|
||||
|
||||
// GetUser implements the Service interface.
|
||||
func (l logging) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetUser(w, r)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis-graph/pkg/config"
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware provides a function to set the middleware option.
|
||||
func Middleware(val ...func(http.Handler) http.Handler) Option {
|
||||
return func(o *Options) {
|
||||
o.Middleware = val
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
type Service interface {
|
||||
ServeHTTP(http.ResponseWriter, *http.Request)
|
||||
GetMe(http.ResponseWriter, *http.Request)
|
||||
GetUsers(http.ResponseWriter, *http.Request)
|
||||
GetUser(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// NewService returns a service implementation for Service.
|
||||
func NewService(opts ...Option) Service {
|
||||
options := newOptions(opts...)
|
||||
|
||||
m := chi.NewMux()
|
||||
m.Use(options.Middleware...)
|
||||
|
||||
svc := Graph{
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
logger: &options.Logger,
|
||||
}
|
||||
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
r.Use(middleware.StripSlashes)
|
||||
r.Route("/v1.0", func(r chi.Router) {
|
||||
r.Route("/me", func(r chi.Router) {
|
||||
r.Get("/", svc.GetMe)
|
||||
r.Get("/drive/root/children", svc.GetRootDriveChildren)
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", svc.GetUsers)
|
||||
r.Route("/{userID}", func(r chi.Router) {
|
||||
r.Use(svc.UserCtx)
|
||||
r.Get("/", svc.GetUser)
|
||||
})
|
||||
})
|
||||
r.Route("/groups", func(r chi.Router) {
|
||||
r.Get("/", svc.GetGroups)
|
||||
r.Route("/{groupID}", func(r chi.Router) {
|
||||
r.Use(svc.GroupCtx)
|
||||
r.Get("/", svc.GetGroup)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return svc
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewTracing returns a service that instruments traces.
|
||||
func NewTracing(next Service) Service {
|
||||
return tracing{
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
type tracing struct {
|
||||
next Service
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (t tracing) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetMe(w, r)
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
func (t tracing) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetUsers(w, r)
|
||||
}
|
||||
|
||||
// GetUser implements the Service interface.
|
||||
func (t tracing) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetUser(w, r)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis-graph/pkg/service/v0/errorcode"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/owncloud/ocis-pkg/v2/oidc"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
// UserCtx middleware is used to load an User object from
|
||||
// the URL parameters passed through as the request. In case
|
||||
// the User could not be found, we stop here and return a 404.
|
||||
func (g Graph) UserCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var user *ldap.Entry
|
||||
var err error
|
||||
|
||||
userID := chi.URLParam(r, "userID")
|
||||
if userID == "" {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
filter := fmt.Sprintf("(entryuuid=%s)", userID)
|
||||
user, err = g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter)
|
||||
if err != nil {
|
||||
g.logger.Info().Err(err).Msgf("Failed to read user %s", userID)
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), userIDKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
claims := oidc.FromContext(r.Context())
|
||||
g.logger.Info().Interface("Claims", claims).Msg("Claims in /me")
|
||||
|
||||
filter := fmt.Sprintf("(uid=%s)", claims.PreferredUsername)
|
||||
user, err := g.ldapGetSingleEntry(g.config.Ldap.BaseDNUsers, filter)
|
||||
if err != nil {
|
||||
g.logger.Info().Err(err).Msgf("Failed to read user %s", claims.PreferredUsername)
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
me := createUserModelFromLDAP(user)
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, me)
|
||||
}
|
||||
|
||||
// GetUsers implements the Service interface.
|
||||
func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
con, err := g.initLdap()
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("Failed to initialize ldap")
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := g.ldapSearch(con, "(objectclass=*)", g.config.Ldap.BaseDNUsers)
|
||||
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("Failed search ldap with filter: '(objectclass=*)'")
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var users []*msgraph.User
|
||||
|
||||
for _, user := range result.Entries {
|
||||
users = append(
|
||||
users,
|
||||
createUserModelFromLDAP(
|
||||
user,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &listResponse{Value: users})
|
||||
}
|
||||
|
||||
// GetUser implements the Service interface.
|
||||
func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.Context().Value(userIDKey).(*ldap.Entry)
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, createUserModelFromLDAP(user))
|
||||
}
|
||||
Reference in New Issue
Block a user