diff --git a/README.md b/README.md index 81bc017..de3a2ea 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ If you start `cc-backend` with the `-dev` flag, the GraphQL Playground UI is ava This project integrates [swagger ui] (https://swagger.io/tools/swagger-ui/) to document and test its REST API. The swagger documentation files can be found in `./api/`. You can generate the swagger-ui configuration by running `go run github.com/swaggo/swag/cmd/swag init -d ./internal/api,./pkg/schema -g rest.go -o ./api `. -You need to move the created `./api/doc.go` to `./internal/api/doc.go`. +You need to move the created `./api/docs.go` to `./internal/api/docs.go`. If you start cc-backend with the `-dev` flag, the Swagger interface is available at http://localhost:8080/swagger/. You must enter a JWT key for a user with the API role. diff --git a/api/swagger.json b/api/swagger.json index 87a7de5..2a4c43a 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -12,7 +12,7 @@ "name": "MIT License", "url": "https://opensource.org/licenses/MIT" }, - "version": "1" + "version": "1.0.0" }, "host": "localhost:8080", "basePath": "/api", @@ -707,6 +707,314 @@ } } } + }, + "/user/{id}": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Modifies user defined by username (id) in one of four possible ways.\nIf more than one formValue is set then only the highest priority field is used.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "add and modify" + ], + "summary": "Updates an existing user", + "parameters": [ + { + "type": "string", + "description": "Database ID of User", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Priority 1: Role to add, one of: [admin, support, manager, user, api]", + "name": "add-role", + "in": "formData" + }, + { + "type": "string", + "description": "Priority 2: Role to remove, one of: [admin, support, manager, user, api]", + "name": "remove-role", + "in": "formData" + }, + { + "type": "string", + "description": "Priority 3: Project to add", + "name": "add-project", + "in": "formData" + }, + { + "type": "string", + "description": "Priority 4: Project to remove", + "name": "remove-project", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "Task successful", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "422": { + "description": "Unprocessable Entity: The user could not be updated", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/users/": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns a JSON-encoded list of users.\nRequired query-parameter defines if all users or only users with additional special roles are returned.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "query" + ], + "summary": "Returns a list of users", + "parameters": [ + { + "type": "boolean", + "description": "If returned list should contain all users or only users with additional special roles", + "name": "not-just-user", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Users returned successfully", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "User specified in form data will be saved to database.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "add and modify" + ], + "summary": "Adds a new user", + "parameters": [ + { + "type": "string", + "description": "Unique user ID", + "name": "username", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "User password", + "name": "password", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "User role, one of: [admin, support, manager, user, api]", + "name": "role", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Managed project, required for new manager role user", + "name": "project", + "in": "formData" + }, + { + "type": "string", + "description": "Users name", + "name": "name", + "in": "formData" + }, + { + "type": "string", + "description": "Users email", + "name": "email", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "User added successfully", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "422": { + "description": "Unprocessable Entity: creating user failed", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "User defined by username in form data will be deleted from database.", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "remove" + ], + "summary": "Deletes a user", + "parameters": [ + { + "type": "string", + "description": "User ID to delete", + "name": "username", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "User deleted successfully" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "422": { + "description": "Unprocessable Entity: deleting user failed", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } } }, "definitions": { @@ -1366,7 +1674,7 @@ "type": "object", "properties": { "id": { - "description": "The unique DB identifier of a tag\nThe unique DB identifier of a tag", + "description": "The unique DB identifier of a tag", "type": "integer" }, "name": { diff --git a/api/swagger.yaml b/api/swagger.yaml index 093266d..d19f79b 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -495,9 +495,7 @@ definitions: description: Defines a tag using name and type. properties: id: - description: |- - The unique DB identifier of a tag - The unique DB identifier of a tag + description: The unique DB identifier of a tag type: integer name: description: Tag Name @@ -526,7 +524,7 @@ info: name: MIT License url: https://opensource.org/licenses/MIT title: ClusterCockpit REST API - version: "1" + version: 1.0.0 paths: /jobs/: get: @@ -996,6 +994,213 @@ paths: summary: Adds one or more tags to a job tags: - add and modify + /user/{id}: + post: + consumes: + - multipart/form-data + description: |- + Modifies user defined by username (id) in one of four possible ways. + If more than one formValue is set then only the highest priority field is used. + parameters: + - description: Database ID of User + in: path + name: id + required: true + type: string + - description: 'Priority 1: Role to add, one of: [admin, support, manager, user, + api]' + in: formData + name: add-role + type: string + - description: 'Priority 2: Role to remove, one of: [admin, support, manager, + user, api]' + in: formData + name: remove-role + type: string + - description: 'Priority 3: Project to add' + in: formData + name: add-project + type: string + - description: 'Priority 4: Project to remove' + in: formData + name: remove-project + type: string + produces: + - text/plain + responses: + "200": + description: Task successful + schema: + type: string + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.ErrorResponse' + "422": + description: 'Unprocessable Entity: The user could not be updated' + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Updates an existing user + tags: + - add and modify + /users/: + delete: + consumes: + - multipart/form-data + description: User defined by username in form data will be deleted from database. + parameters: + - description: User ID to delete + in: formData + name: username + required: true + type: string + responses: + "200": + description: User deleted successfully + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.ErrorResponse' + "422": + description: 'Unprocessable Entity: deleting user failed' + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Deletes a user + tags: + - remove + get: + consumes: + - application/json + description: |- + Returns a JSON-encoded list of users. + Required query-parameter defines if all users or only users with additional special roles are returned. + parameters: + - description: If returned list should contain all users or only users with + additional special roles + in: query + name: not-just-user + required: true + type: boolean + produces: + - application/json + responses: + "200": + description: Users returned successfully + schema: + type: string + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Returns a list of users + tags: + - query + post: + consumes: + - multipart/form-data + description: User specified in form data will be saved to database. + parameters: + - description: Unique user ID + in: formData + name: username + required: true + type: string + - description: User password + in: formData + name: password + required: true + type: string + - description: 'User role, one of: [admin, support, manager, user, api]' + in: formData + name: role + required: true + type: string + - description: Managed project, required for new manager role user + in: formData + name: project + type: string + - description: Users name + in: formData + name: name + type: string + - description: Users email + in: formData + name: email + type: string + produces: + - text/plain + responses: + "200": + description: User added successfully + schema: + type: string + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/api.ErrorResponse' + "403": + description: Forbidden + schema: + $ref: '#/definitions/api.ErrorResponse' + "422": + description: 'Unprocessable Entity: creating user failed' + schema: + $ref: '#/definitions/api.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.ErrorResponse' + security: + - ApiKeyAuth: [] + summary: Adds a new user + tags: + - add and modify securityDefinitions: ApiKeyAuth: in: header diff --git a/internal/api/docs.go b/internal/api/docs.go index 85acc92..4f161e6 100644 --- a/internal/api/docs.go +++ b/internal/api/docs.go @@ -713,6 +713,314 @@ const docTemplate = `{ } } } + }, + "/user/{id}": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Modifies user defined by username (id) in one of four possible ways.\nIf more than one formValue is set then only the highest priority field is used.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "add and modify" + ], + "summary": "Updates an existing user", + "parameters": [ + { + "type": "string", + "description": "Database ID of User", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Priority 1: Role to add, one of: [admin, support, manager, user, api]", + "name": "add-role", + "in": "formData" + }, + { + "type": "string", + "description": "Priority 2: Role to remove, one of: [admin, support, manager, user, api]", + "name": "remove-role", + "in": "formData" + }, + { + "type": "string", + "description": "Priority 3: Project to add", + "name": "add-project", + "in": "formData" + }, + { + "type": "string", + "description": "Priority 4: Project to remove", + "name": "remove-project", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "Task successful", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "422": { + "description": "Unprocessable Entity: The user could not be updated", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } + }, + "/users/": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns a JSON-encoded list of users.\nRequired query-parameter defines if all users or only users with additional special roles are returned.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "query" + ], + "summary": "Returns a list of users", + "parameters": [ + { + "type": "boolean", + "description": "If returned list should contain all users or only users with additional special roles", + "name": "not-just-user", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Users returned successfully", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "User specified in form data will be saved to database.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "add and modify" + ], + "summary": "Adds a new user", + "parameters": [ + { + "type": "string", + "description": "Unique user ID", + "name": "username", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "User password", + "name": "password", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "User role, one of: [admin, support, manager, user, api]", + "name": "role", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Managed project, required for new manager role user", + "name": "project", + "in": "formData" + }, + { + "type": "string", + "description": "Users name", + "name": "name", + "in": "formData" + }, + { + "type": "string", + "description": "Users email", + "name": "email", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "User added successfully", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "422": { + "description": "Unprocessable Entity: creating user failed", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "User defined by username in form data will be deleted from database.", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "remove" + ], + "summary": "Deletes a user", + "parameters": [ + { + "type": "string", + "description": "User ID to delete", + "name": "username", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "User deleted successfully" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "422": { + "description": "Unprocessable Entity: deleting user failed", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.ErrorResponse" + } + } + } + } } }, "definitions": { @@ -1372,7 +1680,7 @@ const docTemplate = `{ "type": "object", "properties": { "id": { - "description": "The unique DB identifier of a tag\nThe unique DB identifier of a tag", + "description": "The unique DB identifier of a tag", "type": "integer" }, "name": { @@ -1415,7 +1723,7 @@ const docTemplate = `{ // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ - Version: "1", + Version: "1.0.0", Host: "localhost:8080", BasePath: "/api", Schemes: []string{}, diff --git a/internal/api/rest.go b/internal/api/rest.go index 23ae53a..67e4dbe 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -77,8 +77,6 @@ func (api *RestApi) MountRoutes(r *mux.Router) { r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete) - // r.HandleFunc("/secured/addProject/{id}/{project}", api.secureUpdateUser).Methods(http.MethodPost) - // r.HandleFunc("/secured/addRole/{id}/{role}", api.secureUpdateUser).Methods(http.MethodPost) if api.MachineStateDir != "" { r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet) @@ -947,43 +945,31 @@ func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) { }) } -func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { - err := securedCheck(r) - if err != nil { - http.Error(rw, err.Error(), http.StatusForbidden) - } - - rw.Header().Set("Content-Type", "text/plain") - username := r.FormValue("username") - me := repository.GetUserFromContext(r.Context()) - if !me.HasRole(schema.RoleAdmin) { - if username != me.Username { - http.Error(rw, "Only admins are allowed to sign JWTs not for themselves", - http.StatusForbidden) - return - } - } - - user, err := repository.GetUserRepository().GetUser(username) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnprocessableEntity) - return - } - - jwt, err := api.Authentication.JwtAuth.ProvideJWT(user) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnprocessableEntity) - return - } - - rw.WriteHeader(http.StatusOK) - rw.Write([]byte(jwt)) -} - +// createUser godoc +// @summary Adds a new user +// @tags add and modify +// @description User specified in form data will be saved to database. +// @accept mpfd +// @produce plain +// @param username formData string true "Unique user ID" +// @param password formData string true "User password" +// @param role formData string true "User role, one of: [admin, support, manager, user, api]" +// @param project formData string false "Managed project, required for new manager role user" +// @param name formData string false "Users name" +// @param email formData string false "Users email" +// @success 200 {string} string "User added successfully" +// @failure 400 {object} api.ErrorResponse "Bad Request" +// @failure 401 {object} api.ErrorResponse "Unauthorized" +// @failure 403 {object} api.ErrorResponse "Forbidden" +// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: creating user failed" +// @failure 500 {object} api.ErrorResponse "Internal Server Error" +// @security ApiKeyAuth +// @router /users/ [post] func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { err := securedCheck(r) if err != nil { http.Error(rw, err.Error(), http.StatusForbidden) + return } rw.Header().Set("Content-Type", "text/plain") @@ -1026,10 +1012,25 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte(fmt.Sprintf("User %v successfully created!\n", username))) } +// deleteUser godoc +// @summary Deletes a user +// @tags remove +// @description User defined by username in form data will be deleted from database. +// @accept mpfd +// @param username formData string true "User ID to delete" +// @success 200 "User deleted successfully" +// @failure 400 {object} api.ErrorResponse "Bad Request" +// @failure 401 {object} api.ErrorResponse "Unauthorized" +// @failure 403 {object} api.ErrorResponse "Forbidden" +// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: deleting user failed" +// @failure 500 {object} api.ErrorResponse "Internal Server Error" +// @security ApiKeyAuth +// @router /users/ [delete] func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) { err := securedCheck(r) if err != nil { http.Error(rw, err.Error(), http.StatusForbidden) + return } if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { @@ -1046,10 +1047,26 @@ func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(http.StatusOK) } +// getUsers godoc +// @summary Returns a list of users +// @tags query +// @description Returns a JSON-encoded list of users. +// @description Required query-parameter defines if all users or only users with additional special roles are returned. +// @accept json +// @produce json +// @param not-just-user query bool true "If returned list should contain all users or only users with additional special roles" +// @success 200 {string} json "Users returned successfully" +// @failure 400 {object} api.ErrorResponse "Bad Request" +// @failure 401 {object} api.ErrorResponse "Unauthorized" +// @failure 403 {object} api.ErrorResponse "Forbidden" +// @failure 500 {object} api.ErrorResponse "Internal Server Error" +// @security ApiKeyAuth +// @router /users/ [get] func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) { err := securedCheck(r) if err != nil { http.Error(rw, err.Error(), http.StatusForbidden) + return } if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { @@ -1066,31 +1083,31 @@ func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) { json.NewEncoder(rw).Encode(users) } -func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) { - err := securedCheck(r) - if err != nil { - http.Error(rw, err.Error(), http.StatusForbidden) - } - - user := repository.GetUserFromContext(r.Context()) - if !user.HasRole(schema.RoleAdmin) { - http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden) - return - } - - roles, err := schema.GetValidRoles(user) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - json.NewEncoder(rw).Encode(roles) -} - +// updateUser godoc +// @summary Updates an existing user +// @tags add and modify +// @description Modifies user defined by username (id) in one of four possible ways. +// @description If more than one formValue is set then only the highest priority field is used. +// @accept mpfd +// @produce plain +// @param id path string true "Database ID of User" +// @param add-role formData string false "Priority 1: Role to add, one of: [admin, support, manager, user, api]" +// @param remove-role formData string false "Priority 2: Role to remove, one of: [admin, support, manager, user, api]" +// @param add-project formData string false "Priority 3: Project to add" +// @param remove-project formData string false "Priority 4: Project to remove" +// @success 200 {string} string "Task successful" +// @failure 400 {object} api.ErrorResponse "Bad Request" +// @failure 401 {object} api.ErrorResponse "Unauthorized" +// @failure 403 {object} api.ErrorResponse "Forbidden" +// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: The user could not be updated" +// @failure 500 {object} api.ErrorResponse "Internal Server Error" +// @security ApiKeyAuth +// @router /user/{id} [post] func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { err := securedCheck(r) if err != nil { http.Error(rw, err.Error(), http.StatusForbidden) + return } if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { @@ -1134,6 +1151,62 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { } } +func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + return + } + + rw.Header().Set("Content-Type", "text/plain") + username := r.FormValue("username") + me := repository.GetUserFromContext(r.Context()) + if !me.HasRole(schema.RoleAdmin) { + if username != me.Username { + http.Error(rw, "Only admins are allowed to sign JWTs not for themselves", + http.StatusForbidden) + return + } + } + + user, err := repository.GetUserRepository().GetUser(username) + if err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + + jwt, err := api.Authentication.JwtAuth.ProvideJWT(user) + if err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(jwt)) +} + +func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) { + err := securedCheck(r) + if err != nil { + http.Error(rw, err.Error(), http.StatusForbidden) + return + } + + user := repository.GetUserFromContext(r.Context()) + if !user.HasRole(schema.RoleAdmin) { + http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden) + return + } + + roles, err := schema.GetValidRoles(user) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(rw).Encode(roles) +} + func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "text/plain") key, value := r.FormValue("key"), r.FormValue("value")