Skip to content

Commit

Permalink
graph: add appRoleAssignments and minimal application resource (#5318)
Browse files Browse the repository at this point in the history
* bump libregraph-go lib

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* add appRoleAssignment stubs

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* add get application stub

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* fetch appRoles for application from settings service

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* initial list appRoleAssignments implementation

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* initial create appRoleAssignment implementation, extract assignmentToAppRoleAssignment, configurable app id and displayname

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* initial delete appRoleAssignment implementation, changed error handling and logging

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* initial expand appRoleAssignment on users

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* test user expand appRoleAssignment

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* test appRoleAssignment

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* fix education test by actually using the mocked roleManager

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* test getapplication

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* list assignments

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* use common not exists error handling

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* default to just 'ownCloud Infinite Scale' as application name

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* fix store_test

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* roll application uuid on init

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* fix tests

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* extract method

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>

* Apply suggestions from code review

Co-authored-by: Michael Barz <[email protected]>

Signed-off-by: Jörn Friedrich Dreyer <[email protected]>
Co-authored-by: Michael Barz <[email protected]>
  • Loading branch information
butonic and micbar authored Jan 12, 2023
1 parent 1b6c269 commit 078698f
Show file tree
Hide file tree
Showing 29 changed files with 1,085 additions and 115 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
14 changes: 11 additions & 3 deletions ocis/pkg/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -306,6 +311,9 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin
},
},
Graph: GraphService{
Application: GraphApplication{
ID: graphApplicationID,
},
Identity: LdapBasedService{
Ldap: LdapSettings{
BindPassword: idmServicePassword,
Expand Down
7 changes: 7 additions & 0 deletions services/graph/pkg/config/application.go
Original file line number Diff line number Diff line change
@@ -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"`
}
7 changes: 4 additions & 3 deletions services/graph/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
}
Expand Down
3 changes: 3 additions & 0 deletions services/graph/pkg/config/defaults/defaultconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
10 changes: 10 additions & 0 deletions services/graph/pkg/config/parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 changes: 77 additions & 0 deletions services/graph/pkg/service/v0/application.go
Original file line number Diff line number Diff line change
@@ -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 changes: 135 additions & 0 deletions services/graph/pkg/service/v0/application_test.go
Original file line number Diff line number Diff line change
@@ -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"))
})
})

})
Loading

0 comments on commit 078698f

Please sign in to comment.