admin.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. /* eslint-disable no-use-before-define */
  2. module.exports = function(crowi, app) {
  3. const debug = require('debug')('growi:routes:admin');
  4. const logger = require('@alias/logger')('growi:routes:admin');
  5. const models = crowi.models;
  6. const User = models.User;
  7. const ExternalAccount = models.ExternalAccount;
  8. const UserGroup = models.UserGroup;
  9. const UserGroupRelation = models.UserGroupRelation;
  10. const GlobalNotificationSetting = models.GlobalNotificationSetting;
  11. const GlobalNotificationMailSetting = models.GlobalNotificationMailSetting;
  12. const GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting; // eslint-disable-line no-unused-vars
  13. const {
  14. configManager,
  15. aclService,
  16. slackNotificationService,
  17. exportService,
  18. } = crowi;
  19. const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist');
  20. const PluginUtils = require('../plugins/plugin-utils');
  21. const ApiResponse = require('../util/apiResponse');
  22. const importer = require('../util/importer')(crowi);
  23. const searchEvent = crowi.event('search');
  24. const pluginUtils = new PluginUtils();
  25. const MAX_PAGE_LIST = 50;
  26. const actions = {};
  27. const { check } = require('express-validator/check');
  28. const api = {};
  29. function createPager(total, limit, page, pagesCount, maxPageList) {
  30. const pager = {
  31. page,
  32. pagesCount,
  33. pages: [],
  34. total,
  35. previous: null,
  36. previousDots: false,
  37. next: null,
  38. nextDots: false,
  39. };
  40. if (page > 1) {
  41. pager.previous = page - 1;
  42. }
  43. if (page < pagesCount) {
  44. pager.next = page + 1;
  45. }
  46. let pagerMin = Math.max(1, Math.ceil(page - maxPageList / 2));
  47. let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList / 2));
  48. if (pagerMin === 1) {
  49. if (MAX_PAGE_LIST < pagesCount) {
  50. pagerMax = MAX_PAGE_LIST;
  51. }
  52. else {
  53. pagerMax = pagesCount;
  54. }
  55. }
  56. if (pagerMax === pagesCount) {
  57. if ((pagerMax - MAX_PAGE_LIST) < 1) {
  58. pagerMin = 1;
  59. }
  60. else {
  61. pagerMin = pagerMax - MAX_PAGE_LIST;
  62. }
  63. }
  64. pager.previousDots = null;
  65. if (pagerMin > 1) {
  66. pager.previousDots = true;
  67. }
  68. pager.nextDots = null;
  69. if (pagerMax < pagesCount) {
  70. pager.nextDots = true;
  71. }
  72. for (let i = pagerMin; i <= pagerMax; i++) {
  73. pager.pages.push(i);
  74. }
  75. return pager;
  76. }
  77. // setup websocket event for rebuild index
  78. searchEvent.on('addPageProgress', (total, current, skip) => {
  79. crowi.getIo().sockets.emit('admin:addPageProgress', { total, current, skip });
  80. });
  81. searchEvent.on('finishAddPage', (total, current, skip) => {
  82. crowi.getIo().sockets.emit('admin:finishAddPage', { total, current, skip });
  83. });
  84. actions.index = function(req, res) {
  85. return res.render('admin/index', {
  86. plugins: pluginUtils.listPlugins(crowi.rootDir),
  87. });
  88. };
  89. // app.get('/admin/app' , admin.app.index);
  90. actions.app = {};
  91. actions.app.index = function(req, res) {
  92. return res.render('admin/app');
  93. };
  94. actions.app.settingUpdate = function(req, res) {
  95. };
  96. // app.get('/admin/security' , admin.security.index);
  97. actions.security = {};
  98. actions.security.index = function(req, res) {
  99. const isWikiModeForced = aclService.isWikiModeForced();
  100. const guestModeValue = aclService.getGuestModeValue();
  101. return res.render('admin/security', {
  102. isWikiModeForced,
  103. guestModeValue,
  104. });
  105. };
  106. // app.get('/admin/markdown' , admin.markdown.index);
  107. actions.markdown = {};
  108. actions.markdown.index = function(req, res) {
  109. const markdownSetting = configManager.getConfigByPrefix('markdown', 'markdown:');
  110. return res.render('admin/markdown', {
  111. markdownSetting,
  112. recommendedWhitelist,
  113. });
  114. };
  115. // app.get('/admin/customize' , admin.customize.index);
  116. actions.customize = {};
  117. actions.customize.index = function(req, res) {
  118. const settingForm = configManager.getConfigByPrefix('crowi', 'customize:');
  119. // TODO delete after apiV3
  120. /* eslint-disable quote-props, no-multi-spaces */
  121. const highlightJsCssSelectorOptions = {
  122. 'github': { name: '[Light] GitHub', border: false },
  123. 'github-gist': { name: '[Light] GitHub Gist', border: true },
  124. 'atom-one-light': { name: '[Light] Atom One Light', border: true },
  125. 'xcode': { name: '[Light] Xcode', border: true },
  126. 'vs': { name: '[Light] Vs', border: true },
  127. 'atom-one-dark': { name: '[Dark] Atom One Dark', border: false },
  128. 'hybrid': { name: '[Dark] Hybrid', border: false },
  129. 'monokai': { name: '[Dark] Monokai', border: false },
  130. 'tomorrow-night': { name: '[Dark] Tomorrow Night', border: false },
  131. 'vs2015': { name: '[Dark] Vs 2015', border: false },
  132. };
  133. /* eslint-enable quote-props, no-multi-spaces */
  134. return res.render('admin/customize', {
  135. settingForm,
  136. highlightJsCssSelectorOptions,
  137. });
  138. };
  139. // app.get('/admin/notification' , admin.notification.index);
  140. actions.notification = {};
  141. actions.notification.index = async(req, res) => {
  142. const UpdatePost = crowi.model('UpdatePost');
  143. let slackSetting = configManager.getConfigByPrefix('notification', 'slack:');
  144. const hasSlackIwhUrl = !!configManager.getConfig('notification', 'slack:incomingWebhookUrl');
  145. const hasSlackToken = !!configManager.getConfig('notification', 'slack:token');
  146. if (!hasSlackIwhUrl) {
  147. slackSetting['slack:incomingWebhookUrl'] = '';
  148. }
  149. if (req.session.slackSetting) {
  150. slackSetting = req.session.slackSetting;
  151. req.session.slackSetting = null;
  152. }
  153. const globalNotifications = await GlobalNotificationSetting.findAll();
  154. const userNotifications = await UpdatePost.findAll();
  155. return res.render('admin/notification', {
  156. userNotifications,
  157. slackSetting,
  158. hasSlackIwhUrl,
  159. hasSlackToken,
  160. globalNotifications,
  161. });
  162. };
  163. // app.post('/admin/notification/slackSetting' , admin.notification.slackauth);
  164. actions.notification.slackSetting = async function(req, res) {
  165. const slackSetting = req.form.slackSetting;
  166. if (req.form.isValid) {
  167. await configManager.updateConfigsInTheSameNamespace('notification', slackSetting);
  168. req.flash('successMessage', ['Successfully Updated!']);
  169. // Re-setup
  170. crowi.setupSlack().then(() => {
  171. });
  172. }
  173. else {
  174. req.flash('errorMessage', req.form.errors);
  175. }
  176. return res.redirect('/admin/notification');
  177. };
  178. // app.get('/admin/notification/slackAuth' , admin.notification.slackauth);
  179. actions.notification.slackAuth = function(req, res) {
  180. const code = req.query.code;
  181. if (!code || !slackNotificationService.hasSlackConfig()) {
  182. return res.redirect('/admin/notification');
  183. }
  184. const slack = crowi.slack;
  185. slack.getOauthAccessToken(code)
  186. .then(async(data) => {
  187. debug('oauth response', data);
  188. try {
  189. await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': data.access_token });
  190. req.flash('successMessage', ['Successfully Connected!']);
  191. }
  192. catch (err) {
  193. req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
  194. }
  195. return res.redirect('/admin/notification');
  196. })
  197. .catch((err) => {
  198. debug('oauth response ERROR', err);
  199. req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']);
  200. return res.redirect('/admin/notification');
  201. });
  202. };
  203. // app.post('/admin/notification/slackIwhSetting' , admin.notification.slackIwhSetting);
  204. actions.notification.slackIwhSetting = async function(req, res) {
  205. const slackIwhSetting = req.form.slackIwhSetting;
  206. if (req.form.isValid) {
  207. await configManager.updateConfigsInTheSameNamespace('notification', slackIwhSetting);
  208. req.flash('successMessage', ['Successfully Updated!']);
  209. // Re-setup
  210. crowi.setupSlack().then(() => {
  211. return res.redirect('/admin/notification#slack-incoming-webhooks');
  212. });
  213. }
  214. else {
  215. req.flash('errorMessage', req.form.errors);
  216. return res.redirect('/admin/notification#slack-incoming-webhooks');
  217. }
  218. };
  219. // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack);
  220. actions.notification.disconnectFromSlack = async function(req, res) {
  221. await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': '' });
  222. req.flash('successMessage', ['Successfully Disconnected!']);
  223. return res.redirect('/admin/notification');
  224. };
  225. actions.globalNotification = {};
  226. actions.globalNotification.detail = async(req, res) => {
  227. const notificationSettingId = req.params.id;
  228. const renderVars = {};
  229. if (notificationSettingId) {
  230. try {
  231. renderVars.setting = await GlobalNotificationSetting.findOne({ _id: notificationSettingId });
  232. }
  233. catch (err) {
  234. logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
  235. }
  236. }
  237. return res.render('admin/global-notification-detail', renderVars);
  238. };
  239. actions.globalNotification.create = (req, res) => {
  240. const form = req.form.notificationGlobal;
  241. let setting;
  242. switch (form.notifyToType) {
  243. case GlobalNotificationSetting.TYPE.MAIL:
  244. setting = new GlobalNotificationMailSetting(crowi);
  245. setting.toEmail = form.toEmail;
  246. break;
  247. case GlobalNotificationSetting.TYPE.SLACK:
  248. setting = new GlobalNotificationSlackSetting(crowi);
  249. setting.slackChannels = form.slackChannels;
  250. break;
  251. default:
  252. logger.error('GlobalNotificationSetting Type Error: undefined type');
  253. req.flash('errorMessage', 'Error occurred in creating a new global notification setting: undefined notification type');
  254. return res.redirect('/admin/notification#global-notification');
  255. }
  256. setting.triggerPath = form.triggerPath;
  257. setting.triggerEvents = getNotificationEvents(form);
  258. setting.save();
  259. return res.redirect('/admin/notification#global-notification');
  260. };
  261. actions.globalNotification.update = async(req, res) => {
  262. const form = req.form.notificationGlobal;
  263. const models = {
  264. [GlobalNotificationSetting.TYPE.MAIL]: GlobalNotificationMailSetting,
  265. [GlobalNotificationSetting.TYPE.SLACK]: GlobalNotificationSlackSetting,
  266. };
  267. let setting = await GlobalNotificationSetting.findOne({ _id: form.id });
  268. setting = setting.toObject();
  269. // when switching from one type to another,
  270. // remove toEmail from slack setting and slackChannels from mail setting
  271. if (setting.__t !== form.notifyToType) {
  272. setting = models[setting.__t].hydrate(setting);
  273. setting.toEmail = undefined;
  274. setting.slackChannels = undefined;
  275. await setting.save();
  276. setting = setting.toObject();
  277. }
  278. switch (form.notifyToType) {
  279. case GlobalNotificationSetting.TYPE.MAIL:
  280. setting = GlobalNotificationMailSetting.hydrate(setting);
  281. setting.toEmail = form.toEmail;
  282. break;
  283. case GlobalNotificationSetting.TYPE.SLACK:
  284. setting = GlobalNotificationSlackSetting.hydrate(setting);
  285. setting.slackChannels = form.slackChannels;
  286. break;
  287. default:
  288. logger.error('GlobalNotificationSetting Type Error: undefined type');
  289. req.flash('errorMessage', 'Error occurred in updating the global notification setting: undefined notification type');
  290. return res.redirect('/admin/notification#global-notification');
  291. }
  292. setting.__t = form.notifyToType;
  293. setting.triggerPath = form.triggerPath;
  294. setting.triggerEvents = getNotificationEvents(form);
  295. await setting.save();
  296. return res.redirect('/admin/notification#global-notification');
  297. };
  298. actions.globalNotification.remove = async(req, res) => {
  299. const id = req.params.id;
  300. try {
  301. await GlobalNotificationSetting.findOneAndRemove({ _id: id });
  302. return res.redirect('/admin/notification#global-notification');
  303. }
  304. catch (err) {
  305. req.flash('errorMessage', 'Error in deleting global notification setting');
  306. return res.redirect('/admin/notification#global-notification');
  307. }
  308. };
  309. const getNotificationEvents = (form) => {
  310. const triggerEvents = [];
  311. const triggerEventKeys = Object.keys(form).filter((key) => { return key.match(/^triggerEvent/) });
  312. triggerEventKeys.forEach((key) => {
  313. if (form[key]) {
  314. triggerEvents.push(form[key]);
  315. }
  316. });
  317. return triggerEvents;
  318. };
  319. actions.search = {};
  320. actions.search.index = function(req, res) {
  321. const search = crowi.getSearcher();
  322. if (!search) {
  323. return res.redirect('/admin');
  324. }
  325. return res.render('admin/search', {});
  326. };
  327. actions.user = {};
  328. actions.user.index = async function(req, res) {
  329. return res.render('admin/users');
  330. };
  331. // これやったときの relation の挙動未確認
  332. actions.user.removeCompletely = function(req, res) {
  333. // ユーザーの物理削除
  334. const id = req.params.id;
  335. User.removeCompletelyById(id, (err, removed) => {
  336. if (err) {
  337. debug('Error while removing user.', err, id);
  338. req.flash('errorMessage', '完全な削除に失敗しました。');
  339. }
  340. else {
  341. req.flash('successMessage', '削除しました');
  342. }
  343. return res.redirect('/admin/users');
  344. });
  345. };
  346. // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
  347. actions.user.resetPassword = async function(req, res) {
  348. const id = req.body.user_id;
  349. const User = crowi.model('User');
  350. try {
  351. const newPassword = await User.resetPasswordByRandomString(id);
  352. const user = await User.findById(id);
  353. const result = { user: user.toObject(), newPassword };
  354. return res.json(ApiResponse.success(result));
  355. }
  356. catch (err) {
  357. debug('Error on reseting password', err);
  358. return res.json(ApiResponse.error(err));
  359. }
  360. };
  361. actions.externalAccount = {};
  362. actions.externalAccount.index = function(req, res) {
  363. return res.render('admin/external-accounts');
  364. };
  365. actions.externalAccount.remove = async function(req, res) {
  366. const id = req.params.id;
  367. let account = null;
  368. try {
  369. account = await ExternalAccount.findByIdAndRemove(id);
  370. if (account == null) {
  371. throw new Error('削除に失敗しました。');
  372. }
  373. }
  374. catch (err) {
  375. req.flash('errorMessage', err.message);
  376. return res.redirect('/admin/users/external-accounts');
  377. }
  378. req.flash('successMessage', `外部アカウント '${account.providerType}/${account.accountId}' を削除しました`);
  379. return res.redirect('/admin/users/external-accounts');
  380. };
  381. actions.userGroup = {};
  382. actions.userGroup.index = function(req, res) {
  383. const page = parseInt(req.query.page) || 1;
  384. const isAclEnabled = aclService.isAclEnabled();
  385. const renderVar = {
  386. userGroups: [],
  387. userGroupRelations: new Map(),
  388. pager: null,
  389. isAclEnabled,
  390. };
  391. UserGroup.findUserGroupsWithPagination({ page })
  392. .then((result) => {
  393. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  394. const userGroups = result.docs;
  395. renderVar.userGroups = userGroups;
  396. renderVar.pager = pager;
  397. return userGroups.map((userGroup) => {
  398. return new Promise((resolve, reject) => {
  399. UserGroupRelation.findAllRelationForUserGroup(userGroup)
  400. .then((relations) => {
  401. return resolve({
  402. id: userGroup._id,
  403. relatedUsers: relations.map((relation) => {
  404. return relation.relatedUser;
  405. }),
  406. });
  407. });
  408. });
  409. });
  410. })
  411. .then((allRelationsPromise) => {
  412. return Promise.all(allRelationsPromise);
  413. })
  414. .then((relations) => {
  415. for (const relation of relations) {
  416. renderVar.userGroupRelations[relation.id] = relation.relatedUsers;
  417. }
  418. debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations);
  419. return res.render('admin/user-groups', renderVar);
  420. })
  421. .catch((err) => {
  422. debug('Error on find all relations', err);
  423. return res.json(ApiResponse.error('Error'));
  424. });
  425. };
  426. // グループ詳細
  427. actions.userGroup.detail = async function(req, res) {
  428. const userGroupId = req.params.id;
  429. const userGroup = await UserGroup.findOne({ _id: userGroupId });
  430. if (userGroup == null) {
  431. logger.error('no userGroup is exists. ', userGroupId);
  432. return res.redirect('/admin/user-groups');
  433. }
  434. return res.render('admin/user-group-detail', { userGroup });
  435. };
  436. // Importer management
  437. actions.importer = {};
  438. actions.importer.api = api;
  439. api.validators = {};
  440. api.validators.importer = {};
  441. actions.importer.index = function(req, res) {
  442. const settingForm = configManager.getConfigByPrefix('crowi', 'importer:');
  443. return res.render('admin/importer', {
  444. settingForm,
  445. });
  446. };
  447. api.validators.importer.esa = function() {
  448. const validator = [
  449. check('importer:esa:team_name').not().isEmpty().withMessage('Error. Empty esa:team_name'),
  450. check('importer:esa:access_token').not().isEmpty().withMessage('Error. Empty esa:access_token'),
  451. ];
  452. return validator;
  453. };
  454. api.validators.importer.qiita = function() {
  455. const validator = [
  456. check('importer:qiita:team_name').not().isEmpty().withMessage('Error. Empty qiita:team_name'),
  457. check('importer:qiita:access_token').not().isEmpty().withMessage('Error. Empty qiita:access_token'),
  458. ];
  459. return validator;
  460. };
  461. // Export management
  462. actions.export = {};
  463. actions.export.index = (req, res) => {
  464. return res.render('admin/export');
  465. };
  466. actions.export.download = (req, res) => {
  467. // TODO: add express validator
  468. const { fileName } = req.params;
  469. try {
  470. const zipFile = exportService.getFile(fileName);
  471. return res.download(zipFile);
  472. }
  473. catch (err) {
  474. // TODO: use ApiV3Error
  475. logger.error(err);
  476. return res.json(ApiResponse.error());
  477. }
  478. };
  479. actions.api = {};
  480. actions.api.appSetting = async function(req, res) {
  481. const form = req.form.settingForm;
  482. if (req.form.isValid) {
  483. debug('form content', form);
  484. // mail setting ならここで validation
  485. if (form['mail:from']) {
  486. validateMailSetting(req, form, async(err, data) => {
  487. debug('Error validate mail setting: ', err, data);
  488. if (err) {
  489. req.form.errors.push('SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。');
  490. return res.json({ status: false, message: req.form.errors.join('\n') });
  491. }
  492. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  493. return res.json({ status: true });
  494. });
  495. }
  496. else {
  497. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  498. return res.json({ status: true });
  499. }
  500. }
  501. else {
  502. return res.json({ status: false, message: req.form.errors.join('\n') });
  503. }
  504. };
  505. actions.api.asyncAppSetting = async(req, res) => {
  506. const form = req.form.settingForm;
  507. if (!req.form.isValid) {
  508. return res.json({ status: false, message: req.form.errors.join('\n') });
  509. }
  510. debug('form content', form);
  511. try {
  512. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  513. return res.json({ status: true });
  514. }
  515. catch (err) {
  516. logger.error(err);
  517. return res.json({ status: false });
  518. }
  519. };
  520. actions.api.securitySetting = async function(req, res) {
  521. if (!req.form.isValid) {
  522. return res.json({ status: false, message: req.form.errors.join('\n') });
  523. }
  524. const form = req.form.settingForm;
  525. if (aclService.isWikiModeForced()) {
  526. logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
  527. delete form['security:restrictGuestMode'];
  528. }
  529. try {
  530. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  531. return res.json({ status: true });
  532. }
  533. catch (err) {
  534. logger.error(err);
  535. return res.json({ status: false });
  536. }
  537. };
  538. actions.api.securityPassportLocalSetting = async function(req, res) {
  539. const form = req.form.settingForm;
  540. if (!req.form.isValid) {
  541. return res.json({ status: false, message: req.form.errors.join('\n') });
  542. }
  543. debug('form content', form);
  544. try {
  545. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  546. // reset strategy
  547. crowi.passportService.resetLocalStrategy();
  548. // setup strategy
  549. if (configManager.getConfig('crowi', 'security:passport-local:isEnabled')) {
  550. crowi.passportService.setupLocalStrategy(true);
  551. }
  552. }
  553. catch (err) {
  554. logger.error(err);
  555. return res.json({ status: false, message: err.message });
  556. }
  557. return res.json({ status: true });
  558. };
  559. actions.api.securityPassportLdapSetting = async function(req, res) {
  560. const form = req.form.settingForm;
  561. if (!req.form.isValid) {
  562. return res.json({ status: false, message: req.form.errors.join('\n') });
  563. }
  564. debug('form content', form);
  565. try {
  566. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  567. // reset strategy
  568. crowi.passportService.resetLdapStrategy();
  569. // setup strategy
  570. if (configManager.getConfig('crowi', 'security:passport-ldap:isEnabled')) {
  571. crowi.passportService.setupLdapStrategy(true);
  572. }
  573. }
  574. catch (err) {
  575. logger.error(err);
  576. return res.json({ status: false, message: err.message });
  577. }
  578. return res.json({ status: true });
  579. };
  580. actions.api.securityPassportSamlSetting = async(req, res) => {
  581. const form = req.form.settingForm;
  582. validateSamlSettingForm(req.form, req.t);
  583. if (!req.form.isValid) {
  584. return res.json({ status: false, message: req.form.errors.join('\n') });
  585. }
  586. debug('form content', form);
  587. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  588. // reset strategy
  589. await crowi.passportService.resetSamlStrategy();
  590. // setup strategy
  591. if (configManager.getConfig('crowi', 'security:passport-saml:isEnabled')) {
  592. try {
  593. await crowi.passportService.setupSamlStrategy(true);
  594. }
  595. catch (err) {
  596. // reset
  597. await crowi.passportService.resetSamlStrategy();
  598. return res.json({ status: false, message: err.message });
  599. }
  600. }
  601. return res.json({ status: true });
  602. };
  603. actions.api.securityPassportBasicSetting = async(req, res) => {
  604. const form = req.form.settingForm;
  605. if (!req.form.isValid) {
  606. return res.json({ status: false, message: req.form.errors.join('\n') });
  607. }
  608. debug('form content', form);
  609. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  610. // reset strategy
  611. await crowi.passportService.resetBasicStrategy();
  612. // setup strategy
  613. if (configManager.getConfig('crowi', 'security:passport-basic:isEnabled')) {
  614. try {
  615. await crowi.passportService.setupBasicStrategy(true);
  616. }
  617. catch (err) {
  618. // reset
  619. await crowi.passportService.resetBasicStrategy();
  620. return res.json({ status: false, message: err.message });
  621. }
  622. }
  623. return res.json({ status: true });
  624. };
  625. actions.api.securityPassportGoogleSetting = async(req, res) => {
  626. const form = req.form.settingForm;
  627. if (!req.form.isValid) {
  628. return res.json({ status: false, message: req.form.errors.join('\n') });
  629. }
  630. debug('form content', form);
  631. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  632. // reset strategy
  633. await crowi.passportService.resetGoogleStrategy();
  634. // setup strategy
  635. if (configManager.getConfig('crowi', 'security:passport-google:isEnabled')) {
  636. try {
  637. await crowi.passportService.setupGoogleStrategy(true);
  638. }
  639. catch (err) {
  640. // reset
  641. await crowi.passportService.resetGoogleStrategy();
  642. return res.json({ status: false, message: err.message });
  643. }
  644. }
  645. return res.json({ status: true });
  646. };
  647. actions.api.securityPassportGitHubSetting = async(req, res) => {
  648. const form = req.form.settingForm;
  649. if (!req.form.isValid) {
  650. return res.json({ status: false, message: req.form.errors.join('\n') });
  651. }
  652. debug('form content', form);
  653. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  654. // reset strategy
  655. await crowi.passportService.resetGitHubStrategy();
  656. // setup strategy
  657. if (configManager.getConfig('crowi', 'security:passport-github:isEnabled')) {
  658. try {
  659. await crowi.passportService.setupGitHubStrategy(true);
  660. }
  661. catch (err) {
  662. // reset
  663. await crowi.passportService.resetGitHubStrategy();
  664. return res.json({ status: false, message: err.message });
  665. }
  666. }
  667. return res.json({ status: true });
  668. };
  669. actions.api.securityPassportTwitterSetting = async(req, res) => {
  670. const form = req.form.settingForm;
  671. if (!req.form.isValid) {
  672. return res.json({ status: false, message: req.form.errors.join('\n') });
  673. }
  674. debug('form content', form);
  675. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  676. // reset strategy
  677. await crowi.passportService.resetTwitterStrategy();
  678. // setup strategy
  679. if (configManager.getConfig('crowi', 'security:passport-twitter:isEnabled')) {
  680. try {
  681. await crowi.passportService.setupTwitterStrategy(true);
  682. }
  683. catch (err) {
  684. // reset
  685. await crowi.passportService.resetTwitterStrategy();
  686. return res.json({ status: false, message: err.message });
  687. }
  688. }
  689. return res.json({ status: true });
  690. };
  691. actions.api.securityPassportOidcSetting = async(req, res) => {
  692. const form = req.form.settingForm;
  693. if (!req.form.isValid) {
  694. return res.json({ status: false, message: req.form.errors.join('\n') });
  695. }
  696. debug('form content', form);
  697. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  698. // reset strategy
  699. await crowi.passportService.resetOidcStrategy();
  700. // setup strategy
  701. if (configManager.getConfig('crowi', 'security:passport-oidc:isEnabled')) {
  702. try {
  703. await crowi.passportService.setupOidcStrategy(true);
  704. }
  705. catch (err) {
  706. // reset
  707. await crowi.passportService.resetOidcStrategy();
  708. return res.json({ status: false, message: err.message });
  709. }
  710. }
  711. return res.json({ status: true });
  712. };
  713. // app.post('/_api/admin/notifications.add' , admin.api.notificationAdd);
  714. actions.api.notificationAdd = function(req, res) {
  715. const UpdatePost = crowi.model('UpdatePost');
  716. const pathPattern = req.body.pathPattern;
  717. const channel = req.body.channel;
  718. debug('notification.add', pathPattern, channel);
  719. UpdatePost.create(pathPattern, channel, req.user)
  720. .then((doc) => {
  721. debug('Successfully save updatePost', doc);
  722. // fixme: うーん
  723. doc.creator = doc.creator._id.toString();
  724. return res.json(ApiResponse.success({ updatePost: doc }));
  725. })
  726. .catch((err) => {
  727. debug('Failed to save updatePost', err);
  728. return res.json(ApiResponse.error());
  729. });
  730. };
  731. // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove);
  732. actions.api.notificationRemove = function(req, res) {
  733. const UpdatePost = crowi.model('UpdatePost');
  734. const id = req.body.id;
  735. UpdatePost.remove(id)
  736. .then(() => {
  737. debug('Successfully remove updatePost');
  738. return res.json(ApiResponse.success({}));
  739. })
  740. .catch((err) => {
  741. debug('Failed to remove updatePost', err);
  742. return res.json(ApiResponse.error());
  743. });
  744. };
  745. // app.get('/_api/admin/users.search' , admin.api.userSearch);
  746. actions.api.usersSearch = function(req, res) {
  747. const User = crowi.model('User');
  748. const email = req.query.email;
  749. User.findUsersByPartOfEmail(email, {})
  750. .then((users) => {
  751. const result = {
  752. data: users,
  753. };
  754. return res.json(ApiResponse.success(result));
  755. })
  756. .catch((err) => {
  757. return res.json(ApiResponse.error());
  758. });
  759. };
  760. actions.api.toggleIsEnabledForGlobalNotification = async(req, res) => {
  761. const id = req.query.id;
  762. const isEnabled = (req.query.isEnabled === 'true');
  763. try {
  764. if (isEnabled) {
  765. await GlobalNotificationSetting.enable(id);
  766. }
  767. else {
  768. await GlobalNotificationSetting.disable(id);
  769. }
  770. return res.json(ApiResponse.success());
  771. }
  772. catch (err) {
  773. return res.json(ApiResponse.error());
  774. }
  775. };
  776. /**
  777. * save esa settings, update config cache, and response json
  778. *
  779. * @param {*} req
  780. * @param {*} res
  781. */
  782. actions.api.importerSettingEsa = async(req, res) => {
  783. const form = req.body;
  784. const { validationResult } = require('express-validator');
  785. const errors = validationResult(req);
  786. if (!errors.isEmpty()) {
  787. return res.json(ApiResponse.error('esa.io form is blank'));
  788. }
  789. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  790. importer.initializeEsaClient(); // let it run in the back aftert res
  791. return res.json(ApiResponse.success());
  792. };
  793. /**
  794. * save qiita settings, update config cache, and response json
  795. *
  796. * @param {*} req
  797. * @param {*} res
  798. */
  799. actions.api.importerSettingQiita = async(req, res) => {
  800. const form = req.body;
  801. const { validationResult } = require('express-validator');
  802. const errors = validationResult(req);
  803. if (!errors.isEmpty()) {
  804. return res.json(ApiResponse.error('Qiita form is blank'));
  805. }
  806. await configManager.updateConfigsInTheSameNamespace('crowi', form);
  807. importer.initializeQiitaClient(); // let it run in the back aftert res
  808. return res.json(ApiResponse.success());
  809. };
  810. /**
  811. * Import all posts from esa
  812. *
  813. * @param {*} req
  814. * @param {*} res
  815. */
  816. actions.api.importDataFromEsa = async(req, res) => {
  817. const user = req.user;
  818. let errors;
  819. try {
  820. errors = await importer.importDataFromEsa(user);
  821. }
  822. catch (err) {
  823. errors = [err];
  824. }
  825. if (errors.length > 0) {
  826. return res.json(ApiResponse.error(`<br> - ${errors.join('<br> - ')}`));
  827. }
  828. return res.json(ApiResponse.success());
  829. };
  830. /**
  831. * Import all posts from qiita
  832. *
  833. * @param {*} req
  834. * @param {*} res
  835. */
  836. actions.api.importDataFromQiita = async(req, res) => {
  837. const user = req.user;
  838. let errors;
  839. try {
  840. errors = await importer.importDataFromQiita(user);
  841. }
  842. catch (err) {
  843. errors = [err];
  844. }
  845. if (errors.length > 0) {
  846. return res.json(ApiResponse.error(`<br> - ${errors.join('<br> - ')}`));
  847. }
  848. return res.json(ApiResponse.success());
  849. };
  850. /**
  851. * Test connection to esa and response result with json
  852. *
  853. * @param {*} req
  854. * @param {*} res
  855. */
  856. actions.api.testEsaAPI = async(req, res) => {
  857. try {
  858. await importer.testConnectionToEsa();
  859. return res.json(ApiResponse.success());
  860. }
  861. catch (err) {
  862. return res.json(ApiResponse.error(err));
  863. }
  864. };
  865. /**
  866. * Test connection to qiita and response result with json
  867. *
  868. * @param {*} req
  869. * @param {*} res
  870. */
  871. actions.api.testQiitaAPI = async(req, res) => {
  872. try {
  873. await importer.testConnectionToQiita();
  874. return res.json(ApiResponse.success());
  875. }
  876. catch (err) {
  877. return res.json(ApiResponse.error(err));
  878. }
  879. };
  880. actions.api.searchBuildIndex = async function(req, res) {
  881. const search = crowi.getSearcher();
  882. if (!search) {
  883. return res.json(ApiResponse.error('ElasticSearch Integration is not set up.'));
  884. }
  885. try {
  886. search.buildIndex();
  887. }
  888. catch (err) {
  889. return res.json(ApiResponse.error(err));
  890. }
  891. return res.json(ApiResponse.success());
  892. };
  893. function validateMailSetting(req, form, callback) {
  894. const mailer = crowi.mailer;
  895. const option = {
  896. host: form['mail:smtpHost'],
  897. port: form['mail:smtpPort'],
  898. };
  899. if (form['mail:smtpUser'] && form['mail:smtpPassword']) {
  900. option.auth = {
  901. user: form['mail:smtpUser'],
  902. pass: form['mail:smtpPassword'],
  903. };
  904. }
  905. if (option.port === 465) {
  906. option.secure = true;
  907. }
  908. const smtpClient = mailer.createSMTPClient(option);
  909. debug('mailer setup for validate SMTP setting', smtpClient);
  910. smtpClient.sendMail({
  911. from: form['mail:from'],
  912. to: req.user.email,
  913. subject: 'Wiki管理設定のアップデートによるメール通知',
  914. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。',
  915. }, callback);
  916. }
  917. /**
  918. * validate setting form values for SAML
  919. *
  920. * This validation checks, for the value of each mandatory items,
  921. * whether it from the environment variables is empty and form value to update it is empty.
  922. */
  923. function validateSamlSettingForm(form, t) {
  924. for (const key of crowi.passportService.mandatoryConfigKeysForSaml) {
  925. const formValue = form.settingForm[key];
  926. if (configManager.getConfigFromEnvVars('crowi', key) === null && formValue === '') {
  927. const formItemName = t(`security_setting.form_item_name.${key}`);
  928. form.errors.push(t('form_validation.required', formItemName));
  929. }
  930. }
  931. }
  932. return actions;
  933. };