pgsql.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. //go:build !nopgsql
  2. // +build !nopgsql
  3. package dataprovider
  4. import (
  5. "context"
  6. "crypto/x509"
  7. "database/sql"
  8. "errors"
  9. "fmt"
  10. "strings"
  11. "time"
  12. // we import lib/pq here to be able to disable PostgreSQL support using a build tag
  13. _ "github.com/lib/pq"
  14. "github.com/drakkan/sftpgo/v2/logger"
  15. "github.com/drakkan/sftpgo/v2/version"
  16. "github.com/drakkan/sftpgo/v2/vfs"
  17. )
  18. const (
  19. pgsqlResetSQL = `DROP TABLE IF EXISTS "{{api_keys}}" CASCADE;
  20. DROP TABLE IF EXISTS "{{folders_mapping}}" CASCADE;
  21. DROP TABLE IF EXISTS "{{users_folders_mapping}}" CASCADE;
  22. DROP TABLE IF EXISTS "{{users_groups_mapping}}" CASCADE;
  23. DROP TABLE IF EXISTS "{{groups_folders_mapping}}" CASCADE;
  24. DROP TABLE IF EXISTS "{{admins}}" CASCADE;
  25. DROP TABLE IF EXISTS "{{folders}}" CASCADE;
  26. DROP TABLE IF EXISTS "{{shares}}" CASCADE;
  27. DROP TABLE IF EXISTS "{{users}}" CASCADE;
  28. DROP TABLE IF EXISTS "{{groups}}" CASCADE;
  29. DROP TABLE IF EXISTS "{{defender_events}}" CASCADE;
  30. DROP TABLE IF EXISTS "{{defender_hosts}}" CASCADE;
  31. DROP TABLE IF EXISTS "{{active_transfers}}" CASCADE;
  32. DROP TABLE IF EXISTS "{{schema_version}}" CASCADE;
  33. `
  34. pgsqlInitial = `CREATE TABLE "{{schema_version}}" ("id" serial NOT NULL PRIMARY KEY, "version" integer NOT NULL);
  35. CREATE TABLE "{{admins}}" ("id" serial NOT NULL PRIMARY KEY, "username" varchar(255) NOT NULL UNIQUE,
  36. "description" varchar(512) NULL, "password" varchar(255) NOT NULL, "email" varchar(255) NULL, "status" integer NOT NULL,
  37. "permissions" text NOT NULL, "filters" text NULL, "additional_info" text NULL, "last_login" bigint NOT NULL,
  38. "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL);
  39. CREATE TABLE "{{defender_hosts}}" ("id" bigserial NOT NULL PRIMARY KEY, "ip" varchar(50) NOT NULL UNIQUE,
  40. "ban_time" bigint NOT NULL, "updated_at" bigint NOT NULL);
  41. CREATE TABLE "{{defender_events}}" ("id" bigserial NOT NULL PRIMARY KEY, "date_time" bigint NOT NULL, "score" integer NOT NULL,
  42. "host_id" bigint NOT NULL);
  43. ALTER TABLE "{{defender_events}}" ADD CONSTRAINT "{{prefix}}defender_events_host_id_fk_defender_hosts_id" FOREIGN KEY
  44. ("host_id") REFERENCES "{{defender_hosts}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  45. CREATE TABLE "{{folders}}" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(255) NOT NULL UNIQUE, "description" varchar(512) NULL,
  46. "path" text NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL,
  47. "filesystem" text NULL);
  48. CREATE TABLE "{{users}}" ("id" serial NOT NULL PRIMARY KEY, "username" varchar(255) NOT NULL UNIQUE, "status" integer NOT NULL,
  49. "expiration_date" bigint NOT NULL, "description" varchar(512) NULL, "password" text NULL, "public_keys" text NULL,
  50. "home_dir" text NOT NULL, "uid" bigint NOT NULL, "gid" bigint NOT NULL, "max_sessions" integer NOT NULL,
  51. "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL,
  52. "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL,
  53. "download_bandwidth" integer NOT NULL, "last_login" bigint NOT NULL, "filters" text NULL, "filesystem" text NULL,
  54. "additional_info" text NULL, "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "email" varchar(255) NULL);
  55. CREATE TABLE "{{folders_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "virtual_path" text NOT NULL,
  56. "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "folder_id" integer NOT NULL, "user_id" integer NOT NULL);
  57. ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_mapping" UNIQUE ("user_id", "folder_id");
  58. ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}folders_mapping_folder_id_fk_folders_id"
  59. FOREIGN KEY ("folder_id") REFERENCES "{{folders}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  60. ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}folders_mapping_user_id_fk_users_id"
  61. FOREIGN KEY ("user_id") REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  62. CREATE TABLE "{{shares}}" ("id" serial NOT NULL PRIMARY KEY,
  63. "share_id" varchar(60) NOT NULL UNIQUE, "name" varchar(255) NOT NULL, "description" varchar(512) NULL,
  64. "scope" integer NOT NULL, "paths" text NOT NULL, "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL,
  65. "last_use_at" bigint NOT NULL, "expires_at" bigint NOT NULL, "password" text NULL,
  66. "max_tokens" integer NOT NULL, "used_tokens" integer NOT NULL, "allow_from" text NULL,
  67. "user_id" integer NOT NULL);
  68. ALTER TABLE "{{shares}}" ADD CONSTRAINT "{{prefix}}shares_user_id_fk_users_id" FOREIGN KEY ("user_id")
  69. REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  70. CREATE TABLE "{{api_keys}}" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(255) NOT NULL,
  71. "key_id" varchar(50) NOT NULL UNIQUE, "api_key" varchar(255) NOT NULL UNIQUE, "scope" integer NOT NULL,
  72. "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "last_use_at" bigint NOT NULL,"expires_at" bigint NOT NULL,
  73. "description" text NULL, "admin_id" integer NULL, "user_id" integer NULL);
  74. ALTER TABLE "{{api_keys}}" ADD CONSTRAINT "{{prefix}}api_keys_admin_id_fk_admins_id" FOREIGN KEY ("admin_id")
  75. REFERENCES "{{admins}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  76. ALTER TABLE "{{api_keys}}" ADD CONSTRAINT "{{prefix}}api_keys_user_id_fk_users_id" FOREIGN KEY ("user_id")
  77. REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  78. CREATE INDEX "{{prefix}}folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
  79. CREATE INDEX "{{prefix}}folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
  80. CREATE INDEX "{{prefix}}api_keys_admin_id_idx" ON "{{api_keys}}" ("admin_id");
  81. CREATE INDEX "{{prefix}}api_keys_user_id_idx" ON "{{api_keys}}" ("user_id");
  82. CREATE INDEX "{{prefix}}users_updated_at_idx" ON "{{users}}" ("updated_at");
  83. CREATE INDEX "{{prefix}}shares_user_id_idx" ON "{{shares}}" ("user_id");
  84. CREATE INDEX "{{prefix}}defender_hosts_updated_at_idx" ON "{{defender_hosts}}" ("updated_at");
  85. CREATE INDEX "{{prefix}}defender_hosts_ban_time_idx" ON "{{defender_hosts}}" ("ban_time");
  86. CREATE INDEX "{{prefix}}defender_events_date_time_idx" ON "{{defender_events}}" ("date_time");
  87. CREATE INDEX "{{prefix}}defender_events_host_id_idx" ON "{{defender_events}}" ("host_id");
  88. INSERT INTO {{schema_version}} (version) VALUES (15);
  89. `
  90. pgsqlV16SQL = `ALTER TABLE "{{users}}" ADD COLUMN "download_data_transfer" integer DEFAULT 0 NOT NULL;
  91. ALTER TABLE "{{users}}" ALTER COLUMN "download_data_transfer" DROP DEFAULT;
  92. ALTER TABLE "{{users}}" ADD COLUMN "total_data_transfer" integer DEFAULT 0 NOT NULL;
  93. ALTER TABLE "{{users}}" ALTER COLUMN "total_data_transfer" DROP DEFAULT;
  94. ALTER TABLE "{{users}}" ADD COLUMN "upload_data_transfer" integer DEFAULT 0 NOT NULL;
  95. ALTER TABLE "{{users}}" ALTER COLUMN "upload_data_transfer" DROP DEFAULT;
  96. ALTER TABLE "{{users}}" ADD COLUMN "used_download_data_transfer" integer DEFAULT 0 NOT NULL;
  97. ALTER TABLE "{{users}}" ALTER COLUMN "used_download_data_transfer" DROP DEFAULT;
  98. ALTER TABLE "{{users}}" ADD COLUMN "used_upload_data_transfer" integer DEFAULT 0 NOT NULL;
  99. ALTER TABLE "{{users}}" ALTER COLUMN "used_upload_data_transfer" DROP DEFAULT;
  100. CREATE TABLE "{{active_transfers}}" ("id" bigserial NOT NULL PRIMARY KEY, "connection_id" varchar(100) NOT NULL,
  101. "transfer_id" bigint NOT NULL, "transfer_type" integer NOT NULL, "username" varchar(255) NOT NULL,
  102. "folder_name" varchar(255) NULL, "ip" varchar(50) NOT NULL, "truncated_size" bigint NOT NULL,
  103. "current_ul_size" bigint NOT NULL, "current_dl_size" bigint NOT NULL, "created_at" bigint NOT NULL,
  104. "updated_at" bigint NOT NULL);
  105. CREATE INDEX "{{prefix}}active_transfers_connection_id_idx" ON "{{active_transfers}}" ("connection_id");
  106. CREATE INDEX "{{prefix}}active_transfers_transfer_id_idx" ON "{{active_transfers}}" ("transfer_id");
  107. CREATE INDEX "{{prefix}}active_transfers_updated_at_idx" ON "{{active_transfers}}" ("updated_at");
  108. `
  109. pgsqlV16DownSQL = `ALTER TABLE "{{users}}" DROP COLUMN "used_upload_data_transfer" CASCADE;
  110. ALTER TABLE "{{users}}" DROP COLUMN "used_download_data_transfer" CASCADE;
  111. ALTER TABLE "{{users}}" DROP COLUMN "upload_data_transfer" CASCADE;
  112. ALTER TABLE "{{users}}" DROP COLUMN "total_data_transfer" CASCADE;
  113. ALTER TABLE "{{users}}" DROP COLUMN "download_data_transfer" CASCADE;
  114. DROP TABLE "{{active_transfers}}" CASCADE;
  115. `
  116. pgsqlV17SQL = `CREATE TABLE "{{groups}}" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(255) NOT NULL UNIQUE,
  117. "description" varchar(512) NULL, "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "user_settings" text NULL);
  118. CREATE TABLE "{{groups_folders_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "group_id" integer NOT NULL,
  119. "folder_id" integer NOT NULL, "virtual_path" text NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL);
  120. CREATE TABLE "{{users_groups_mapping}}" ("id" serial NOT NULL PRIMARY KEY, "user_id" integer NOT NULL,
  121. "group_id" integer NOT NULL, "group_type" integer NOT NULL);
  122. DROP INDEX "{{prefix}}folders_mapping_folder_id_idx";
  123. DROP INDEX "{{prefix}}folders_mapping_user_id_idx";
  124. ALTER TABLE "{{folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_mapping";
  125. ALTER TABLE "{{folders_mapping}}" RENAME TO "{{users_folders_mapping}}";
  126. ALTER TABLE "{{users_folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_user_folder_mapping" UNIQUE ("user_id", "folder_id");
  127. CREATE INDEX "{{prefix}}users_folders_mapping_folder_id_idx" ON "{{users_folders_mapping}}" ("folder_id");
  128. CREATE INDEX "{{prefix}}users_folders_mapping_user_id_idx" ON "{{users_folders_mapping}}" ("user_id");
  129. ALTER TABLE "{{users_groups_mapping}}" ADD CONSTRAINT "{{prefix}}unique_user_group_mapping" UNIQUE ("user_id", "group_id");
  130. ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_group_folder_mapping" UNIQUE ("group_id", "folder_id");
  131. CREATE INDEX "{{prefix}}users_groups_mapping_group_id_idx" ON "{{users_groups_mapping}}" ("group_id");
  132. ALTER TABLE "{{users_groups_mapping}}" ADD CONSTRAINT "{{prefix}}users_groups_mapping_group_id_fk_groups_id"
  133. FOREIGN KEY ("group_id") REFERENCES "{{groups}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION;
  134. CREATE INDEX "{{prefix}}users_groups_mapping_user_id_idx" ON "{{users_groups_mapping}}" ("user_id");
  135. ALTER TABLE "{{users_groups_mapping}}" ADD CONSTRAINT "{{prefix}}users_groups_mapping_user_id_fk_users_id"
  136. FOREIGN KEY ("user_id") REFERENCES "{{users}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  137. CREATE INDEX "{{prefix}}groups_folders_mapping_folder_id_idx" ON "{{groups_folders_mapping}}" ("folder_id");
  138. ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}groups_folders_mapping_folder_id_fk_folders_id"
  139. FOREIGN KEY ("folder_id") REFERENCES "{{folders}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  140. CREATE INDEX "{{prefix}}groups_folders_mapping_group_id_idx" ON "{{groups_folders_mapping}}" ("group_id");
  141. ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}groups_folders_mapping_group_id_fk_groups_id"
  142. FOREIGN KEY ("group_id") REFERENCES "groups" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
  143. CREATE INDEX "{{prefix}}groups_updated_at_idx" ON "{{groups}}" ("updated_at");
  144. `
  145. pgsqlV17DownSQL = `DROP TABLE "{{users_groups_mapping}}" CASCADE;
  146. DROP TABLE "{{groups_folders_mapping}}" CASCADE;
  147. DROP TABLE "{{groups}}" CASCADE;
  148. DROP INDEX "{{prefix}}users_folders_mapping_folder_id_idx";
  149. DROP INDEX "{{prefix}}users_folders_mapping_user_id_idx";
  150. ALTER TABLE "{{users_folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_user_folder_mapping";
  151. ALTER TABLE "{{users_folders_mapping}}" RENAME TO "{{folders_mapping}}";
  152. ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "{{prefix}}unique_mapping" UNIQUE ("user_id", "folder_id");
  153. CREATE INDEX "{{prefix}}folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
  154. CREATE INDEX "{{prefix}}folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
  155. `
  156. )
  157. // PGSQLProvider defines the auth provider for PostgreSQL database
  158. type PGSQLProvider struct {
  159. dbHandle *sql.DB
  160. }
  161. func init() {
  162. version.AddFeature("+pgsql")
  163. }
  164. func initializePGSQLProvider() error {
  165. var err error
  166. dbHandle, err := sql.Open("postgres", getPGSQLConnectionString(false))
  167. if err == nil {
  168. providerLog(logger.LevelDebug, "postgres database handle created, connection string: %#v, pool size: %v",
  169. getPGSQLConnectionString(true), config.PoolSize)
  170. dbHandle.SetMaxOpenConns(config.PoolSize)
  171. if config.PoolSize > 0 {
  172. dbHandle.SetMaxIdleConns(config.PoolSize)
  173. } else {
  174. dbHandle.SetMaxIdleConns(2)
  175. }
  176. dbHandle.SetConnMaxLifetime(240 * time.Second)
  177. provider = &PGSQLProvider{dbHandle: dbHandle}
  178. } else {
  179. providerLog(logger.LevelError, "error creating postgres database handler, connection string: %#v, error: %v",
  180. getPGSQLConnectionString(true), err)
  181. }
  182. return err
  183. }
  184. func getPGSQLConnectionString(redactedPwd bool) string {
  185. var connectionString string
  186. if config.ConnectionString == "" {
  187. password := config.Password
  188. if redactedPwd && password != "" {
  189. password = "[redacted]"
  190. }
  191. connectionString = fmt.Sprintf("host='%v' port=%v dbname='%v' user='%v' password='%v' sslmode=%v connect_timeout=10",
  192. config.Host, config.Port, config.Name, config.Username, password, getSSLMode())
  193. if config.RootCert != "" {
  194. connectionString += fmt.Sprintf(" sslrootcert='%v'", config.RootCert)
  195. }
  196. if config.ClientCert != "" && config.ClientKey != "" {
  197. connectionString += fmt.Sprintf(" sslcert='%v' sslkey='%v'", config.ClientCert, config.ClientKey)
  198. }
  199. } else {
  200. connectionString = config.ConnectionString
  201. }
  202. return connectionString
  203. }
  204. func (p *PGSQLProvider) checkAvailability() error {
  205. return sqlCommonCheckAvailability(p.dbHandle)
  206. }
  207. func (p *PGSQLProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
  208. return sqlCommonValidateUserAndPass(username, password, ip, protocol, p.dbHandle)
  209. }
  210. func (p *PGSQLProvider) validateUserAndTLSCert(username, protocol string, tlsCert *x509.Certificate) (User, error) {
  211. return sqlCommonValidateUserAndTLSCertificate(username, protocol, tlsCert, p.dbHandle)
  212. }
  213. func (p *PGSQLProvider) validateUserAndPubKey(username string, publicKey []byte, isSSHCert bool) (User, string, error) {
  214. return sqlCommonValidateUserAndPubKey(username, publicKey, isSSHCert, p.dbHandle)
  215. }
  216. func (p *PGSQLProvider) updateTransferQuota(username string, uploadSize, downloadSize int64, reset bool) error {
  217. return sqlCommonUpdateTransferQuota(username, uploadSize, downloadSize, reset, p.dbHandle)
  218. }
  219. func (p *PGSQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
  220. return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
  221. }
  222. func (p *PGSQLProvider) getUsedQuota(username string) (int, int64, int64, int64, error) {
  223. return sqlCommonGetUsedQuota(username, p.dbHandle)
  224. }
  225. func (p *PGSQLProvider) setUpdatedAt(username string) {
  226. sqlCommonSetUpdatedAt(username, p.dbHandle)
  227. }
  228. func (p *PGSQLProvider) updateLastLogin(username string) error {
  229. return sqlCommonUpdateLastLogin(username, p.dbHandle)
  230. }
  231. func (p *PGSQLProvider) updateAdminLastLogin(username string) error {
  232. return sqlCommonUpdateAdminLastLogin(username, p.dbHandle)
  233. }
  234. func (p *PGSQLProvider) userExists(username string) (User, error) {
  235. return sqlCommonGetUserByUsername(username, p.dbHandle)
  236. }
  237. func (p *PGSQLProvider) addUser(user *User) error {
  238. return sqlCommonAddUser(user, p.dbHandle)
  239. }
  240. func (p *PGSQLProvider) updateUser(user *User) error {
  241. return sqlCommonUpdateUser(user, p.dbHandle)
  242. }
  243. func (p *PGSQLProvider) deleteUser(user User) error {
  244. return sqlCommonDeleteUser(user, p.dbHandle)
  245. }
  246. func (p *PGSQLProvider) updateUserPassword(username, password string) error {
  247. return sqlCommonUpdateUserPassword(username, password, p.dbHandle)
  248. }
  249. func (p *PGSQLProvider) dumpUsers() ([]User, error) {
  250. return sqlCommonDumpUsers(p.dbHandle)
  251. }
  252. func (p *PGSQLProvider) getRecentlyUpdatedUsers(after int64) ([]User, error) {
  253. return sqlCommonGetRecentlyUpdatedUsers(after, p.dbHandle)
  254. }
  255. func (p *PGSQLProvider) getUsers(limit int, offset int, order string) ([]User, error) {
  256. return sqlCommonGetUsers(limit, offset, order, p.dbHandle)
  257. }
  258. func (p *PGSQLProvider) getUsersForQuotaCheck(toFetch map[string]bool) ([]User, error) {
  259. return sqlCommonGetUsersForQuotaCheck(toFetch, p.dbHandle)
  260. }
  261. func (p *PGSQLProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) {
  262. return sqlCommonDumpFolders(p.dbHandle)
  263. }
  264. func (p *PGSQLProvider) getFolders(limit, offset int, order string, minimal bool) ([]vfs.BaseVirtualFolder, error) {
  265. return sqlCommonGetFolders(limit, offset, order, minimal, p.dbHandle)
  266. }
  267. func (p *PGSQLProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, error) {
  268. ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
  269. defer cancel()
  270. return sqlCommonGetFolderByName(ctx, name, p.dbHandle)
  271. }
  272. func (p *PGSQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
  273. return sqlCommonAddFolder(folder, p.dbHandle)
  274. }
  275. func (p *PGSQLProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
  276. return sqlCommonUpdateFolder(folder, p.dbHandle)
  277. }
  278. func (p *PGSQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
  279. return sqlCommonDeleteFolder(folder, p.dbHandle)
  280. }
  281. func (p *PGSQLProvider) updateFolderQuota(name string, filesAdd int, sizeAdd int64, reset bool) error {
  282. return sqlCommonUpdateFolderQuota(name, filesAdd, sizeAdd, reset, p.dbHandle)
  283. }
  284. func (p *PGSQLProvider) getUsedFolderQuota(name string) (int, int64, error) {
  285. return sqlCommonGetFolderUsedQuota(name, p.dbHandle)
  286. }
  287. func (p *PGSQLProvider) getGroups(limit, offset int, order string, minimal bool) ([]Group, error) {
  288. return sqlCommonGetGroups(limit, offset, order, minimal, p.dbHandle)
  289. }
  290. func (p *PGSQLProvider) getGroupsWithNames(names []string) ([]Group, error) {
  291. return sqlCommonGetGroupsWithNames(names, p.dbHandle)
  292. }
  293. func (p *PGSQLProvider) getUsersInGroups(names []string) ([]string, error) {
  294. return sqlCommonGetUsersInGroups(names, p.dbHandle)
  295. }
  296. func (p *PGSQLProvider) groupExists(name string) (Group, error) {
  297. return sqlCommonGetGroupByName(name, p.dbHandle)
  298. }
  299. func (p *PGSQLProvider) addGroup(group *Group) error {
  300. return sqlCommonAddGroup(group, p.dbHandle)
  301. }
  302. func (p *PGSQLProvider) updateGroup(group *Group) error {
  303. return sqlCommonUpdateGroup(group, p.dbHandle)
  304. }
  305. func (p *PGSQLProvider) deleteGroup(group Group) error {
  306. return sqlCommonDeleteGroup(group, p.dbHandle)
  307. }
  308. func (p *PGSQLProvider) dumpGroups() ([]Group, error) {
  309. return sqlCommonDumpGroups(p.dbHandle)
  310. }
  311. func (p *PGSQLProvider) adminExists(username string) (Admin, error) {
  312. return sqlCommonGetAdminByUsername(username, p.dbHandle)
  313. }
  314. func (p *PGSQLProvider) addAdmin(admin *Admin) error {
  315. return sqlCommonAddAdmin(admin, p.dbHandle)
  316. }
  317. func (p *PGSQLProvider) updateAdmin(admin *Admin) error {
  318. return sqlCommonUpdateAdmin(admin, p.dbHandle)
  319. }
  320. func (p *PGSQLProvider) deleteAdmin(admin Admin) error {
  321. return sqlCommonDeleteAdmin(admin, p.dbHandle)
  322. }
  323. func (p *PGSQLProvider) getAdmins(limit int, offset int, order string) ([]Admin, error) {
  324. return sqlCommonGetAdmins(limit, offset, order, p.dbHandle)
  325. }
  326. func (p *PGSQLProvider) dumpAdmins() ([]Admin, error) {
  327. return sqlCommonDumpAdmins(p.dbHandle)
  328. }
  329. func (p *PGSQLProvider) validateAdminAndPass(username, password, ip string) (Admin, error) {
  330. return sqlCommonValidateAdminAndPass(username, password, ip, p.dbHandle)
  331. }
  332. func (p *PGSQLProvider) apiKeyExists(keyID string) (APIKey, error) {
  333. return sqlCommonGetAPIKeyByID(keyID, p.dbHandle)
  334. }
  335. func (p *PGSQLProvider) addAPIKey(apiKey *APIKey) error {
  336. return sqlCommonAddAPIKey(apiKey, p.dbHandle)
  337. }
  338. func (p *PGSQLProvider) updateAPIKey(apiKey *APIKey) error {
  339. return sqlCommonUpdateAPIKey(apiKey, p.dbHandle)
  340. }
  341. func (p *PGSQLProvider) deleteAPIKey(apiKey APIKey) error {
  342. return sqlCommonDeleteAPIKey(apiKey, p.dbHandle)
  343. }
  344. func (p *PGSQLProvider) getAPIKeys(limit int, offset int, order string) ([]APIKey, error) {
  345. return sqlCommonGetAPIKeys(limit, offset, order, p.dbHandle)
  346. }
  347. func (p *PGSQLProvider) dumpAPIKeys() ([]APIKey, error) {
  348. return sqlCommonDumpAPIKeys(p.dbHandle)
  349. }
  350. func (p *PGSQLProvider) updateAPIKeyLastUse(keyID string) error {
  351. return sqlCommonUpdateAPIKeyLastUse(keyID, p.dbHandle)
  352. }
  353. func (p *PGSQLProvider) shareExists(shareID, username string) (Share, error) {
  354. return sqlCommonGetShareByID(shareID, username, p.dbHandle)
  355. }
  356. func (p *PGSQLProvider) addShare(share *Share) error {
  357. return sqlCommonAddShare(share, p.dbHandle)
  358. }
  359. func (p *PGSQLProvider) updateShare(share *Share) error {
  360. return sqlCommonUpdateShare(share, p.dbHandle)
  361. }
  362. func (p *PGSQLProvider) deleteShare(share Share) error {
  363. return sqlCommonDeleteShare(share, p.dbHandle)
  364. }
  365. func (p *PGSQLProvider) getShares(limit int, offset int, order, username string) ([]Share, error) {
  366. return sqlCommonGetShares(limit, offset, order, username, p.dbHandle)
  367. }
  368. func (p *PGSQLProvider) dumpShares() ([]Share, error) {
  369. return sqlCommonDumpShares(p.dbHandle)
  370. }
  371. func (p *PGSQLProvider) updateShareLastUse(shareID string, numTokens int) error {
  372. return sqlCommonUpdateShareLastUse(shareID, numTokens, p.dbHandle)
  373. }
  374. func (p *PGSQLProvider) getDefenderHosts(from int64, limit int) ([]DefenderEntry, error) {
  375. return sqlCommonGetDefenderHosts(from, limit, p.dbHandle)
  376. }
  377. func (p *PGSQLProvider) getDefenderHostByIP(ip string, from int64) (DefenderEntry, error) {
  378. return sqlCommonGetDefenderHostByIP(ip, from, p.dbHandle)
  379. }
  380. func (p *PGSQLProvider) isDefenderHostBanned(ip string) (DefenderEntry, error) {
  381. return sqlCommonIsDefenderHostBanned(ip, p.dbHandle)
  382. }
  383. func (p *PGSQLProvider) updateDefenderBanTime(ip string, minutes int) error {
  384. return sqlCommonDefenderIncrementBanTime(ip, minutes, p.dbHandle)
  385. }
  386. func (p *PGSQLProvider) deleteDefenderHost(ip string) error {
  387. return sqlCommonDeleteDefenderHost(ip, p.dbHandle)
  388. }
  389. func (p *PGSQLProvider) addDefenderEvent(ip string, score int) error {
  390. return sqlCommonAddDefenderHostAndEvent(ip, score, p.dbHandle)
  391. }
  392. func (p *PGSQLProvider) setDefenderBanTime(ip string, banTime int64) error {
  393. return sqlCommonSetDefenderBanTime(ip, banTime, p.dbHandle)
  394. }
  395. func (p *PGSQLProvider) cleanupDefender(from int64) error {
  396. return sqlCommonDefenderCleanup(from, p.dbHandle)
  397. }
  398. func (p *PGSQLProvider) addActiveTransfer(transfer ActiveTransfer) error {
  399. return sqlCommonAddActiveTransfer(transfer, p.dbHandle)
  400. }
  401. func (p *PGSQLProvider) updateActiveTransferSizes(ulSize, dlSize, transferID int64, connectionID string) error {
  402. return sqlCommonUpdateActiveTransferSizes(ulSize, dlSize, transferID, connectionID, p.dbHandle)
  403. }
  404. func (p *PGSQLProvider) removeActiveTransfer(transferID int64, connectionID string) error {
  405. return sqlCommonRemoveActiveTransfer(transferID, connectionID, p.dbHandle)
  406. }
  407. func (p *PGSQLProvider) cleanupActiveTransfers(before time.Time) error {
  408. return sqlCommonCleanupActiveTransfers(before, p.dbHandle)
  409. }
  410. func (p *PGSQLProvider) getActiveTransfers(from time.Time) ([]ActiveTransfer, error) {
  411. return sqlCommonGetActiveTransfers(from, p.dbHandle)
  412. }
  413. func (p *PGSQLProvider) close() error {
  414. return p.dbHandle.Close()
  415. }
  416. func (p *PGSQLProvider) reloadConfig() error {
  417. return nil
  418. }
  419. // initializeDatabase creates the initial database structure
  420. func (p *PGSQLProvider) initializeDatabase() error {
  421. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, false)
  422. if err == nil && dbVersion.Version > 0 {
  423. return ErrNoInitRequired
  424. }
  425. if errors.Is(err, sql.ErrNoRows) {
  426. return errSchemaVersionEmpty
  427. }
  428. logger.InfoToConsole("creating initial database schema, version 15")
  429. providerLog(logger.LevelInfo, "creating initial database schema, version 15")
  430. initialSQL := strings.ReplaceAll(pgsqlInitial, "{{schema_version}}", sqlTableSchemaVersion)
  431. initialSQL = strings.ReplaceAll(initialSQL, "{{admins}}", sqlTableAdmins)
  432. initialSQL = strings.ReplaceAll(initialSQL, "{{folders}}", sqlTableFolders)
  433. initialSQL = strings.ReplaceAll(initialSQL, "{{users}}", sqlTableUsers)
  434. initialSQL = strings.ReplaceAll(initialSQL, "{{folders_mapping}}", sqlTableFoldersMapping)
  435. initialSQL = strings.ReplaceAll(initialSQL, "{{api_keys}}", sqlTableAPIKeys)
  436. initialSQL = strings.ReplaceAll(initialSQL, "{{shares}}", sqlTableShares)
  437. initialSQL = strings.ReplaceAll(initialSQL, "{{defender_events}}", sqlTableDefenderEvents)
  438. initialSQL = strings.ReplaceAll(initialSQL, "{{defender_hosts}}", sqlTableDefenderHosts)
  439. initialSQL = strings.ReplaceAll(initialSQL, "{{prefix}}", config.SQLTablesPrefix)
  440. if config.Driver == CockroachDataProviderName {
  441. // Cockroach does not support deferrable constraint validation, we don't need them,
  442. // we keep these definitions for the PostgreSQL driver to avoid changes for users
  443. // upgrading from old SFTPGo versions
  444. initialSQL = strings.ReplaceAll(initialSQL, "DEFERRABLE INITIALLY DEFERRED", "")
  445. }
  446. return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{initialSQL}, 15)
  447. }
  448. func (p *PGSQLProvider) migrateDatabase() error {
  449. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
  450. if err != nil {
  451. return err
  452. }
  453. switch version := dbVersion.Version; {
  454. case version == sqlDatabaseVersion:
  455. providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", version)
  456. return ErrNoInitRequired
  457. case version < 15:
  458. err = fmt.Errorf("database version %v is too old, please see the upgrading docs", version)
  459. providerLog(logger.LevelError, "%v", err)
  460. logger.ErrorToConsole("%v", err)
  461. return err
  462. case version == 15:
  463. return updatePGSQLDatabaseFromV15(p.dbHandle)
  464. case version == 16:
  465. return updatePGSQLDatabaseFromV16(p.dbHandle)
  466. default:
  467. if version > sqlDatabaseVersion {
  468. providerLog(logger.LevelError, "database version %v is newer than the supported one: %v", version,
  469. sqlDatabaseVersion)
  470. logger.WarnToConsole("database version %v is newer than the supported one: %v", version,
  471. sqlDatabaseVersion)
  472. return nil
  473. }
  474. return fmt.Errorf("database version not handled: %v", version)
  475. }
  476. }
  477. func (p *PGSQLProvider) revertDatabase(targetVersion int) error {
  478. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
  479. if err != nil {
  480. return err
  481. }
  482. if dbVersion.Version == targetVersion {
  483. return errors.New("current version match target version, nothing to do")
  484. }
  485. switch dbVersion.Version {
  486. case 16:
  487. return downgradePGSQLDatabaseFromV16(p.dbHandle)
  488. case 17:
  489. return downgradePGSQLDatabaseFromV17(p.dbHandle)
  490. default:
  491. return fmt.Errorf("database version not handled: %v", dbVersion.Version)
  492. }
  493. }
  494. func (p *PGSQLProvider) resetDatabase() error {
  495. sql := sqlReplaceAll(pgsqlResetSQL)
  496. return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{sql}, 0)
  497. }
  498. func updatePGSQLDatabaseFromV15(dbHandle *sql.DB) error {
  499. if err := updatePGSQLDatabaseFrom15To16(dbHandle); err != nil {
  500. return err
  501. }
  502. return updatePGSQLDatabaseFromV16(dbHandle)
  503. }
  504. func updatePGSQLDatabaseFromV16(dbHandle *sql.DB) error {
  505. return updatePGSQLDatabaseFrom16To17(dbHandle)
  506. }
  507. func downgradePGSQLDatabaseFromV17(dbHandle *sql.DB) error {
  508. if err := downgradePGSQLDatabaseFrom17To16(dbHandle); err != nil {
  509. return err
  510. }
  511. return downgradePGSQLDatabaseFromV16(dbHandle)
  512. }
  513. func downgradePGSQLDatabaseFromV16(dbHandle *sql.DB) error {
  514. return downgradePGSQLDatabaseFrom16To15(dbHandle)
  515. }
  516. func updatePGSQLDatabaseFrom15To16(dbHandle *sql.DB) error {
  517. logger.InfoToConsole("updating database version: 15 -> 16")
  518. providerLog(logger.LevelInfo, "updating database version: 15 -> 16")
  519. sql := strings.ReplaceAll(pgsqlV16SQL, "{{users}}", sqlTableUsers)
  520. sql = strings.ReplaceAll(sql, "{{active_transfers}}", sqlTableActiveTransfers)
  521. sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
  522. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 16)
  523. }
  524. func updatePGSQLDatabaseFrom16To17(dbHandle *sql.DB) error {
  525. logger.InfoToConsole("updating database version: 16 -> 17")
  526. providerLog(logger.LevelInfo, "updating database version: 16 -> 17")
  527. sql := pgsqlV17SQL
  528. if config.Driver == CockroachDataProviderName {
  529. sql = strings.ReplaceAll(sql, `ALTER TABLE "{{folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_mapping";`,
  530. `DROP INDEX "{{prefix}}unique_mapping" CASCADE;`)
  531. }
  532. sql = strings.ReplaceAll(sql, "{{groups}}", sqlTableGroups)
  533. sql = strings.ReplaceAll(sql, "{{users}}", sqlTableUsers)
  534. sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
  535. sql = strings.ReplaceAll(sql, "{{folders_mapping}}", sqlTableFoldersMapping)
  536. sql = strings.ReplaceAll(sql, "{{users_folders_mapping}}", sqlTableUsersFoldersMapping)
  537. sql = strings.ReplaceAll(sql, "{{users_groups_mapping}}", sqlTableUsersGroupsMapping)
  538. sql = strings.ReplaceAll(sql, "{{groups_folders_mapping}}", sqlTableGroupsFoldersMapping)
  539. sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
  540. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 17)
  541. }
  542. func downgradePGSQLDatabaseFrom16To15(dbHandle *sql.DB) error {
  543. logger.InfoToConsole("downgrading database version: 16 -> 15")
  544. providerLog(logger.LevelInfo, "downgrading database version: 16 -> 15")
  545. sql := strings.ReplaceAll(pgsqlV16DownSQL, "{{users}}", sqlTableUsers)
  546. sql = strings.ReplaceAll(sql, "{{active_transfers}}", sqlTableActiveTransfers)
  547. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 15)
  548. }
  549. func downgradePGSQLDatabaseFrom17To16(dbHandle *sql.DB) error {
  550. logger.InfoToConsole("downgrading database version: 17 -> 16")
  551. providerLog(logger.LevelInfo, "downgrading database version: 17 -> 16")
  552. sql := pgsqlV17DownSQL
  553. if config.Driver == CockroachDataProviderName {
  554. sql = strings.ReplaceAll(sql, `ALTER TABLE "{{users_folders_mapping}}" DROP CONSTRAINT "{{prefix}}unique_user_folder_mapping";`,
  555. `DROP INDEX "{{prefix}}unique_user_folder_mapping" CASCADE;`)
  556. }
  557. sql = strings.ReplaceAll(sql, "{{groups}}", sqlTableGroups)
  558. sql = strings.ReplaceAll(sql, "{{users}}", sqlTableUsers)
  559. sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
  560. sql = strings.ReplaceAll(sql, "{{folders_mapping}}", sqlTableFoldersMapping)
  561. sql = strings.ReplaceAll(sql, "{{users_folders_mapping}}", sqlTableUsersFoldersMapping)
  562. sql = strings.ReplaceAll(sql, "{{users_groups_mapping}}", sqlTableUsersGroupsMapping)
  563. sql = strings.ReplaceAll(sql, "{{groups_folders_mapping}}", sqlTableGroupsFoldersMapping)
  564. sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
  565. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 16)
  566. }