diff --git a/docs/docs.go b/docs/docs.go index a250e24c..312e81c3 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1711,6 +1711,33 @@ const docTemplate = `{ } }, "/api/v3/iam/user": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List of identities in IAM", + "produces": [ + "application/json" + ], + "tags": [ + "v16.?.?" + ], + "summary": "List of identities in IAM", + "operationId": "iam-3-list-identities", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.IAMUser" + } + } + } + } + }, "post": { "security": [ { @@ -1759,6 +1786,12 @@ const docTemplate = `{ "$ref": "#/definitions/api.Error" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "500": { "description": "Internal Server Error", "schema": { @@ -1806,6 +1839,12 @@ const docTemplate = `{ "$ref": "#/definitions/api.IAMUser" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { @@ -1869,6 +1908,12 @@ const docTemplate = `{ "$ref": "#/definitions/api.Error" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { @@ -1920,6 +1965,12 @@ const docTemplate = `{ "type": "string" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { @@ -1997,6 +2048,12 @@ const docTemplate = `{ "$ref": "#/definitions/api.Error" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { diff --git a/docs/swagger.json b/docs/swagger.json index 9422e20e..e1fc98ff 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1703,6 +1703,33 @@ } }, "/api/v3/iam/user": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List of identities in IAM", + "produces": [ + "application/json" + ], + "tags": [ + "v16.?.?" + ], + "summary": "List of identities in IAM", + "operationId": "iam-3-list-identities", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/api.IAMUser" + } + } + } + } + }, "post": { "security": [ { @@ -1751,6 +1778,12 @@ "$ref": "#/definitions/api.Error" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "500": { "description": "Internal Server Error", "schema": { @@ -1798,6 +1831,12 @@ "$ref": "#/definitions/api.IAMUser" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { @@ -1861,6 +1900,12 @@ "$ref": "#/definitions/api.Error" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { @@ -1912,6 +1957,12 @@ "type": "string" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { @@ -1989,6 +2040,12 @@ "$ref": "#/definitions/api.Error" } }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.Error" + } + }, "404": { "description": "Not Found", "schema": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d3974685..cc3fc269 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3489,6 +3489,23 @@ paths: tags: - v16.7.2 /api/v3/iam/user: + get: + description: List of identities in IAM + operationId: iam-3-list-identities + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/api.IAMUser' + type: array + security: + - ApiKeyAuth: [] + summary: List of identities in IAM + tags: + - v16.?.? post: consumes: - application/json @@ -3516,6 +3533,10 @@ paths: description: Bad Request schema: $ref: '#/definitions/api.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.Error' "500": description: Internal Server Error schema: @@ -3546,6 +3567,10 @@ paths: description: OK schema: type: string + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.Error' "404": description: Not Found schema: @@ -3579,6 +3604,10 @@ paths: description: OK schema: $ref: '#/definitions/api.IAMUser' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.Error' "404": description: Not Found schema: @@ -3620,6 +3649,10 @@ paths: description: Bad Request schema: $ref: '#/definitions/api.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.Error' "404": description: Not Found schema: @@ -3670,6 +3703,10 @@ paths: description: Bad Request schema: $ref: '#/definitions/api.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.Error' "404": description: Not Found schema: diff --git a/http/handler/api/cluster.go b/http/handler/api/cluster.go index 1e1c05ff..3df7b324 100644 --- a/http/handler/api/cluster.go +++ b/http/handler/api/cluster.go @@ -812,7 +812,7 @@ func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error { err = h.cluster.SetPolicies("", name, accessPolicies) if err != nil { - return api.Err(http.StatusInternalServerError, "", "set policies: %w", err) + return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error()) } return c.JSON(http.StatusOK, policies) diff --git a/http/handler/api/iam.go b/http/handler/api/iam.go index 81846760..fb2dac44 100644 --- a/http/handler/api/iam.go +++ b/http/handler/api/iam.go @@ -32,6 +32,7 @@ func NewIAM(iam iam.IAM) *IAMHandler { // @Param domain query string false "Domain of the acting user" // @Success 200 {object} api.IAMUser // @Failure 400 {object} api.Error +// @Failure 403 {object} api.Error // @Failure 500 {object} api.Error // @Security ApiKeyAuth // @Router /api/v3/iam/user [post] @@ -83,6 +84,7 @@ func (h *IAMHandler) AddIdentity(c echo.Context) error { // @Param name path string true "Username" // @Param domain query string false "Domain of the acting user" // @Success 200 {string} string +// @Failure 403 {object} api.Error // @Failure 404 {object} api.Error // @Failure 500 {object} api.Error // @Security ApiKeyAuth @@ -130,6 +132,7 @@ func (h *IAMHandler) RemoveIdentity(c echo.Context) error { // @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 @@ -211,6 +214,7 @@ func (h *IAMHandler) UpdateIdentity(c echo.Context) error { // @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 @@ -271,6 +275,50 @@ func (h *IAMHandler) UpdateIdentityPolicies(c echo.Context) error { return c.JSON(http.StatusOK, policies) } +// ListIdentities returns the list of identities stored in IAM +// @Summary List of identities in IAM +// @Description List of identities in IAM +// @Tags v16.?.? +// @ID iam-3-list-identities +// @Produce json +// @Success 200 {array} api.IAMUser +// @Security ApiKeyAuth +// @Router /api/v3/iam/user [get] +func (h *IAMHandler) ListIdentities(c echo.Context) error { + ctxuser := util.DefaultContext(c, "user", "") + domain := util.DefaultQuery(c, "domain", "$none") + + 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) + + users[i].Marshal(iamuser, policies) + } + + anon := identity.User{ + Name: "$anon", + } + + policies := h.iam.ListPolicies("$anon", "", "", nil) + + users[len(users)-1].Marshal(anon, policies) + + return c.JSON(http.StatusOK, users) +} + // GetIdentity returns the user with the given name // @Summary List an user by its name // @Description List aa user by its name @@ -280,6 +328,7 @@ func (h *IAMHandler) UpdateIdentityPolicies(c echo.Context) error { // @Param name path string true "Username" // @Param domain query string false "Domain of the acting user" // @Success 200 {object} api.IAMUser +// @Failure 403 {object} api.Error // @Failure 404 {object} api.Error // @Security ApiKeyAuth // @Router /api/v3/iam/user/{name} [get] diff --git a/http/server.go b/http/server.go index 8242f636..68c44c45 100644 --- a/http/server.go +++ b/http/server.go @@ -556,11 +556,15 @@ func (s *server) setRoutesV3(v3 *echo.Group) { // v3 IAM if s.v3handler.iam != nil { - v3.POST("/iam/user", s.v3handler.iam.AddIdentity) + v3.GET("/iam/user", s.v3handler.iam.ListIdentities) v3.GET("/iam/user/:name", s.v3handler.iam.GetIdentity) - v3.PUT("/iam/user/:name", s.v3handler.iam.UpdateIdentity) - v3.PUT("/iam/user/:name/policy", s.v3handler.iam.UpdateIdentityPolicies) - v3.DELETE("/iam/user/:name", s.v3handler.iam.RemoveIdentity) + + if !s.readOnly { + v3.POST("/iam/user", s.v3handler.iam.AddIdentity) + v3.PUT("/iam/user/:name", s.v3handler.iam.UpdateIdentity) + v3.PUT("/iam/user/:name/policy", s.v3handler.iam.UpdateIdentityPolicies) + v3.DELETE("/iam/user/:name", s.v3handler.iam.RemoveIdentity) + } } // v3 Restreamer