From e8e361d32f45826b77be3cbae84ef44196256b62 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Thu, 17 Feb 2022 12:14:49 +0100 Subject: [PATCH] Add permission checks to `users` and `groups` Graph API Only users with the account management permission should be able to create, update or delete users. This also restricts access to the APIs that allow listing all Groups/all Users. Fixes #3177 --- graph/pkg/middleware/requireadmin.go | 53 ++++++++++++++++++++++++++++ graph/pkg/service/v0/option.go | 18 ++++++++++ graph/pkg/service/v0/service.go | 44 +++++++++++++++++------ 3 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 graph/pkg/middleware/requireadmin.go diff --git a/graph/pkg/middleware/requireadmin.go b/graph/pkg/middleware/requireadmin.go new file mode 100644 index 0000000000..ee1c82fe2d --- /dev/null +++ b/graph/pkg/middleware/requireadmin.go @@ -0,0 +1,53 @@ +package middleware + +import ( + "net/http" + + revactx "github.com/cs3org/reva/pkg/ctx" + accounts "github.com/owncloud/ocis/accounts/pkg/service/v0" + "github.com/owncloud/ocis/graph/pkg/service/v0/errorcode" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/roles" +) + +// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions +func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.Handler) http.Handler { + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + u, ok := revactx.ContextGetUser(r.Context()) + if !ok { + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + return + } + if u.Id == nil || u.Id.OpaqueId == "" { + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is missing an id") + return + } + // get roles from context + roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) + if !ok { + logger.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service") + var err error + roleIDs, err = rm.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId) + if err != nil { + logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user") + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + return + } + if len(roleIDs) == 0 { + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + return + } + } + + // check if permission is present in roles of the authenticated account + if rm.FindPermissionByID(r.Context(), roleIDs, accounts.AccountManagementPermissionID) != nil { + next.ServeHTTP(w, r) + return + } + + errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") + }) + } +} diff --git a/graph/pkg/service/v0/option.go b/graph/pkg/service/v0/option.go index a60edd85a5..cd19048774 100644 --- a/graph/pkg/service/v0/option.go +++ b/graph/pkg/service/v0/option.go @@ -5,6 +5,8 @@ import ( "github.com/owncloud/ocis/graph/pkg/config" "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/roles" + settingssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/settings/v0" ) // Option defines a single option function. @@ -17,6 +19,8 @@ type Options struct { Middleware []func(http.Handler) http.Handler GatewayClient GatewayClient HTTPClient HTTPClient + RoleService settingssvc.RoleService + RoleManager *roles.Manager } // newOptions initializes the available default options. @@ -64,3 +68,17 @@ func WithHTTPClient(val HTTPClient) Option { o.HTTPClient = val } } + +// RoleService provides a function to set the RoleService option. +func RoleService(val settingssvc.RoleService) Option { + return func(o *Options) { + o.RoleService = val + } +} + +// RoleManager provides a function to set the RoleManager option. +func RoleManager(val *roles.Manager) Option { + return func(o *Options) { + o.RoleManager = val + } +} diff --git a/graph/pkg/service/v0/service.go b/graph/pkg/service/v0/service.go index d8753ad940..a2bf0567b8 100644 --- a/graph/pkg/service/v0/service.go +++ b/graph/pkg/service/v0/service.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "net/http" "strconv" + "time" "github.com/ReneKroon/ttlcache/v2" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" @@ -12,8 +13,12 @@ import ( "github.com/owncloud/ocis/graph/pkg/identity" "github.com/owncloud/ocis/graph/pkg/identity/ldap" + graphm "github.com/owncloud/ocis/graph/pkg/middleware" "github.com/owncloud/ocis/ocis-pkg/account" opkgm "github.com/owncloud/ocis/ocis-pkg/middleware" + "github.com/owncloud/ocis/ocis-pkg/roles" + "github.com/owncloud/ocis/ocis-pkg/service/grpc" + settingssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/settings/v0" ) const ( @@ -111,6 +116,23 @@ func NewService(opts ...Option) Service { svc.httpClient = options.HTTPClient } + roleService := options.RoleService + if roleService == nil { + roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient) + } + roleManager := options.RoleManager + if roleManager == nil { + m := roles.NewManager( + roles.CacheSize(1024), + roles.CacheTTL(time.Hour), + roles.Logger(options.Logger), + roles.RoleService(roleService), + ) + roleManager = &m + } + + requireAdmin := graphm.RequireAdmin(roleManager, options.Logger) + m.Route(options.Config.HTTP.Root, func(r chi.Router) { r.Use(middleware.StripSlashes) r.Route("/v1.0", func(r chi.Router) { @@ -120,25 +142,25 @@ func NewService(opts ...Option) Service { r.Get("/drive/root/children", svc.GetRootDriveChildren) }) r.Route("/users", func(r chi.Router) { - r.Get("/", svc.GetUsers) - r.Post("/", svc.PostUser) + r.With(requireAdmin).Get("/", svc.GetUsers) + r.With(requireAdmin).Post("/", svc.PostUser) r.Route("/{userID}", func(r chi.Router) { r.Get("/", svc.GetUser) - r.Delete("/", svc.DeleteUser) - r.Patch("/", svc.PatchUser) + r.With(requireAdmin).Delete("/", svc.DeleteUser) + r.With(requireAdmin).Patch("/", svc.PatchUser) }) }) r.Route("/groups", func(r chi.Router) { - r.Get("/", svc.GetGroups) - r.Post("/", svc.PostGroup) + r.With(requireAdmin).Get("/", svc.GetGroups) + r.With(requireAdmin).Post("/", svc.PostGroup) r.Route("/{groupID}", func(r chi.Router) { r.Get("/", svc.GetGroup) - r.Delete("/", svc.DeleteGroup) - r.Patch("/", svc.PatchGroup) + r.With(requireAdmin).Delete("/", svc.DeleteGroup) + r.With(requireAdmin).Patch("/", svc.PatchGroup) r.Route("/members", func(r chi.Router) { - r.Get("/", svc.GetGroupMembers) - r.Post("/$ref", svc.PostGroupMember) - r.Delete("/{memberID}/$ref", svc.DeleteGroupMember) + r.With(requireAdmin).Get("/", svc.GetGroupMembers) + r.With(requireAdmin).Post("/$ref", svc.PostGroupMember) + r.With(requireAdmin).Delete("/{memberID}/$ref", svc.DeleteGroupMember) }) }) })