Files
opencloud/services/graph/pkg/service/v0/graph.go
Florian Schade ad06a192d8 enhancement: add graph beta listPermissions endpoint (#7753)
* enhancement: add graph beta listPermissions endpoint

besides the new api endpoint it includes several utilities to simplify the graph api development.

* resolve drive and item id from the request path
* generic pointer and value utilities
* space root detection

* update GetDriveAndItemIDParam signature to return a error

* move errorcode package

* enhancement: add generic error code handling

* fix: rebase
2023-11-28 17:06:04 +01:00

153 lines
5.9 KiB
Go

package svc
import (
"context"
"errors"
"net/http"
"net/url"
"path"
"strings"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/go-chi/chi/v5"
"github.com/jellydator/ttlcache/v3"
"go-micro.dev/v4/client"
mevents "go-micro.dev/v4/events"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/types/known/emptypb"
"github.com/owncloud/ocis/v2/ocis-pkg/keycloak"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0"
searchsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/search/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
)
//go:generate make -C ../../.. generate
// Publisher is the interface for events publisher
type Publisher interface {
Publish(string, interface{}, ...mevents.PublishOption) error
}
// Permissions is the interface used to access the permissions service
type Permissions interface {
ListPermissions(ctx context.Context, req *settingssvc.ListPermissionsRequest, opts ...client.CallOption) (*settingssvc.ListPermissionsResponse, error)
GetPermissionByID(ctx context.Context, request *settingssvc.GetPermissionByIDRequest, opts ...client.CallOption) (*settingssvc.GetPermissionByIDResponse, error)
ListPermissionsByResource(ctx context.Context, in *settingssvc.ListPermissionsByResourceRequest, opts ...client.CallOption) (*settingssvc.ListPermissionsByResourceResponse, error)
}
// HTTPClient is the subset of the http.Client that is being used to interact with the download gateway
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// GetGatewayServiceClientFunc is a callback used to pass in a mock during testing
type GetGatewayServiceClientFunc func() (gateway.GatewayAPIClient, error)
// RoleService is the interface used to access the role service
type RoleService interface {
ListRoles(ctx context.Context, in *settingssvc.ListBundlesRequest, opts ...client.CallOption) (*settingssvc.ListBundlesResponse, error)
ListRoleAssignments(ctx context.Context, in *settingssvc.ListRoleAssignmentsRequest, opts ...client.CallOption) (*settingssvc.ListRoleAssignmentsResponse, error)
AssignRoleToUser(ctx context.Context, in *settingssvc.AssignRoleToUserRequest, opts ...client.CallOption) (*settingssvc.AssignRoleToUserResponse, error)
RemoveRoleFromUser(ctx context.Context, in *settingssvc.RemoveRoleFromUserRequest, opts ...client.CallOption) (*emptypb.Empty, error)
}
// Graph defines implements the business logic for Service.
type Graph struct {
config *config.Config
mux *chi.Mux
logger *log.Logger
identityBackend identity.Backend
identityEducationBackend identity.EducationBackend
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
roleService RoleService
permissionsService Permissions
valueService settingssvc.ValueService
specialDriveItemsCache *ttlcache.Cache[string, interface{}]
identityCache identity.IdentityCache
eventsPublisher events.Publisher
searchService searchsvc.SearchProviderService
keycloakClient keycloak.Client
historyClient ehsvc.EventHistoryService
traceProvider trace.TracerProvider
}
// ServeHTTP implements the Service interface.
func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// There was a number of issues with the chi router and parameters with
// slashes/percentage/other characters that didn't get properly escaped.
// This is a workaround to fix this. Also, we're not the only ones who have
// tried to fix this, as seen in this issue:
// https://github.com/go-chi/chi/issues/641#issuecomment-883156692
r.URL.RawPath = r.URL.EscapedPath()
g.mux.ServeHTTP(w, r)
}
func (g Graph) publishEvent(ctx context.Context, ev interface{}) {
if g.eventsPublisher != nil {
if err := events.Publish(ctx, g.eventsPublisher, ev); err != nil {
g.logger.Error().
Err(err).
Msg("could not publish user created event")
}
}
}
func (g Graph) getWebDavBaseURL() (*url.URL, error) {
webDavBaseURL, err := url.Parse(g.config.Spaces.WebDavBase)
if err != nil {
return nil, err
}
webDavBaseURL.Path = path.Join(webDavBaseURL.Path, g.config.Spaces.WebDavPath)
return webDavBaseURL, nil
}
// ListResponse is used for proper marshalling of Graph list responses
type ListResponse struct {
Value interface{} `json:"value,omitempty"`
}
const (
// ReadmeSpecialFolderName for the drive specialFolder property
ReadmeSpecialFolderName = "readme"
// SpaceImageSpecialFolderName for the drive specialFolder property
SpaceImageSpecialFolderName = "image"
)
// TODO might be different for /education/users vs /users
func (g Graph) parseMemberRef(ref string) (string, string, error) {
memberURL, err := url.ParseRequestURI(ref)
if err != nil {
return "", "", err
}
segments := strings.Split(memberURL.Path, "/")
if len(segments) < 2 {
return "", "", errors.New("invalid member reference")
}
id := segments[len(segments)-1]
memberType := segments[len(segments)-2]
return memberType, id, nil
}
func parseIDParam(r *http.Request, param string) (storageprovider.ResourceId, error) {
driveID, err := url.PathUnescape(chi.URLParam(r, param))
if err != nil {
return storageprovider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error())
}
id, err := storagespace.ParseID(driveID)
if err != nil {
return storageprovider.ResourceId{}, errorcode.New(errorcode.InvalidRequest, err.Error())
}
return id, nil
}