system.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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. WIKI.events.outbound.emit('reloadGroups')
  188. }
  189. client.close()
  190. return {
  191. responseResult: graphHelper.generateSuccess('Import completed.'),
  192. usersCount: usersCount,
  193. groupsCount: groupsCount,
  194. failed: failed
  195. }
  196. } else {
  197. throw new Error('MongoDB Connection String is missing or invalid.')
  198. }
  199. } catch (err) {
  200. return graphHelper.generateError(err)
  201. }
  202. },
  203. /**
  204. * Set HTTPS Redirection State
  205. */
  206. async setHTTPSRedirection (obj, args, context) {
  207. _.set(WIKI.config, 'server.sslRedir', args.enabled)
  208. await WIKI.configSvc.saveToDb(['server'])
  209. return {
  210. responseResult: graphHelper.generateSuccess('HTTP Redirection state set successfully.')
  211. }
  212. },
  213. /**
  214. * Renew SSL Certificate
  215. */
  216. async renewHTTPSCertificate (obj, args, context) {
  217. try {
  218. if (!WIKI.config.ssl.enabled) {
  219. throw new WIKI.Error.SystemSSLDisabled()
  220. } else if (WIKI.config.ssl.provider !== `letsencrypt`) {
  221. throw new WIKI.Error.SystemSSLRenewInvalidProvider()
  222. } else if (!WIKI.servers.le) {
  223. throw new WIKI.Error.SystemSSLLEUnavailable()
  224. } else {
  225. await WIKI.servers.le.requestCertificate()
  226. await WIKI.servers.restartServer('https')
  227. return {
  228. responseResult: graphHelper.generateSuccess('SSL Certificate renewed successfully.')
  229. }
  230. }
  231. } catch (err) {
  232. return graphHelper.generateError(err)
  233. }
  234. }
  235. },
  236. SystemInfo: {
  237. configFile () {
  238. return path.join(process.cwd(), 'config.yml')
  239. },
  240. cpuCores () {
  241. return os.cpus().length
  242. },
  243. currentVersion () {
  244. return WIKI.version
  245. },
  246. dbType () {
  247. return _.get(dbTypes, WIKI.config.db.type, 'Unknown DB')
  248. },
  249. async dbVersion () {
  250. let version = 'Unknown Version'
  251. switch (WIKI.config.db.type) {
  252. case 'mariadb':
  253. case 'mysql':
  254. const resultMYSQL = await WIKI.models.knex.raw('SELECT VERSION() as version;')
  255. version = _.get(resultMYSQL, '[0][0].version', 'Unknown Version')
  256. break
  257. case 'mssql':
  258. const resultMSSQL = await WIKI.models.knex.raw('SELECT @@VERSION as version;')
  259. version = _.get(resultMSSQL, '[0].version', 'Unknown Version')
  260. break
  261. case 'postgres':
  262. version = _.get(WIKI.models, 'knex.client.version', 'Unknown Version')
  263. break
  264. case 'sqlite':
  265. version = _.get(WIKI.models, 'knex.client.driver.VERSION', 'Unknown Version')
  266. break
  267. }
  268. return version
  269. },
  270. dbHost () {
  271. if (WIKI.config.db.type === 'sqlite') {
  272. return WIKI.config.db.storage
  273. } else {
  274. return WIKI.config.db.host
  275. }
  276. },
  277. hostname () {
  278. return os.hostname()
  279. },
  280. httpPort () {
  281. return WIKI.servers.servers.http ? _.get(WIKI.servers.servers.http.address(), 'port', 0) : 0
  282. },
  283. httpRedirection () {
  284. return _.get(WIKI.config, 'server.sslRedir', false)
  285. },
  286. httpsPort () {
  287. return WIKI.servers.servers.https ? _.get(WIKI.servers.servers.https.address(), 'port', 0) : 0
  288. },
  289. latestVersion () {
  290. return WIKI.system.updates.version
  291. },
  292. latestVersionReleaseDate () {
  293. return moment.utc(WIKI.system.updates.releaseDate)
  294. },
  295. nodeVersion () {
  296. return process.version.substr(1)
  297. },
  298. async operatingSystem () {
  299. let osLabel = `${os.type()} (${os.platform()}) ${os.release()} ${os.arch()}`
  300. if (os.platform() === 'linux') {
  301. const osInfo = await getos()
  302. osLabel = `${os.type()} - ${osInfo.dist} (${osInfo.codename || os.platform()}) ${osInfo.release || os.release()} ${os.arch()}`
  303. }
  304. return osLabel
  305. },
  306. async platform () {
  307. const isDockerized = await fs.pathExists('/.dockerenv')
  308. if (isDockerized) {
  309. return 'docker'
  310. }
  311. return os.platform()
  312. },
  313. ramTotal () {
  314. return filesize(os.totalmem())
  315. },
  316. sslDomain () {
  317. return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? WIKI.config.ssl.domain : null
  318. },
  319. sslExpirationDate () {
  320. return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? _.get(WIKI.config.letsencrypt, 'payload.expires', null) : null
  321. },
  322. sslProvider () {
  323. return WIKI.config.ssl.enabled ? WIKI.config.ssl.provider : null
  324. },
  325. sslStatus () {
  326. return 'OK'
  327. },
  328. sslSubscriberEmail () {
  329. return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? WIKI.config.ssl.subscriberEmail : null
  330. },
  331. telemetry () {
  332. return WIKI.telemetry.enabled
  333. },
  334. telemetryClientId () {
  335. return WIKI.config.telemetry.clientId
  336. },
  337. async upgradeCapable () {
  338. return !_.isNil(process.env.UPGRADE_COMPANION)
  339. },
  340. workingDirectory () {
  341. return process.cwd()
  342. },
  343. async groupsTotal () {
  344. const total = await WIKI.models.groups.query().count('* as total').first()
  345. return _.toSafeInteger(total.total)
  346. },
  347. async pagesTotal () {
  348. const total = await WIKI.models.pages.query().count('* as total').first()
  349. return _.toSafeInteger(total.total)
  350. },
  351. async usersTotal () {
  352. const total = await WIKI.models.users.query().count('* as total').first()
  353. return _.toSafeInteger(total.total)
  354. },
  355. async tagsTotal () {
  356. const total = await WIKI.models.tags.query().count('* as total').first()
  357. return _.toSafeInteger(total.total)
  358. }
  359. }
  360. }