Merge pull request #194 from ClusterCockpit/105_modify_user_via_api

first iteraton of implementing ip-secured enpoint
This commit is contained in:
Jan Eitzinger 2023-08-23 08:57:40 +02:00 committed by GitHub
commit b7aacd1b33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1105 additions and 129 deletions

View File

@ -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. 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/`. 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 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 If you start cc-backend with the `-dev` flag, the Swagger interface is available
at http://localhost:8080/swagger/. at http://localhost:8080/swagger/.
You must enter a JWT key for a user with the API role. You must enter a JWT key for a user with the API role.

View File

@ -12,7 +12,7 @@
"name": "MIT License", "name": "MIT License",
"url": "https://opensource.org/licenses/MIT" "url": "https://opensource.org/licenses/MIT"
}, },
"version": "1" "version": "1.0.0"
}, },
"host": "localhost:8080", "host": "localhost:8080",
"basePath": "/api", "basePath": "/api",
@ -707,9 +707,367 @@
} }
} }
} }
},
"/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
},
{
"enum": [
"admin",
"support",
"manager",
"user",
"api"
],
"type": "string",
"description": "Priority 1: Role to add",
"name": "add-role",
"in": "formData"
},
{
"enum": [
"admin",
"support",
"manager",
"user",
"api"
],
"type": "string",
"description": "Priority 2: Role to remove",
"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": "Success Response Message",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"422": {
"description": "Unprocessable Entity: The user could not be updated",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/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.",
"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": "List of users returned successfully",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ApiReturnedUser"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
},
"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
},
{
"enum": [
"admin",
"support",
"manager",
"user",
"api"
],
"type": "string",
"description": "User role",
"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": "Success Response",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"422": {
"description": "Unprocessable Entity: creating user failed",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
},
"delete": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "User defined by username in form data will be deleted from database.",
"consumes": [
"multipart/form-data"
],
"produces": [
"text/plain"
],
"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": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"422": {
"description": "Unprocessable Entity: deleting user failed",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
"api.ApiReturnedUser": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"name": {
"type": "string"
},
"projects": {
"type": "array",
"items": {
"type": "string"
}
},
"roles": {
"type": "array",
"items": {
"type": "string"
}
},
"username": {
"type": "string"
}
}
},
"api.ApiTag": { "api.ApiTag": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1366,7 +1724,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "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" "type": "integer"
}, },
"name": { "name": {

View File

@ -1,5 +1,22 @@
basePath: /api basePath: /api
definitions: definitions:
api.ApiReturnedUser:
properties:
email:
type: string
name:
type: string
projects:
items:
type: string
type: array
roles:
items:
type: string
type: array
username:
type: string
type: object
api.ApiTag: api.ApiTag:
properties: properties:
name: name:
@ -495,9 +512,7 @@ definitions:
description: Defines a tag using name and type. description: Defines a tag using name and type.
properties: properties:
id: id:
description: |- description: The unique DB identifier of a tag
The unique DB identifier of a tag
The unique DB identifier of a tag
type: integer type: integer
name: name:
description: Tag Name description: Tag Name
@ -526,7 +541,7 @@ info:
name: MIT License name: MIT License
url: https://opensource.org/licenses/MIT url: https://opensource.org/licenses/MIT
title: ClusterCockpit REST API title: ClusterCockpit REST API
version: "1" version: 1.0.0
paths: paths:
/jobs/: /jobs/:
get: get:
@ -996,6 +1011,231 @@ paths:
summary: Adds one or more tags to a job summary: Adds one or more tags to a job
tags: tags:
- add and modify - 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'
enum:
- admin
- support
- manager
- user
- api
in: formData
name: add-role
type: string
- description: 'Priority 2: Role to remove'
enum:
- 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: Success Response Message
schema:
type: string
"400":
description: Bad Request
schema:
type: string
"401":
description: Unauthorized
schema:
type: string
"403":
description: Forbidden
schema:
type: string
"422":
description: 'Unprocessable Entity: The user could not be updated'
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
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
produces:
- text/plain
responses:
"200":
description: User deleted successfully
"400":
description: Bad Request
schema:
type: string
"401":
description: Unauthorized
schema:
type: string
"403":
description: Forbidden
schema:
type: string
"422":
description: 'Unprocessable Entity: deleting user failed'
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
security:
- ApiKeyAuth: []
summary: Deletes a user
tags:
- remove
get:
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: List of users returned successfully
schema:
items:
$ref: '#/definitions/api.ApiReturnedUser'
type: array
"400":
description: Bad Request
schema:
type: string
"401":
description: Unauthorized
schema:
type: string
"403":
description: Forbidden
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
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
enum:
- 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: Success Response
schema:
type: string
"400":
description: Bad Request
schema:
type: string
"401":
description: Unauthorized
schema:
type: string
"403":
description: Forbidden
schema:
type: string
"422":
description: 'Unprocessable Entity: creating user failed'
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
security:
- ApiKeyAuth: []
summary: Adds a new user
tags:
- add and modify
securityDefinitions: securityDefinitions:
ApiKeyAuth: ApiKeyAuth:
in: header in: header

View File

@ -713,9 +713,367 @@ 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
},
{
"enum": [
"admin",
"support",
"manager",
"user",
"api"
],
"type": "string",
"description": "Priority 1: Role to add",
"name": "add-role",
"in": "formData"
},
{
"enum": [
"admin",
"support",
"manager",
"user",
"api"
],
"type": "string",
"description": "Priority 2: Role to remove",
"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": "Success Response Message",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"422": {
"description": "Unprocessable Entity: The user could not be updated",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/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.",
"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": "List of users returned successfully",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ApiReturnedUser"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
},
"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
},
{
"enum": [
"admin",
"support",
"manager",
"user",
"api"
],
"type": "string",
"description": "User role",
"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": "Success Response",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"422": {
"description": "Unprocessable Entity: creating user failed",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
},
"delete": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "User defined by username in form data will be deleted from database.",
"consumes": [
"multipart/form-data"
],
"produces": [
"text/plain"
],
"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": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"422": {
"description": "Unprocessable Entity: deleting user failed",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
"api.ApiReturnedUser": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"name": {
"type": "string"
},
"projects": {
"type": "array",
"items": {
"type": "string"
}
},
"roles": {
"type": "array",
"items": {
"type": "string"
}
},
"username": {
"type": "string"
}
}
},
"api.ApiTag": { "api.ApiTag": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1372,7 +1730,7 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "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" "type": "integer"
}, },
"name": { "name": {
@ -1415,7 +1773,7 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it // SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{ var SwaggerInfo = &swag.Spec{
Version: "1", Version: "1.0.0",
Host: "localhost:8080", Host: "localhost:8080",
BasePath: "/api", BasePath: "/api",
Schemes: []string{}, Schemes: []string{},

View File

@ -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/", api.deleteJobByRequest).Methods(http.MethodDelete)
r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).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("/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 != "" { if api.MachineStateDir != "" {
r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet) r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet)
@ -165,6 +163,14 @@ type JobMetricWithName struct {
Metric *schema.JobMetric `json:"metric"` Metric *schema.JobMetric `json:"metric"`
} }
type ApiReturnedUser struct {
Username string `json:"username"`
Name string `json:"name"`
Roles []string `json:"roles"`
Email string `json:"email"`
Projects []string `json:"projects"`
}
func handleError(err error, statusCode int, rw http.ResponseWriter) { func handleError(err error, statusCode int, rw http.ResponseWriter) {
log.Warnf("REST ERROR : %s", err.Error()) log.Warnf("REST ERROR : %s", err.Error())
rw.Header().Add("Content-Type", "application/json") rw.Header().Add("Content-Type", "application/json")
@ -193,6 +199,10 @@ func securedCheck(r *http.Request) error {
return fmt.Errorf("missing configuration key ApiAllowedIPs") return fmt.Errorf("missing configuration key ApiAllowedIPs")
} }
if config.Keys.ApiAllowedIPs[0] == "*" {
return nil
}
// extract IP address // extract IP address
IPAddress := r.Header.Get("X-Real-Ip") IPAddress := r.Header.Get("X-Real-Ip")
if IPAddress == "" { if IPAddress == "" {
@ -943,43 +953,31 @@ func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) {
}) })
} }
func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { // createUser godoc
err := securedCheck(r) // @summary Adds a new user
if err != nil { // @tags add and modify
http.Error(rw, err.Error(), http.StatusForbidden) // @description User specified in form data will be saved to database.
} // @accept mpfd
// @produce plain
rw.Header().Set("Content-Type", "text/plain") // @param username formData string true "Unique user ID"
username := r.FormValue("username") // @param password formData string true "User password"
me := repository.GetUserFromContext(r.Context()) // @param role formData string true "User role" Enums(admin, support, manager, user, api)
if !me.HasRole(schema.RoleAdmin) { // @param project formData string false "Managed project, required for new manager role user"
if username != me.Username { // @param name formData string false "Users name"
http.Error(rw, "Only admins are allowed to sign JWTs not for themselves", // @param email formData string false "Users email"
http.StatusForbidden) // @success 200 {string} string "Success Response"
return // @failure 400 {string} string "Bad Request"
} // @failure 401 {string} string "Unauthorized"
} // @failure 403 {string} string "Forbidden"
// @failure 422 {string} string "Unprocessable Entity: creating user failed"
user, err := repository.GetUserRepository().GetUser(username) // @failure 500 {string} string "Internal Server Error"
if err != nil { // @security ApiKeyAuth
http.Error(rw, err.Error(), http.StatusUnprocessableEntity) // @router /users/ [post]
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) createUser(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
err := securedCheck(r) err := securedCheck(r)
if err != nil { if err != nil {
http.Error(rw, err.Error(), http.StatusForbidden) http.Error(rw, err.Error(), http.StatusForbidden)
return
} }
rw.Header().Set("Content-Type", "text/plain") rw.Header().Set("Content-Type", "text/plain")
@ -1022,10 +1020,26 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte(fmt.Sprintf("User %v successfully created!\n", username))) 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
// @produce plain
// @param username formData string true "User ID to delete"
// @success 200 "User deleted successfully"
// @failure 400 {string} string "Bad Request"
// @failure 401 {string} string "Unauthorized"
// @failure 403 {string} string "Forbidden"
// @failure 422 {string} string "Unprocessable Entity: deleting user failed"
// @failure 500 {string} string "Internal Server Error"
// @security ApiKeyAuth
// @router /users/ [delete]
func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) {
err := securedCheck(r) err := securedCheck(r)
if err != nil { if err != nil {
http.Error(rw, err.Error(), http.StatusForbidden) http.Error(rw, err.Error(), http.StatusForbidden)
return
} }
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
@ -1042,10 +1056,25 @@ func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK) 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.
// @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 {array} api.ApiReturnedUser "List of users returned successfully"
// @failure 400 {string} string "Bad Request"
// @failure 401 {string} string "Unauthorized"
// @failure 403 {string} string "Forbidden"
// @failure 500 {string} string "Internal Server Error"
// @security ApiKeyAuth
// @router /users/ [get]
func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) {
err := securedCheck(r) err := securedCheck(r)
if err != nil { if err != nil {
http.Error(rw, err.Error(), http.StatusForbidden) http.Error(rw, err.Error(), http.StatusForbidden)
return
} }
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
@ -1062,31 +1091,31 @@ func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) {
json.NewEncoder(rw).Encode(users) json.NewEncoder(rw).Encode(users)
} }
func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) { // updateUser godoc
err := securedCheck(r) // @summary Updates an existing user
if err != nil { // @tags add and modify
http.Error(rw, err.Error(), http.StatusForbidden) // @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
user := repository.GetUserFromContext(r.Context()) // @produce plain
if !user.HasRole(schema.RoleAdmin) { // @param id path string true "Database ID of User"
http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden) // @param add-role formData string false "Priority 1: Role to add" Enums(admin, support, manager, user, api)
return // @param remove-role formData string false "Priority 2: Role to remove" Enums(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"
roles, err := schema.GetValidRoles(user) // @success 200 {string} string "Success Response Message"
if err != nil { // @failure 400 {string} string "Bad Request"
http.Error(rw, err.Error(), http.StatusInternalServerError) // @failure 401 {string} string "Unauthorized"
return // @failure 403 {string} string "Forbidden"
} // @failure 422 {string} string "Unprocessable Entity: The user could not be updated"
// @failure 500 {string} string "Internal Server Error"
json.NewEncoder(rw).Encode(roles) // @security ApiKeyAuth
} // @router /user/{id} [post]
func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
err := securedCheck(r) err := securedCheck(r)
if err != nil { if err != nil {
http.Error(rw, err.Error(), http.StatusForbidden) http.Error(rw, err.Error(), http.StatusForbidden)
return
} }
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) { if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
@ -1130,70 +1159,61 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
} }
} }
// func (api *RestApi) secureUpdateUser(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) {
// if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) { err := securedCheck(r)
// handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw) if err != nil {
// return http.Error(rw, err.Error(), http.StatusForbidden)
// } return
// }
// // IP CHECK HERE (WIP)
// // Probably better as private routine rw.Header().Set("Content-Type", "text/plain")
// IPAddress := r.Header.Get("X-Real-Ip") username := r.FormValue("username")
// if IPAddress == "" { me := repository.GetUserFromContext(r.Context())
// IPAddress = r.Header.Get("X-Forwarded-For") if !me.HasRole(schema.RoleAdmin) {
// } if username != me.Username {
// if IPAddress == "" { http.Error(rw, "Only admins are allowed to sign JWTs not for themselves",
// IPAddress = r.RemoteAddr http.StatusForbidden)
// } return
// }
// // Also This }
// ipOk := false
// for _, a := range config.Keys.ApiAllowedAddrs { user, err := repository.GetUserRepository().GetUser(username)
// if a == IPAddress { if err != nil {
// ipOk = true http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
// } return
// } }
//
// if IPAddress == "" || ipOk == false { jwt, err := api.Authentication.JwtAuth.ProvideJWT(user)
// handleError(fmt.Errorf("unknown ip: %v", IPAddress), http.StatusForbidden, rw) if err != nil {
// return http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
// } return
// // IP CHECK END }
//
// // Get Values rw.WriteHeader(http.StatusOK)
// id := mux.Vars(r)["id"] rw.Write([]byte(jwt))
// newproj := mux.Vars(r)["project"] }
// newrole := mux.Vars(r)["role"]
// func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) {
// // TODO: Handle anything but roles... err := securedCheck(r)
// if newrole != "" { if err != nil {
// if err := api.Authentication.AddRole(r.Context(), id, newrole); err != nil { http.Error(rw, err.Error(), http.StatusForbidden)
// handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) return
// return }
// }
// user := repository.GetUserFromContext(r.Context())
// rw.Header().Add("Content-Type", "application/json") if !user.HasRole(schema.RoleAdmin) {
// rw.WriteHeader(http.StatusOK) http.Error(rw, "only admins are allowed to fetch a list of roles", http.StatusForbidden)
// json.NewEncoder(rw).Encode(UpdateUserApiResponse{ return
// Message: fmt.Sprintf("Successfully added role %s to %s", newrole, id), }
// })
// roles, err := schema.GetValidRoles(user)
// } else if newproj != "" { if err != nil {
// if err := api.Authentication.AddProject(r.Context(), id, newproj); err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError)
// handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) return
// return }
// }
// json.NewEncoder(rw).Encode(roles)
// rw.Header().Add("Content-Type", "application/json") }
// rw.WriteHeader(http.StatusOK)
// json.NewEncoder(rw).Encode(UpdateUserApiResponse{
// Message: fmt.Sprintf("Successfully added project %s to %s", newproj, id),
// })
//
// } else {
// handleError(errors.New("Not Add [role|project]?"), http.StatusBadRequest, rw)
// }
// }
func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/plain") rw.Header().Set("Content-Type", "text/plain")