mirror of https://github.com/portainer/portainer
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 <xAt0mZ@users.noreply.github.com>pull/6735/head
parent
77e48bfb74
commit
f9f937f844
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -102,27 +102,20 @@
|
|||
is-sidebar-open="$ctrl.isSidebarOpen"
|
||||
children-paths="['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration']"
|
||||
>
|
||||
<div ng-if="$ctrl.adminAccess">
|
||||
<sidebar-menu-item
|
||||
authorization="PortainerEndpointUpdateSettings"
|
||||
path="docker.featuresConfiguration"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="dockerSidebar-setup"
|
||||
>
|
||||
Setup
|
||||
</sidebar-menu-item>
|
||||
<sidebar-menu-item
|
||||
ng-if="$ctrl.adminAccess"
|
||||
authorization="PortainerEndpointUpdateSettings"
|
||||
path="docker.featuresConfiguration"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="dockerSidebar-setup"
|
||||
>
|
||||
Setup
|
||||
</sidebar-menu-item>
|
||||
|
||||
<sidebar-menu-item
|
||||
authorization="PortainerRegistryList"
|
||||
path="docker.registries"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="dockerSidebar-registries"
|
||||
>
|
||||
Registries
|
||||
</sidebar-menu-item>
|
||||
</div>
|
||||
<sidebar-menu-item path="docker.registries" path-params="{ endpointId: $ctrl.endpointId }" class-name="sidebar-sublist" data-cy="dockerSidebar-registries">
|
||||
Registries
|
||||
</sidebar-menu-item>
|
||||
</sidebar-menu>
|
||||
|
||||
<sidebar-menu
|
||||
|
@ -134,25 +127,18 @@
|
|||
is-sidebar-open="$ctrl.isSidebarOpen"
|
||||
children-paths="['docker.registries', 'docker.registries.access', 'docker.featuresConfiguration']"
|
||||
>
|
||||
<div ng-if="$ctrl.adminAccess">
|
||||
<sidebar-menu-item
|
||||
authorization="PortainerEndpointUpdateSettings"
|
||||
path="docker.featuresConfiguration"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="swarmSidebar-setup"
|
||||
>
|
||||
Setup
|
||||
</sidebar-menu-item>
|
||||
<sidebar-menu-item
|
||||
ng-if="$ctrl.adminAccess"
|
||||
authorization="PortainerEndpointUpdateSettings"
|
||||
path="docker.featuresConfiguration"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="swarmSidebar-setup"
|
||||
>
|
||||
Setup
|
||||
</sidebar-menu-item>
|
||||
|
||||
<sidebar-menu-item
|
||||
authorization="PortainerRegistryList"
|
||||
path="docker.registries"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="swarmSidebar-registries"
|
||||
>
|
||||
Registries
|
||||
</sidebar-menu-item>
|
||||
</div>
|
||||
<sidebar-menu-item path="docker.registries" path-params="{ endpointId: $ctrl.endpointId }" class-name="sidebar-sublist" data-cy="swarmSidebar-registries">
|
||||
Registries
|
||||
</sidebar-menu-item>
|
||||
</sidebar-menu>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -71,25 +71,24 @@
|
|||
children-paths="['kubernetes.cluster', 'portainer.k8sendpoint.kubernetesConfig', 'kubernetes.registries', 'kubernetes.registries.access']"
|
||||
data-cy="k8sSidebar-cluster"
|
||||
>
|
||||
<div ng-if="$ctrl.adminAccess">
|
||||
<sidebar-menu-item
|
||||
authorization="K8sClusterSetupRW"
|
||||
path="portainer.k8sendpoint.kubernetesConfig"
|
||||
path-params="{ id: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="k8sSidebar-setup"
|
||||
>
|
||||
Setup
|
||||
</sidebar-menu-item>
|
||||
<sidebar-menu-item
|
||||
ng-if="$ctrl.adminAccess"
|
||||
authorization="K8sClusterSetupRW"
|
||||
path="portainer.k8sendpoint.kubernetesConfig"
|
||||
path-params="{ id: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="k8sSidebar-setup"
|
||||
>
|
||||
Setup
|
||||
</sidebar-menu-item>
|
||||
|
||||
<sidebar-menu-item
|
||||
authorization="PortainerRegistryList"
|
||||
path="kubernetes.registries"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="k8sSidebar-registries"
|
||||
>
|
||||
Registries
|
||||
</sidebar-menu-item>
|
||||
</div>
|
||||
<sidebar-menu-item
|
||||
authorization="PortainerRegistryList"
|
||||
path="kubernetes.registries"
|
||||
path-params="{ endpointId: $ctrl.endpointId }"
|
||||
class-name="sidebar-sublist"
|
||||
data-cy="k8sSidebar-registries"
|
||||
>
|
||||
Registries
|
||||
</sidebar-menu-item>
|
||||
</sidebar-menu>
|
||||
|
|
|
@ -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 }));
|
||||
}
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Manage registry access inside this environment</rd-header-content>
|
||||
<rd-header-content>Registry management</rd-header-content>
|
||||
</rd-header>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
@ -16,6 +16,7 @@
|
|||
order-by="Name"
|
||||
endpoint-type="$ctrl.endpointType"
|
||||
can-manage-access="$ctrl.canManageAccess"
|
||||
can-browse="$ctrl.canBrowse"
|
||||
></registries-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue