mirror of
https://github.com/gotify/server.git
synced 2024-01-28 15:20:56 +03:00
Can be enabled via the registration config flag. (disabled per default) Fixes gotify/server#395 Co-authored-by: pigpig <pigpig@pig.pig> Co-authored-by: Karmanyaah Malhotra <32671690+karmanyaahm@users.noreply.github.com> Co-authored-by: Jannis Mattheis <contact@jmattheis.de>
464 lines
12 KiB
Go
464 lines
12 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gotify/server/v2/auth"
|
|
"github.com/gotify/server/v2/auth/password"
|
|
"github.com/gotify/server/v2/model"
|
|
)
|
|
|
|
// The UserDatabase interface for encapsulating database access.
|
|
type UserDatabase interface {
|
|
GetUsers() ([]*model.User, error)
|
|
GetUserByID(id uint) (*model.User, error)
|
|
GetUserByName(name string) (*model.User, error)
|
|
DeleteUserByID(id uint) error
|
|
UpdateUser(user *model.User) error
|
|
CreateUser(user *model.User) error
|
|
CountUser(condition ...interface{}) (int, error)
|
|
}
|
|
|
|
// UserChangeNotifier notifies listeners for user changes.
|
|
type UserChangeNotifier struct {
|
|
userDeletedCallbacks []func(uid uint) error
|
|
userAddedCallbacks []func(uid uint) error
|
|
}
|
|
|
|
// OnUserDeleted is called on user deletion.
|
|
func (c *UserChangeNotifier) OnUserDeleted(cb func(uid uint) error) {
|
|
c.userDeletedCallbacks = append(c.userDeletedCallbacks, cb)
|
|
}
|
|
|
|
// OnUserAdded is called on user creation.
|
|
func (c *UserChangeNotifier) OnUserAdded(cb func(uid uint) error) {
|
|
c.userAddedCallbacks = append(c.userAddedCallbacks, cb)
|
|
}
|
|
|
|
func (c *UserChangeNotifier) fireUserDeleted(uid uint) error {
|
|
for _, cb := range c.userDeletedCallbacks {
|
|
if err := cb(uid); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *UserChangeNotifier) fireUserAdded(uid uint) error {
|
|
for _, cb := range c.userAddedCallbacks {
|
|
if err := cb(uid); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// The UserAPI provides handlers for managing users.
|
|
type UserAPI struct {
|
|
DB UserDatabase
|
|
PasswordStrength int
|
|
UserChangeNotifier *UserChangeNotifier
|
|
Registration bool
|
|
}
|
|
|
|
// GetUsers returns all the users
|
|
// swagger:operation GET /user user getUsers
|
|
//
|
|
// Return all users.
|
|
//
|
|
// ---
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// type: array
|
|
// items:
|
|
// $ref: "#/definitions/User"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) GetUsers(ctx *gin.Context) {
|
|
users, err := a.DB.GetUsers()
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
var resp []*model.UserExternal
|
|
for _, user := range users {
|
|
resp = append(resp, toExternalUser(user))
|
|
}
|
|
|
|
ctx.JSON(200, resp)
|
|
}
|
|
|
|
// GetCurrentUser returns the current user
|
|
// swagger:operation GET /current/user user currentUser
|
|
//
|
|
// Return the current user.
|
|
//
|
|
// ---
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// $ref: "#/definitions/User"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) GetCurrentUser(ctx *gin.Context) {
|
|
user, err := a.DB.GetUserByID(auth.GetUserID(ctx))
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
ctx.JSON(200, toExternalUser(user))
|
|
}
|
|
|
|
// CreateUser create a user.
|
|
// swagger:operation POST /user user createUser
|
|
//
|
|
// Create a user.
|
|
//
|
|
// With enabled registration: non admin users can be created without authentication.
|
|
// With disabled registrations: users can only be created by admin users.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// parameters:
|
|
// - name: body
|
|
// in: body
|
|
// description: the user to add
|
|
// required: true
|
|
// schema:
|
|
// $ref: "#/definitions/UserWithPass"
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// $ref: "#/definitions/User"
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) CreateUser(ctx *gin.Context) {
|
|
user := model.UserExternalWithPass{}
|
|
if err := ctx.Bind(&user); err == nil {
|
|
internal := a.toInternalUser(&user, []byte{})
|
|
existingUser, err := a.DB.GetUserByName(internal.Name)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
|
|
var requestedBy *model.User
|
|
uid := auth.TryGetUserID(ctx)
|
|
if uid != nil {
|
|
requestedBy, err = a.DB.GetUserByID(*uid)
|
|
if err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, fmt.Errorf("could not get user: %s", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
if requestedBy == nil || !requestedBy.Admin {
|
|
status := http.StatusUnauthorized
|
|
if requestedBy != nil {
|
|
status = http.StatusForbidden
|
|
}
|
|
if !a.Registration {
|
|
ctx.AbortWithError(status, errors.New("you are not allowed to access this api"))
|
|
return
|
|
}
|
|
if internal.Admin {
|
|
ctx.AbortWithError(status, errors.New("you are not allowed to create an admin user"))
|
|
return
|
|
}
|
|
}
|
|
|
|
if existingUser == nil {
|
|
if success := successOrAbort(ctx, 500, a.DB.CreateUser(internal)); !success {
|
|
return
|
|
}
|
|
if err := a.UserChangeNotifier.fireUserAdded(internal.ID); err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
ctx.JSON(200, toExternalUser(internal))
|
|
} else {
|
|
ctx.AbortWithError(400, errors.New("username already exists"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetUserByID returns the user by id
|
|
// swagger:operation GET /user/{id} user getUser
|
|
//
|
|
// Get a user.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the user id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// $ref: "#/definitions/User"
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) GetUserByID(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
user, err := a.DB.GetUserByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if user != nil {
|
|
ctx.JSON(200, toExternalUser(user))
|
|
} else {
|
|
ctx.AbortWithError(404, errors.New("user does not exist"))
|
|
}
|
|
})
|
|
}
|
|
|
|
// DeleteUserByID deletes the user by id
|
|
// swagger:operation DELETE /user/{id} user deleteUser
|
|
//
|
|
// Deletes a user.
|
|
//
|
|
// ---
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the user id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) DeleteUserByID(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
user, err := a.DB.GetUserByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if user != nil {
|
|
adminCount, err := a.DB.CountUser(&model.User{Admin: true})
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if user.Admin && adminCount == 1 {
|
|
ctx.AbortWithError(400, errors.New("cannot delete last admin"))
|
|
return
|
|
}
|
|
if err := a.UserChangeNotifier.fireUserDeleted(id); err != nil {
|
|
ctx.AbortWithError(500, err)
|
|
return
|
|
}
|
|
successOrAbort(ctx, 500, a.DB.DeleteUserByID(id))
|
|
} else {
|
|
ctx.AbortWithError(404, errors.New("user does not exist"))
|
|
}
|
|
})
|
|
}
|
|
|
|
// ChangePassword changes the password from the current user
|
|
// swagger:operation POST /current/user/password user updateCurrentUser
|
|
//
|
|
// Update the password of the current user.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// parameters:
|
|
// - name: body
|
|
// in: body
|
|
// description: the user
|
|
// required: true
|
|
// schema:
|
|
// $ref: "#/definitions/UserPass"
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) ChangePassword(ctx *gin.Context) {
|
|
pw := model.UserExternalPass{}
|
|
if err := ctx.Bind(&pw); err == nil {
|
|
user, err := a.DB.GetUserByID(auth.GetUserID(ctx))
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
user.Pass = password.CreatePassword(pw.Pass, a.PasswordStrength)
|
|
successOrAbort(ctx, 500, a.DB.UpdateUser(user))
|
|
}
|
|
}
|
|
|
|
// UpdateUserByID updates and user by id
|
|
// swagger:operation POST /user/{id} user updateUser
|
|
//
|
|
// Update a user.
|
|
//
|
|
// ---
|
|
// consumes: [application/json]
|
|
// produces: [application/json]
|
|
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: the user id
|
|
// required: true
|
|
// type: integer
|
|
// format: int64
|
|
// - name: body
|
|
// in: body
|
|
// description: the updated user
|
|
// required: true
|
|
// schema:
|
|
// $ref: "#/definitions/UserWithPass"
|
|
// responses:
|
|
// 200:
|
|
// description: Ok
|
|
// schema:
|
|
// $ref: "#/definitions/User"
|
|
// 400:
|
|
// description: Bad Request
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 401:
|
|
// description: Unauthorized
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 403:
|
|
// description: Forbidden
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
// 404:
|
|
// description: Not Found
|
|
// schema:
|
|
// $ref: "#/definitions/Error"
|
|
func (a *UserAPI) UpdateUserByID(ctx *gin.Context) {
|
|
withID(ctx, "id", func(id uint) {
|
|
var user *model.UserExternalWithPass
|
|
if err := ctx.Bind(&user); err == nil {
|
|
oldUser, err := a.DB.GetUserByID(id)
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if oldUser != nil {
|
|
adminCount, err := a.DB.CountUser(&model.User{Admin: true})
|
|
if success := successOrAbort(ctx, 500, err); !success {
|
|
return
|
|
}
|
|
if !user.Admin && oldUser.Admin && adminCount == 1 {
|
|
ctx.AbortWithError(400, errors.New("cannot delete last admin"))
|
|
return
|
|
}
|
|
internal := a.toInternalUser(user, oldUser.Pass)
|
|
internal.ID = id
|
|
if success := successOrAbort(ctx, 500, a.DB.UpdateUser(internal)); !success {
|
|
return
|
|
}
|
|
ctx.JSON(200, toExternalUser(internal))
|
|
} else {
|
|
ctx.AbortWithError(404, errors.New("user does not exist"))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (a *UserAPI) toInternalUser(response *model.UserExternalWithPass, pw []byte) *model.User {
|
|
user := &model.User{
|
|
Name: response.Name,
|
|
Admin: response.Admin,
|
|
}
|
|
if response.Pass != "" {
|
|
user.Pass = password.CreatePassword(response.Pass, a.PasswordStrength)
|
|
} else {
|
|
user.Pass = pw
|
|
}
|
|
return user
|
|
}
|
|
|
|
func toExternalUser(internal *model.User) *model.UserExternal {
|
|
return &model.UserExternal{
|
|
Name: internal.Name,
|
|
Admin: internal.Admin,
|
|
ID: internal.ID,
|
|
}
|
|
}
|