You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
12 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. const _ = require('lodash')
  2. const Promise = require('bluebird')
  3. const getos = Promise.promisify(require('getos'))
  4. const os = require('os')
  5. const filesize = require('filesize')
  6. const path = require('path')
  7. const fs = require('fs-extra')
  8. const moment = require('moment')
  9. const graphHelper = require('../../helpers/graph')
  10. const request = require('request-promise')
  11. const crypto = require('crypto')
  12. const nanoid = require('nanoid/non-secure').customAlphabet('1234567890abcdef', 10)
  13. /* global WIKI */
  14. const dbTypes = {
  15. mysql: 'MySQL',
  16. mariadb: 'MariaDB',
  17. postgres: 'PostgreSQL',
  18. sqlite: 'SQLite',
  19. mssql: 'MS SQL Server'
  20. }
  21. module.exports = {
  22. Query: {
  23. async system () { return {} }
  24. },
  25. Mutation: {
  26. async system () { return {} }
  27. },
  28. SystemQuery: {
  29. flags () {
  30. return _.transform(WIKI.config.flags, (result, value, key) => {
  31. result.push({ key, value })
  32. }, [])
  33. },
  34. async info() { return {} }
  35. },
  36. SystemMutation: {
  37. async updateFlags (obj, args, context) {
  38. WIKI.config.flags = _.transform(args.flags, (result, row) => {
  39. _.set(result, row.key, row.value)
  40. }, {})
  41. await WIKI.configSvc.applyFlags()
  42. await WIKI.configSvc.saveToDb(['flags'])
  43. return {
  44. responseResult: graphHelper.generateSuccess('System Flags applied successfully')
  45. }
  46. },
  47. async resetTelemetryClientId (obj, args, context) {
  48. try {
  49. WIKI.telemetry.generateClientId()
  50. await WIKI.configSvc.saveToDb(['telemetry'])
  51. return {
  52. responseResult: graphHelper.generateSuccess('Telemetry state updated successfully')
  53. }
  54. } catch (err) {
  55. return graphHelper.generateError(err)
  56. }
  57. },
  58. async setTelemetry (obj, args, context) {
  59. try {
  60. _.set(WIKI.config, 'telemetry.isEnabled', args.enabled)
  61. WIKI.telemetry.enabled = args.enabled
  62. await WIKI.configSvc.saveToDb(['telemetry'])
  63. return {
  64. responseResult: graphHelper.generateSuccess('Telemetry Client ID has been reset successfully')
  65. }
  66. } catch (err) {
  67. return graphHelper.generateError(err)
  68. }
  69. },
  70. async performUpgrade (obj, args, context) {
  71. try {
  72. if (process.env.UPGRADE_COMPANION) {
  73. await request({
  74. method: 'POST',
  75. uri: 'http://wiki-update-companion/upgrade'
  76. })
  77. return {
  78. responseResult: graphHelper.generateSuccess('Upgrade has started.')
  79. }
  80. } else {
  81. throw new Error('You must run the wiki-update-companion container and pass the UPGRADE_COMPANION env var in order to use this feature.')
  82. }
  83. } catch (err) {
  84. return graphHelper.generateError(err)
  85. }
  86. },
  87. /**
  88. * Import Users from a v1 installation
  89. */
  90. async importUsersFromV1(obj, args, context) {
  91. try {
  92. const MongoClient = require('mongodb').MongoClient
  93. if (args.mongoDbConnString && args.mongoDbConnString.length > 10) {
  94. // -> Connect to DB
  95. const client = await MongoClient.connect(args.mongoDbConnString, {
  96. appname: `Wiki.js ${WIKI.version} Migration Tool`
  97. })
  98. const dbUsers = client.db().collection('users')
  99. const userCursor = dbUsers.find({ email: { '$ne': 'guest' } })
  100. const curDateISO = new Date().toISOString()
  101. let failed = []
  102. let usersCount = 0
  103. let groupsCount = 0
  104. let assignableGroups = []
  105. let reuseGroups = []
  106. // -> Create SINGLE group
  107. if (args.groupMode === `SINGLE`) {
  108. const singleGroup = await WIKI.models.groups.query().insert({
  109. name: `Import_${curDateISO}`,
  110. permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
  111. pageRules: JSON.stringify(WIKI.data.groups.defaultPageRules)
  112. })
  113. groupsCount++
  114. assignableGroups.push(singleGroup.id)
  115. }
  116. // -> Iterate all users
  117. while (await userCursor.hasNext()) {
  118. const usr = await userCursor.next()
  119. let usrGroup = []
  120. if (args.groupMode === `MULTI`) {
  121. // -> Check if global admin
  122. if (_.some(usr.rights, ['role', 'admin'])) {
  123. usrGroup.push(1)
  124. } else {
  125. // -> Check if identical group already exists
  126. const currentRights = _.sortBy(_.map(usr.rights, r => _.pick(r, ['role', 'path', 'exact', 'deny'])), ['role', 'path', 'exact', 'deny'])
  127. const ruleSetId = crypto.createHash('sha1').update(JSON.stringify(currentRights)).digest('base64')
  128. const existingGroup = _.find(reuseGroups, ['hash', ruleSetId])
  129. if (existingGroup) {
  130. usrGroup.push(existingGroup.groupId)
  131. } else {
  132. // -> Build new group
  133. const pageRules = _.map(usr.rights, r => {
  134. let roles = ['read:pages', 'read:assets', 'read:comments', 'write:comments']
  135. if (r.role === `write`) {
  136. roles = _.concat(roles, ['write:pages', 'manage:pages', 'read:source', 'read:history', 'write:assets', 'manage:assets'])
  137. }
  138. return {
  139. id: nanoid(),
  140. roles: roles,
  141. match: r.exact ? 'EXACT' : 'START',
  142. deny: r.deny,
  143. path: (r.path.indexOf('/') === 0) ? r.path.substring(1) : r.path,
  144. locales: []
  145. }
  146. })
  147. const perms = _.chain(pageRules).reject('deny').map('roles').union().flatten().value()
  148. // -> Create new group
  149. const newGroup = await WIKI.models.groups.query().insert({
  150. name: `Import_${curDateISO}_${groupsCount + 1}`,
  151. permissions: JSON.stringify(perms),
  152. pageRules: JSON.stringify(pageRules)
  153. })
  154. reuseGroups.push({
  155. groupId: newGroup.id,
  156. hash: ruleSetId
  157. })
  158. groupsCount++
  159. usrGroup.push(newGroup.id)
  160. }
  161. }
  162. }
  163. // -> Create User
  164. try {
  165. await WIKI.models.users.createNewUser({
  166. providerKey: usr.provider,
  167. email: usr.email,
  168. name: usr.name,
  169. passwordRaw: usr.password,
  170. groups: (usrGroup.length > 0) ? usrGroup : assignableGroups,
  171. mustChangePassword: false,
  172. sendWelcomeEmail: false
  173. })
  174. usersCount++
  175. } catch (err) {
  176. failed.push({
  177. provider: usr.provider,
  178. email: usr.email,
  179. error: err.message
  180. })
  181. WIKI.logger.warn(`${usr.email}: ${err}`)
  182. }
  183. }
  184. // -> Reload group permissions
  185. if (args.groupMode !== `NONE`) {
  186. await WIKI.auth.reloadGroups()
  187. }
  188. client.close()
  189. return {
  190. responseResult: graphHelper.generateSuccess('Import completed.'),
  191. usersCount: usersCount,
  192. groupsCount: groupsCount,
  193. failed: failed
  194. }
  195. } else {
  196. throw new Error('MongoDB Connection String is missing or invalid.')
  197. }
  198. } catch (err) {
  199. return graphHelper.generateError(err)
  200. }
  201. },
  202. /**
  203. * Set HTTPS Redirection State
  204. */
  205. async setHTTPSRedirection (obj, args, context) {
  206. _.set(WIKI.config, 'server.sslRedir', args.enabled)
  207. await WIKI.configSvc.saveToDb(['server'])
  208. return {
  209. responseResult: graphHelper.generateSuccess('HTTP Redirection state set successfully.')
  210. }
  211. },
  212. /**
  213. * Renew SSL Certificate
  214. */
  215. async renewHTTPSCertificate (obj, args, context) {
  216. try {
  217. if (!WIKI.config.ssl.enabled) {
  218. throw new WIKI.Error.SystemSSLDisabled()
  219. } else if (WIKI.config.ssl.provider !== `letsencrypt`) {
  220. throw new WIKI.Error.SystemSSLRenewInvalidProvider()
  221. } else if (!WIKI.servers.le) {
  222. throw new WIKI.Error.SystemSSLLEUnavailable()
  223. } else {
  224. await WIKI.servers.le.requestCertificate()
  225. await WIKI.servers.restartServer('https')
  226. return {
  227. responseResult: graphHelper.generateSuccess('SSL Certificate renewed successfully.')
  228. }
  229. }
  230. } catch (err) {
  231. return graphHelper.generateError(err)
  232. }
  233. }
  234. },
  235. SystemInfo: {
  236. configFile () {
  237. return path.join(process.cwd(), 'config.yml')
  238. },
  239. cpuCores () {
  240. return os.cpus().length
  241. },
  242. currentVersion () {
  243. return WIKI.version
  244. },
  245. dbType () {
  246. return _.get(dbTypes, WIKI.config.db.type, 'Unknown DB')
  247. },
  248. async dbVersion () {
  249. let version = 'Unknown Version'
  250. switch (WIKI.config.db.type) {
  251. case 'mariadb':
  252. case 'mysql':
  253. const resultMYSQL = await WIKI.models.knex.raw('SELECT VERSION() as version;')
  254. version = _.get(resultMYSQL, '[0][0].version', 'Unknown Version')
  255. break
  256. case 'mssql':
  257. const resultMSSQL = await WIKI.models.knex.raw('SELECT @@VERSION as version;')
  258. version = _.get(resultMSSQL, '[0].version', 'Unknown Version')
  259. break
  260. case 'postgres':
  261. version = _.get(WIKI.models, 'knex.client.version', 'Unknown Version')
  262. break
  263. case 'sqlite':
  264. version = _.get(WIKI.models, 'knex.client.driver.VERSION', 'Unknown Version')
  265. break
  266. }
  267. return version
  268. },
  269. dbHost () {
  270. if (WIKI.config.db.type === 'sqlite') {
  271. return WIKI.config.db.storage
  272. } else {
  273. return WIKI.config.db.host
  274. }
  275. },
  276. hostname () {
  277. return os.hostname()
  278. },
  279. httpPort () {
  280. return WIKI.servers.servers.http ? _.get(WIKI.servers.servers.http.address(), 'port', 0) : 0
  281. },
  282. httpRedirection () {
  283. return _.get(WIKI.config, 'server.sslRedir', false)
  284. },
  285. httpsPort () {
  286. return WIKI.servers.servers.https ? _.get(WIKI.servers.servers.https.address(), 'port', 0) : 0
  287. },
  288. latestVersion () {
  289. return WIKI.system.updates.version
  290. },
  291. latestVersionReleaseDate () {
  292. return moment.utc(WIKI.system.updates.releaseDate)
  293. },
  294. nodeVersion () {
  295. return process.version.substr(1)
  296. },
  297. async operatingSystem () {
  298. let osLabel = `${os.type()} (${os.platform()}) ${os.release()} ${os.arch()}`
  299. if (os.platform() === 'linux') {
  300. const osInfo = await getos()
  301. osLabel = `${os.type()} - ${osInfo.dist} (${osInfo.codename || os.platform()}) ${osInfo.release || os.release()} ${os.arch()}`
  302. }
  303. return osLabel
  304. },
  305. async platform () {
  306. const isDockerized = await fs.pathExists('/.dockerenv')
  307. if (isDockerized) {
  308. return 'docker'
  309. }
  310. return os.platform()
  311. },
  312. ramTotal () {
  313. return filesize(os.totalmem())
  314. },
  315. sslDomain () {
  316. return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? WIKI.config.ssl.domain : null
  317. },
  318. sslExpirationDate () {
  319. return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? _.get(WIKI.config.letsencrypt, 'payload.expires', null) : null
  320. },
  321. sslProvider () {
  322. return WIKI.config.ssl.enabled ? WIKI.config.ssl.provider : null
  323. },
  324. sslStatus () {
  325. return 'OK'
  326. },
  327. sslSubscriberEmail () {
  328. return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? WIKI.config.ssl.subscriberEmail : null
  329. },
  330. telemetry () {
  331. return WIKI.telemetry.enabled
  332. },
  333. telemetryClientId () {
  334. return WIKI.config.telemetry.clientId
  335. },
  336. async upgradeCapable () {
  337. return !_.isNil(process.env.UPGRADE_COMPANION)
  338. },
  339. workingDirectory () {
  340. return process.cwd()
  341. },
  342. async groupsTotal () {
  343. const total = await WIKI.models.groups.query().count('* as total').first()
  344. return _.toSafeInteger(total.total)
  345. },
  346. async pagesTotal () {
  347. const total = await WIKI.models.pages.query().count('* as total').first()
  348. return _.toSafeInteger(total.total)
  349. },
  350. async usersTotal () {
  351. const total = await WIKI.models.users.query().count('* as total').first()
  352. return _.toSafeInteger(total.total)
  353. },
  354. async tagsTotal () {
  355. const total = await WIKI.models.tags.query().count('* as total').first()
  356. return _.toSafeInteger(total.total)
  357. }
  358. }
  359. }