package handler import ( "strings" "github.com/gofiber/fiber/v2" "github.com/gosec/gsc-ops-api/internal/middleware" "github.com/gosec/gsc-ops-api/internal/service" "github.com/gosec/gsc-ops-api/pkg/types" ) // LDAPUserHandler handles LDAP user endpoints type LDAPUserHandler struct { svc *service.LDAPService } // NewLDAPUserHandler creates a new LDAP user handler func NewLDAPUserHandler(svc *service.LDAPService) *LDAPUserHandler { return &LDAPUserHandler{svc: svc} } // List handles GET /api/v1/ldap/users // // Query parameters: // - search: free-text search across uid, givenName, sn, mail // - services: comma-separated service domains (mail,calendar) // - attr.: filter by any LDAP attribute (e.g. attr.gscTenantId=abc123) // - limit: max results (default 50, max 500) func (h *LDAPUserHandler) List(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) search := c.Query("search") limit := c.QueryInt("limit", 50) if limit > 500 { limit = 500 } // Parse services filter: ?services=mail,calendar var serviceFilters []string if svcParam := c.Query("services"); svcParam != "" { serviceFilters = strings.Split(svcParam, ",") } // Parse dynamic attribute filters: ?attr.gscTenantId=abc&attr.mail=*@example.com attrFilters := make(map[string]string) c.Context().QueryArgs().VisitAll(func(key, value []byte) { k := string(key) if strings.HasPrefix(k, "attr.") && len(k) > 5 { attrName := k[5:] attrFilters[attrName] = string(value) } }) users, err := h.svc.ListUsers(search, limit, serviceFilters, attrFilters) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewPagedResponse(users, int64(len(users)), limit, 0, reqID)) } // Get handles GET /api/v1/ldap/users/:uid func (h *LDAPUserHandler) Get(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") user, err := h.svc.GetUser(uid) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if user == nil { apiErr := types.NewNotFound("User not found: " + uid) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(user, reqID)) } // Create handles POST /api/v1/ldap/users func (h *LDAPUserHandler) Create(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) var req types.LDAPUserCreate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.UID == "" || req.FirstName == "" || req.LastName == "" { apiErr := types.NewValidation("uid, firstName, and lastName are required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } user, err := h.svc.CreateUser(&req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.Status(fiber.StatusCreated).JSON(types.NewDataResponse(user, reqID)) } // Update handles PUT /api/v1/ldap/users/:uid func (h *LDAPUserHandler) Update(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") var req types.LDAPUserUpdate if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } user, err := h.svc.UpdateUser(uid, &req) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(user, reqID)) } // Delete handles DELETE /api/v1/ldap/users/:uid (disables the user) func (h *LDAPUserHandler) Delete(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") if err := h.svc.DisableUser(uid); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"uid": uid, "disabled": true}, reqID)) } // ResetPassword handles POST /api/v1/ldap/users/:uid/password func (h *LDAPUserHandler) ResetPassword(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") var req types.PasswordReset if err := c.BodyParser(&req); err != nil { apiErr := types.NewBadRequest("Invalid request body: " + err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if req.NewPassword == "" { apiErr := types.NewValidation("newPassword is required") return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if err := h.svc.ResetPassword(uid, req.NewPassword); err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(fiber.Map{"uid": uid, "passwordReset": true}, reqID)) } // ListGroups handles GET /api/v1/ldap/users/:uid/groups func (h *LDAPUserHandler) ListGroups(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") groups, err := h.svc.GetUserGroups(uid) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if groups == nil { apiErr := types.NewNotFound("User not found: " + uid) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(groups, reqID)) } // ListServices handles GET /api/v1/ldap/users/:uid/services func (h *LDAPUserHandler) ListServices(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") services, err := h.svc.GetUserServices(uid, "") if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if services == nil { apiErr := types.NewNotFound("User not found: " + uid) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(services, reqID)) } // GetService handles GET /api/v1/ldap/users/:uid/services/:domain func (h *LDAPUserHandler) GetService(c *fiber.Ctx) error { reqID := middleware.GetRequestID(c) uid := c.Params("uid") domain := c.Params("domain") services, err := h.svc.GetUserServices(uid, domain) if err != nil { apiErr := types.NewInternal(err.Error()) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } if services == nil { apiErr := types.NewNotFound("User not found: " + uid) return c.Status(apiErr.Status).JSON(types.NewErrorResponse(apiErr, reqID)) } return c.JSON(types.NewDataResponse(services, reqID)) }