feat(api): introduces swagger.yml (#1112)

pull/1114/head
Anthony Lapenna 2017-08-13 16:45:55 +02:00 committed by GitHub
parent d3e87b2435
commit 92391254bc
19 changed files with 2691 additions and 286 deletions

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"log"
"net/http"
"strings"
)
// errorResponse is a generic response for sending a error.
@ -21,10 +20,3 @@ func WriteErrorResponse(w http.ResponseWriter, err error, code int, logger *log.
w.WriteHeader(code)
json.NewEncoder(w).Encode(&errorResponse{Err: err.Error()})
}
// WriteMethodNotAllowedResponse writes an error message to the response and sets the Allow header.
func WriteMethodNotAllowedResponse(w http.ResponseWriter, allowedMethods []string) {
w.Header().Set("Allow", strings.Join(allowedMethods, ", "))
w.WriteHeader(http.StatusMethodNotAllowed)
json.NewEncoder(w).Encode(&errorResponse{Err: http.StatusText(http.StatusMethodNotAllowed)})
}

View File

@ -44,17 +44,23 @@ func NewAuthHandler(bouncer *security.RequestBouncer, authDisabled bool) *AuthHa
authDisabled: authDisabled,
}
h.Handle("/auth",
bouncer.PublicAccess(http.HandlerFunc(h.handlePostAuth)))
bouncer.PublicAccess(http.HandlerFunc(h.handlePostAuth))).Methods(http.MethodPost)
return h
}
func (handler *AuthHandler) handlePostAuth(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
httperror.WriteMethodNotAllowedResponse(w, []string{http.MethodPost})
return
type (
postAuthRequest struct {
Username string `valid:"required"`
Password string `valid:"required"`
}
postAuthResponse struct {
JWT string `json:"jwt"`
}
)
func (handler *AuthHandler) handlePostAuth(w http.ResponseWriter, r *http.Request) {
if handler.authDisabled {
httperror.WriteErrorResponse(w, ErrAuthDisabled, http.StatusServiceUnavailable, handler.Logger)
return
@ -118,12 +124,3 @@ func (handler *AuthHandler) handlePostAuth(w http.ResponseWriter, r *http.Reques
encodeJSON(w, &postAuthResponse{JWT: token}, handler.Logger)
}
type postAuthRequest struct {
Username string `valid:"required"`
Password string `valid:"required"`
}
type postAuthResponse struct {
JWT string `json:"jwt"`
}

View File

@ -22,20 +22,28 @@ type DockerHubHandler struct {
DockerHubService portainer.DockerHubService
}
// NewDockerHubHandler returns a new instance of OldDockerHubHandler.
// NewDockerHubHandler returns a new instance of NewDockerHubHandler.
func NewDockerHubHandler(bouncer *security.RequestBouncer) *DockerHubHandler {
h := &DockerHubHandler{
Router: mux.NewRouter(),
Logger: log.New(os.Stderr, "", log.LstdFlags),
}
h.Handle("/dockerhub",
bouncer.PublicAccess(http.HandlerFunc(h.handleGetDockerHub))).Methods(http.MethodGet)
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleGetDockerHub))).Methods(http.MethodGet)
h.Handle("/dockerhub",
bouncer.AdministratorAccess(http.HandlerFunc(h.handlePutDockerHub))).Methods(http.MethodPut)
return h
}
type (
putDockerHubRequest struct {
Authentication bool `valid:""`
Username string `valid:""`
Password string `valid:""`
}
)
// handleGetDockerHub handles GET requests on /dockerhub
func (handler *DockerHubHandler) handleGetDockerHub(w http.ResponseWriter, r *http.Request) {
dockerhub, err := handler.DockerHubService.DockerHub()
@ -79,9 +87,3 @@ func (handler *DockerHubHandler) handlePutDockerHub(w http.ResponseWriter, r *ht
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
}
}
type putDockerHubRequest struct {
Authentication bool `valid:""`
Username string `valid:""`
Password string `valid:""`
}

