adjust imports

This commit is contained in:
A.Unger
2021-02-04 11:46:32 +01:00
parent 13dbfc6e6c
commit f55a8092cb
67 changed files with 27 additions and 27 deletions
+140
View File
@@ -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]
}
+39
View File
@@ -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-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
"github.com/owncloud/ocis/graph/pkg/cs3"
)
// 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"`
}
+77
View File
@@ -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))
}
+40
View File
@@ -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)
}
+94
View File
@@ -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,
},
},
}
}
+40
View File
@@ -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)
}
+50
View File
@@ -0,0 +1,50 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis/graph/pkg/config"
)
// 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
}
}
+56
View File
@@ -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
}
+36
View File
@@ -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)
}
+100
View File
@@ -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))
}