func (_ FilesResource) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response { // Verify that tmp path is set either in metadata or on model. file := obj.(kit.File) if file.GetTmpPath() == "" { file.SetTmpPath(r.GetMeta().String("file")) } filePath := file.GetTmpPath() if filePath == "" { return kit.NewErrorResponse("no_tmp_path", "A tmp path must be set when creating a file", true) } tmpPath := getTmpPath(res) if !strings.HasPrefix(filePath, tmpPath) && filePath[0] != '/' { filePath = tmpPath + string(os.PathSeparator) + filePath file.SetTmpPath(filePath) } // Build the file, save it to backend and persist it to the db. err := res.Registry().FileService().BuildFile(file, r.GetUser(), true, true) if err != nil { kit.NewErrorResponse(err) } return &kit.AppResponse{ Data: file, } }
func (PageResource) BeforeDelete(res kit.Resource, obj kit.Model, user kit.User) apperror.Error { // Note: other relationship deletion handling happens in models BeforeDelete() hook. // Files have to be deleted here, since they require the fileService. // Delete files. fileService := res.Registry().FileService() m2m, err := res.Backend().M2M(obj, "Files") if err != nil { return err } files, err := m2m.All() if err != nil { return err } // Delete m2m relation. if err := m2m.Clear(); err != nil { return err } for _, file := range files { if err := fileService.Delete(file.(kit.File), user); err != nil { return err } } return nil }
func (UserResource) AllowDelete(res kit.Resource, obj kit.Model, user kit.User) bool { if user == nil { return false } if obj.(kit.UserModel).GetUserId() == user.GetId() { return true } return user.HasRole("admin") || user.HasPermission(res.Collection()+".delete") }
func (SessionResourceHooks) ApiDelete(res kit.Resource, id string, r kit.Request) kit.Response { if id != r.GetSession().GetStrId() { return kit.NewErrorResponse("permission_denied", "Permission denied", 403) } if err := res.Backend().Delete(r.GetSession()); err != nil { return kit.NewErrorResponse("db_delete_error", err, true) } return &kit.AppResponse{} }
func getTmpPath(res kit.Resource) string { c := res.Registry().Config() tmpPath := c.UPath("tmpUploadDir") if tmpPath == "" { tmpPath = c.TmpDir() if tmpPath == "" { panic("config.TmpDir() empty") } tmpPath = path.Join(tmpPath, "uploads") } return tmpPath }
func (s *Service) RegisterResource(res kit.Resource) { if res.Backend() == nil { if s.defaultBackend == nil { s.registry.Logger().Panic("Registering resource without backend, but no default backend set on resources.Service") } s.defaultBackend.RegisterModel(res.Model()) res.SetBackend(s.defaultBackend) } if res.Collection() == "" { s.registry.Logger().Panic("Registering resource without a model type") } s.resources[res.Collection()] = res }
func (hooks UserResourceHooks) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response { meta := r.GetMeta() adaptor := meta.String("adaptor") if adaptor == "" { return kit.NewErrorResponse("adaptor_missing", "Expected 'adaptor' in metadata.", true) } rawData, ok := meta.Get("authData") if !ok { return kit.NewErrorResponse("auth_data_missing", "Expected 'authData' in metadata.", true) } data, ok := rawData.(map[string]interface{}) if !ok { return kit.NewErrorResponse("invalid_auth_data", "Invalid auth data: expected dictionary", true) } user := obj.(kit.User) service := res.Registry().UserService() // If a profile model was registered, and profile data is in meta, // create the profile model. if profiles := service.ProfileResource(); profiles != nil { profile := profiles.CreateModel().(kit.UserProfile) if rawData, ok := meta.Get("profile"); ok { if data, ok := rawData.(map[string]interface{}); ok { // Profile data present in meta. // Update profile with data. if err := res.ModelInfo().UpdateModelFromData(profile, data); err != nil { return kit.NewErrorResponse("invalid_profile_data", "Invalid profile data.", err, true) } } } user.SetProfile(profile) } if err := service.CreateUser(user, adaptor, data); err != nil { return kit.NewErrorResponse(err) } return &kit.AppResponse{ Data: user, } }
func (PageResource) Methods(res kit.Resource) []kit.Method { publish := &methods.Method{ Name: "cms.page.publish", Blocking: true, Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response { user := r.GetUser() if user == nil || !user.HasRole("admin") { return kit.NewErrorResponse("permission_denied") } id := utils.GetMapStringKey(r.GetData(), "id") if id == "" { return kit.NewErrorResponse("no_id_in_data", "Expected 'id' key in data.") } rawPage, err := res.Backend().FindOne("pages", id) if err != nil { return kit.NewErrorResponse("db_error", err) } else if rawPage == nil { return kit.NewErrorResponse("not_found", "The specified page id does not exist.") } err = res.ModelInfo().UpdateModelFromData(rawPage, map[string]interface{}{ "published": true, "published_at": time.Now(), }) if err != nil { return kit.NewErrorResponse("db_error", err) } return &kit.AppResponse{ Data: map[string]interface{}{"success": true}, } }, } return []kit.Method{publish} }
// ApiFindOne verifies the session and returns user and profile in meta if valid. func (SessionResourceHooks) ApiFindOne(res kit.Resource, rawId string, r kit.Request) kit.Response { if rawId == "" { return kit.NewErrorResponse("empty_token", "Empty token") } userService := res.Registry().UserService() user, session, err := userService.VerifySession(rawId) if err != nil { return kit.NewErrorResponse(err) } meta := make(map[string]interface{}) if user != nil { userData, err := res.Backend().ModelToMap(user, true, false) if err != nil { return kit.NewErrorResponse("marshal_error", err) } meta["user"] = userData if user.GetProfile() != nil { profileData, err := res.Backend().ModelToMap(user.GetProfile(), true, false) if err != nil { return kit.NewErrorResponse("marshal_error", err) } meta["profile"] = profileData } } return &kit.AppResponse{ Data: session, Meta: meta, } }
// Creating a session is equivalent to logging in. func (hooks SessionResourceHooks) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response { userService := res.Registry().UserService() meta := r.GetMeta() isAnonymous, _ := meta.Bool("anonymous") // Find user. userIdentifier := meta.String("user") adaptor := meta.String("adaptor") data, _ := meta.Map("authData") var user kit.User if !isAnonymous { if adaptor == "" { return kit.NewErrorResponse("adaptor_missing", "Expected 'adaptor' in metadata.", true) } if data == nil { kit.NewErrorResponse("no_or_invalid_auth_data", "Expected 'authData' dictionary in metadata.") } var err apperror.Error user, err = userService.AuthenticateUser(userIdentifier, adaptor, data) if err != nil { return kit.NewErrorResponse(err) } } session, err := userService.StartSession(user, r.GetFrontend()) if err != nil { return kit.NewErrorResponse(err) } responseMeta := make(map[string]interface{}) if !isAnonymous { userData, err := res.Backend().ModelToMap(user, true, false) if err != nil { return kit.NewErrorResponse("marshal_error", err) } responseMeta["user"] = userData if user.GetProfile() != nil { profileData, err := res.Backend().ModelToMap(user.GetProfile(), true, false) if err != nil { return kit.NewErrorResponse("marshal_error", err) } responseMeta["profile"] = profileData } } return &kit.AppResponse{ Data: session, Meta: responseMeta, } }
func (UserResourceHooks) Methods(res kit.Resource) []kit.Method { sendConfirmationEmail := &methods.Method{ Name: "users.send-confirmation-email", Blocking: false, Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response { user := r.GetUser() if user == nil { return kit.NewErrorResponse("not_authenticated", "") } if user.IsEmailConfirmed() { return kit.NewErrorResponse("email_already_confirmed", "The users email address is already confirmed") } err := registry.UserService().SendConfirmationEmail(user) if err != nil { return kit.NewErrorResponse("confirm_failed", "Could not confirm email") } return &kit.AppResponse{ Data: map[string]interface{}{"success": true}, } }, } confirmEmail := &methods.Method{ Name: "users.confirm-email", Blocking: false, Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response { data, ok := r.GetData().(map[string]interface{}) if !ok { return kit.NewErrorResponse("invalid_data", "Expected data dict with 'token' key") } token, ok := data["token"].(string) if !ok { return kit.NewErrorResponse("invalid_data", "Expected 'token' string key in data") } if token == "" { return kit.NewErrorResponse("empty_token", "") } _, err := registry.UserService().ConfirmEmail(token) if err != nil { return kit.NewErrorResponse("confirm_failed", "Could not confirm email") } return &kit.AppResponse{ Data: map[string]interface{}{"success": true}, } }, } requestPwReset := &methods.Method{ Name: "users.request-password-reset", Blocking: false, Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response { data, ok := r.GetData().(map[string]interface{}) if !ok { return kit.NewErrorResponse("invalid_data", "Expected data dict with 'user' key", true) } userIdentifier, ok := data["user"].(string) if !ok { return kit.NewErrorResponse("invalid_data", "Expected data dict with 'user' string key", true) } rawUser, err := res.Q().Filter("email", userIdentifier).Or("username", userIdentifier).First() if err != nil { return kit.NewErrorResponse(err) } if rawUser == nil { return kit.NewErrorResponse("unknown_user", fmt.Sprintf("The user %v does not exist", userIdentifier), true) } user := rawUser.(kit.User) err = registry.UserService().SendPasswordResetEmail(user) if err != nil { registry.Logger().Errorf("Could not send password reset email for user %v: %v", user, err) return kit.NewErrorResponse("reset_email_send_failed", "Could not send the reset password mail.", true) } return &kit.AppResponse{ Data: map[string]interface{}{"success": true}, } }, } pwReset := &methods.Method{ Name: "users.password-reset", Blocking: false, Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response { // Verify that token is in data. data, ok := r.GetData().(map[string]interface{}) if !ok { return kit.NewErrorResponse("invalid_data", "Expected 'token' key in data", true) } token, ok := data["token"].(string) if !ok { return kit.NewErrorResponse("invalid_data", "Expected 'token' string key in data", true) } if token == "" { return kit.NewErrorResponse("empty_token", "", true) } // Verify that password is in data. newPw, ok := data["password"].(string) if !ok { return kit.NewErrorResponse("invalid_passord", "Expected 'password' string key in data", true) } if newPw == "" { return kit.NewErrorResponse("empty_password", "Password may not be empty", true) } user, err := registry.UserService().ResetPassword(token, newPw) if err != nil { if err.IsPublic() { return kit.NewErrorResponse(err) } else { return kit.NewErrorResponse("password_reset_failed", "Could not reset the password.", true) } } return &kit.AppResponse{ Data: map[string]interface{}{ "success": true, "userId": user.GetId(), "userEmail": user.GetEmail(), }, } }, } changePassword := &methods.Method{ Name: "users.change-password", Blocking: false, Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response { // Get userId and password from request. userId := utils.GetMapStringKey(r.GetData(), "userId") if userId == "" { return kit.NewErrorResponse("no_userid", "Expected userId key in data", true) } password := utils.GetMapStringKey(r.GetData(), "password") if password == "" { return kit.NewErrorResponse("no_password", "Expected password key in data", true) } // Permission check. user := r.GetUser() if user == nil { return kit.NewErrorResponse("permission_denied", true) } // Users can only change their own password, unless they are admins. if userId != user.GetStrId() { if !(user.HasRole("admin") || user.HasPermission("users.change_passwords")) { return kit.NewErrorResponse("permission_denied", true) } } // User has the right permissions. userService := registry.UserService() // Find the user. rawUser, err := userService.UserResource().FindOne(userId) if err != nil { return kit.NewErrorResponse("db_error", true, err) } if rawUser == nil { return kit.NewErrorResponse("user_does_not_exist", true) } targetUser := rawUser.(kit.User) if err := userService.ChangePassword(targetUser, password); err != nil { return kit.NewErrorResponse(err) } // Everything worked fine. return &kit.AppResponse{ Data: map[string]interface{}{"success": true}, } }, } return []kit.Method{ AuthenticateMethod, ResumeSessionMethod, UnAuthenticateMethod, sendConfirmationEmail, confirmEmail, requestPwReset, pwReset, changePassword, } }
func (hooks FilesResource) HttpRoutes(res kit.Resource) []kit.HttpRoute { maxRunning := res.Registry().Config().UInt("files.thumbGenerator.maxRunning", 10) maxPerIPPerMinute := res.Registry().Config().UInt("files.thumbGenerator.maxPerIPPerMinute", 100) maxQueueSize := res.Registry().Config().UInt("files.thumbGenerator.maxQueueSize", 100) hooks.thumbnailRateLimiter = newRateLimiter(maxRunning, maxPerIPPerMinute, maxQueueSize) routes := make([]kit.HttpRoute, 0) // Upload route. uploadOptionsHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) { header := r.GetHttpResponseWriter().Header() allowedOrigins := registry.Config().UString("fileHandler.allowedOrigins", "*") header.Set("Access-Control-Allow-Origin", allowedOrigins) header.Set("Access-Control-Allow-Methods", "OPTIONS, POST") allowedHeaders := registry.Config().UString("accessControl.allowedHeaders") if allowedHeaders == "" { allowedHeaders = "Authentication, Content-Type, Content-Range, Content-Disposition" } else { allowedHeaders += ", Authentication, Content-Type, Content-Range, Content-Disposition" } header.Set("Access-Control-Allow-Headers", allowedHeaders) return &kit.AppResponse{ HttpStatus: 200, RawData: []byte{}, }, true } uploadOptionsRoute := kit.NewHttpRoute("/api/file-upload", "OPTIONS", uploadOptionsHandler) routes = append(routes, uploadOptionsRoute) tmpPath := getTmpPath(res) if tmpPath == "" { panic("Empty tmp path") } uploadHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) { if registry.Config().UBool("fileHandler.requiresAuth", false) { if r.GetUser() == nil { return kit.NewErrorResponse("permission_denied", ""), false } } var files []string var err apperror.Error if err == nil { files, err = handleUpload(registry, tmpPath, r.GetHttpRequest()) if err != nil { return kit.NewErrorResponse(err), false } } data := map[string]interface{}{ "data": files, } return &kit.AppResponse{Data: data}, false } uploadRoute := kit.NewHttpRoute("/api/file-upload", "POST", uploadHandler) routes = append(routes, uploadRoute) serveFileHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) { file, err := registry.FileService().FindOne(r.GetContext().String("id")) if err != nil { return kit.NewErrorResponse(err), false } if file == nil { return &kit.AppResponse{ HttpStatus: 404, RawData: []byte("File not found"), }, false } reader, err := file.Reader() if err != nil { return kit.NewErrorResponse(err), false } defer reader.Close() w := r.GetHttpResponseWriter() err = serveFile(w, r.GetHttpRequest(), file.GetMime(), file.GetSize(), reader) reader.Close() if err != nil { registry.Logger().Errorf("Error while serving file %v(%v): %v", file.GetId(), file.GetBackendId(), err) } return nil, true } serveFileRoute := kit.NewHttpRoute("/files/:id/*rest", "GET", serveFileHandler) routes = append(routes, serveFileRoute) serveImageHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) { file, err := registry.FileService().FindOne(r.GetContext().String("id")) if err != nil { return kit.NewErrorResponse(err), false } if file == nil { return &kit.AppResponse{ HttpStatus: 404, RawData: []byte("File not found"), }, false } if !file.GetIsImage() { return &kit.AppResponse{ Error: &apperror.Err{ Code: "file_is_no_image", Message: "The requested file is not an image", }, }, false } httpRequest := r.GetHttpRequest() query := httpRequest.URL.Query() rawWidth := query.Get("width") rawHeight := query.Get("height") var width, height int64 if rawWidth != "" { width, _ = strconv.ParseInt(rawWidth, 10, 64) } if rawHeight != "" { height, _ = strconv.ParseInt(rawHeight, 10, 64) } rawFilters := query.Get("filters") filters := strings.Split(rawFilters, ",") thumbDir := registry.Config().UString("thumbnailDir") if thumbDir == "" { thumbDir = tmpPath + string(os.PathSeparator) + "thumbnails" } ip := strings.Split(httpRequest.RemoteAddr, ":")[0] if ip == "" { ip = httpRequest.Header.Get("X-Forwarded-For") } reader, size, err := hooks.getImageReader(registry, thumbDir, file, width, height, filters, ip) if err != nil { return kit.NewErrorResponse(err), false } w := r.GetHttpResponseWriter() err = serveFile(w, httpRequest, file.GetMime(), size, reader) reader.Close() if err != nil { registry.Logger().Errorf("Error while serving image %v(%v): %v", file.GetId(), file.GetBackendId(), err) } return nil, true } serveImageRoute := kit.NewHttpRoute("/images/:id/*rest", "GET", serveImageHandler) routes = append(routes, serveImageRoute) return routes }
func (AdminResource) AllowDelete(res kit.Resource, obj kit.Model, user kit.User) bool { return user != nil && (user.HasRole("admin") || user.HasPermission(res.Collection()+".delete")) }
func (a *App) RegisterResource(res kit.Resource) { if res.Backend() == nil { if a.registry.DefaultBackend() == nil { a.Logger().Panic("Registering resource without backend, but no default backend set.") } // Set backend. res.SetBackend(a.registry.DefaultBackend()) } if res.Registry() == nil { res.SetRegistry(a.registry) } res.SetDebug(a.Debug()) // Allow a resource to register custom http routes and methods. if res.Hooks() != nil { // Handle http routes. if resRoutes, ok := res.Hooks().(resources.ApiHttpRoutes); ok { for _, route := range resRoutes.HttpRoutes(res) { a.RegisterHttpHandler(route.Method(), route.Route(), route.Handler()) } } // Handle methods. if resMethods, ok := res.Hooks().(resources.MethodsHook); ok { for _, method := range resMethods.Methods(res) { a.RegisterMethod(method) } } } a.registry.AddResource(res) }
func (d *Registry) AddResource(res kit.Resource) { d.resources[res.Collection()] = res }
func Find(res kit.Resource, request kit.Request) (kit.Response, apperror.Error) { collection := res.Collection() info := res.Backend().ModelInfo(collection) var query *db.Query jsonQuery := request.GetContext().String("query") if jsonQuery != "" { var rawQuery map[string]interface{} if err := json.Unmarshal([]byte(jsonQuery), &rawQuery); err != nil { return nil, apperror.Wrap(err, "invalid_query_json") } rawQuery["collection"] = collection // A custom query was supplied. // Try to parse the query. var err apperror.Error query, err = db.ParseQuery(res.Backend(), rawQuery) if err != nil { return nil, apperror.Wrap(err, "invalid_query", "", false) } } if query == nil { query = res.Backend().Q(collection) } // Check paging parameters. var limit, offset int context := request.GetContext() if context.Has("limit") { val, err := context.Int("limit") if err != nil { return nil, &apperror.Err{ Public: true, Code: "non_numeric_limit_parameter", Message: "The get query contains a non-numeric ?limit", } } limit = val } if context.Has("offset") { val, err := context.Int("offset") if err != nil { return nil, &apperror.Err{ Public: true, Code: "non_numeric_offset_parameter", Message: "The get query contains a non-numeric ?offset", } } offset = val } var page, perPage int if context.Has("page") { val, err := context.Int("page") if err != nil { return nil, &apperror.Err{ Public: true, Code: "non_numeric_page_parameter", Message: "The get query contains a non-numeric ?page", } } page = val } if context.Has("per_page") { val, err := context.Int("per_page") if err != nil { return nil, &apperror.Err{ Public: true, Code: "non_numeric_per_page_parameter", Message: "The get query contains a non-numeric ?per_page", } } perPage = val } if perPage > 0 { limit = perPage } if page > 1 { offset = (page - 1) * limit } if limit > 0 { query.Limit(int(limit)).Offset(int(offset)) } // Add joins. if context.Has("joins") { parts := strings.Split(context.String("joins"), ",") for _, name := range parts { relation := info.FindRelation(name) if relation == nil { return nil, &apperror.Err{ Code: "invalid_join", Message: fmt.Sprintf("Tried to join a NON-existant relationship %v", name), Public: true, } } query.Join(relation.Name()) } } // Add filters. if context.Has("filters") { parts := strings.Split(context.String("filters"), ",") for _, filter := range parts { filterParts := strings.Split(filter, ":") if len(filterParts) != 2 { return nil, &apperror.Err{ Public: true, Code: "invalid_filter", Message: fmt.Sprintf("Invalid filter: %v", filter), } } fieldName := filterParts[0] // COnvert id query to pk field. if fieldName == "id" { fieldName = info.PkAttribute().BackendName() } var typ reflect.Type if attr := info.FindAttribute(fieldName); attr != nil { fieldName = attr.BackendName() typ = attr.Type() } else if rel := info.FindRelation(fieldName); rel != nil { if rel.RelationType() == db.RELATION_TYPE_HAS_ONE { fieldName = rel.LocalField() typ = info.Attribute(rel.LocalField()).Type() } else { return nil, &apperror.Err{ Public: true, Code: "cant_filter_on_relation", Message: fmt.Sprintf("Tried to filter on relationship field %v (only possible for has-one relations)", fieldName), } } } else { return nil, &apperror.Err{ Public: true, Code: "filter_for_inexistant_field", Message: fmt.Sprintf("Tried to filter with inexistant field %v", fieldName), } } converted, err := reflector.R(filterParts[1]).ConvertTo(typ) if err != nil { return nil, &apperror.Err{ Public: true, Code: "inconvertible_filter_value", Message: fmt.Sprintf("Coult not convert filter value %v for field %v (should be %v)", filterParts[1], fieldName, typ), } } query.Filter(fieldName, converted) } } return res.ApiFind(query, request), nil }