View File

@ -55,6 +55,31 @@ func NewEndpointHandler(bouncer *security.RequestBouncer, authorizeEndpointManag
return h
}
type (
postEndpointsRequest struct {
Name string `valid:"required"`
URL string `valid:"required"`
PublicURL string `valid:"-"`
TLS bool
}
postEndpointsResponse struct {
ID int `json:"Id"`
}
putEndpointAccessRequest struct {
AuthorizedUsers []int `valid:"-"`
AuthorizedTeams []int `valid:"-"`
}
putEndpointsRequest struct {
Name string `valid:"-"`
URL string `valid:"-"`
PublicURL string `valid:"-"`
TLS bool `valid:"-"`
}
)
// handleGetEndpoints handles GET requests on /endpoints
func (handler *EndpointHandler) handleGetEndpoints(w http.ResponseWriter, r *http.Request) {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@ -130,17 +155,6 @@ func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *ht
encodeJSON(w, &postEndpointsResponse{ID: int(endpoint.ID)}, handler.Logger)
}
type postEndpointsRequest struct {
Name string `valid:"required"`
URL string `valid:"required"`
PublicURL string `valid:"-"`
TLS bool
}
type postEndpointsResponse struct {
ID int `json:"Id"`
}
// handleGetEndpoint handles GET requests on /endpoints/:id
func (handler *EndpointHandler) handleGetEndpoint(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -219,11 +233,6 @@ func (handler *EndpointHandler) handlePutEndpointAccess(w http.ResponseWriter, r
}
}
type putEndpointAccessRequest struct {
AuthorizedUsers []int `valid:"-"`
AuthorizedTeams []int `valid:"-"`
}
// handlePutEndpoint handles PUT requests on /endpoints/:id
func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http.Request) {
if !handler.authorizeEndpointManagement {
@ -307,13 +316,6 @@ func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http
}
}
type putEndpointsRequest struct {
Name string `valid:"-"`
URL string `valid:"-"`
PublicURL string `valid:"-"`
TLS bool `valid:"-"`
}
// handleDeleteEndpoint handles DELETE requests on /endpoints/:id
func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *http.Request) {
if !handler.authorizeEndpointManagement {

View File

@ -36,48 +36,48 @@ const (
ErrInvalidRequestFormat = portainer.Error("Invalid request data format")
// ErrInvalidQueryFormat defines an error raised when the data sent in the query or the URL is invalid
ErrInvalidQueryFormat = portainer.Error("Invalid query format")
// ErrEmptyResponseBody defines an error raised when portainer excepts to parse the body of a HTTP response and there is nothing to parse
// ErrEmptyResponseBody = portainer.Error("Empty response body")
)
// ServeHTTP delegates a request to the appropriate subhandler.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/api/auth") {
switch {
case strings.HasPrefix(r.URL.Path, "/api/auth"):
http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/users") {
http.StripPrefix("/api", h.UserHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/teams") {
http.StripPrefix("/api", h.TeamHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/team_memberships") {
http.StripPrefix("/api", h.TeamMembershipHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/endpoints") {
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/endpoints"):
if strings.Contains(r.URL.Path, "/docker") {
http.StripPrefix("/api/endpoints", h.DockerHandler).ServeHTTP(w, r)
} else {
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
}
} else if strings.HasPrefix(r.URL.Path, "/api/registries") {
case strings.HasPrefix(r.URL.Path, "/api/registries"):
http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/dockerhub") {
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/resource_controls") {
case strings.HasPrefix(r.URL.Path, "/api/resource_controls"):
http.StripPrefix("/api", h.ResourceHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/settings") {
case strings.HasPrefix(r.URL.Path, "/api/settings"):
http.StripPrefix("/api", h.SettingsHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/status") {
case strings.HasPrefix(r.URL.Path, "/api/status"):
http.StripPrefix("/api", h.StatusHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/templates") {
case strings.HasPrefix(r.URL.Path, "/api/templates"):
http.StripPrefix("/api", h.TemplatesHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/upload") {
case strings.HasPrefix(r.URL.Path, "/api/upload"):
http.StripPrefix("/api", h.UploadHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/websocket") {
case strings.HasPrefix(r.URL.Path, "/api/users"):
http.StripPrefix("/api", h.UserHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/teams"):
http.StripPrefix("/api", h.TeamHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/team_memberships"):
http.StripPrefix("/api", h.TeamMembershipHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/websocket"):
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r)
} else if strings.HasPrefix(r.URL.Path, "/") {
case strings.HasPrefix(r.URL.Path, "/"):
h.FileHandler.ServeHTTP(w, r)
}
}
// encodeJSON encodes v to w in JSON format. Error() is called if encoding fails.
// encodeJSON encodes v to w in JSON format. WriteErrorResponse() is called if encoding fails.
func encodeJSON(w http.ResponseWriter, v interface{}, logger *log.Logger) {
if err := json.NewEncoder(w).Encode(v); err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, logger)

View File

@ -44,6 +44,33 @@ func NewRegistryHandler(bouncer *security.RequestBouncer) *RegistryHandler {
return h
}
type (
postRegistriesRequest struct {
Name string `valid:"required"`
URL string `valid:"required"`
Authentication bool `valid:""`
Username string `valid:""`
Password string `valid:""`
}
postRegistriesResponse struct {
ID int `json:"Id"`
}
putRegistryAccessRequest struct {
AuthorizedUsers []int `valid:"-"`
AuthorizedTeams []int `valid:"-"`
}
putRegistriesRequest struct {
Name string `valid:"required"`
URL string `valid:"required"`
Authentication bool `valid:""`
Username string `valid:""`
Password string `valid:""`
}
)
// handleGetRegistries handles GET requests on /registries
func (handler *RegistryHandler) handleGetRegistries(w http.ResponseWriter, r *http.Request) {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@ -112,18 +139,6 @@ func (handler *RegistryHandler) handlePostRegistries(w http.ResponseWriter, r *h
encodeJSON(w, &postRegistriesResponse{ID: int(registry.ID)}, handler.Logger)
}
type postRegistriesRequest struct {
Name string `valid:"required"`
URL string `valid:"required"`
Authentication bool `valid:""`
Username string `valid:""`
Password string `valid:""`
}
type postRegistriesResponse struct {
ID int `json:"Id"`
}
// handleGetRegistry handles GET requests on /registries/:id
func (handler *RegistryHandler) handleGetRegistry(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -202,11 +217,6 @@ func (handler *RegistryHandler) handlePutRegistryAccess(w http.ResponseWriter, r
}
}
type putRegistryAccessRequest struct {
AuthorizedUsers []int `valid:"-"`
AuthorizedTeams []int `valid:"-"`
}
// handlePutRegistry handles PUT requests on /registries/:id
func (handler *RegistryHandler) handlePutRegistry(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -276,14 +286,6 @@ func (handler *RegistryHandler) handlePutRegistry(w http.ResponseWriter, r *http
}
}
type putRegistriesRequest struct {
Name string `valid:"required"`
URL string `valid:"required"`
Authentication bool `valid:""`
Username string `valid:""`
Password string `valid:""`
}
// handleDeleteRegistry handles DELETE requests on /registries/:id
func (handler *RegistryHandler) handleDeleteRegistry(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

View File

@ -39,6 +39,23 @@ func NewResourceHandler(bouncer *security.RequestBouncer) *ResourceHandler {
return h
}
type (
postResourcesRequest struct {
ResourceID string `valid:"required"`
Type string `valid:"required"`
AdministratorsOnly bool `valid:"-"`
Users []int `valid:"-"`
Teams []int `valid:"-"`
SubResourceIDs []string `valid:"-"`
}
putResourcesRequest struct {
AdministratorsOnly bool `valid:"-"`
Users []int `valid:"-"`
Teams []int `valid:"-"`
}
)
// handlePostResources handles POST requests on /resources
func (handler *ResourceHandler) handlePostResources(w http.ResponseWriter, r *http.Request) {
var req postResourcesRequest
@ -121,22 +138,13 @@ func (handler *ResourceHandler) handlePostResources(w http.ResponseWriter, r *ht
err = handler.ResourceControlService.CreateResourceControl(&resourceControl)
if err != nil {
httperror.WriteErrorResponse(w, ErrInvalidRequestFormat, http.StatusBadRequest, handler.Logger)
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
return
}
type postResourcesRequest struct {
ResourceID string `valid:"required"`
Type string `valid:"required"`
AdministratorsOnly bool `valid:"-"`
Users []int `valid:"-"`
Teams []int `valid:"-"`
SubResourceIDs []string `valid:"-"`
}
// handlePutResources handles PUT requests on /resources/:id
func (handler *ResourceHandler) handlePutResources(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -210,12 +218,6 @@ func (handler *ResourceHandler) handlePutResources(w http.ResponseWriter, r *htt
}
}
type putResourcesRequest struct {
AdministratorsOnly bool `valid:"-"`
Users []int `valid:"-"`
Teams []int `valid:"-"`
}
// handleDeleteResources handles DELETE requests on /resources/:id
func (handler *ResourceHandler) handleDeleteResources(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

View File

@ -43,6 +43,27 @@ func NewSettingsHandler(bouncer *security.RequestBouncer) *SettingsHandler {
return h
}
type (
publicSettingsResponse struct {
LogoURL string `json:"LogoURL"`
DisplayExternalContributors bool `json:"DisplayExternalContributors"`
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
}
putSettingsRequest struct {
TemplatesURL string `valid:"required"`
LogoURL string `valid:""`
BlackListedLabels []portainer.Pair `valid:""`
DisplayExternalContributors bool `valid:""`
AuthenticationMethod int `valid:"required"`
LDAPSettings portainer.LDAPSettings `valid:""`
}
putSettingsLDAPCheckRequest struct {
LDAPSettings portainer.LDAPSettings `valid:""`
}
)
// handleGetSettings handles GET requests on /settings
func (handler *SettingsHandler) handleGetSettings(w http.ResponseWriter, r *http.Request) {
settings, err := handler.SettingsService.Settings()
@ -73,12 +94,6 @@ func (handler *SettingsHandler) handleGetPublicSettings(w http.ResponseWriter, r
return
}
type publicSettingsResponse struct {
LogoURL string `json:"LogoURL"`
DisplayExternalContributors bool `json:"DisplayExternalContributors"`
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
}
// handlePutSettings handles PUT requests on /settings
func (handler *SettingsHandler) handlePutSettings(w http.ResponseWriter, r *http.Request) {
var req putSettingsRequest
@ -127,15 +142,6 @@ func (handler *SettingsHandler) handlePutSettings(w http.ResponseWriter, r *http
}
}
type putSettingsRequest struct {
TemplatesURL string `valid:"required"`
LogoURL string `valid:""`
BlackListedLabels []portainer.Pair `valid:""`
DisplayExternalContributors bool `valid:""`
AuthenticationMethod int `valid:"required"`
LDAPSettings portainer.LDAPSettings `valid:""`
}
// handlePutSettingsLDAPCheck handles PUT requests on /settings/ldap/check
func (handler *SettingsHandler) handlePutSettingsLDAPCheck(w http.ResponseWriter, r *http.Request) {
var req putSettingsLDAPCheckRequest
@ -161,7 +167,3 @@ func (handler *SettingsHandler) handlePutSettingsLDAPCheck(w http.ResponseWriter
return
}
}
type putSettingsLDAPCheckRequest struct {
LDAPSettings portainer.LDAPSettings `valid:""`
}

View File

@ -34,7 +34,7 @@ func NewTeamHandler(bouncer *security.RequestBouncer) *TeamHandler {
h.Handle("/teams",
bouncer.AdministratorAccess(http.HandlerFunc(h.handlePostTeams))).Methods(http.MethodPost)
h.Handle("/teams",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleGetTeams))).Methods(http.MethodGet)
bouncer.RestrictedAccess(http.HandlerFunc(h.handleGetTeams))).Methods(http.MethodGet)
h.Handle("/teams/{id}",
bouncer.RestrictedAccess(http.HandlerFunc(h.handleGetTeam))).Methods(http.MethodGet)
h.Handle("/teams/{id}",
@ -47,6 +47,20 @@ func NewTeamHandler(bouncer *security.RequestBouncer) *TeamHandler {
return h
}
type (
postTeamsRequest struct {
Name string `valid:"required"`
}
postTeamsResponse struct {
ID int `json:"Id"`
}
putTeamRequest struct {
Name string `valid:"-"`
}
)
// handlePostTeams handles POST requests on /teams
func (handler *TeamHandler) handlePostTeams(w http.ResponseWriter, r *http.Request) {
var req postTeamsRequest
@ -84,23 +98,23 @@ func (handler *TeamHandler) handlePostTeams(w http.ResponseWriter, r *http.Reque
encodeJSON(w, &postTeamsResponse{ID: int(team.ID)}, handler.Logger)
}
type postTeamsResponse struct {
ID int `json:"Id"`
}
type postTeamsRequest struct {
Name string `valid:"required"`
}
// handleGetTeams handles GET requests on /teams
func (handler *TeamHandler) handleGetTeams(w http.ResponseWriter, r *http.Request) {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
teams, err := handler.TeamService.Teams()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
encodeJSON(w, teams, handler.Logger)
filteredTeams := security.FilterUserTeams(teams, securityContext)
encodeJSON(w, filteredTeams, handler.Logger)
}
// handleGetTeam handles GET requests on /teams/:id
@ -181,10 +195,6 @@ func (handler *TeamHandler) handlePutTeam(w http.ResponseWriter, r *http.Request
}
}
type putTeamRequest struct {
Name string `valid:"-"`
}
// handleDeleteTeam handles DELETE requests on /teams/:id
func (handler *TeamHandler) handleDeleteTeam(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

View File

@ -42,6 +42,24 @@ func NewTeamMembershipHandler(bouncer *security.RequestBouncer) *TeamMembershipH
return h
}
type (
postTeamMembershipsRequest struct {
UserID int `valid:"required"`
TeamID int `valid:"required"`
Role int `valid:"required"`
}
postTeamMembershipsResponse struct {
ID int `json:"Id"`
}
putTeamMembershipRequest struct {
UserID int `valid:"required"`
TeamID int `valid:"required"`
Role int `valid:"required"`
}
)
// handlePostTeamMemberships handles POST requests on /team_memberships
func (handler *TeamMembershipHandler) handlePostTeamMemberships(w http.ResponseWriter, r *http.Request) {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@ -100,16 +118,6 @@ func (handler *TeamMembershipHandler) handlePostTeamMemberships(w http.ResponseW
encodeJSON(w, &postTeamMembershipsResponse{ID: int(membership.ID)}, handler.Logger)
}
type postTeamMembershipsResponse struct {
ID int `json:"Id"`
}
type postTeamMembershipsRequest struct {
UserID int `valid:"required"`
TeamID int `valid:"required"`
Role int `valid:"required"`
}
// handleGetTeamsMemberships handles GET requests on /team_memberships
func (handler *TeamMembershipHandler) handleGetTeamsMemberships(w http.ResponseWriter, r *http.Request) {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@ -195,12 +203,6 @@ func (handler *TeamMembershipHandler) handlePutTeamMembership(w http.ResponseWri
}
}
type putTeamMembershipRequest struct {
UserID int `valid:"required"`
TeamID int `valid:"required"`
Role int `valid:"required"`
}
// handleDeleteTeamMembership handles DELETE requests on /team_memberships/:id
func (handler *TeamMembershipHandler) handleDeleteTeamMembership(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

View File

@ -30,17 +30,12 @@ func NewTemplatesHandler(bouncer *security.RequestBouncer) *TemplatesHandler {
Logger: log.New(os.Stderr, "", log.LstdFlags),
}
h.Handle("/templates",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleGetTemplates)))
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleGetTemplates))).Methods(http.MethodGet)
return h
}
// handleGetTemplates handles GET requests on /templates?key=<key>
func (handler *TemplatesHandler) handleGetTemplates(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
httperror.WriteMethodNotAllowedResponse(w, []string{http.MethodGet})
return
}
key := r.FormValue("key")
if key == "" {
httperror.WriteErrorResponse(w, ErrInvalidQueryFormat, http.StatusBadRequest, handler.Logger)

View File

@ -26,17 +26,12 @@ func NewUploadHandler(bouncer *security.RequestBouncer) *UploadHandler {
Logger: log.New(os.Stderr, "", log.LstdFlags),
}
h.Handle("/upload/tls/{certificate:(?:ca|cert|key)}",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handlePostUploadTLS)))
bouncer.AdministratorAccess(http.HandlerFunc(h.handlePostUploadTLS))).Methods(http.MethodPost)
return h
}
// handlePostUploadTLS handles POST requests on /upload/tls/{certificate:(?:ca|cert|key)}?folder=folder
func (handler *UploadHandler) handlePostUploadTLS(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
httperror.WriteMethodNotAllowedResponse(w, []string{http.MethodPost})
return
}
vars := mux.Vars(r)
certificate := vars["certificate"]

View File

@ -47,18 +47,45 @@ func NewUserHandler(bouncer *security.RequestBouncer) *UserHandler {
bouncer.AdministratorAccess(http.HandlerFunc(h.handleDeleteUser))).Methods(http.MethodDelete)
h.Handle("/users/{id}/memberships",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handleGetMemberships))).Methods(http.MethodGet)
h.Handle("/users/{id}/teams",
bouncer.RestrictedAccess(http.HandlerFunc(h.handleGetTeams))).Methods(http.MethodGet)
h.Handle("/users/{id}/passwd",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handlePostUserPasswd)))
bouncer.AuthenticatedAccess(http.HandlerFunc(h.handlePostUserPasswd))).Methods(http.MethodPost)
h.Handle("/users/admin/check",
bouncer.PublicAccess(http.HandlerFunc(h.handleGetAdminCheck)))
bouncer.PublicAccess(http.HandlerFunc(h.handleGetAdminCheck))).Methods(http.MethodGet)
h.Handle("/users/admin/init",
bouncer.PublicAccess(http.HandlerFunc(h.handlePostAdminInit)))
bouncer.PublicAccess(http.HandlerFunc(h.handlePostAdminInit))).Methods(http.MethodPost)
return h
}
type (
postUsersRequest struct {
Username string `valid:"required"`
Password string `valid:""`
Role int `valid:"required"`
}
postUsersResponse struct {
ID int `json:"Id"`
}
postUserPasswdRequest struct {
Password string `valid:"required"`
}
postUserPasswdResponse struct {
Valid bool `json:"valid"`
}
putUserRequest struct {
Password string `valid:"-"`
Role int `valid:"-"`
}
postAdminInitRequest struct {
Password string `valid:"required"`
}
)
// handlePostUsers handles POST requests on /users
func (handler *UserHandler) handlePostUsers(w http.ResponseWriter, r *http.Request) {
var req postUsersRequest
@ -139,16 +166,6 @@ func (handler *UserHandler) handlePostUsers(w http.ResponseWriter, r *http.Reque
encodeJSON(w, &postUsersResponse{ID: int(user.ID)}, handler.Logger)
}
type postUsersResponse struct {
ID int `json:"Id"`
}
type postUsersRequest struct {
Username string `valid:"required"`
Password string `valid:""`
Role int `valid:"required"`
}
// handleGetUsers handles GET requests on /users
func (handler *UserHandler) handleGetUsers(w http.ResponseWriter, r *http.Request) {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
@ -174,11 +191,6 @@ func (handler *UserHandler) handleGetUsers(w http.ResponseWriter, r *http.Reques
// handlePostUserPasswd handles POST requests on /users/:id/passwd
func (handler *UserHandler) handlePostUserPasswd(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
httperror.WriteMethodNotAllowedResponse(w, []string{http.MethodPost})
return
}
vars := mux.Vars(r)
id := vars["id"]
@ -220,14 +232,6 @@ func (handler *UserHandler) handlePostUserPasswd(w http.ResponseWriter, r *http.
encodeJSON(w, &postUserPasswdResponse{Valid: valid}, handler.Logger)
}
type postUserPasswdRequest struct {
Password string `valid:"required"`
}
type postUserPasswdResponse struct {
Valid bool `json:"valid"`
}
// handleGetUser handles GET requests on /users/:id
func (handler *UserHandler) handleGetUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -327,18 +331,8 @@ func (handler *UserHandler) handlePutUser(w http.ResponseWriter, r *http.Request
}
}
type putUserRequest struct {
Password string `valid:"-"`
Role int `valid:"-"`
}
// handlePostAdminInit handles GET requests on /users/admin/check
// handleGetAdminCheck handles GET requests on /users/admin/check
func (handler *UserHandler) handleGetAdminCheck(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
httperror.WriteMethodNotAllowedResponse(w, []string{http.MethodGet})
return
}
users, err := handler.UserService.UsersByRole(portainer.AdministratorRole)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
@ -352,11 +346,6 @@ func (handler *UserHandler) handleGetAdminCheck(w http.ResponseWriter, r *http.R
// handlePostAdminInit handles POST requests on /users/admin/init
func (handler *UserHandler) handlePostAdminInit(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
httperror.WriteMethodNotAllowedResponse(w, []string{http.MethodPost})
return
}
var req postAdminInitRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
httperror.WriteErrorResponse(w, ErrInvalidJSON, http.StatusBadRequest, handler.Logger)
@ -391,15 +380,11 @@ func (handler *UserHandler) handlePostAdminInit(w http.ResponseWriter, r *http.R
return
}
if user != nil {
httperror.WriteErrorResponse(w, portainer.ErrAdminAlreadyInitialized, http.StatusForbidden, handler.Logger)
httperror.WriteErrorResponse(w, portainer.ErrAdminAlreadyInitialized, http.StatusConflict, handler.Logger)
return
}
}
type postAdminInitRequest struct {
Password string `valid:"required"`
}
// handleDeleteUser handles DELETE requests on /users/:id
func (handler *UserHandler) handleDeleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -464,37 +449,3 @@ func (handler *UserHandler) handleGetMemberships(w http.ResponseWriter, r *http.
encodeJSON(w, memberships, handler.Logger)
}
// handleGetTeams handles GET requests on /users/:id/teams
func (handler *UserHandler) handleGetTeams(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
uid, err := strconv.Atoi(id)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
return
}
userID := portainer.UserID(uid)
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
if !security.AuthorizedUserManagement(userID, securityContext) {
httperror.WriteErrorResponse(w, portainer.ErrResourceAccessDenied, http.StatusForbidden, handler.Logger)
return
}
teams, err := handler.TeamService.Teams()
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
filteredTeams := security.FilterUserTeams(teams, securityContext)
encodeJSON(w, filteredTeams, handler.Logger)
}

View File

@ -50,7 +50,7 @@ func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler
return h
}
// RestrictedAccess defines defines a security check for restricted endpoints.
// RestrictedAccess defines a security check for restricted endpoints.
// Authentication is required to access these endpoints.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to authorize/filter access to resources.

2471
api/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
angular.module('portainer')
.controller('porAccessControlFormController', ['$q', 'UserService', 'Notifications', 'Authentication', 'ResourceControlService',
function ($q, UserService, Notifications, Authentication, ResourceControlService) {
.controller('porAccessControlFormController', ['$q', 'UserService', 'TeamService', 'Notifications', 'Authentication', 'ResourceControlService',
function ($q, UserService, TeamService, Notifications, Authentication, ResourceControlService) {
var ctrl = this;
ctrl.availableTeams = [];
@ -42,7 +42,7 @@ function ($q, UserService, Notifications, Authentication, ResourceControlService
}
$q.all({
availableTeams: UserService.userTeams(userDetails.ID),
availableTeams: TeamService.teams(),
availableUsers: isAdmin ? UserService.users(false) : []
})
.then(function success(data) {

View File

@ -1,6 +1,6 @@
angular.module('portainer')
.controller('porAccessControlPanelController', ['$q', '$state', 'UserService', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'FormValidator',
function ($q, $state, UserService, ResourceControlService, Notifications, Authentication, ModalService, FormValidator) {
.controller('porAccessControlPanelController', ['$q', '$state', 'UserService', 'TeamService', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'FormValidator',
function ($q, $state, UserService, TeamService, ResourceControlService, Notifications, Authentication, ModalService, FormValidator) {
var ctrl = this;
@ -121,7 +121,7 @@ function ($q, $state, UserService, ResourceControlService, Notifications, Authen
return $q.all({
availableUsers: isAdmin ? UserService.users(false) : [],
availableTeams: isAdmin || data.isPartOfRestrictedUsers ? UserService.userTeams(userId) : []
availableTeams: isAdmin || data.isPartOfRestrictedUsers ? TeamService.teams() : []
});
})
.then(function success(data) {

View File

@ -8,7 +8,6 @@ angular.module('portainer.rest')
update: { method: 'PUT', params: { id: '@id' } },
remove: { method: 'DELETE', params: { id: '@id'} },
queryMemberships: { method: 'GET', isArray: true, params: { id: '@id', entity: 'memberships' } },
queryTeams: { method: 'GET', isArray: true, params: { id: '@id', entity: 'teams' } },
// RPCs should be moved to a specific endpoint
checkPassword: { method: 'POST', params: { id: '@id', entity: 'passwd' } },
checkAdminUser: { method: 'GET', params: { id: 'admin', entity: 'check' }, isArray: true },

View File

@ -1,5 +1,5 @@
angular.module('portainer.services')
.factory('UserService', ['$q', 'Users', 'UserHelper', 'TeamMembershipService', function UserServiceFactory($q, Users, UserHelper, TeamMembershipService) {
.factory('UserService', ['$q', 'Users', 'UserHelper', 'TeamService', 'TeamMembershipService', function UserServiceFactory($q, Users, UserHelper, TeamService, TeamMembershipService) {
'use strict';
var service = {};
@ -110,28 +110,11 @@ angular.module('portainer.services')
return deferred.promise;
};
service.userTeams = function(id) {
var deferred = $q.defer();
Users.queryTeams({id: id}).$promise
.then(function success(data) {
var teams = data.map(function (item) {
return new TeamViewModel(item);
});
deferred.resolve(teams);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve user teams', err: err });
});
return deferred.promise;
};
service.userLeadingTeams = function(id) {
var deferred = $q.defer();
$q.all({
teams: service.userTeams(id),
teams: TeamService.teams(),
memberships: service.userMemberships(id)
})
.then(function success(data) {