From f9f937f8440aa4f77fe1486803c3acee569ef3ac Mon Sep 17 00:00:00 2001 From: Marcelo Rydel Date: Thu, 7 Apr 2022 10:22:31 -0300 Subject: [PATCH] feat(registries): Registry browser for non-admins [EE-2459] (#6549) * feat(registries): allow non-admin users to see environment registries * remove unused function * fix error message * fix test * fix imports order * feat(registry): check access first, add parameters name * use registryID * fix(sidebar): allow standard users to see endpoint registries view Co-authored-by: LP B --- .../endpoints/endpoint_registries_inspect.go | 63 --------- .../endpoints/endpoint_registries_list.go | 132 +++++++++++++----- api/http/handler/endpoints/handler.go | 2 - api/http/handler/registries/handler.go | 60 +++++--- .../handler/registries/registry_inspect.go | 10 +- .../registries/registry_update_test.go | 6 +- .../docker-sidebar/docker-sidebar.html | 66 ++++----- .../access/registryAccessController.js | 11 +- .../kubernetes-sidebar.html | 39 +++--- .../kube-registry-access-view.controller.js | 5 +- app/portainer/rest/endpoint.js | 5 - app/portainer/services/api/endpointService.js | 5 - .../services/registryGitlabService.js | 7 +- .../views/endpoint-registries/registries.html | 3 +- .../registriesController.js | 14 +- 15 files changed, 226 insertions(+), 202 deletions(-) delete mode 100644 api/http/handler/endpoints/endpoint_registries_inspect.go diff --git a/api/http/handler/endpoints/endpoint_registries_inspect.go b/api/http/handler/endpoints/endpoint_registries_inspect.go deleted file mode 100644 index 50a2a1641..000000000 --- a/api/http/handler/endpoints/endpoint_registries_inspect.go +++ /dev/null @@ -1,63 +0,0 @@ -package endpoints - -import ( - "net/http" - - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" - portainer "github.com/portainer/portainer/api" - httperrors "github.com/portainer/portainer/api/http/errors" - "github.com/portainer/portainer/api/http/security" -) - -// @id endpointRegistryInspect -// @summary get registry for environment -// @description **Access policy**: authenticated -// @tags endpoints -// @security ApiKeyAuth -// @security jwt -// @produce json -// @param id path int true "identifier" -// @param registryId path int true "Registry identifier" -// @success 200 {object} portainer.Registry "Success" -// @failure 400 "Invalid request" -// @failure 403 "Permission denied" -// @failure 404 "Registry not found" -// @failure 500 "Server error" -// @router /endpoints/{id}/registries/{registryId} [get] -func (handler *Handler) endpointRegistryInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid environment identifier route variable", Err: err} - } - - registryID, err := request.RetrieveNumericRouteVariableValue(r, "registryId") - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid registry identifier route variable", Err: err} - } - - registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return &httperror.HandlerError{StatusCode: http.StatusNotFound, Message: "Unable to find a registry with the specified identifier inside the database", Err: err} - } else if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to find a registry with the specified identifier inside the database", Err: err} - } - - securityContext, err := security.RetrieveRestrictedRequestContext(r) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve info from request context", Err: err} - } - - user, err := handler.DataStore.User().User(securityContext.UserID) - if err != nil { - return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve user from the database", Err: err} - } - - if !security.AuthorizedRegistryAccess(registry, user, securityContext.UserMemberships, portainer.EndpointID(endpointID)) { - return &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "Access denied to resource", Err: httperrors.ErrResourceAccessDenied} - } - - hideRegistryFields(registry, !securityContext.IsAdmin) - return response.JSON(w, registry) -} diff --git a/api/http/handler/endpoints/endpoint_registries_list.go b/api/http/handler/endpoints/endpoint_registries_list.go index 049e4aa94..81b28b099 100644 --- a/api/http/handler/endpoints/endpoint_registries_list.go +++ b/api/http/handler/endpoints/endpoint_registries_list.go @@ -55,29 +55,9 @@ func (handler *Handler) endpointRegistriesList(w http.ResponseWriter, r *http.Re return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err} } - if endpointutils.IsKubernetesEndpoint(endpoint) { - namespace, _ := request.RetrieveQueryParameter(r, "namespace", true) - - if namespace == "" && !isAdmin { - return &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "Missing namespace query parameter", Err: errors.New("missing namespace query parameter")} - } - - if namespace != "" { - - authorized, err := handler.isNamespaceAuthorized(endpoint, namespace, user.ID, securityContext.UserMemberships, isAdmin) - if err != nil { - return &httperror.HandlerError{http.StatusNotFound, "Unable to check for namespace authorization", err} - } - - if !authorized { - return &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "User is not authorized to use namespace", Err: errors.New("user is not authorized to use namespace")} - } - - registries = filterRegistriesByNamespace(registries, endpoint.ID, namespace) - } - - } else if !isAdmin { - registries = security.FilterRegistries(registries, user, securityContext.UserMemberships, endpoint.ID) + registries, handleError := handler.filterRegistriesByAccess(r, registries, endpoint, user, securityContext.UserMemberships) + if handleError != nil { + return handleError } for idx := range registries { @@ -87,6 +67,40 @@ func (handler *Handler) endpointRegistriesList(w http.ResponseWriter, r *http.Re return response.JSON(w, registries) } +func (handler *Handler) filterRegistriesByAccess(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User, memberships []portainer.TeamMembership) ([]portainer.Registry, *httperror.HandlerError) { + if !endpointutils.IsKubernetesEndpoint(endpoint) { + return security.FilterRegistries(registries, user, memberships, endpoint.ID), nil + } + + return handler.filterKubernetesEndpointRegistries(r, registries, endpoint, user, memberships) +} + +func (handler *Handler) filterKubernetesEndpointRegistries(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User, memberships []portainer.TeamMembership) ([]portainer.Registry, *httperror.HandlerError) { + namespaceParam, _ := request.RetrieveQueryParameter(r, "namespace", true) + isAdmin, err := security.IsAdmin(r) + if err != nil { + return nil, &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check user role", Err: err} + } + + if namespaceParam != "" { + authorized, err := handler.isNamespaceAuthorized(endpoint, namespaceParam, user.ID, memberships, isAdmin) + if err != nil { + return nil, &httperror.HandlerError{StatusCode: http.StatusNotFound, Message: "Unable to check for namespace authorization", Err: err} + } + if !authorized { + return nil, &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "User is not authorized to use namespace", Err: errors.New("user is not authorized to use namespace")} + } + + return filterRegistriesByNamespaces(registries, endpoint.ID, []string{namespaceParam}), nil + } + + if isAdmin { + return registries, nil + } + + return handler.filterKubernetesRegistriesByUserRole(r, registries, endpoint, user) +} + func (handler *Handler) isNamespaceAuthorized(endpoint *portainer.Endpoint, namespace string, userId portainer.UserID, memberships []portainer.TeamMembership, isAdmin bool) (bool, error) { if isAdmin || namespace == "" { return true, nil @@ -114,24 +128,78 @@ func (handler *Handler) isNamespaceAuthorized(endpoint *portainer.Endpoint, name return security.AuthorizedAccess(userId, memberships, namespacePolicy.UserAccessPolicies, namespacePolicy.TeamAccessPolicies), nil } -func filterRegistriesByNamespace(registries []portainer.Registry, endpointId portainer.EndpointID, namespace string) []portainer.Registry { - if namespace == "" { - return registries - } - +func filterRegistriesByNamespaces(registries []portainer.Registry, endpointId portainer.EndpointID, namespaces []string) []portainer.Registry { filteredRegistries := []portainer.Registry{} for _, registry := range registries { - for _, authorizedNamespace := range registry.RegistryAccesses[endpointId].Namespaces { - if authorizedNamespace == namespace { - filteredRegistries = append(filteredRegistries, registry) - } + if registryAccessPoliciesContainsNamespace(registry.RegistryAccesses[endpointId], namespaces) { + filteredRegistries = append(filteredRegistries, registry) } } return filteredRegistries } +func registryAccessPoliciesContainsNamespace(registryAccess portainer.RegistryAccessPolicies, namespaces []string) bool { + for _, authorizedNamespace := range registryAccess.Namespaces { + for _, namespace := range namespaces { + if namespace == authorizedNamespace { + return true + } + } + } + return false +} + +func (handler *Handler) filterKubernetesRegistriesByUserRole(r *http.Request, registries []portainer.Registry, endpoint *portainer.Endpoint, user *portainer.User) ([]portainer.Registry, *httperror.HandlerError) { + err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) + if err == security.ErrAuthorizationRequired { + return nil, &httperror.HandlerError{StatusCode: http.StatusForbidden, Message: "User is not authorized", Err: errors.New("missing namespace query parameter")} + } + if err != nil { + return nil, &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve info from request context", Err: err} + } + + userNamespaces, err := handler.userNamespaces(endpoint, user) + if err != nil { + return nil, &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "unable to retrieve user namespaces", Err: err} + } + + return filterRegistriesByNamespaces(registries, endpoint.ID, userNamespaces), nil +} + +func (handler *Handler) userNamespaces(endpoint *portainer.Endpoint, user *portainer.User) ([]string, error) { + kcl, err := handler.K8sClientFactory.GetKubeClient(endpoint) + if err != nil { + return nil, err + } + + namespaceAuthorizations, err := kcl.GetNamespaceAccessPolicies() + if err != nil { + return nil, err + } + + userMemberships, err := handler.DataStore.TeamMembership().TeamMembershipsByUserID(user.ID) + if err != nil { + return nil, err + } + + var userNamespaces []string + for namespace, namespaceAuthorization := range namespaceAuthorizations { + if _, ok := namespaceAuthorization.UserAccessPolicies[user.ID]; ok { + userNamespaces = append(userNamespaces, namespace) + continue + } + for _, userTeam := range userMemberships { + if _, ok := namespaceAuthorization.TeamAccessPolicies[userTeam.TeamID]; ok { + userNamespaces = append(userNamespaces, namespace) + continue + } + } + } + return userNamespaces, nil +} + func hideRegistryFields(registry *portainer.Registry, hideAccesses bool) { registry.Password = "" registry.ManagementConfiguration = nil diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index 99de0e2de..e4de0bad3 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -66,8 +66,6 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { bouncer.AdminAccess(httperror.LoggerHandler(h.endpointSnapshot))).Methods(http.MethodPost) h.Handle("/endpoints/{id}/registries", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointRegistriesList))).Methods(http.MethodGet) - h.Handle("/endpoints/{id}/registries/{registryId}", - bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointRegistryInspect))).Methods(http.MethodGet) h.Handle("/endpoints/{id}/registries/{registryId}", bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointRegistryAccess))).Methods(http.MethodPut) return h diff --git a/api/http/handler/registries/handler.go b/api/http/handler/registries/handler.go index cfa414049..10760c34a 100644 --- a/api/http/handler/registries/handler.go +++ b/api/http/handler/registries/handler.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/mux" httperror "github.com/portainer/libhttp/error" + "github.com/portainer/libhttp/request" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/http/proxy" @@ -45,27 +46,27 @@ func newHandler(bouncer *security.RequestBouncer) *Handler { } } -func (h *Handler) initRouter(bouncer accessGuard) { - h.Handle("/registries", - bouncer.AdminAccess(httperror.LoggerHandler(h.registryCreate))).Methods(http.MethodPost) - h.Handle("/registries", - bouncer.RestrictedAccess(httperror.LoggerHandler(h.registryList))).Methods(http.MethodGet) - h.Handle("/registries/{id}", - bouncer.RestrictedAccess(httperror.LoggerHandler(h.registryInspect))).Methods(http.MethodGet) - h.Handle("/registries/{id}", - bouncer.AdminAccess(httperror.LoggerHandler(h.registryUpdate))).Methods(http.MethodPut) - h.Handle("/registries/{id}/configure", - bouncer.AdminAccess(httperror.LoggerHandler(h.registryConfigure))).Methods(http.MethodPost) - h.Handle("/registries/{id}", - bouncer.AdminAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete) - h.PathPrefix("/registries/proxies/gitlab").Handler( - bouncer.AdminAccess(httperror.LoggerHandler(h.proxyRequestsToGitlabAPIWithoutRegistry))) +func (handler *Handler) initRouter(bouncer accessGuard) { + adminRouter := handler.NewRoute().Subrouter() + adminRouter.Use(bouncer.AdminAccess) + + authenticatedRouter := handler.NewRoute().Subrouter() + authenticatedRouter.Use(bouncer.AuthenticatedAccess) + + adminRouter.Handle("/registries", httperror.LoggerHandler(handler.registryList)).Methods(http.MethodGet) + adminRouter.Handle("/registries", httperror.LoggerHandler(handler.registryCreate)).Methods(http.MethodPost) + adminRouter.Handle("/registries/{id}", httperror.LoggerHandler(handler.registryUpdate)).Methods(http.MethodPut) + adminRouter.Handle("/registries/{id}/configure", httperror.LoggerHandler(handler.registryConfigure)).Methods(http.MethodPost) + adminRouter.Handle("/registries/{id}", httperror.LoggerHandler(handler.registryDelete)).Methods(http.MethodDelete) + + authenticatedRouter.Handle("/registries/{id}", httperror.LoggerHandler(handler.registryInspect)).Methods(http.MethodGet) + authenticatedRouter.PathPrefix("/registries/proxies/gitlab").Handler(httperror.LoggerHandler(handler.proxyRequestsToGitlabAPIWithoutRegistry)) } type accessGuard interface { AdminAccess(h http.Handler) http.Handler - RestrictedAccess(h http.Handler) http.Handler AuthenticatedAccess(h http.Handler) http.Handler + AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error } func (handler *Handler) registriesHaveSameURLAndCredentials(r1, r2 *portainer.Registry) bool { @@ -78,3 +79,30 @@ func (handler *Handler) registriesHaveSameURLAndCredentials(r1, r2 *portainer.Re return hasSameUrl && hasSameCredentials && r1.Gitlab.ProjectPath == r2.Gitlab.ProjectPath } + +func (handler *Handler) userHasRegistryAccess(r *http.Request) (hasAccess bool, isAdmin bool, err error) { + securityContext, err := security.RetrieveRestrictedRequestContext(r) + if err != nil { + return false, false, err + } + + if securityContext.IsAdmin { + return true, true, nil + } + + endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false) + if err != nil { + return false, false, err + } + endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) + if err != nil { + return false, false, err + } + + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) + if err != nil { + return false, false, err + } + + return true, false, nil +} diff --git a/api/http/handler/registries/registry_inspect.go b/api/http/handler/registries/registry_inspect.go index 99ba68ec7..600873f91 100644 --- a/api/http/handler/registries/registry_inspect.go +++ b/api/http/handler/registries/registry_inspect.go @@ -8,6 +8,7 @@ import ( httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" + httperrors "github.com/portainer/portainer/api/http/errors" ) // @id RegistryInspect @@ -26,6 +27,13 @@ import ( // @failure 500 "Server error" // @router /registries/{id} [get] func (handler *Handler) registryInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + hasAccess, isAdmin, err := handler.userHasRegistryAccess(r) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err} + } + if !hasAccess { + return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", httperrors.ErrResourceAccessDenied} + } registryID, err := request.RetrieveNumericRouteVariableValue(r, "id") if err != nil { @@ -39,6 +47,6 @@ func (handler *Handler) registryInspect(w http.ResponseWriter, r *http.Request) return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err} } - hideFields(registry, false) + hideFields(registry, !isAdmin) return response.JSON(w, registry) } diff --git a/api/http/handler/registries/registry_update_test.go b/api/http/handler/registries/registry_update_test.go index 73bf65d64..2516b097b 100644 --- a/api/http/handler/registries/registry_update_test.go +++ b/api/http/handler/registries/registry_update_test.go @@ -26,12 +26,12 @@ func (t TestBouncer) AdminAccess(h http.Handler) http.Handler { return h } -func (t TestBouncer) RestrictedAccess(h http.Handler) http.Handler { +func (t TestBouncer) AuthenticatedAccess(h http.Handler) http.Handler { return h } -func (t TestBouncer) AuthenticatedAccess(h http.Handler) http.Handler { - return h +func (t TestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error { + return nil } // TODO, no i don't know what this is actually intended to test either. diff --git a/app/docker/components/docker-sidebar/docker-sidebar.html b/app/docker/components/docker-sidebar/docker-sidebar.html index 7992dd60c..ad5762461 100644 --- a/app/docker/components/docker-sidebar/docker-sidebar.html +++ b/app/docker/components/docker-sidebar/docker-sidebar.html @@ -102,27 +102,20 @@ is-sidebar-open="$ctrl.isSidebarOpen" children-paths="['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration']" > -
- - Setup - + + Setup + - - Registries - -
+ + Registries + -
- - Setup - + + Setup + - - Registries - -
+ + Registries +
diff --git a/app/docker/views/registries/access/registryAccessController.js b/app/docker/views/registries/access/registryAccessController.js index c30d25dd4..c3b249d0e 100644 --- a/app/docker/views/registries/access/registryAccessController.js +++ b/app/docker/views/registries/access/registryAccessController.js @@ -2,12 +2,13 @@ import { TeamAccessViewModel, UserAccessViewModel } from 'Portainer/models/acces class DockerRegistryAccessController { /* @ngInject */ - constructor($async, $state, Notifications, EndpointService, GroupService) { + constructor($async, $state, Notifications, EndpointService, GroupService, RegistryService) { this.$async = $async; this.$state = $state; this.Notifications = Notifications; this.EndpointService = EndpointService; this.GroupService = GroupService; + this.RegistryService = RegistryService; this.updateAccess = this.updateAccess.bind(this); this.filterUsers = this.filterUsers.bind(this); @@ -35,10 +36,10 @@ class DockerRegistryAccessController { const endpointGroupTeams = this.endpointGroup.TeamAccessPolicies; return users.filter((userOrTeam) => { - const userRole = userOrTeam instanceof UserAccessViewModel && (endpointUsers[userOrTeam.Id] || endpointGroupUsers[userOrTeam.Id]); - const teamRole = userOrTeam instanceof TeamAccessViewModel && (endpointTeams[userOrTeam.Id] || endpointGroupTeams[userOrTeam.Id]); + const userAccess = userOrTeam instanceof UserAccessViewModel && (endpointUsers[userOrTeam.Id] || endpointGroupUsers[userOrTeam.Id]); + const teamAccess = userOrTeam instanceof TeamAccessViewModel && (endpointTeams[userOrTeam.Id] || endpointGroupTeams[userOrTeam.Id]); - return userRole || teamRole; + return userAccess || teamAccess; }); } @@ -51,7 +52,7 @@ class DockerRegistryAccessController { endpointId: this.$state.params.endpointId, registryId: this.$state.params.id, }; - this.registry = await this.EndpointService.registry(this.state.endpointId, this.state.registryId); + this.registry = await this.RegistryService.registry(this.state.registryId, this.state.endpointId); this.registryEndpointAccesses = this.registry.RegistryAccesses[this.state.endpointId] || {}; this.endpointGroup = await this.GroupService.group(this.endpoint.GroupId); } catch (err) { diff --git a/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html b/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html index 8d351715f..abdc16274 100644 --- a/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html +++ b/app/kubernetes/components/kubernetes-sidebar/kubernetes-sidebar.html @@ -71,25 +71,24 @@ children-paths="['kubernetes.cluster', 'portainer.k8sendpoint.kubernetesConfig', 'kubernetes.registries', 'kubernetes.registries.access']" data-cy="k8sSidebar-cluster" > -
- - Setup - + + Setup + - - Registries - -
+ + Registries + diff --git a/app/kubernetes/registries/kube-registry-access-view/kube-registry-access-view.controller.js b/app/kubernetes/registries/kube-registry-access-view/kube-registry-access-view.controller.js index 3eb4e1c85..5458ee6f2 100644 --- a/app/kubernetes/registries/kube-registry-access-view/kube-registry-access-view.controller.js +++ b/app/kubernetes/registries/kube-registry-access-view/kube-registry-access-view.controller.js @@ -2,12 +2,13 @@ import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper'; export default class KubernetesRegistryAccessController { /* @ngInject */ - constructor($async, $state, ModalService, EndpointService, Notifications, KubernetesResourcePoolService) { + constructor($async, $state, ModalService, EndpointService, Notifications, RegistryService, KubernetesResourcePoolService) { this.$async = $async; this.$state = $state; this.ModalService = ModalService; this.Notifications = Notifications; this.KubernetesResourcePoolService = KubernetesResourcePoolService; + this.RegistryService = RegistryService; this.EndpointService = EndpointService; this.state = { @@ -57,7 +58,7 @@ export default class KubernetesRegistryAccessController { this.state = { registryId: this.$state.params.id, }; - this.registry = await this.EndpointService.registry(this.endpoint.Id, this.state.registryId); + this.registry = await this.RegistryService.registry(this.state.registryId, this.endpoint.Id); if (this.registry.RegistryAccesses && this.registry.RegistryAccesses[this.endpoint.Id]) { this.savedResourcePools = this.registry.RegistryAccesses[this.endpoint.Id].Namespaces.map((value) => ({ value })); } diff --git a/app/portainer/rest/endpoint.js b/app/portainer/rest/endpoint.js index 9e25024c2..47f82ae93 100644 --- a/app/portainer/rest/endpoint.js +++ b/app/portainer/rest/endpoint.js @@ -33,11 +33,6 @@ angular.module('portainer.app').factory('Endpoints', [ params: { id: '@id', namespace: '@namespace' }, isArray: true, }, - registry: { - url: `${API_ENDPOINT_ENDPOINTS}/:id/registries/:registryId`, - method: 'GET', - params: { id: '@id', namespace: '@namespace', registryId: '@registryId' }, - }, updateRegistryAccess: { method: 'PUT', url: `${API_ENDPOINT_ENDPOINTS}/:id/registries/:registryId`, diff --git a/app/portainer/services/api/endpointService.js b/app/portainer/services/api/endpointService.js index 51a4fc341..7d8b75b5c 100644 --- a/app/portainer/services/api/endpointService.js +++ b/app/portainer/services/api/endpointService.js @@ -9,7 +9,6 @@ angular.module('portainer.app').factory('EndpointService', [ var service = { updateSecuritySettings, registries, - registry, updateRegistryAccess, }; @@ -184,9 +183,5 @@ angular.module('portainer.app').factory('EndpointService', [ function updateSecuritySettings(id, securitySettings) { return Endpoints.updateSecuritySettings({ id }, securitySettings).$promise; } - - function registry(endpointId, registryId) { - return Endpoints.registry({ registryId, id: endpointId }).$promise; - } }, ]); diff --git a/app/portainer/services/registryGitlabService.js b/app/portainer/services/registryGitlabService.js index f1115cbf2..7849cad5d 100644 --- a/app/portainer/services/registryGitlabService.js +++ b/app/portainer/services/registryGitlabService.js @@ -50,10 +50,11 @@ angular.module('portainer.app').factory('RegistryGitlabService', [ return repositories; } - async function repositoriesAsync(registry) { + async function repositoriesAsync(registry, endpointId) { try { const params = { id: registry.Id, + endpointId: endpointId, projectId: registry.Gitlab.ProjectId, page: 1, }; @@ -76,8 +77,8 @@ angular.module('portainer.app').factory('RegistryGitlabService', [ return $async(projectsAsync, url, token); } - function repositories(registry) { - return $async(repositoriesAsync, registry); + function repositories(registry, endpointId) { + return $async(repositoriesAsync, registry, endpointId); } service.projects = projects; diff --git a/app/portainer/views/endpoint-registries/registries.html b/app/portainer/views/endpoint-registries/registries.html index 5ed4f74b3..b1dca2536 100644 --- a/app/portainer/views/endpoint-registries/registries.html +++ b/app/portainer/views/endpoint-registries/registries.html @@ -4,7 +4,7 @@ - Manage registry access inside this environment + Registry management
@@ -16,6 +16,7 @@ order-by="Name" endpoint-type="$ctrl.endpointType" can-manage-access="$ctrl.canManageAccess" + can-browse="$ctrl.canBrowse" >
diff --git a/app/portainer/views/endpoint-registries/registriesController.js b/app/portainer/views/endpoint-registries/registriesController.js index 857d9cf6c..72be8f17a 100644 --- a/app/portainer/views/endpoint-registries/registriesController.js +++ b/app/portainer/views/endpoint-registries/registriesController.js @@ -1,24 +1,30 @@ +import _ from 'lodash-es'; import { RegistryTypes } from 'Portainer/models/registryTypes'; class EndpointRegistriesController { /* @ngInject */ - constructor($async, Notifications, EndpointService) { + constructor($async, Notifications, EndpointService, Authentication) { this.$async = $async; this.Notifications = Notifications; this.EndpointService = EndpointService; + this.Authentication = Authentication; this.canManageAccess = this.canManageAccess.bind(this); + this.canBrowse = this.canBrowse.bind(this); } canManageAccess(item) { - return item.Type !== RegistryTypes.ANONYMOUS; + return item.Type !== RegistryTypes.ANONYMOUS && this.Authentication.isAdmin(); + } + + canBrowse(item) { + return !_.includes([RegistryTypes.ANONYMOUS, RegistryTypes.DOCKERHUB, RegistryTypes.QUAY], item.Type); } getRegistries() { return this.$async(async () => { try { - const registries = await this.EndpointService.registries(this.endpointId); - this.registries = registries; + this.registries = await this.EndpointService.registries(this.endpointId); } catch (err) { this.Notifications.error('Failure', err, 'Unable to retrieve registries'); }