mirror of
				https://github.com/datarhei/core.git
				synced 2025-10-31 03:16:21 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			409 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"net/http"
 | |
| 
 | |
| 	"github.com/datarhei/core/v16/http/api"
 | |
| 	"github.com/datarhei/core/v16/http/handler/util"
 | |
| 	"github.com/datarhei/core/v16/iam/access"
 | |
| 	"github.com/datarhei/core/v16/iam/identity"
 | |
| 	"github.com/labstack/echo/v4"
 | |
| )
 | |
| 
 | |
| // Add adds a new identity to the cluster
 | |
| // @Summary Add a new identiy
 | |
| // @Description Add a new identity
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-add-identity
 | |
| // @Accept json
 | |
| // @Produce json
 | |
| // @Param config body api.IAMUser true "Identity"
 | |
| // @Success 200 {object} api.IAMUser
 | |
| // @Failure 400 {object} api.Error
 | |
| // @Failure 403 {object} api.Error
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/user [post]
 | |
| func (h *ClusterHandler) AddIdentity(c echo.Context) error {
 | |
| 	ctxuser := util.DefaultContext(c, "user", "")
 | |
| 	superuser := util.DefaultContext(c, "superuser", false)
 | |
| 	domain := util.DefaultQuery(c, "domain", "")
 | |
| 
 | |
| 	user := api.IAMUser{}
 | |
| 
 | |
| 	if err := util.ShouldBindJSON(c, &user); err != nil {
 | |
| 		return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	iamuser, iampolicies := user.Unmarshal()
 | |
| 
 | |
| 	if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") {
 | |
| 		return api.Err(http.StatusForbidden, "", "Not allowed to create user '%s'", iamuser.Name)
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range iampolicies {
 | |
| 		if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") {
 | |
| 			return api.Err(http.StatusForbidden, "", "Not allowed to write policy: %v", p)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !superuser && iamuser.Superuser {
 | |
| 		return api.Err(http.StatusForbidden, "", "Only superusers can add superusers")
 | |
| 	}
 | |
| 
 | |
| 	if err := h.cluster.AddIdentity("", iamuser); err != nil {
 | |
| 		return api.Err(http.StatusBadRequest, "", "invalid identity: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	if err := h.cluster.SetPolicies("", iamuser.Name, iampolicies); err != nil {
 | |
| 		return api.Err(http.StatusBadRequest, "", "Invalid policies: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, user)
 | |
| }
 | |
| 
 | |
| // UpdateIdentity replaces an existing user
 | |
| // @Summary Replace an existing user
 | |
| // @Description Replace an existing user.
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-update-identity
 | |
| // @Accept json
 | |
| // @Produce json
 | |
| // @Param name path string true "Username"
 | |
| // @Param domain query string false "Domain of the acting user"
 | |
| // @Param user body api.IAMUser true "User definition"
 | |
| // @Success 200 {object} api.IAMUser
 | |
| // @Failure 400 {object} api.Error
 | |
| // @Failure 403 {object} api.Error
 | |
| // @Failure 404 {object} api.Error
 | |
| // @Failure 500 {object} api.Error
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/user/{name} [put]
 | |
| func (h *ClusterHandler) UpdateIdentity(c echo.Context) error {
 | |
| 	ctxuser := util.DefaultContext(c, "user", "")
 | |
| 	superuser := util.DefaultContext(c, "superuser", false)
 | |
| 	domain := util.DefaultQuery(c, "domain", "")
 | |
| 	name := util.PathParam(c, "name")
 | |
| 
 | |
| 	if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
 | |
| 		return api.Err(http.StatusForbidden, "", "not allowed to modify this user")
 | |
| 	}
 | |
| 
 | |
| 	var iamuser identity.User
 | |
| 	var err error
 | |
| 
 | |
| 	if name != "$anon" {
 | |
| 		iamuser, err = h.iam.GetIdentity(name)
 | |
| 		if err != nil {
 | |
| 			return api.Err(http.StatusNotFound, "", "user not found: %s", err.Error())
 | |
| 		}
 | |
| 	} else {
 | |
| 		iamuser = identity.User{
 | |
| 			Name: "$anon",
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	iampolicies := h.iam.ListPolicies(name, "", nil, "", nil)
 | |
| 
 | |
| 	user := api.IAMUser{}
 | |
| 	user.Marshal(iamuser, iampolicies)
 | |
| 
 | |
| 	if err := util.ShouldBindJSON(c, &user); err != nil {
 | |
| 		return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	iamuser, iampolicies = user.Unmarshal()
 | |
| 
 | |
| 	if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") {
 | |
| 		return api.Err(http.StatusForbidden, "", "not allowed to create user '%s'", iamuser.Name)
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range iampolicies {
 | |
| 		if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") {
 | |
| 			return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !superuser && iamuser.Superuser {
 | |
| 		return api.Err(http.StatusForbidden, "", "only superusers can modify superusers")
 | |
| 	}
 | |
| 
 | |
| 	if name != "$anon" {
 | |
| 		err = h.cluster.UpdateIdentity("", name, iamuser)
 | |
| 		if err != nil {
 | |
| 			return api.Err(http.StatusBadRequest, "", "%s", err.Error())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	err = h.cluster.SetPolicies("", iamuser.Name, iampolicies)
 | |
| 	if err != nil {
 | |
| 		return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, user)
 | |
| }
 | |
| 
 | |
| // UpdateIdentityPolicies replaces existing user policies
 | |
| // @Summary Replace policies of an user
 | |
| // @Description Replace policies of an user
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-update-user-policies
 | |
| // @Accept json
 | |
| // @Produce json
 | |
| // @Param name path string true "Username"
 | |
| // @Param domain query string false "Domain of the acting user"
 | |
| // @Param user body []api.IAMPolicy true "Policy definitions"
 | |
| // @Success 200 {array} api.IAMPolicy
 | |
| // @Failure 400 {object} api.Error
 | |
| // @Failure 403 {object} api.Error
 | |
| // @Failure 404 {object} api.Error
 | |
| // @Failure 500 {object} api.Error
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/user/{name}/policy [put]
 | |
| func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error {
 | |
| 	ctxuser := util.DefaultContext(c, "user", "")
 | |
| 	superuser := util.DefaultContext(c, "superuser", false)
 | |
| 	domain := util.DefaultQuery(c, "domain", "")
 | |
| 	name := util.PathParam(c, "name")
 | |
| 
 | |
| 	if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
 | |
| 		return api.Err(http.StatusForbidden, "", "not allowed to modify this user")
 | |
| 	}
 | |
| 
 | |
| 	var iamuser identity.User
 | |
| 	var err error
 | |
| 
 | |
| 	if name != "$anon" {
 | |
| 		iamuser, err = h.iam.GetIdentity(name)
 | |
| 		if err != nil {
 | |
| 			return api.Err(http.StatusNotFound, "", "unknown identity: %s", err.Error())
 | |
| 		}
 | |
| 	} else {
 | |
| 		iamuser = identity.User{
 | |
| 			Name: "$anon",
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	policies := []api.IAMPolicy{}
 | |
| 
 | |
| 	if err := util.ShouldBindJSONValidation(c, &policies, false); err != nil {
 | |
| 		return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range policies {
 | |
| 		err := c.Validate(p)
 | |
| 		if err != nil {
 | |
| 			return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	accessPolicies := []access.Policy{}
 | |
| 
 | |
| 	for _, p := range policies {
 | |
| 		if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") {
 | |
| 			return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p)
 | |
| 		}
 | |
| 
 | |
| 		accessPolicies = append(accessPolicies, access.Policy{
 | |
| 			Name:     name,
 | |
| 			Domain:   p.Domain,
 | |
| 			Types:    p.Types,
 | |
| 			Resource: p.Resource,
 | |
| 			Actions:  p.Actions,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	if !superuser && iamuser.Superuser {
 | |
| 		return api.Err(http.StatusForbidden, "", "only superusers can modify superusers")
 | |
| 	}
 | |
| 
 | |
| 	err = h.cluster.SetPolicies("", name, accessPolicies)
 | |
| 	if err != nil {
 | |
| 		return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, policies)
 | |
| }
 | |
| 
 | |
| // ReloadIAM reloads the identities and policies from the cluster store to IAM
 | |
| // @Summary Reload identities and policies
 | |
| // @Description Reload identities and policies
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-iam-reload
 | |
| // @Produce json
 | |
| // @Success 200 {string} string
 | |
| // @Success 500 {object} api.Error
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/reload [get]
 | |
| func (h *ClusterHandler) ReloadIAM(c echo.Context) error {
 | |
| 	err := h.iam.ReloadIndentities()
 | |
| 	if err != nil {
 | |
| 		return api.Err(http.StatusInternalServerError, "", "reload identities: %w", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	err = h.iam.ReloadPolicies()
 | |
| 	if err != nil {
 | |
| 		return api.Err(http.StatusInternalServerError, "", "reload policies: %w", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, "OK")
 | |
| }
 | |
| 
 | |
| // ListIdentities returns the list of identities stored in IAM
 | |
| // @Summary List of identities in IAM
 | |
| // @Description List of identities in IAM
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-iam-list-identities
 | |
| // @Produce json
 | |
| // @Success 200 {array} api.IAMUser
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/user [get]
 | |
| func (h *ClusterHandler) ListIdentities(c echo.Context) error {
 | |
| 	ctxuser := util.DefaultContext(c, "user", "")
 | |
| 	domain := util.DefaultQuery(c, "domain", "")
 | |
| 
 | |
| 	identities := h.iam.ListIdentities()
 | |
| 
 | |
| 	users := make([]api.IAMUser, len(identities)+1)
 | |
| 
 | |
| 	for i, iamuser := range identities {
 | |
| 		if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") {
 | |
| 			iamuser = identity.User{
 | |
| 				Name: iamuser.Name,
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		policies := h.iam.ListPolicies(iamuser.Name, "", nil, "", nil)
 | |
| 
 | |
| 		users[i].Marshal(iamuser, policies)
 | |
| 	}
 | |
| 
 | |
| 	anon := identity.User{
 | |
| 		Name: "$anon",
 | |
| 	}
 | |
| 
 | |
| 	policies := h.iam.ListPolicies("$anon", "", nil, "", nil)
 | |
| 
 | |
| 	users[len(users)-1].Marshal(anon, policies)
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, users)
 | |
| }
 | |
| 
 | |
| // ListIdentity returns the identity stored in IAM
 | |
| // @Summary Identity in IAM
 | |
| // @Description Identity in IAM
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-iam-list-identity
 | |
| // @Produce json
 | |
| // @Success 200 {object} api.IAMUser
 | |
| // @Failure 403 {object} api.Error
 | |
| // @Failure 404 {object} api.Error
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/user/{name} [get]
 | |
| func (h *ClusterHandler) ListIdentity(c echo.Context) error {
 | |
| 	ctxuser := util.DefaultContext(c, "user", "")
 | |
| 	domain := util.DefaultQuery(c, "domain", "")
 | |
| 	name := util.PathParam(c, "name")
 | |
| 
 | |
| 	if !h.iam.Enforce(ctxuser, domain, "iam", name, "read") {
 | |
| 		return api.Err(http.StatusForbidden, "", "Not allowed to access this user")
 | |
| 	}
 | |
| 
 | |
| 	var iamuser identity.User
 | |
| 	var err error
 | |
| 
 | |
| 	if name != "$anon" {
 | |
| 		iamuser, err = h.iam.GetIdentity(name)
 | |
| 		if err != nil {
 | |
| 			return api.Err(http.StatusNotFound, "", "%s", err.Error())
 | |
| 		}
 | |
| 
 | |
| 		if ctxuser != iamuser.Name {
 | |
| 			if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
 | |
| 				iamuser = identity.User{
 | |
| 					Name: iamuser.Name,
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		iamuser = identity.User{
 | |
| 			Name: "$anon",
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	iampolicies := h.iam.ListPolicies(name, "", nil, "", nil)
 | |
| 
 | |
| 	user := api.IAMUser{}
 | |
| 	user.Marshal(iamuser, iampolicies)
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, user)
 | |
| }
 | |
| 
 | |
| // ListPolicies returns the list of policies stored in IAM
 | |
| // @Summary List of policies in IAM
 | |
| // @Description List of policies IAM
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-iam-list-policies
 | |
| // @Produce json
 | |
| // @Success 200 {array} api.IAMPolicy
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/policies [get]
 | |
| func (h *ClusterHandler) ListPolicies(c echo.Context) error {
 | |
| 	iampolicies := h.iam.ListPolicies("", "", nil, "", nil)
 | |
| 
 | |
| 	policies := []api.IAMPolicy{}
 | |
| 
 | |
| 	for _, pol := range iampolicies {
 | |
| 		policies = append(policies, api.IAMPolicy{
 | |
| 			Name:     pol.Name,
 | |
| 			Domain:   pol.Domain,
 | |
| 			Types:    pol.Types,
 | |
| 			Resource: pol.Resource,
 | |
| 			Actions:  pol.Actions,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, policies)
 | |
| }
 | |
| 
 | |
| // Delete deletes the identity with the given name from the cluster
 | |
| // @Summary Delete an identity by its name
 | |
| // @Description Delete an identity by its name
 | |
| // @Tags v16.?.?
 | |
| // @ID cluster-3-delete-identity
 | |
| // @Produce json
 | |
| // @Param name path string true "Identity name"
 | |
| // @Success 200 {string} string
 | |
| // @Failure 403 {object} api.Error
 | |
| // @Failure 404 {object} api.Error
 | |
| // @Security ApiKeyAuth
 | |
| // @Router /api/v3/cluster/iam/user/{name} [delete]
 | |
| func (h *ClusterHandler) RemoveIdentity(c echo.Context) error {
 | |
| 	ctxuser := util.DefaultContext(c, "user", "")
 | |
| 	superuser := util.DefaultContext(c, "superuser", false)
 | |
| 	domain := util.DefaultQuery(c, "domain", "$none")
 | |
| 	name := util.PathParam(c, "name")
 | |
| 
 | |
| 	if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
 | |
| 		return api.Err(http.StatusForbidden, "", "Not allowed to delete this user")
 | |
| 	}
 | |
| 
 | |
| 	iamuser, err := h.iam.GetIdentity(name)
 | |
| 	if err != nil {
 | |
| 		return api.Err(http.StatusNotFound, "", "%s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	if !superuser && iamuser.Superuser {
 | |
| 		return api.Err(http.StatusForbidden, "", "Only superusers can remove superusers")
 | |
| 	}
 | |
| 
 | |
| 	if err := h.cluster.RemoveIdentity("", name); err != nil {
 | |
| 		return api.Err(http.StatusBadRequest, "", "invalid identity: %s", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	return c.JSON(http.StatusOK, "OK")
 | |
| }
 | 
