mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-08 14:39:09 +02:00
* use libopenapi OpenAPI generator Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added a handler for v1alpha1/peers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wire up request validator Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wired up spec-based validation Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use sync validation; set base url to v1alpha1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * moved stuff around, extracted runtime libopenapu deps into runtime_tooling Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * testing /peers path params Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * fixed tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added user schemas and paths Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use api validation in tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * post-merge fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * deleted tmp command used to test validator integration Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * require request body in post/put requests in order to force validation Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * making linter happy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * switch to api/v1alpha1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * go mod tidy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * keep the original order of middleware: metrics, cors, then auth Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * return 422 on validation errors Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * v1alpha1 spec cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * no need for a double-pointer Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more linter fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * removed more double pointers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use di to inject api_v0 and api_v1 http routers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * do not re-add middleware on repeated call to ApiHandler Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * calling ApiRouter() now also calls ApiV1Router() Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> --------- Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
276 lines
7.5 KiB
Go
276 lines
7.5 KiB
Go
package users
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gorilla/mux"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/management/server/account"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
"github.com/netbirdio/netbird/shared/management/http/apiv1alpha1"
|
|
"github.com/netbirdio/netbird/shared/management/http/util"
|
|
"github.com/netbirdio/netbird/shared/management/status"
|
|
|
|
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
|
)
|
|
|
|
type Handler struct {
|
|
accountManager account.Manager
|
|
}
|
|
|
|
func NewHandler(accountManager account.Manager) *Handler {
|
|
return &Handler{
|
|
accountManager: accountManager,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) WithEndpointsForRouter(router *mux.Router) *mux.Router {
|
|
router.HandleFunc("/users", h.getAllUsers).Methods("GET", "OPTIONS")
|
|
router.HandleFunc("/users", h.createUser).Methods("POST", "OPTIONS")
|
|
router.HandleFunc("/users/{userId}", h.updateUser).Methods("PUT", "OPTIONS")
|
|
router.HandleFunc("/users/{userId}", h.deleteUser).Methods("DELETE", "OPTIONS")
|
|
return router
|
|
}
|
|
|
|
// updateUser is a PUT requests to update User data
|
|
func (h *Handler) updateUser(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPut {
|
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
|
return
|
|
}
|
|
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
vars := mux.Vars(r)
|
|
targetUserID := vars["userId"]
|
|
if len(targetUserID) == 0 {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
|
return
|
|
}
|
|
|
|
existingUser, err := h.accountManager.GetUserByID(r.Context(), targetUserID)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
req := &apiv1alpha1.UserRequest{}
|
|
err = json.NewDecoder(r.Body).Decode(req)
|
|
if err != nil {
|
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
|
return
|
|
}
|
|
|
|
if req.AutoGroups == nil {
|
|
util.WriteErrorResponse("auto_groups field can't be absent", http.StatusBadRequest, w)
|
|
return
|
|
}
|
|
|
|
userRole := types.StrRoleToUserRole(req.Role)
|
|
if userRole == types.UserRoleUnknown {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid user role"), w)
|
|
return
|
|
}
|
|
|
|
newUser, err := h.accountManager.SaveUser(r.Context(), accountID, userID, &types.User{
|
|
Id: targetUserID,
|
|
Role: userRole,
|
|
AutoGroups: req.AutoGroups,
|
|
Blocked: req.IsBlocked,
|
|
Issued: existingUser.Issued,
|
|
IntegrationReference: existingUser.IntegrationReference,
|
|
})
|
|
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
util.WriteJSONObject(r.Context(), w, toUserResponse(newUser, userID))
|
|
}
|
|
|
|
// deleteUser is a DELETE request to delete a user
|
|
func (h *Handler) deleteUser(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete {
|
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
|
return
|
|
}
|
|
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
vars := mux.Vars(r)
|
|
targetUserID := vars["userId"]
|
|
if len(targetUserID) == 0 {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
|
return
|
|
}
|
|
|
|
err = h.accountManager.DeleteUser(r.Context(), accountID, userID, targetUserID)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
util.WriteJSONObject(r.Context(), w, util.EmptyObject{})
|
|
}
|
|
|
|
// createUser creates a User in the system with a status "invited" (effectively this is a user invite).
|
|
func (h *Handler) createUser(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
|
return
|
|
}
|
|
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
|
|
req := &apiv1alpha1.UserCreateRequest{}
|
|
err = json.NewDecoder(r.Body).Decode(req)
|
|
if err != nil {
|
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
|
return
|
|
}
|
|
|
|
if types.StrRoleToUserRole(req.Role) == types.UserRoleUnknown {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "unknown user role %s", req.Role), w)
|
|
return
|
|
}
|
|
|
|
email := ""
|
|
if req.Email != nil {
|
|
email = *req.Email
|
|
}
|
|
|
|
name := ""
|
|
if req.Name != nil {
|
|
name = *req.Name
|
|
}
|
|
|
|
newUser, err := h.accountManager.CreateUser(r.Context(), accountID, userID, &types.UserInfo{
|
|
Email: email,
|
|
Name: name,
|
|
Role: req.Role,
|
|
AutoGroups: req.AutoGroups,
|
|
IsServiceUser: req.IsServiceUser,
|
|
Issued: types.UserIssuedAPI,
|
|
})
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
util.WriteJSONObject(r.Context(), w, toUserResponse(newUser, userID))
|
|
}
|
|
|
|
// getAllUsers returns a list of users of the account this user belongs to.
|
|
// It also gathers additional user data (like email and name) from the IDP manager.
|
|
func (h *Handler) getAllUsers(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
|
return
|
|
}
|
|
|
|
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
|
data, err := h.accountManager.GetUsersFromAccount(r.Context(), accountID, userID)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), err, w)
|
|
return
|
|
}
|
|
|
|
serviceUser := r.URL.Query().Get("service_user")
|
|
|
|
users := make([]*apiv1alpha1.User, 0)
|
|
for _, d := range data {
|
|
if d.NonDeletable {
|
|
continue
|
|
}
|
|
if serviceUser == "" {
|
|
users = append(users, toUserResponse(d, userID))
|
|
continue
|
|
}
|
|
|
|
includeServiceUser, err := strconv.ParseBool(serviceUser)
|
|
log.WithContext(r.Context()).Tracef("Should include service user: %v", includeServiceUser)
|
|
if err != nil {
|
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid service_user query parameter"), w)
|
|
return
|
|
}
|
|
if includeServiceUser == d.IsServiceUser {
|
|
users = append(users, toUserResponse(d, userID))
|
|
}
|
|
}
|
|
|
|
util.WriteJSONObject(r.Context(), w, users)
|
|
}
|
|
|
|
func toUserResponse(user *types.UserInfo, currenUserID string) *apiv1alpha1.User {
|
|
autoGroups := user.AutoGroups
|
|
if autoGroups == nil {
|
|
autoGroups = []string{}
|
|
}
|
|
|
|
var userStatus apiv1alpha1.UserStatus
|
|
switch user.Status {
|
|
case "active":
|
|
userStatus = apiv1alpha1.UserStatusActive
|
|
case "invited":
|
|
userStatus = apiv1alpha1.UserStatusInvited
|
|
default:
|
|
userStatus = apiv1alpha1.UserStatusBlocked
|
|
}
|
|
|
|
if user.IsBlocked {
|
|
userStatus = apiv1alpha1.UserStatusBlocked
|
|
}
|
|
|
|
isCurrent := user.ID == currenUserID
|
|
|
|
var password *string
|
|
if user.Password != "" {
|
|
password = &user.Password
|
|
}
|
|
|
|
var idpID *string
|
|
if user.IdPID != "" {
|
|
idpID = &user.IdPID
|
|
}
|
|
|
|
return &apiv1alpha1.User{
|
|
Id: user.ID,
|
|
Name: user.Name,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
AutoGroups: autoGroups,
|
|
Status: userStatus,
|
|
IsCurrent: &isCurrent,
|
|
IsServiceUser: &user.IsServiceUser,
|
|
IsBlocked: user.IsBlocked,
|
|
LastLogin: &user.LastLogin,
|
|
Issued: &user.Issued,
|
|
PendingApproval: user.PendingApproval,
|
|
Password: password,
|
|
IdpId: idpID,
|
|
}
|
|
}
|