mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-13 23:49:05 -06:00
graph: add appRoleAssignments and minimal application resource (#5318)
* bump libregraph-go lib Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * add appRoleAssignment stubs Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * add get application stub Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * fetch appRoles for application from settings service Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * initial list appRoleAssignments implementation Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * initial create appRoleAssignment implementation, extract assignmentToAppRoleAssignment, configurable app id and displayname Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * initial delete appRoleAssignment implementation, changed error handling and logging Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * initial expand appRoleAssignment on users Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * test user expand appRoleAssignment Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * test appRoleAssignment Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * fix education test by actually using the mocked roleManager Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * test getapplication Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * list assignments Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * use common not exists error handling Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * default to just 'ownCloud Infinite Scale' as application name Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * fix store_test Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * roll application uuid on init Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * fix tests Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * extract method Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * Apply suggestions from code review Co-authored-by: Michael Barz <mbarz@owncloud.com> Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> Co-authored-by: Michael Barz <mbarz@owncloud.com>
This commit is contained in:
committed by
GitHub
parent
1b6c269de5
commit
078698fdf4
2
go.mod
2
go.mod
@@ -55,7 +55,7 @@ require (
|
||||
github.com/onsi/ginkgo/v2 v2.7.0
|
||||
github.com/onsi/gomega v1.24.1
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/owncloud/libre-graph-api-go v1.0.1
|
||||
github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1060,8 +1060,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk
|
||||
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
|
||||
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
||||
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
|
||||
github.com/owncloud/libre-graph-api-go v1.0.1 h1:wj3aQQr/yDPoc97ddg7DCadvMx6ui6N7re/oRV9+yNs=
|
||||
github.com/owncloud/libre-graph-api-go v1.0.1/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
|
||||
github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d h1:aqVf2yJEdSgFQd3k5fnwtYxjTwC/UREAKTZIzeupwHg=
|
||||
github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d/go.mod h1:iKdVH6nYpI8RBeK9sjeLfzrPByST6r9d+NG2IJHoJmU=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
|
||||
@@ -50,11 +50,15 @@ type LdapBasedService struct {
|
||||
type Events struct {
|
||||
TLSInsecure bool `yaml:"tls_insecure"`
|
||||
}
|
||||
type GraphApplication struct {
|
||||
ID string `yaml:"id"`
|
||||
}
|
||||
|
||||
type GraphService struct {
|
||||
Events Events
|
||||
Spaces InsecureService
|
||||
Identity LdapBasedService
|
||||
Application GraphApplication
|
||||
Events Events
|
||||
Spaces InsecureService
|
||||
Identity LdapBasedService
|
||||
}
|
||||
|
||||
type ServiceUserPasswordsSettings struct {
|
||||
@@ -219,6 +223,7 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin
|
||||
|
||||
systemUserID := uuid.Must(uuid.NewV4()).String()
|
||||
adminUserID := uuid.Must(uuid.NewV4()).String()
|
||||
graphApplicationID := uuid.Must(uuid.NewV4()).String()
|
||||
storageUsersMountID := uuid.Must(uuid.NewV4()).String()
|
||||
|
||||
idmServicePassword, err := generators.GenerateRandomPassword(passwordLength)
|
||||
@@ -306,6 +311,9 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin
|
||||
},
|
||||
},
|
||||
Graph: GraphService{
|
||||
Application: GraphApplication{
|
||||
ID: graphApplicationID,
|
||||
},
|
||||
Identity: LdapBasedService{
|
||||
Ldap: LdapSettings{
|
||||
BindPassword: idmServicePassword,
|
||||
|
||||
7
services/graph/pkg/config/application.go
Normal file
7
services/graph/pkg/config/application.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package config
|
||||
|
||||
// Application defines the available graph application configuration.
|
||||
type Application struct {
|
||||
ID string `yaml:"id" env:"GRAPH_APPLICATION_ID" desc:"The ocis application id shown in the graph. All app roles are tied to this."`
|
||||
DisplayName string `yaml:"displayname" env:"GRAPH_APPLICATION_DISPLAYNAME" desc:"The oCIS application name"`
|
||||
}
|
||||
@@ -25,9 +25,10 @@ type Config struct {
|
||||
TokenManager *TokenManager `yaml:"token_manager"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
|
||||
Spaces Spaces `yaml:"spaces"`
|
||||
Identity Identity `yaml:"identity"`
|
||||
Events Events `yaml:"events"`
|
||||
Application Application `yaml:"application"`
|
||||
Spaces Spaces `yaml:"spaces"`
|
||||
Identity Identity `yaml:"identity"`
|
||||
Events Events `yaml:"events"`
|
||||
|
||||
Context context.Context `yaml:"-"`
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ func DefaultConfig() *config.Config {
|
||||
Service: config.Service{
|
||||
Name: "graph",
|
||||
},
|
||||
Application: config.Application{
|
||||
DisplayName: "ownCloud Infinite Scale",
|
||||
},
|
||||
API: config.API{
|
||||
GroupMembersPatchLimit: 20,
|
||||
},
|
||||
|
||||
@@ -2,8 +2,10 @@ package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
|
||||
defaults2 "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
|
||||
@@ -42,5 +44,13 @@ func Validate(cfg *config.Config) error {
|
||||
return shared.MissingLDAPBindPassword(cfg.Service.Name)
|
||||
}
|
||||
|
||||
if cfg.Application.ID == "" {
|
||||
return fmt.Errorf("The application ID has not been configured for %s. "+
|
||||
"Make sure your %s config contains the proper values "+
|
||||
"(e.g. by running ocis init or setting it manually in "+
|
||||
"the config/corresponding environment variable).",
|
||||
"graph", defaults2.BaseConfigPath())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
77
services/graph/pkg/service/v0/application.go
Normal file
77
services/graph/pkg/service/v0/application.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
|
||||
)
|
||||
|
||||
// ListApplications implements the Service interface.
|
||||
func (g Graph) ListApplications(w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Interface("query", r.URL.Query()).Msg("calling list applications")
|
||||
|
||||
lbr, err := g.roleService.ListRoles(r.Context(), &settingssvc.ListBundlesRequest{})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not list roles: transport error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
roles := make([]libregraph.AppRole, 0, len(lbr.Bundles))
|
||||
for _, bundle := range lbr.GetBundles() {
|
||||
role := libregraph.NewAppRole(bundle.GetId())
|
||||
role.SetDisplayName(bundle.GetDisplayName())
|
||||
roles = append(roles, *role)
|
||||
}
|
||||
|
||||
application := libregraph.NewApplication(g.config.Application.ID)
|
||||
application.SetDisplayName(g.config.Application.DisplayName)
|
||||
application.SetAppRoles(roles)
|
||||
|
||||
applications := []*libregraph.Application{
|
||||
application,
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{Value: applications})
|
||||
}
|
||||
|
||||
// GetApplication implements the Service interface.
|
||||
func (g Graph) GetApplication(w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Interface("query", r.URL.Query()).Msg("calling get application")
|
||||
|
||||
applicationID := chi.URLParam(r, "applicationID")
|
||||
|
||||
if applicationID != g.config.Application.ID {
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, fmt.Sprintf("requested id %s does not match expected application id %v", applicationID, g.config.Application.ID))
|
||||
return
|
||||
}
|
||||
|
||||
lbr, err := g.roleService.ListRoles(r.Context(), &settingssvc.ListBundlesRequest{})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not list roles: transport error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
roles := make([]libregraph.AppRole, 0, len(lbr.Bundles))
|
||||
for _, bundle := range lbr.GetBundles() {
|
||||
role := libregraph.NewAppRole(bundle.GetId())
|
||||
role.SetDisplayName(bundle.GetDisplayName())
|
||||
roles = append(roles, *role)
|
||||
}
|
||||
|
||||
application := libregraph.NewApplication(applicationID)
|
||||
application.SetDisplayName(g.config.Application.DisplayName)
|
||||
application.SetAppRoles(roles)
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, application)
|
||||
}
|
||||
135
services/graph/pkg/service/v0/application_test.go
Normal file
135
services/graph/pkg/service/v0/application_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package svc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/mocks"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
|
||||
identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks"
|
||||
service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
|
||||
)
|
||||
|
||||
type applicationList struct {
|
||||
Value []*libregraph.Application
|
||||
}
|
||||
|
||||
var _ = Describe("Applications", func() {
|
||||
var (
|
||||
svc service.Service
|
||||
ctx context.Context
|
||||
cfg *config.Config
|
||||
gatewayClient *mocks.GatewayClient
|
||||
eventsPublisher mocks.Publisher
|
||||
roleService *mocks.RoleService
|
||||
identityBackend *identitymocks.Backend
|
||||
|
||||
rr *httptest.ResponseRecorder
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
identityBackend = &identitymocks.Backend{}
|
||||
roleService = &mocks.RoleService{}
|
||||
gatewayClient = &mocks.GatewayClient{}
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
ctx = context.Background()
|
||||
|
||||
cfg = defaults.FullDefaultConfig()
|
||||
cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests
|
||||
cfg.TokenManager.JWTSecret = "loremipsum"
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
cfg.Application.ID = "some-application-ID"
|
||||
|
||||
_ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...)
|
||||
svc, _ = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.WithGatewayClient(gatewayClient),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
service.WithRoleService(roleService),
|
||||
)
|
||||
})
|
||||
|
||||
Describe("ListApplications", func() {
|
||||
It("lists the configured application with appRoles", func() {
|
||||
roleService.On("ListRoles", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListBundlesResponse{
|
||||
Bundles: []*settingsmsg.Bundle{
|
||||
{
|
||||
Id: "some-appRole-ID",
|
||||
Type: settingsmsg.Bundle_TYPE_ROLE,
|
||||
DisplayName: "A human readable name for a role",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/applications", nil)
|
||||
svc.ListApplications(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
responseList := applicationList{}
|
||||
err = json.Unmarshal(data, &responseList)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(responseList.Value)).To(Equal(1))
|
||||
Expect(responseList.Value[0].Id).To(Equal(cfg.Application.ID))
|
||||
Expect(len(responseList.Value[0].GetAppRoles())).To(Equal(1))
|
||||
Expect(responseList.Value[0].GetAppRoles()[0].GetId()).To(Equal("some-appRole-ID"))
|
||||
Expect(responseList.Value[0].GetAppRoles()[0].GetDisplayName()).To(Equal("A human readable name for a role"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetApplication", func() {
|
||||
It("gets the application with appRoles", func() {
|
||||
roleService.On("ListRoles", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListBundlesResponse{
|
||||
Bundles: []*settingsmsg.Bundle{
|
||||
{
|
||||
Id: "some-appRole-ID",
|
||||
Type: settingsmsg.Bundle_TYPE_ROLE,
|
||||
DisplayName: "A human readable name for a role",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/applications/some-application-ID", nil)
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("applicationID", cfg.Application.ID)
|
||||
r = r.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx))
|
||||
svc.GetApplication(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
application := libregraph.Application{}
|
||||
err = json.Unmarshal(data, &application)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(application.Id).To(Equal(cfg.Application.ID))
|
||||
Expect(len(application.GetAppRoles())).To(Equal(1))
|
||||
Expect(application.GetAppRoles()[0].GetId()).To(Equal("some-appRole-ID"))
|
||||
Expect(application.GetAppRoles()[0].GetDisplayName()).To(Equal("A human readable name for a role"))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
129
services/graph/pkg/service/v0/approleassignments.go
Normal file
129
services/graph/pkg/service/v0/approleassignments.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
|
||||
)
|
||||
|
||||
const principalTypeUser = "User"
|
||||
|
||||
// ListAppRoleAssignments implements the Service interface.
|
||||
func (g Graph) ListAppRoleAssignments(w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Interface("query", r.URL.Query()).Msg("calling list appRoleAssignments")
|
||||
|
||||
userID := chi.URLParam(r, "userID")
|
||||
|
||||
lrar, err := g.roleService.ListRoleAssignments(r.Context(), &settingssvc.ListRoleAssignmentsRequest{
|
||||
AccountUuid: userID,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO check the error type and return proper error code
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
values := make([]libregraph.AppRoleAssignment, 0, len(lrar.GetAssignments()))
|
||||
for _, assignment := range lrar.GetAssignments() {
|
||||
values = append(values, g.assignmentToAppRoleAssignment(assignment))
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{Value: values})
|
||||
}
|
||||
|
||||
// CreateAppRoleAssignment implements the Service interface.
|
||||
func (g Graph) CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Interface("query", r.URL.Query()).Msg("calling create appRoleAssignment")
|
||||
|
||||
appRoleAssignment := libregraph.NewAppRoleAssignmentWithDefaults()
|
||||
err := json.NewDecoder(r.Body).Decode(appRoleAssignment)
|
||||
if err != nil {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
userID := chi.URLParam(r, "userID")
|
||||
|
||||
if appRoleAssignment.GetPrincipalId() != userID {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("user id %s does not match principal id %v", userID, appRoleAssignment.GetPrincipalId()))
|
||||
return
|
||||
}
|
||||
if appRoleAssignment.GetResourceId() != g.config.Application.ID {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("resource id %s does not match expected application id %v", userID, g.config.Application.ID))
|
||||
return
|
||||
}
|
||||
|
||||
artur, err := g.roleService.AssignRoleToUser(r.Context(), &settingssvc.AssignRoleToUserRequest{
|
||||
AccountUuid: userID,
|
||||
RoleId: appRoleAssignment.AppRoleId,
|
||||
})
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.JSON(w, r, g.assignmentToAppRoleAssignment(artur.GetAssignment()))
|
||||
}
|
||||
|
||||
// DeleteAppRoleAssignment implements the Service interface.
|
||||
func (g Graph) DeleteAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Interface("body", r.Body).Msg("calling delete appRoleAssignment")
|
||||
|
||||
userID := chi.URLParam(r, "userID")
|
||||
|
||||
// check assignment belongs to the user
|
||||
lrar, err := g.roleService.ListRoleAssignments(r.Context(), &settingssvc.ListRoleAssignmentsRequest{
|
||||
AccountUuid: userID,
|
||||
})
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
appRoleAssignmentID := chi.URLParam(r, "appRoleAssignmentID")
|
||||
|
||||
assignmentFound := false
|
||||
for _, roleAssignment := range lrar.GetAssignments() {
|
||||
if roleAssignment.Id == appRoleAssignmentID {
|
||||
assignmentFound = true
|
||||
}
|
||||
}
|
||||
if !assignmentFound {
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, fmt.Sprintf("appRoleAssignment %v not found for user %v", appRoleAssignmentID, userID))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = g.roleService.RemoveRoleFromUser(r.Context(), &settingssvc.RemoveRoleFromUserRequest{
|
||||
Id: appRoleAssignmentID,
|
||||
})
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func (g Graph) assignmentToAppRoleAssignment(assignment *settingsmsg.UserRoleAssignment) libregraph.AppRoleAssignment {
|
||||
appRoleAssignment := libregraph.NewAppRoleAssignmentWithDefaults()
|
||||
appRoleAssignment.SetId(assignment.Id)
|
||||
appRoleAssignment.SetAppRoleId(assignment.RoleId)
|
||||
appRoleAssignment.SetPrincipalType(principalTypeUser) // currently always assigned to the user
|
||||
appRoleAssignment.SetResourceId(g.config.Application.ID)
|
||||
appRoleAssignment.SetResourceDisplayName(g.config.Application.DisplayName)
|
||||
appRoleAssignment.SetPrincipalId(assignment.AccountUuid)
|
||||
// appRoleAssignment.SetPrincipalDisplayName() // TODO fetch and cache
|
||||
return *appRoleAssignment
|
||||
}
|
||||
198
services/graph/pkg/service/v0/approleassignments_test.go
Normal file
198
services/graph/pkg/service/v0/approleassignments_test.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package svc_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/mocks"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
|
||||
identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks"
|
||||
service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
|
||||
)
|
||||
|
||||
type assignmentList struct {
|
||||
Value []*libregraph.AppRoleAssignment
|
||||
}
|
||||
|
||||
var _ = Describe("AppRoleAssignments", func() {
|
||||
var (
|
||||
svc service.Service
|
||||
ctx context.Context
|
||||
cfg *config.Config
|
||||
gatewayClient *mocks.GatewayClient
|
||||
eventsPublisher mocks.Publisher
|
||||
roleService *mocks.RoleService
|
||||
identityBackend *identitymocks.Backend
|
||||
|
||||
rr *httptest.ResponseRecorder
|
||||
|
||||
currentUser = &userv1beta1.User{
|
||||
Id: &userv1beta1.UserId{
|
||||
OpaqueId: "user",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
identityBackend = &identitymocks.Backend{}
|
||||
roleService = &mocks.RoleService{}
|
||||
gatewayClient = &mocks.GatewayClient{}
|
||||
|
||||
rr = httptest.NewRecorder()
|
||||
ctx = context.Background()
|
||||
|
||||
cfg = defaults.FullDefaultConfig()
|
||||
cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests
|
||||
cfg.TokenManager.JWTSecret = "loremipsum"
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
cfg.Application.ID = "some-application-ID"
|
||||
|
||||
_ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...)
|
||||
svc, _ = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.WithGatewayClient(gatewayClient),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
service.WithRoleService(roleService),
|
||||
)
|
||||
})
|
||||
|
||||
Describe("ListAppRoleAssignments", func() {
|
||||
It("lists the appRoleAssignments", func() {
|
||||
user := &libregraph.User{
|
||||
Id: libregraph.PtrString("user1"),
|
||||
}
|
||||
assignments := []*settingsmsg.UserRoleAssignment{
|
||||
{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: user.GetId(),
|
||||
RoleId: "some-appRole-ID",
|
||||
},
|
||||
}
|
||||
roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users/user1/appRoleAssignments", nil)
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("userID", user.GetId())
|
||||
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
|
||||
svc.ListAppRoleAssignments(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
responseList := assignmentList{}
|
||||
err = json.Unmarshal(data, &responseList)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(responseList.Value)).To(Equal(1))
|
||||
Expect(responseList.Value[0].GetId()).ToNot(BeEmpty())
|
||||
Expect(responseList.Value[0].GetAppRoleId()).To(Equal("some-appRole-ID"))
|
||||
Expect(responseList.Value[0].GetPrincipalId()).To(Equal(user.GetId()))
|
||||
Expect(responseList.Value[0].GetResourceId()).To(Equal(cfg.Application.ID))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("CreateAppRoleAssignment", func() {
|
||||
It("creates an appRoleAssignment", func() {
|
||||
user := &libregraph.User{
|
||||
Id: libregraph.PtrString("user1"),
|
||||
}
|
||||
userRoleAssignment := &settingsmsg.UserRoleAssignment{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: user.GetId(),
|
||||
RoleId: "some-appRole-ID",
|
||||
}
|
||||
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything, mock.Anything).Return(&settings.AssignRoleToUserResponse{Assignment: userRoleAssignment}, nil)
|
||||
|
||||
ara := libregraph.NewAppRoleAssignmentWithDefaults()
|
||||
ara.SetAppRoleId("some-appRole-ID")
|
||||
ara.SetPrincipalId(user.GetId())
|
||||
ara.SetResourceId(cfg.Application.ID)
|
||||
|
||||
araJson, err := json.Marshal(ara)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users/user1/appRoleAssignments", bytes.NewBuffer(araJson))
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("userID", user.GetId())
|
||||
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
|
||||
svc.CreateAppRoleAssignment(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusCreated))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
assignment := libregraph.AppRoleAssignment{}
|
||||
err = json.Unmarshal(data, &assignment)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(assignment.GetId()).ToNot(BeEmpty())
|
||||
Expect(assignment.GetAppRoleId()).To(Equal("some-appRole-ID"))
|
||||
Expect(assignment.GetPrincipalId()).To(Equal("user1"))
|
||||
Expect(assignment.GetResourceId()).To(Equal(cfg.Application.ID))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("DeleteAppRoleAssignment", func() {
|
||||
It("deletes an appRoleAssignment", func() {
|
||||
user := &libregraph.User{
|
||||
Id: libregraph.PtrString("user1"),
|
||||
}
|
||||
|
||||
assignments := []*settingsmsg.UserRoleAssignment{
|
||||
{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: user.GetId(),
|
||||
RoleId: "some-appRole-ID",
|
||||
},
|
||||
}
|
||||
roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil)
|
||||
|
||||
roleService.On("RemoveRoleFromUser", mock.Anything, mock.Anything, mock.Anything).Return(&empty.Empty{}, nil)
|
||||
|
||||
ara := libregraph.NewAppRoleAssignmentWithDefaults()
|
||||
ara.SetAppRoleId("some-appRole-ID")
|
||||
ara.SetPrincipalId(user.GetId())
|
||||
ara.SetResourceId(cfg.Application.ID)
|
||||
|
||||
araJson, err := json.Marshal(ara)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users/user1/appRoleAssignments/some-appRoleAssignment-ID", bytes.NewBuffer(araJson))
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("userID", user.GetId())
|
||||
rctx.URLParams.Add("appRoleAssignmentID", "some-appRoleAssignment-ID")
|
||||
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
|
||||
svc.DeleteAppRoleAssignment(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusNoContent))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
@@ -74,7 +74,7 @@ var _ = Describe("EducationUsers", func() {
|
||||
service.WithGatewayClient(gatewayClient),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityEducationBackend(identityEducationBackend),
|
||||
//service.WithRoleService(roleService),
|
||||
service.WithRoleService(roleService),
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -24,6 +24,16 @@ func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListApplications implements the Service interface.
|
||||
func (i instrument) ListApplications(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ListApplications(w, r)
|
||||
}
|
||||
|
||||
// GetApplication implements the Service interface.
|
||||
func (i instrument) GetApplication(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetApplication(w, r)
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (i instrument) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetMe(w, r)
|
||||
@@ -59,6 +69,21 @@ func (i instrument) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ChangeOwnPassword(w, r)
|
||||
}
|
||||
|
||||
// ListAppRoleAssignments implements the Service interface.
|
||||
func (i instrument) ListAppRoleAssignments(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ListAppRoleAssignments(w, r)
|
||||
}
|
||||
|
||||
// CreateAppRoleAssignment implements the Service interface.
|
||||
func (i instrument) CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.CreateAppRoleAssignment(w, r)
|
||||
}
|
||||
|
||||
// DeleteAppRoleAssignment implements the Service interface.
|
||||
func (i instrument) DeleteAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.DeleteAppRoleAssignment(w, r)
|
||||
}
|
||||
|
||||
// GetGroups implements the Service interface.
|
||||
func (i instrument) GetGroups(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetGroups(w, r)
|
||||
|
||||
@@ -24,6 +24,16 @@ func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListApplications implements the Service interface.
|
||||
func (l logging) ListApplications(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ListApplications(w, r)
|
||||
}
|
||||
|
||||
// GetApplication implements the Service interface.
|
||||
func (l logging) GetApplication(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetApplication(w, r)
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (l logging) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetMe(w, r)
|
||||
@@ -59,6 +69,21 @@ func (l logging) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ChangeOwnPassword(w, r)
|
||||
}
|
||||
|
||||
// ListAppRoleAssignments implements the Service interface.
|
||||
func (l logging) ListAppRoleAssignments(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ListAppRoleAssignments(w, r)
|
||||
}
|
||||
|
||||
// CreateAppRoleAssignment implements the Service interface.
|
||||
func (l logging) CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.CreateAppRoleAssignment(w, r)
|
||||
}
|
||||
|
||||
// DeleteAppRoleAssignment implements the Service interface.
|
||||
func (l logging) DeleteAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.DeleteAppRoleAssignment(w, r)
|
||||
}
|
||||
|
||||
// GetGroups implements the Service interface.
|
||||
func (l logging) GetGroups(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetGroups(w, r)
|
||||
|
||||
@@ -32,6 +32,10 @@ const (
|
||||
// Service defines the service handlers.
|
||||
type Service interface {
|
||||
ServeHTTP(http.ResponseWriter, *http.Request)
|
||||
|
||||
ListApplications(w http.ResponseWriter, r *http.Request)
|
||||
GetApplication(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetMe(http.ResponseWriter, *http.Request)
|
||||
GetUsers(http.ResponseWriter, *http.Request)
|
||||
GetUser(http.ResponseWriter, *http.Request)
|
||||
@@ -40,6 +44,10 @@ type Service interface {
|
||||
PatchUser(http.ResponseWriter, *http.Request)
|
||||
ChangeOwnPassword(http.ResponseWriter, *http.Request)
|
||||
|
||||
ListAppRoleAssignments(http.ResponseWriter, *http.Request)
|
||||
CreateAppRoleAssignment(http.ResponseWriter, *http.Request)
|
||||
DeleteAppRoleAssignment(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetGroups(http.ResponseWriter, *http.Request)
|
||||
GetGroup(http.ResponseWriter, *http.Request)
|
||||
PostGroup(http.ResponseWriter, *http.Request)
|
||||
@@ -127,80 +135,8 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
identityEducationBackend: options.IdentityEducationBackend,
|
||||
}
|
||||
|
||||
if options.IdentityBackend == nil {
|
||||
switch options.Config.Identity.Backend {
|
||||
case "cs3":
|
||||
svc.identityBackend = &identity.CS3{
|
||||
Config: options.Config.Reva,
|
||||
Logger: &options.Logger,
|
||||
}
|
||||
case "ldap":
|
||||
var err error
|
||||
|
||||
var tlsConf *tls.Config
|
||||
if options.Config.Identity.LDAP.Insecure {
|
||||
// When insecure is set to true then we don't need a certificate.
|
||||
options.Config.Identity.LDAP.CACert = ""
|
||||
tlsConf = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
|
||||
InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
|
||||
}
|
||||
}
|
||||
|
||||
if options.Config.Identity.LDAP.CACert != "" {
|
||||
if err := ocisldap.WaitForCA(options.Logger,
|
||||
options.Config.Identity.LDAP.Insecure,
|
||||
options.Config.Identity.LDAP.CACert); err != nil {
|
||||
options.Logger.Fatal().Err(err).Msg("The configured LDAP CA cert does not exist")
|
||||
}
|
||||
if tlsConf == nil {
|
||||
tlsConf = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
certs := x509.NewCertPool()
|
||||
pemData, err := os.ReadFile(options.Config.Identity.LDAP.CACert)
|
||||
if err != nil {
|
||||
options.Logger.Error().Err(err).Msgf("Error initializing LDAP Backend")
|
||||
return svc, err
|
||||
}
|
||||
if !certs.AppendCertsFromPEM(pemData) {
|
||||
options.Logger.Error().Msgf("Error initializing LDAP Backend. Adding CA cert failed")
|
||||
return svc, err
|
||||
}
|
||||
tlsConf.RootCAs = certs
|
||||
}
|
||||
|
||||
conn := ldap.NewLDAPWithReconnect(&options.Logger,
|
||||
ldap.Config{
|
||||
URI: options.Config.Identity.LDAP.URI,
|
||||
BindDN: options.Config.Identity.LDAP.BindDN,
|
||||
BindPassword: options.Config.Identity.LDAP.BindPassword,
|
||||
TLSConfig: tlsConf,
|
||||
},
|
||||
)
|
||||
lb, err := identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger)
|
||||
if err != nil {
|
||||
options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
|
||||
return svc, err
|
||||
}
|
||||
svc.identityBackend = lb
|
||||
if options.IdentityEducationBackend == nil {
|
||||
if options.Config.Identity.LDAP.EducationResourcesEnabled {
|
||||
svc.identityEducationBackend = lb
|
||||
} else {
|
||||
errEduBackend := &identity.ErrEducationBackend{}
|
||||
svc.identityEducationBackend = errEduBackend
|
||||
}
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
|
||||
options.Logger.Err(err)
|
||||
return svc, err
|
||||
}
|
||||
} else {
|
||||
svc.identityBackend = options.IdentityBackend
|
||||
if err := setIdentityBackends(options, &svc); err != nil {
|
||||
return svc, err
|
||||
}
|
||||
|
||||
if options.PermissionService == nil {
|
||||
@@ -209,6 +145,12 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
svc.permissionsService = options.PermissionService
|
||||
}
|
||||
|
||||
if options.RoleService == nil {
|
||||
svc.roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient())
|
||||
} else {
|
||||
svc.roleService = options.RoleService
|
||||
}
|
||||
|
||||
roleManager := options.RoleManager
|
||||
if roleManager == nil {
|
||||
storeOptions := store.OcisStoreOptions{
|
||||
@@ -239,6 +181,10 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
r.Put("/tags", svc.AssignTags)
|
||||
r.Delete("/tags", svc.UnassignTags)
|
||||
})
|
||||
r.Route("/applications", func(r chi.Router) {
|
||||
r.Get("/", svc.ListApplications)
|
||||
r.Get("/{applicationID}", svc.GetApplication)
|
||||
})
|
||||
r.Route("/me", func(r chi.Router) {
|
||||
r.Get("/", svc.GetMe)
|
||||
r.Get("/drives", svc.GetDrives)
|
||||
@@ -252,6 +198,11 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
r.Get("/", svc.GetUser)
|
||||
r.With(requireAdmin).Delete("/", svc.DeleteUser)
|
||||
r.With(requireAdmin).Patch("/", svc.PatchUser)
|
||||
r.With(requireAdmin).Route("/appRoleAssignments", func(r chi.Router) {
|
||||
r.Get("/", svc.ListAppRoleAssignments)
|
||||
r.Post("/", svc.CreateAppRoleAssignment)
|
||||
r.Delete("/{appRoleAssignmentID}", svc.DeleteAppRoleAssignment)
|
||||
})
|
||||
})
|
||||
})
|
||||
r.Route("/groups", func(r chi.Router) {
|
||||
@@ -322,6 +273,88 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func setIdentityBackends(options Options, svc *Graph) error {
|
||||
if options.IdentityBackend == nil {
|
||||
switch options.Config.Identity.Backend {
|
||||
case "cs3":
|
||||
svc.identityBackend = &identity.CS3{
|
||||
Config: options.Config.Reva,
|
||||
Logger: &options.Logger,
|
||||
}
|
||||
case "ldap":
|
||||
var err error
|
||||
|
||||
var tlsConf *tls.Config
|
||||
if options.Config.Identity.LDAP.Insecure {
|
||||
|
||||
// When insecure is set to true then we don't need a certificate.
|
||||
options.Config.Identity.LDAP.CACert = ""
|
||||
tlsConf = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
|
||||
//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
|
||||
InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
|
||||
}
|
||||
}
|
||||
|
||||
if options.Config.Identity.LDAP.CACert != "" {
|
||||
if err := ocisldap.WaitForCA(options.Logger,
|
||||
options.Config.Identity.LDAP.Insecure,
|
||||
options.Config.Identity.LDAP.CACert); err != nil {
|
||||
options.Logger.Fatal().Err(err).Msg("The configured LDAP CA cert does not exist")
|
||||
}
|
||||
if tlsConf == nil {
|
||||
tlsConf = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
certs := x509.NewCertPool()
|
||||
pemData, err := os.ReadFile(options.Config.Identity.LDAP.CACert)
|
||||
if err != nil {
|
||||
options.Logger.Error().Err(err).Msg("Error initializing LDAP Backend")
|
||||
return err
|
||||
}
|
||||
if !certs.AppendCertsFromPEM(pemData) {
|
||||
options.Logger.Error().Msg("Error initializing LDAP Backend. Adding CA cert failed")
|
||||
return err
|
||||
}
|
||||
tlsConf.RootCAs = certs
|
||||
}
|
||||
|
||||
conn := ldap.NewLDAPWithReconnect(&options.Logger,
|
||||
ldap.Config{
|
||||
URI: options.Config.Identity.LDAP.URI,
|
||||
BindDN: options.Config.Identity.LDAP.BindDN,
|
||||
BindPassword: options.Config.Identity.LDAP.BindPassword,
|
||||
TLSConfig: tlsConf,
|
||||
},
|
||||
)
|
||||
lb, err := identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger)
|
||||
if err != nil {
|
||||
options.Logger.Error().Err(err).Msg("Error initializing LDAP Backend")
|
||||
return err
|
||||
}
|
||||
svc.identityBackend = lb
|
||||
if options.IdentityEducationBackend == nil {
|
||||
if options.Config.Identity.LDAP.EducationResourcesEnabled {
|
||||
svc.identityEducationBackend = lb
|
||||
} else {
|
||||
errEduBackend := &identity.ErrEducationBackend{}
|
||||
svc.identityEducationBackend = errEduBackend
|
||||
}
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("unknown identity backend: '%s'", options.Config.Identity.Backend)
|
||||
options.Logger.Err(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
svc.identityBackend = options.IdentityBackend
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseHeaderPurge parses the 'Purge' header.
|
||||
// '1', 't', 'T', 'TRUE', 'true', 'True' are parsed as true
|
||||
// all other values are false.
|
||||
|
||||
@@ -20,6 +20,16 @@ func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListApplications implements the Service interface.
|
||||
func (t tracing) ListApplications(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.ListApplications(w, r)
|
||||
}
|
||||
|
||||
// GetApplication implements the Service interface.
|
||||
func (t tracing) GetApplication(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetApplication(w, r)
|
||||
}
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
func (t tracing) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetMe(w, r)
|
||||
@@ -55,6 +65,21 @@ func (t tracing) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.ChangeOwnPassword(w, r)
|
||||
}
|
||||
|
||||
// ListAppRoleAssignments implements the Service interface.
|
||||
func (t tracing) ListAppRoleAssignments(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.ListAppRoleAssignments(w, r)
|
||||
}
|
||||
|
||||
// CreateAppRoleAssignment implements the Service interface.
|
||||
func (t tracing) CreateAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.CreateAppRoleAssignment(w, r)
|
||||
}
|
||||
|
||||
// DeleteAppRoleAssignment implements the Service interface.
|
||||
func (t tracing) DeleteAppRoleAssignment(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.DeleteAppRoleAssignment(w, r)
|
||||
}
|
||||
|
||||
// GetGroups implements the Service interface.
|
||||
func (t tracing) GetGroups(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetGroups(w, r)
|
||||
|
||||
@@ -58,6 +58,30 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// expand appRoleAssignments if requested
|
||||
if slices.Contains(exp, "appRoleAssignments") {
|
||||
lrar, err := g.roleService.ListRoleAssignments(r.Context(), &settings.ListRoleAssignmentsRequest{
|
||||
AccountUuid: me.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("userid", me.GetId()).Msg("could not get appRoleAssignments for self")
|
||||
var errcode errorcode.Error
|
||||
if errors.As(err, &errcode) {
|
||||
errcode.Render(w, r)
|
||||
} else {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
values := make([]libregraph.AppRoleAssignment, 0, len(lrar.GetAssignments()))
|
||||
for _, assignment := range lrar.GetAssignments() {
|
||||
values = append(values, g.assignmentToAppRoleAssignment(assignment))
|
||||
}
|
||||
me.AppRoleAssignments = values
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, me)
|
||||
}
|
||||
@@ -87,6 +111,26 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// expand appRoleAssignments if requested
|
||||
exp := strings.Split(r.URL.Query().Get("$expand"), ",")
|
||||
if slices.Contains(exp, "appRoleAssignments") {
|
||||
for _, u := range users {
|
||||
lrar, err := g.roleService.ListRoleAssignments(r.Context(), &settings.ListRoleAssignmentsRequest{
|
||||
AccountUuid: u.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("userid", u.GetId()).Msg("could not get appRoleAssignments when listing user")
|
||||
continue
|
||||
}
|
||||
|
||||
values := make([]libregraph.AppRoleAssignment, 0, len(lrar.GetAssignments()))
|
||||
for _, assignment := range lrar.GetAssignments() {
|
||||
values = append(values, g.assignmentToAppRoleAssignment(assignment))
|
||||
}
|
||||
u.AppRoleAssignments = values
|
||||
}
|
||||
}
|
||||
|
||||
users, err = sortUsers(odataReq, users)
|
||||
if err != nil {
|
||||
logger.Debug().Interface("query", odataReq).Msg("error while sorting users according to query")
|
||||
@@ -274,6 +318,29 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// expand appRoleAssignments if requested
|
||||
if slices.Contains(exp, "appRoleAssignments") {
|
||||
|
||||
lrar, err := g.roleService.ListRoleAssignments(r.Context(), &settings.ListRoleAssignmentsRequest{
|
||||
AccountUuid: user.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("userid", user.GetId()).Msg("could not get appRoleAssignments for user")
|
||||
var errcode errorcode.Error
|
||||
if errors.As(err, &errcode) {
|
||||
errcode.Render(w, r)
|
||||
} else {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
values := make([]libregraph.AppRoleAssignment, 0, len(lrar.GetAssignments()))
|
||||
for _, assignment := range lrar.GetAssignments() {
|
||||
values = append(values, g.assignmentToAppRoleAssignment(assignment))
|
||||
}
|
||||
user.AppRoleAssignments = values
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, user)
|
||||
|
||||
@@ -17,11 +17,13 @@ import (
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"go-micro.dev/v4/client"
|
||||
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
ogrpc "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/mocks"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
|
||||
@@ -67,6 +69,7 @@ var _ = Describe("Users", func() {
|
||||
cfg.TokenManager.JWTSecret = "loremipsum"
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
cfg.Application.ID = "some-application-ID"
|
||||
|
||||
_ = ogrpc.Configure(ogrpc.GetClientOptions(cfg.GRPCClientTLS)...)
|
||||
svc, _ = service.NewService(
|
||||
@@ -94,8 +97,13 @@ var _ = Describe("Users", func() {
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("expands the user", func() {
|
||||
user := &libregraph.User{}
|
||||
It("expands the memberOf", func() {
|
||||
user := &libregraph.User{
|
||||
Id: libregraph.PtrString("user1"),
|
||||
MemberOf: []libregraph.Group{
|
||||
{DisplayName: libregraph.PtrString("somegroup")},
|
||||
},
|
||||
}
|
||||
identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me?$expand=memberOf", nil)
|
||||
@@ -103,6 +111,48 @@ var _ = Describe("Users", func() {
|
||||
svc.GetMe(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
responseUser := &libregraph.User{}
|
||||
err = json.Unmarshal(data, &responseUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(responseUser.GetId()).To(Equal("user1"))
|
||||
Expect(responseUser.GetMemberOf()).To(HaveLen(1))
|
||||
Expect(responseUser.GetMemberOf()[0].GetDisplayName()).To(Equal("somegroup"))
|
||||
|
||||
})
|
||||
|
||||
It("expands the appRoleAssignments", func() {
|
||||
assignments := []*settingsmsg.UserRoleAssignment{
|
||||
{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: "user",
|
||||
RoleId: "some-appRole-ID",
|
||||
},
|
||||
}
|
||||
roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me?$expand=appRoleAssignments", nil)
|
||||
r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
|
||||
svc.GetMe(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
responseUser := &libregraph.User{}
|
||||
err = json.Unmarshal(data, &responseUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(responseUser.GetId()).To(Equal("user"))
|
||||
Expect(responseUser.GetAppRoleAssignments()).To(HaveLen(1))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetId()).To(Equal("some-appRoleAssignment-ID"))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetAppRoleId()).To(Equal("some-appRole-ID"))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetPrincipalId()).To(Equal("user"))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetResourceId()).To(Equal("some-application-ID"))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -216,6 +266,62 @@ var _ = Describe("Users", func() {
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
|
||||
It("expands the appRoleAssignments", func() {
|
||||
|
||||
user := &libregraph.User{}
|
||||
user.SetId("user1")
|
||||
user.SetMail("z@example.com")
|
||||
user.SetDisplayName("9")
|
||||
user.SetOnPremisesSamAccountName("9")
|
||||
user2 := &libregraph.User{}
|
||||
user2.SetId("user2")
|
||||
user2.SetMail("a@example.com")
|
||||
user2.SetDisplayName("1")
|
||||
user2.SetOnPremisesSamAccountName("1")
|
||||
users := []*libregraph.User{user, user2}
|
||||
identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(users, nil)
|
||||
|
||||
roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, in *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) *settings.ListRoleAssignmentsResponse {
|
||||
return &settings.ListRoleAssignmentsResponse{Assignments: []*settingsmsg.UserRoleAssignment{
|
||||
{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: in.GetAccountUuid(),
|
||||
RoleId: "some-appRole-ID",
|
||||
},
|
||||
}}
|
||||
}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$expand=appRoleAssignments", nil)
|
||||
r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
|
||||
svc.GetUsers(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
res := userList{}
|
||||
err = json.Unmarshal(data, &res)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
responseUsers := res.Value
|
||||
Expect(len(responseUsers)).To(Equal(2))
|
||||
Expect(responseUsers[0].GetId()).To(Equal("user1"))
|
||||
Expect(responseUsers[0].GetAppRoleAssignments()).To(HaveLen(1))
|
||||
Expect(responseUsers[0].GetAppRoleAssignments()[0].GetId()).To(Equal("some-appRoleAssignment-ID"))
|
||||
Expect(responseUsers[0].GetAppRoleAssignments()[0].GetAppRoleId()).To(Equal("some-appRole-ID"))
|
||||
Expect(responseUsers[0].GetAppRoleAssignments()[0].GetPrincipalId()).To(Equal("user1"))
|
||||
Expect(responseUsers[0].GetAppRoleAssignments()[0].GetResourceId()).To(Equal("some-application-ID"))
|
||||
|
||||
Expect(responseUsers[1].GetId()).To(Equal("user2"))
|
||||
Expect(responseUsers[1].GetAppRoleAssignments()).To(HaveLen(1))
|
||||
Expect(responseUsers[1].GetAppRoleAssignments()[0].GetId()).To(Equal("some-appRoleAssignment-ID"))
|
||||
Expect(responseUsers[1].GetAppRoleAssignments()[0].GetAppRoleId()).To(Equal("some-appRole-ID"))
|
||||
Expect(responseUsers[1].GetAppRoleAssignments()[0].GetPrincipalId()).To(Equal("user2"))
|
||||
Expect(responseUsers[1].GetAppRoleAssignments()[0].GetResourceId()).To(Equal("some-application-ID"))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetUser", func() {
|
||||
@@ -322,6 +428,44 @@ var _ = Describe("Users", func() {
|
||||
Expect(responseUser.GetId()).To(Equal("user1"))
|
||||
Expect(len(responseUser.GetDrives())).To(Equal(1))
|
||||
})
|
||||
|
||||
It("expands the appRoleAssignments", func() {
|
||||
user := &libregraph.User{}
|
||||
user.SetId("user1")
|
||||
|
||||
identityBackend.On("GetUser", mock.Anything, mock.Anything, mock.Anything).Return(user, nil)
|
||||
|
||||
assignments := []*settingsmsg.UserRoleAssignment{
|
||||
{
|
||||
Id: "some-appRoleAssignment-ID",
|
||||
AccountUuid: "user1",
|
||||
RoleId: "some-appRole-ID",
|
||||
},
|
||||
}
|
||||
roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(&settings.ListRoleAssignmentsResponse{Assignments: assignments}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users/user1?$expand=appRoleAssignments", nil)
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("userID", user.GetId())
|
||||
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
|
||||
svc.GetUser(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
data, err := io.ReadAll(rr.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
responseUser := &libregraph.User{}
|
||||
err = json.Unmarshal(data, &responseUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(responseUser.GetId()).To(Equal("user1"))
|
||||
Expect(responseUser.GetAppRoleAssignments()).To(HaveLen(1))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetId()).To(Equal("some-appRoleAssignment-ID"))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetAppRoleId()).To(Equal("some-appRole-ID"))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetPrincipalId()).To(Equal("user1"))
|
||||
Expect(responseUser.GetAppRoleAssignments()[0].GetResourceId()).To(Equal("some-application-ID"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("PostUser", func() {
|
||||
@@ -381,7 +525,7 @@ var _ = Describe("Users", func() {
|
||||
})
|
||||
|
||||
It("creates a user", func() {
|
||||
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settingssvc.AssignRoleToUserResponse{}, nil)
|
||||
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settings.AssignRoleToUserResponse{}, nil)
|
||||
identityBackend.On("CreateUser", mock.Anything, mock.Anything).Return(func(ctx context.Context, user libregraph.User) *libregraph.User {
|
||||
user.SetId("/users/user")
|
||||
return &user
|
||||
|
||||
@@ -79,7 +79,7 @@ func (g Service) CheckPermission(ctx context.Context, req *permissions.CheckPerm
|
||||
|
||||
permission, err := g.manager.ReadPermissionByName(req.Permission, roleIDs)
|
||||
if err != nil {
|
||||
if !errors.Is(err, settings.ErrPermissionNotFound) {
|
||||
if !errors.Is(err, settings.ErrNotFound) {
|
||||
return &permissions.CheckPermissionResponse{
|
||||
Status: status.NewInternal(ctx, err.Error()),
|
||||
}, nil
|
||||
|
||||
@@ -12,7 +12,12 @@ var (
|
||||
Registry = map[string]RegisterFunc{}
|
||||
|
||||
// ErrPermissionNotFound defines a new error for when a permission was not found
|
||||
//
|
||||
// Deprecated use the more generic ErrNotFound
|
||||
ErrPermissionNotFound = errors.New("permission not found")
|
||||
|
||||
// ErrNotFound is the error to use when a resource was not found.
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// RegisterFunc stores store constructors
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package errortypes
|
||||
|
||||
// BundleNotFound is the error to use when a bundle is not found.
|
||||
//
|
||||
// Deprecated: use the genreric services/settings/pkg/settings.NotFound error
|
||||
type BundleNotFound string
|
||||
|
||||
func (e BundleNotFound) Error() string { return "error: bundle not found: " + string(e) }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/store/errortypes"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/settings"
|
||||
)
|
||||
|
||||
var m = &sync.RWMutex{}
|
||||
@@ -111,7 +112,7 @@ func (s Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, err
|
||||
func (s Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) {
|
||||
bundle, err := s.ReadBundle(bundleID)
|
||||
if err != nil {
|
||||
if _, notFound := err.(errortypes.BundleNotFound); !notFound {
|
||||
if !errors.Is(err, settings.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
bundle = new(settingsmsg.Bundle)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/store/errortypes"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/settings"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
func (s Store) parseRecordFromFile(record proto.Message, filePath string) error {
|
||||
_, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return errortypes.BundleNotFound(err.Error())
|
||||
return fmt.Errorf("%q: %w", filePath, settings.ErrNotFound)
|
||||
}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
@@ -28,7 +29,7 @@ func (s Store) parseRecordFromFile(record proto.Message, filePath string) error
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return errortypes.BundleNotFound(filePath)
|
||||
return fmt.Errorf("%q: %w", filePath, settings.ErrNotFound)
|
||||
}
|
||||
|
||||
if err := protojson.Unmarshal(b, record); err != nil {
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, settings.ErrPermissionNotFound
|
||||
return nil, settings.ErrNotFound
|
||||
}
|
||||
|
||||
// extractPermissionsByResource collects all permissions from the provided role that match the requested resource
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/settings"
|
||||
)
|
||||
|
||||
// ListRoleAssignments loads and returns all role assignments matching the given assignment identifier.
|
||||
@@ -15,14 +17,24 @@ func (s *Store) ListRoleAssignments(accountUUID string) ([]*settingsmsg.UserRole
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
assIDs, err := s.mdc.ReadDir(ctx, accountPath(accountUUID))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return make([]*settingsmsg.UserRoleAssignment, 0), nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ass := make([]*settingsmsg.UserRoleAssignment, 0, len(assIDs))
|
||||
for _, assID := range assIDs {
|
||||
b, err := s.mdc.SimpleDownload(ctx, assignmentPath(accountUUID, assID))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
continue
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -42,10 +54,17 @@ func (s *Store) WriteRoleAssignment(accountUUID, roleID string) (*settingsmsg.Us
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
// as per https://github.com/owncloud/product/issues/103 "Each user can have exactly one role"
|
||||
_ = s.mdc.Delete(ctx, accountPath(accountUUID))
|
||||
// TODO: How to differentiate between 'not found' and other errors?
|
||||
err := s.mdc.Delete(ctx, accountPath(accountUUID))
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
// already gone, continue
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := s.mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID))
|
||||
err = s.mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,7 +86,12 @@ func (s *Store) RemoveRoleAssignment(assignmentID string) error {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
accounts, err := s.mdc.ReadDir(ctx, accountsFolderLocation)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return fmt.Errorf("assignmentID '%s' %w", assignmentID, settings.ErrNotFound)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -81,11 +105,13 @@ func (s *Store) RemoveRoleAssignment(assignmentID string) error {
|
||||
|
||||
for _, assID := range assIDs {
|
||||
if assID == assignmentID {
|
||||
return s.mdc.Delete(ctx, assignmentPath(accID, assID))
|
||||
// as per https://github.com/owncloud/product/issues/103 "Each user can have exactly one role"
|
||||
// we also have to delete the cached dir listing
|
||||
return s.mdc.Delete(ctx, accountPath(accID))
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("assignmentID '%s' not found", assignmentID)
|
||||
return fmt.Errorf("assignmentID '%s' %w", assignmentID, settings.ErrNotFound)
|
||||
}
|
||||
|
||||
func accountPath(accountUUID string) string {
|
||||
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/settings"
|
||||
)
|
||||
|
||||
// ListBundles returns all bundles in the dataPath folder that match the given type.
|
||||
@@ -18,7 +20,12 @@ func (s *Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []stri
|
||||
|
||||
if len(bundleIDs) == 0 {
|
||||
bIDs, err := s.mdc.ReadDir(ctx, bundleFolderLocation)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return make([]*settingsmsg.Bundle, 0), nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,7 +34,12 @@ func (s *Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []stri
|
||||
var bundles []*settingsmsg.Bundle
|
||||
for _, id := range bundleIDs {
|
||||
b, err := s.mdc.SimpleDownload(ctx, bundlePath(id))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
continue
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -50,7 +62,12 @@ func (s *Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
b, err := s.mdc.SimpleDownload(ctx, bundlePath(bundleID))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return nil, fmt.Errorf("bundleID '%s' %w", bundleID, settings.ErrNotFound)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -64,7 +81,12 @@ func (s *Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
ids, err := s.mdc.ReadDir(ctx, bundleFolderLocation)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return nil, fmt.Errorf("settingID '%s' %w", settingID, settings.ErrNotFound)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -72,6 +94,9 @@ func (s *Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) {
|
||||
for _, id := range ids {
|
||||
b, err := s.ReadBundle(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, settings.ErrNotFound) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -82,7 +107,7 @@ func (s *Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) {
|
||||
}
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("setting '%s' not found", settingID)
|
||||
return nil, fmt.Errorf("settingID '%s' %w", settingID, settings.ErrNotFound)
|
||||
}
|
||||
|
||||
// WriteBundle sends the givens record to the metadataclient. returns `record` for legacy reasons
|
||||
@@ -102,7 +127,9 @@ func (s *Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting
|
||||
s.Init()
|
||||
b, err := s.ReadBundle(bundleID)
|
||||
if err != nil {
|
||||
// TODO: How to differentiate 'not found'?
|
||||
if !errors.Is(err, settings.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
b = new(settingsmsg.Bundle)
|
||||
b.Id = bundleID
|
||||
b.Type = settingsmsg.Bundle_TYPE_DEFAULT
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s *Store) ReadPermissionByName(name string, roleIDs []string) (*settingsms
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, settings.ErrPermissionNotFound
|
||||
return nil, settings.ErrNotFound
|
||||
}
|
||||
|
||||
// extractPermissionsByResource collects all permissions from the provided role that match the requested resource
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults"
|
||||
)
|
||||
|
||||
@@ -53,9 +54,12 @@ func NewMDC(s *Store) error {
|
||||
return s.initMetadataClient(mdc)
|
||||
}
|
||||
|
||||
// SimpleDownload returns nil if not found
|
||||
// SimpleDownload returns errtypes.NotFound if not found
|
||||
func (m *MockedMetadataClient) SimpleDownload(_ context.Context, id string) ([]byte, error) {
|
||||
return m.data[id], nil
|
||||
if data, ok := m.data[id]; ok {
|
||||
return data, nil
|
||||
}
|
||||
return nil, errtypes.NotFound("not found")
|
||||
}
|
||||
|
||||
// SimpleUpload can't error
|
||||
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/settings"
|
||||
)
|
||||
|
||||
// ListValues reads all values that match the given bundleId and accountUUID.
|
||||
@@ -20,7 +22,12 @@ func (s *Store) ListValues(bundleID, accountUUID string) ([]*settingsmsg.Value,
|
||||
ctx := context.TODO()
|
||||
|
||||
vIDs, err := s.mdc.ReadDir(ctx, valuesFolderLocation)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return make([]*settingsmsg.Value, 0), nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -28,7 +35,12 @@ func (s *Store) ListValues(bundleID, accountUUID string) ([]*settingsmsg.Value,
|
||||
var values []*settingsmsg.Value
|
||||
for _, vid := range vIDs {
|
||||
b, err := s.mdc.SimpleDownload(ctx, valuePath(vid))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
continue
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,7 +73,12 @@ func (s *Store) ReadValue(valueID string) (*settingsmsg.Value, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
b, err := s.mdc.SimpleDownload(ctx, valuePath(valueID))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// continue
|
||||
case errtypes.NotFound:
|
||||
return nil, fmt.Errorf("valueID '%s' %w", valueID, settings.ErrNotFound)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
val := &settingsmsg.Value{}
|
||||
|
||||
Reference in New Issue
Block a user