admin.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413
  1. module.exports = function(crowi, app) {
  2. 'use strict';
  3. const debug = require('debug')('growi:routes:admin');
  4. const logger = require('@alias/logger')('growi:routes:admin');
  5. const fs = require('fs');
  6. const models = crowi.models;
  7. const Page = models.Page;
  8. const PageGroupRelation = models.PageGroupRelation;
  9. const PageTagRelation = models.PageTagRelation;
  10. const User = models.User;
  11. const ExternalAccount = models.ExternalAccount;
  12. const UserGroup = models.UserGroup;
  13. const UserGroupRelation = models.UserGroupRelation;
  14. const Tag = models.Tag;
  15. const Config = models.Config;
  16. const GlobalNotificationSetting = models.GlobalNotificationSetting;
  17. const GlobalNotificationMailSetting = models.GlobalNotificationMailSetting;
  18. const GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting; // eslint-disable-line no-unused-vars
  19. const recommendedXssWhiteList = require('@commons/service/xss/recommendedXssWhiteList');
  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. function createPager(total, limit, page, pagesCount, maxPageList) {
  28. const pager = {
  29. page: page,
  30. pagesCount: pagesCount,
  31. pages: [],
  32. total: total,
  33. previous: null,
  34. previousDots: false,
  35. next: null,
  36. nextDots: false,
  37. };
  38. if (page > 1) {
  39. pager.previous = page - 1;
  40. }
  41. if (page < pagesCount) {
  42. pager.next = page + 1;
  43. }
  44. let pagerMin = Math.max(1, Math.ceil(page - maxPageList/2));
  45. let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList/2));
  46. if (pagerMin === 1) {
  47. if (MAX_PAGE_LIST < pagesCount) {
  48. pagerMax = MAX_PAGE_LIST;
  49. }
  50. else {
  51. pagerMax = pagesCount;
  52. }
  53. }
  54. if (pagerMax === pagesCount) {
  55. if ((pagerMax - MAX_PAGE_LIST) < 1) {
  56. pagerMin = 1;
  57. }
  58. else {
  59. pagerMin = pagerMax - MAX_PAGE_LIST;
  60. }
  61. }
  62. pager.previousDots = null;
  63. if (pagerMin > 1) {
  64. pager.previousDots = true;
  65. }
  66. pager.nextDots = null;
  67. if (pagerMax < pagesCount) {
  68. pager.nextDots = true;
  69. }
  70. for (let i = pagerMin; i <= pagerMax; i++) {
  71. pager.pages.push(i);
  72. }
  73. return pager;
  74. }
  75. actions.index = function(req, res) {
  76. return res.render('admin/index', {
  77. plugins: pluginUtils.listPlugins(crowi.rootDir),
  78. });
  79. };
  80. // app.get('/admin/app' , admin.app.index);
  81. actions.app = {};
  82. actions.app.index = function(req, res) {
  83. var settingForm;
  84. settingForm = Config.setupConfigFormData('crowi', req.config);
  85. return res.render('admin/app', {
  86. settingForm: settingForm,
  87. });
  88. };
  89. actions.app.settingUpdate = function(req, res) {
  90. };
  91. // app.get('/admin/security' , admin.security.index);
  92. actions.security = {};
  93. actions.security.index = function(req, res) {
  94. const settingForm = Config.setupConfigFormData('crowi', req.config);
  95. const isAclEnabled = !Config.isPublicWikiOnly(req.config);
  96. return res.render('admin/security', { settingForm, isAclEnabled });
  97. };
  98. // app.get('/admin/markdown' , admin.markdown.index);
  99. actions.markdown = {};
  100. actions.markdown.index = function(req, res) {
  101. const config = crowi.getConfig();
  102. const markdownSetting = Config.setupConfigFormData('markdown', config);
  103. return res.render('admin/markdown', {
  104. markdownSetting: markdownSetting,
  105. recommendedXssWhiteList: recommendedXssWhiteList,
  106. });
  107. };
  108. // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting);
  109. actions.markdown.lineBreaksSetting = function(req, res) {
  110. var markdownSetting = req.form.markdownSetting;
  111. req.session.markdownSetting = markdownSetting;
  112. if (req.form.isValid) {
  113. Config.updateNamespaceByArray('markdown', markdownSetting, function(err, config) {
  114. Config.updateConfigCache('markdown', config);
  115. req.session.markdownSetting = null;
  116. req.flash('successMessage', ['Successfully updated!']);
  117. return res.redirect('/admin/markdown');
  118. });
  119. }
  120. else {
  121. req.flash('errorMessage', req.form.errors);
  122. return res.redirect('/admin/markdown');
  123. }
  124. };
  125. // app.post('/admin/markdown/presentationSetting' , admin.markdown.presentationSetting);
  126. actions.markdown.presentationSetting = function(req, res) {
  127. let presentationSetting = req.form.markdownSetting;
  128. req.session.markdownSetting = presentationSetting;
  129. if (req.form.isValid) {
  130. Config.updateNamespaceByArray('markdown', presentationSetting, function(err, config) {
  131. Config.updateConfigCache('markdown', config);
  132. req.session.markdownSetting = null;
  133. req.flash('successMessage', ['Successfully updated!']);
  134. return res.redirect('/admin/markdown');
  135. });
  136. }
  137. else {
  138. req.flash('errorMessage', req.form.errors);
  139. return res.redirect('/admin/markdown');
  140. }
  141. };
  142. // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting);
  143. actions.markdown.xssSetting = function(req, res) {
  144. let xssSetting = req.form.markdownSetting;
  145. xssSetting['markdown:xss:tagWhiteList'] = stringToArray(xssSetting['markdown:xss:tagWhiteList']);
  146. xssSetting['markdown:xss:attrWhiteList'] = stringToArray(xssSetting['markdown:xss:attrWhiteList']);
  147. req.session.markdownSetting = xssSetting;
  148. if (req.form.isValid) {
  149. Config.updateNamespaceByArray('markdown', xssSetting, function(err, config) {
  150. Config.updateConfigCache('markdown', config);
  151. req.session.xssSetting = null;
  152. req.flash('successMessage', ['Successfully updated!']);
  153. return res.redirect('/admin/markdown');
  154. });
  155. }
  156. else {
  157. req.flash('errorMessage', req.form.errors);
  158. return res.redirect('/admin/markdown');
  159. }
  160. };
  161. const stringToArray = (string) => {
  162. const array = string.split(',');
  163. return array.map(item => item.trim());
  164. };
  165. // app.get('/admin/customize' , admin.customize.index);
  166. actions.customize = {};
  167. actions.customize.index = function(req, res) {
  168. var settingForm;
  169. settingForm = Config.setupConfigFormData('crowi', req.config);
  170. const highlightJsCssSelectorOptions = {
  171. 'github': { name: '[Light] GitHub', border: false },
  172. 'github-gist': { name: '[Light] GitHub Gist', border: true },
  173. 'atom-one-light': { name: '[Light] Atom One Light', border: true },
  174. 'xcode': { name: '[Light] Xcode', border: true },
  175. 'vs': { name: '[Light] Vs', border: true },
  176. 'atom-one-dark': { name: '[Dark] Atom One Dark', border: false },
  177. 'hybrid': { name: '[Dark] Hybrid', border: false },
  178. 'monokai': { name: '[Dark] Monokai', border: false },
  179. 'tomorrow-night': { name: '[Dark] Tomorrow Night', border: false },
  180. 'vs2015': { name: '[Dark] Vs 2015', border: false },
  181. };
  182. return res.render('admin/customize', {
  183. settingForm: settingForm,
  184. highlightJsCssSelectorOptions: highlightJsCssSelectorOptions
  185. });
  186. };
  187. // app.get('/admin/notification' , admin.notification.index);
  188. actions.notification = {};
  189. actions.notification.index = async(req, res) => {
  190. const config = crowi.getConfig();
  191. const UpdatePost = crowi.model('UpdatePost');
  192. let slackSetting = Config.setupConfigFormData('notification', config);
  193. const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
  194. const hasSlackToken = Config.hasSlackToken(config);
  195. if (!Config.hasSlackIwhUrl(req.config)) {
  196. slackSetting['slack:incomingWebhookUrl'] = '';
  197. }
  198. if (req.session.slackSetting) {
  199. slackSetting = req.session.slackSetting;
  200. req.session.slackSetting = null;
  201. }
  202. const globalNotifications = await GlobalNotificationSetting.findAll();
  203. const userNotifications = await UpdatePost.findAll();
  204. return res.render('admin/notification', {
  205. userNotifications,
  206. slackSetting,
  207. hasSlackIwhUrl,
  208. hasSlackToken,
  209. globalNotifications,
  210. });
  211. };
  212. // app.post('/admin/notification/slackSetting' , admin.notification.slackauth);
  213. actions.notification.slackSetting = function(req, res) {
  214. var slackSetting = req.form.slackSetting;
  215. req.session.slackSetting = slackSetting;
  216. if (req.form.isValid) {
  217. Config.updateNamespaceByArray('notification', slackSetting, function(err, config) {
  218. Config.updateConfigCache('notification', config);
  219. req.flash('successMessage', ['Successfully Updated!']);
  220. req.session.slackSetting = null;
  221. // Re-setup
  222. crowi.setupSlack().then(function() {
  223. return res.redirect('/admin/notification');
  224. });
  225. });
  226. }
  227. else {
  228. req.flash('errorMessage', req.form.errors);
  229. return res.redirect('/admin/notification');
  230. }
  231. };
  232. // app.get('/admin/notification/slackAuth' , admin.notification.slackauth);
  233. actions.notification.slackAuth = function(req, res) {
  234. const code = req.query.code;
  235. const config = crowi.getConfig();
  236. if (!code || !Config.hasSlackConfig(req.config)) {
  237. return res.redirect('/admin/notification');
  238. }
  239. const slack = crowi.slack;
  240. slack.getOauthAccessToken(code)
  241. .then(data => {
  242. debug('oauth response', data);
  243. Config.updateNamespaceByArray('notification', {'slack:token': data.access_token}, function(err, config) {
  244. if (err) {
  245. req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
  246. }
  247. else {
  248. Config.updateConfigCache('notification', config);
  249. req.flash('successMessage', ['Successfully Connected!']);
  250. }
  251. return res.redirect('/admin/notification');
  252. });
  253. }).catch(err => {
  254. debug('oauth response ERROR', err);
  255. req.flash('errorMessage', ['Failed to fetch access_token. Please do connect again.']);
  256. return res.redirect('/admin/notification');
  257. });
  258. };
  259. // app.post('/admin/notification/slackIwhSetting' , admin.notification.slackIwhSetting);
  260. actions.notification.slackIwhSetting = function(req, res) {
  261. var slackIwhSetting = req.form.slackIwhSetting;
  262. if (req.form.isValid) {
  263. Config.updateNamespaceByArray('notification', slackIwhSetting, function(err, config) {
  264. Config.updateConfigCache('notification', config);
  265. req.flash('successMessage', ['Successfully Updated!']);
  266. // Re-setup
  267. crowi.setupSlack().then(function() {
  268. return res.redirect('/admin/notification#slack-incoming-webhooks');
  269. });
  270. });
  271. }
  272. else {
  273. req.flash('errorMessage', req.form.errors);
  274. return res.redirect('/admin/notification#slack-incoming-webhooks');
  275. }
  276. };
  277. // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack);
  278. actions.notification.disconnectFromSlack = function(req, res) {
  279. const config = crowi.getConfig();
  280. const slack = crowi.slack;
  281. Config.updateNamespaceByArray('notification', {'slack:token': ''}, function(err, config) {
  282. Config.updateConfigCache('notification', config);
  283. req.flash('successMessage', ['Successfully Disconnected!']);
  284. return res.redirect('/admin/notification');
  285. });
  286. };
  287. actions.globalNotification = {};
  288. actions.globalNotification.detail = async(req, res) => {
  289. const notificationSettingId = req.params.id;
  290. let renderVars = {};
  291. if (notificationSettingId) {
  292. try {
  293. renderVars.setting = await GlobalNotificationSetting.findOne({_id: notificationSettingId});
  294. }
  295. catch (err) {
  296. logger.error(`Error in finding a global notification setting with {_id: ${notificationSettingId}}`);
  297. }
  298. }
  299. return res.render('admin/global-notification-detail', renderVars);
  300. };
  301. actions.globalNotification.create = (req, res) => {
  302. const form = req.form.notificationGlobal;
  303. let setting;
  304. switch (form.notifyToType) {
  305. case 'mail':
  306. setting = new GlobalNotificationMailSetting(crowi);
  307. setting.toEmail = form.toEmail;
  308. break;
  309. // case 'slack':
  310. // setting = new GlobalNotificationSlackSetting(crowi);
  311. // setting.slackChannels = form.slackChannels;
  312. // break;
  313. default:
  314. logger.error('GlobalNotificationSetting Type Error: undefined type');
  315. req.flash('errorMessage', 'Error occurred in creating a new global notification setting: undefined notification type');
  316. return res.redirect('/admin/notification#global-notification');
  317. }
  318. setting.triggerPath = form.triggerPath;
  319. setting.triggerEvents = getNotificationEvents(form);
  320. setting.save();
  321. return res.redirect('/admin/notification#global-notification');
  322. };
  323. actions.globalNotification.update = async(req, res) => {
  324. const form = req.form.notificationGlobal;
  325. const setting = await GlobalNotificationSetting.findOne({_id: form.id});
  326. switch (form.notifyToType) {
  327. case 'mail':
  328. setting.toEmail = form.toEmail;
  329. break;
  330. // case 'slack':
  331. // setting.slackChannels = form.slackChannels;
  332. // break;
  333. default:
  334. logger.error('GlobalNotificationSetting Type Error: undefined type');
  335. req.flash('errorMessage', 'Error occurred in updating the global notification setting: undefined notification type');
  336. return res.redirect('/admin/notification#global-notification');
  337. }
  338. setting.triggerPath = form.triggerPath;
  339. setting.triggerEvents = getNotificationEvents(form);
  340. setting.save();
  341. return res.redirect('/admin/notification#global-notification');
  342. };
  343. actions.globalNotification.remove = async(req, res) => {
  344. const id = req.params.id;
  345. try {
  346. await GlobalNotificationSetting.findOneAndRemove({_id: id});
  347. return res.redirect('/admin/notification#global-notification');
  348. }
  349. catch (err) {
  350. req.flash('errorMessage', 'Error in deleting global notification setting');
  351. return res.redirect('/admin/notification#global-notification');
  352. }
  353. };
  354. const getNotificationEvents = (form) => {
  355. let triggerEvents = [];
  356. const triggerEventKeys = Object.keys(form).filter(key => key.match(/^triggerEvent/));
  357. triggerEventKeys.forEach(key => {
  358. if (form[key]) {
  359. triggerEvents.push(form[key]);
  360. }
  361. });
  362. return triggerEvents;
  363. };
  364. actions.search = {}
  365. actions.search.index = function(req, res) {
  366. const search = crowi.getSearcher();
  367. if (!search) {
  368. return res.redirect('/admin');
  369. }
  370. return res.render('admin/search', {});
  371. };
  372. actions.user = {};
  373. actions.user.index = async function(req, res) {
  374. const activeUsers = await User.countListByStatus(User.STATUS_ACTIVE);
  375. const Config = crowi.model('Config');
  376. const userUpperLimit = Config.userUpperLimit(crowi);
  377. const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
  378. var page = parseInt(req.query.page) || 1;
  379. const result = await User.findUsersWithPagination({page: page});
  380. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  381. return res.render('admin/users', {
  382. users: result.docs,
  383. pager: pager,
  384. activeUsers: activeUsers,
  385. userUpperLimit: userUpperLimit,
  386. isUserCountExceedsUpperLimit: isUserCountExceedsUpperLimit
  387. });
  388. };
  389. actions.user.invite = function(req, res) {
  390. var form = req.form.inviteForm;
  391. var toSendEmail = form.sendEmail || false;
  392. if (req.form.isValid) {
  393. User.createUsersByInvitation(form.emailList.split('\n'), toSendEmail, function(err, userList) {
  394. if (err) {
  395. req.flash('errorMessage', req.form.errors.join('\n'));
  396. }
  397. else {
  398. req.flash('createdUser', userList);
  399. }
  400. return res.redirect('/admin/users');
  401. });
  402. }
  403. else {
  404. req.flash('errorMessage', req.form.errors.join('\n'));
  405. return res.redirect('/admin/users');
  406. }
  407. };
  408. actions.user.makeAdmin = function(req, res) {
  409. var id = req.params.id;
  410. User.findById(id, function(err, userData) {
  411. userData.makeAdmin(function(err, userData) {
  412. if (err === null) {
  413. req.flash('successMessage', userData.name + 'さんのアカウントを管理者に設定しました。');
  414. }
  415. else {
  416. req.flash('errorMessage', '更新に失敗しました。');
  417. debug(err, userData);
  418. }
  419. return res.redirect('/admin/users');
  420. });
  421. });
  422. };
  423. actions.user.removeFromAdmin = function(req, res) {
  424. var id = req.params.id;
  425. User.findById(id, function(err, userData) {
  426. userData.removeFromAdmin(function(err, userData) {
  427. if (err === null) {
  428. req.flash('successMessage', userData.name + 'さんのアカウントを管理者から外しました。');
  429. }
  430. else {
  431. req.flash('errorMessage', '更新に失敗しました。');
  432. debug(err, userData);
  433. }
  434. return res.redirect('/admin/users');
  435. });
  436. });
  437. };
  438. actions.user.activate = async function(req, res) {
  439. // check user upper limit
  440. const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
  441. if (isUserCountExceedsUpperLimit) {
  442. req.flash('errorMessage', 'ユーザーが上限に達したため有効化できません。');
  443. return res.redirect('/admin/users');
  444. }
  445. var id = req.params.id;
  446. User.findById(id, function(err, userData) {
  447. userData.statusActivate(function(err, userData) {
  448. if (err === null) {
  449. req.flash('successMessage', userData.name + 'さんのアカウントを有効化しました');
  450. }
  451. else {
  452. req.flash('errorMessage', '更新に失敗しました。');
  453. debug(err, userData);
  454. }
  455. return res.redirect('/admin/users');
  456. });
  457. });
  458. };
  459. actions.user.suspend = function(req, res) {
  460. var id = req.params.id;
  461. User.findById(id, function(err, userData) {
  462. userData.statusSuspend(function(err, userData) {
  463. if (err === null) {
  464. req.flash('successMessage', userData.name + 'さんのアカウントを利用停止にしました');
  465. }
  466. else {
  467. req.flash('errorMessage', '更新に失敗しました。');
  468. debug(err, userData);
  469. }
  470. return res.redirect('/admin/users');
  471. });
  472. });
  473. };
  474. actions.user.remove = function(req, res) {
  475. const id = req.params.id;
  476. let username = '';
  477. return new Promise((resolve, reject) => {
  478. User.findById(id, (err, userData) => {
  479. username = userData.username;
  480. return resolve(userData);
  481. });
  482. })
  483. .then((userData) => {
  484. return new Promise((resolve, reject) => {
  485. userData.statusDelete((err, userData) => {
  486. if (err) {
  487. reject(err);
  488. }
  489. resolve(userData);
  490. });
  491. });
  492. })
  493. .then((userData) => {
  494. // remove all External Accounts
  495. return ExternalAccount.remove({user: userData}).then(() => userData);
  496. })
  497. .then((userData) => {
  498. return Page.removeByPath(`/user/${username}`).then(() => userData);
  499. })
  500. .then((userData) => {
  501. req.flash('successMessage', `${username} さんのアカウントを削除しました`);
  502. return res.redirect('/admin/users');
  503. })
  504. .catch((err) => {
  505. req.flash('errorMessage', '削除に失敗しました。');
  506. return res.redirect('/admin/users');
  507. });
  508. };
  509. // これやったときの relation の挙動未確認
  510. actions.user.removeCompletely = function(req, res) {
  511. // ユーザーの物理削除
  512. var id = req.params.id;
  513. User.removeCompletelyById(id, function(err, removed) {
  514. if (err) {
  515. debug('Error while removing user.', err, id);
  516. req.flash('errorMessage', '完全な削除に失敗しました。');
  517. }
  518. else {
  519. req.flash('successMessage', '削除しました');
  520. }
  521. return res.redirect('/admin/users');
  522. });
  523. };
  524. // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
  525. actions.user.resetPassword = function(req, res) {
  526. const id = req.body.user_id;
  527. const User = crowi.model('User');
  528. User.resetPasswordByRandomString(id)
  529. .then(function(data) {
  530. data.user = User.filterToPublicFields(data.user);
  531. return res.json(ApiResponse.success(data));
  532. }).catch(function(err) {
  533. debug('Error on reseting password', err);
  534. return res.json(ApiResponse.error('Error'));
  535. });
  536. };
  537. actions.externalAccount = {};
  538. actions.externalAccount.index = function(req, res) {
  539. const page = parseInt(req.query.page) || 1;
  540. ExternalAccount.findAllWithPagination({page})
  541. .then((result) => {
  542. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  543. return res.render('admin/external-accounts', {
  544. accounts: result.docs,
  545. pager: pager
  546. });
  547. });
  548. };
  549. actions.externalAccount.remove = async function(req, res) {
  550. const id = req.params.id;
  551. let account = null;
  552. try {
  553. account = await ExternalAccount.findByIdAndRemove(id);
  554. if (account == null) {
  555. throw new Error('削除に失敗しました。');
  556. }
  557. }
  558. catch (err) {
  559. req.flash('errorMessage', err.message);
  560. return res.redirect('/admin/users/external-accounts');
  561. }
  562. req.flash('successMessage', `外部アカウント '${account.providerType}/${account.accountId}' を削除しました`);
  563. return res.redirect('/admin/users/external-accounts');
  564. };
  565. actions.userGroup = {};
  566. actions.userGroup.index = function(req, res) {
  567. var page = parseInt(req.query.page) || 1;
  568. const isAclEnabled = !Config.isPublicWikiOnly(req.config);
  569. var renderVar = {
  570. userGroups: [],
  571. userGroupRelations: new Map(),
  572. pager: null,
  573. isAclEnabled,
  574. };
  575. UserGroup.findUserGroupsWithPagination({ page: page })
  576. .then((result) => {
  577. const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
  578. var userGroups = result.docs;
  579. renderVar.userGroups = userGroups;
  580. renderVar.pager = pager;
  581. return userGroups.map((userGroup) => {
  582. return new Promise((resolve, reject) => {
  583. UserGroupRelation.findAllRelationForUserGroup(userGroup)
  584. .then((relations) => {
  585. return resolve([userGroup, relations]);
  586. });
  587. });
  588. });
  589. })
  590. .then((allRelationsPromise) => {
  591. return Promise.all(allRelationsPromise);
  592. })
  593. .then((relations) => {
  594. renderVar.userGroupRelations = new Map(relations);
  595. debug('in findUserGroupsWithPagination findAllRelationForUserGroupResult', renderVar.userGroupRelations);
  596. return res.render('admin/user-groups', renderVar);
  597. })
  598. .catch( function(err) {
  599. debug('Error on find all relations', err);
  600. return res.json(ApiResponse.error('Error'));
  601. });
  602. };
  603. // グループ詳細
  604. actions.userGroup.detail = async function(req, res) {
  605. const userGroupId = req.params.id;
  606. const renderVar = {
  607. userGroup: null,
  608. userGroupRelations: [],
  609. notRelatedusers: [],
  610. relatedPages: [],
  611. };
  612. const userGroup = await UserGroup.findOne({ _id: userGroupId});
  613. if (userGroup == null) {
  614. logger.error('no userGroup is exists. ', userGroupId);
  615. req.flash('errorMessage', 'グループがありません');
  616. return res.redirect('/admin/user-groups');
  617. }
  618. renderVar.userGroup = userGroup;
  619. const resolves = await Promise.all([
  620. // get all user and group relations
  621. UserGroupRelation.findAllRelationForUserGroup(userGroup),
  622. // get all not related users for group
  623. UserGroupRelation.findUserByNotRelatedGroup(userGroup),
  624. // get all related pages
  625. Page.find({grant: Page.GRANT_USER_GROUP, grantedGroup: { $in: [userGroup] }}),
  626. ]);
  627. renderVar.userGroupRelations = resolves[0];
  628. renderVar.notRelatedusers = resolves[1];
  629. renderVar.relatedPages = resolves[2];
  630. return res.render('admin/user-group-detail', renderVar);
  631. };
  632. //グループの生成
  633. actions.userGroup.create = function(req, res) {
  634. const form = req.form.createGroupForm;
  635. if (req.form.isValid) {
  636. const userGroupName = crowi.xss.process(form.userGroupName);
  637. UserGroup.createGroupByName(userGroupName)
  638. .then((newUserGroup) => {
  639. req.flash('successMessage', newUserGroup.name);
  640. req.flash('createdUserGroup', newUserGroup);
  641. return res.redirect('/admin/user-groups');
  642. })
  643. .catch((err) => {
  644. debug('create userGroup error:', err);
  645. req.flash('errorMessage', '同じグループ名が既に存在します。');
  646. });
  647. }
  648. else {
  649. req.flash('errorMessage', req.form.errors.join('\n'));
  650. return res.redirect('/admin/user-groups');
  651. }
  652. };
  653. //
  654. actions.userGroup.update = function(req, res) {
  655. const userGroupId = req.params.userGroupId;
  656. const name = crowi.xss.process(req.body.name);
  657. UserGroup.findById(userGroupId)
  658. .then((userGroupData) => {
  659. if (userGroupData == null) {
  660. req.flash('errorMessage', 'グループの検索に失敗しました。');
  661. return new Promise();
  662. }
  663. else {
  664. // 名前存在チェック
  665. return UserGroup.isRegisterableName(name)
  666. .then((isRegisterableName) => {
  667. // 既に存在するグループ名に更新しようとした場合はエラー
  668. if (!isRegisterableName) {
  669. req.flash('errorMessage', 'グループ名が既に存在します。');
  670. }
  671. else {
  672. return userGroupData.updateName(name)
  673. .then(() => {
  674. req.flash('successMessage', 'グループ名を更新しました。');
  675. })
  676. .catch((err) => {
  677. req.flash('errorMessage', 'グループ名の更新に失敗しました。');
  678. });
  679. }
  680. });
  681. }
  682. })
  683. .then(() => {
  684. return res.redirect('/admin/user-group-detail/' + userGroupId);
  685. });
  686. };
  687. // app.post('/_api/admin/user-group/delete' , admin.userGroup.removeCompletely);
  688. actions.userGroup.removeCompletely = function(req, res) {
  689. const id = req.body.user_group_id;
  690. UserGroup.removeCompletelyById(id)
  691. .then(() => {
  692. req.flash('successMessage', '削除しました');
  693. return res.redirect('/admin/user-groups');
  694. })
  695. .catch((err) => {
  696. debug('Error while removing userGroup.', err, id);
  697. req.flash('errorMessage', '完全な削除に失敗しました。');
  698. return res.redirect('/admin/user-groups');
  699. });
  700. };
  701. actions.userGroupRelation = {};
  702. actions.userGroupRelation.index = function(req, res) {
  703. };
  704. actions.userGroupRelation.create = function(req, res) {
  705. const User = crowi.model('User');
  706. const UserGroup = crowi.model('UserGroup');
  707. const UserGroupRelation = crowi.model('UserGroupRelation');
  708. // req params
  709. const userName = req.body.user_name;
  710. const userGroupId = req.body.user_group_id;
  711. let user = null;
  712. let userGroup = null;
  713. Promise.all([
  714. // ユーザグループをIDで検索
  715. UserGroup.findById(userGroupId),
  716. // ユーザを名前で検索
  717. User.findUserByUsername(userName),
  718. ])
  719. .then((resolves) => {
  720. userGroup = resolves[0];
  721. user = resolves[1];
  722. // Relation を作成
  723. UserGroupRelation.createRelation(userGroup, user);
  724. })
  725. .then((result) => {
  726. return res.redirect('/admin/user-group-detail/' + userGroup.id);
  727. }).catch((err) => {
  728. debug('Error on create user-group relation', err);
  729. req.flash('errorMessage', 'Error on create user-group relation');
  730. return res.redirect('/admin/user-group-detail/' + userGroup.id);
  731. });
  732. };
  733. actions.userGroupRelation.remove = function(req, res) {
  734. const UserGroupRelation = crowi.model('UserGroupRelation');
  735. const userGroupId = req.params.id;
  736. const relationId = req.params.relationId;
  737. UserGroupRelation.removeById(relationId)
  738. .then(() =>{
  739. return res.redirect('/admin/user-group-detail/' + userGroupId);
  740. })
  741. .catch((err) => {
  742. debug('Error on remove user-group-relation', err);
  743. req.flash('errorMessage', 'グループのユーザ削除に失敗しました。');
  744. });
  745. };
  746. // Importer management
  747. actions.importer = {};
  748. actions.importer.index = function(req, res) {
  749. var settingForm;
  750. settingForm = Config.setupConfigFormData('crowi', req.config);
  751. return res.render('admin/importer', {
  752. settingForm: settingForm,
  753. });
  754. };
  755. actions.api = {};
  756. actions.api.appSetting = function(req, res) {
  757. var form = req.form.settingForm;
  758. if (req.form.isValid) {
  759. debug('form content', form);
  760. // mail setting ならここで validation
  761. if (form['mail:from']) {
  762. validateMailSetting(req, form, function(err, data) {
  763. debug('Error validate mail setting: ', err, data);
  764. if (err) {
  765. req.form.errors.push('SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。');
  766. return res.json({status: false, message: req.form.errors.join('\n')});
  767. }
  768. return saveSetting(req, res, form);
  769. });
  770. }
  771. else {
  772. return saveSetting(req, res, form);
  773. }
  774. }
  775. else {
  776. return res.json({status: false, message: req.form.errors.join('\n')});
  777. }
  778. };
  779. actions.api.asyncAppSetting = async(req, res) => {
  780. const form = req.form.settingForm;
  781. if (!req.form.isValid) {
  782. return res.json({status: false, message: req.form.errors.join('\n')});
  783. }
  784. debug('form content', form);
  785. try {
  786. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', form);
  787. return res.json({status: true});
  788. }
  789. catch (err) {
  790. logger.error(err);
  791. return res.json({status: false});
  792. }
  793. };
  794. actions.api.securitySetting = function(req, res) {
  795. const form = req.form.settingForm;
  796. const config = crowi.getConfig();
  797. const isPublicWikiOnly = Config.isPublicWikiOnly(config);
  798. if (isPublicWikiOnly) {
  799. const basicName = form['security:basicName'];
  800. const basicSecret = form['security:basicSecret'];
  801. if (basicName != '' || basicSecret != '') {
  802. req.form.errors.push('Public Wikiのため、Basic認証は利用できません。');
  803. return res.json({status: false, message: req.form.errors.join('\n')});
  804. }
  805. const guestMode = form['security:restrictGuestMode'];
  806. if ( guestMode == 'Deny' ) {
  807. req.form.errors.push('Private Wikiへの設定変更はできません。');
  808. return res.json({status: false, message: req.form.errors.join('\n')});
  809. }
  810. }
  811. if (req.form.isValid) {
  812. debug('form content', form);
  813. return saveSetting(req, res, form);
  814. }
  815. else {
  816. return res.json({status: false, message: req.form.errors.join('\n')});
  817. }
  818. };
  819. actions.api.securityPassportLdapSetting = function(req, res) {
  820. var form = req.form.settingForm;
  821. if (!req.form.isValid) {
  822. return res.json({status: false, message: req.form.errors.join('\n')});
  823. }
  824. debug('form content', form);
  825. return saveSettingAsync(form)
  826. .then(() => {
  827. const config = crowi.getConfig();
  828. // reset strategy
  829. crowi.passportService.resetLdapStrategy();
  830. // setup strategy
  831. if (Config.isEnabledPassportLdap(config)) {
  832. crowi.passportService.setupLdapStrategy(true);
  833. }
  834. return;
  835. })
  836. .then(() => {
  837. res.json({status: true});
  838. });
  839. };
  840. actions.api.securityPassportSamlSetting = async(req, res) => {
  841. const form = req.form.settingForm;
  842. validateSamlSettingForm(req.form, req.t);
  843. if (!req.form.isValid) {
  844. return res.json({status: false, message: req.form.errors.join('\n')});
  845. }
  846. debug('form content', form);
  847. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', form);
  848. // reset strategy
  849. await crowi.passportService.resetSamlStrategy();
  850. // setup strategy
  851. if (crowi.configManager.getConfig('crowi', 'security:passport-saml:isEnabled')) {
  852. try {
  853. await crowi.passportService.setupSamlStrategy(true);
  854. }
  855. catch (err) {
  856. // reset
  857. await crowi.passportService.resetSamlStrategy();
  858. return res.json({status: false, message: err.message});
  859. }
  860. }
  861. return res.json({status: true});
  862. };
  863. actions.api.securityPassportGoogleSetting = async(req, res) => {
  864. const form = req.form.settingForm;
  865. if (!req.form.isValid) {
  866. return res.json({status: false, message: req.form.errors.join('\n')});
  867. }
  868. debug('form content', form);
  869. await saveSettingAsync(form);
  870. const config = await crowi.getConfig();
  871. // reset strategy
  872. await crowi.passportService.resetGoogleStrategy();
  873. // setup strategy
  874. if (Config.isEnabledPassportGoogle(config)) {
  875. try {
  876. await crowi.passportService.setupGoogleStrategy(true);
  877. }
  878. catch (err) {
  879. // reset
  880. await crowi.passportService.resetGoogleStrategy();
  881. return res.json({status: false, message: err.message});
  882. }
  883. }
  884. return res.json({status: true});
  885. };
  886. actions.api.securityPassportGitHubSetting = async(req, res) => {
  887. const form = req.form.settingForm;
  888. if (!req.form.isValid) {
  889. return res.json({status: false, message: req.form.errors.join('\n')});
  890. }
  891. debug('form content', form);
  892. await saveSettingAsync(form);
  893. const config = await crowi.getConfig();
  894. // reset strategy
  895. await crowi.passportService.resetGitHubStrategy();
  896. // setup strategy
  897. if (Config.isEnabledPassportGitHub(config)) {
  898. try {
  899. await crowi.passportService.setupGitHubStrategy(true);
  900. }
  901. catch (err) {
  902. // reset
  903. await crowi.passportService.resetGitHubStrategy();
  904. return res.json({status: false, message: err.message});
  905. }
  906. }
  907. return res.json({status: true});
  908. };
  909. actions.api.securityPassportTwitterSetting = async(req, res) => {
  910. const form = req.form.settingForm;
  911. if (!req.form.isValid) {
  912. return res.json({status: false, message: req.form.errors.join('\n')});
  913. }
  914. debug('form content', form);
  915. await saveSettingAsync(form);
  916. const config = await crowi.getConfig();
  917. // reset strategy
  918. await crowi.passportService.resetTwitterStrategy();
  919. // setup strategy
  920. if (Config.isEnabledPassportTwitter(config)) {
  921. try {
  922. await crowi.passportService.setupTwitterStrategy(true);
  923. }
  924. catch (err) {
  925. // reset
  926. await crowi.passportService.resetTwitterStrategy();
  927. return res.json({status: false, message: err.message});
  928. }
  929. }
  930. return res.json({status: true});
  931. };
  932. actions.api.customizeSetting = function(req, res) {
  933. const form = req.form.settingForm;
  934. if (req.form.isValid) {
  935. debug('form content', form);
  936. return saveSetting(req, res, form);
  937. }
  938. else {
  939. return res.json({status: false, message: req.form.errors.join('\n')});
  940. }
  941. };
  942. actions.api.customizeSetting = function(req, res) {
  943. const form = req.form.settingForm;
  944. if (req.form.isValid) {
  945. debug('form content', form);
  946. return saveSetting(req, res, form);
  947. }
  948. else {
  949. return res.json({status: false, message: req.form.errors.join('\n')});
  950. }
  951. };
  952. // app.post('/_api/admin/notifications.add' , admin.api.notificationAdd);
  953. actions.api.notificationAdd = function(req, res) {
  954. var UpdatePost = crowi.model('UpdatePost');
  955. var pathPattern = req.body.pathPattern;
  956. var channel = req.body.channel;
  957. debug('notification.add', pathPattern, channel);
  958. UpdatePost.create(pathPattern, channel, req.user)
  959. .then(function(doc) {
  960. debug('Successfully save updatePost', doc);
  961. // fixme: うーん
  962. doc.creator = doc.creator._id.toString();
  963. return res.json(ApiResponse.success({updatePost: doc}));
  964. }).catch(function(err) {
  965. debug('Failed to save updatePost', err);
  966. return res.json(ApiResponse.error());
  967. });
  968. };
  969. // app.post('/_api/admin/notifications.remove' , admin.api.notificationRemove);
  970. actions.api.notificationRemove = function(req, res) {
  971. var UpdatePost = crowi.model('UpdatePost');
  972. var id = req.body.id;
  973. UpdatePost.remove(id)
  974. .then(function() {
  975. debug('Successfully remove updatePost');
  976. return res.json(ApiResponse.success({}));
  977. }).catch(function(err) {
  978. debug('Failed to remove updatePost', err);
  979. return res.json(ApiResponse.error());
  980. });
  981. };
  982. // app.get('/_api/admin/users.search' , admin.api.userSearch);
  983. actions.api.usersSearch = function(req, res) {
  984. const User = crowi.model('User');
  985. const email =req.query.email;
  986. User.findUsersByPartOfEmail(email, {})
  987. .then(users => {
  988. const result = {
  989. data: users
  990. };
  991. return res.json(ApiResponse.success(result));
  992. }).catch(err => {
  993. return res.json(ApiResponse.error());
  994. });
  995. };
  996. actions.api.toggleIsEnabledForGlobalNotification = async(req, res) => {
  997. const id = req.query.id;
  998. const isEnabled = (req.query.isEnabled == 'true');
  999. try {
  1000. if (isEnabled) {
  1001. await GlobalNotificationSetting.enable(id);
  1002. }
  1003. else {
  1004. await GlobalNotificationSetting.disable(id);
  1005. }
  1006. return res.json(ApiResponse.success());
  1007. }
  1008. catch (err) {
  1009. return res.json(ApiResponse.error());
  1010. }
  1011. };
  1012. /**
  1013. * save esa settings, update config cache, and response json
  1014. *
  1015. * @param {*} req
  1016. * @param {*} res
  1017. */
  1018. actions.api.importerSettingEsa = async(req, res) => {
  1019. const form = req.form.settingForm;
  1020. if (!req.form.isValid) {
  1021. return res.json({status: false, message: req.form.errors.join('\n')});
  1022. }
  1023. await saveSetting(req, res, form);
  1024. await importer.initializeEsaClient();
  1025. };
  1026. /**
  1027. * save qiita settings, update config cache, and response json
  1028. *
  1029. * @param {*} req
  1030. * @param {*} res
  1031. */
  1032. actions.api.importerSettingQiita = async(req, res) => {
  1033. const form = req.form.settingForm;
  1034. if (!req.form.isValid) {
  1035. return res.json({status: false, message: req.form.errors.join('\n')});
  1036. }
  1037. await saveSetting(req, res, form);
  1038. await importer.initializeQiitaClient();
  1039. };
  1040. /**
  1041. * Import all posts from esa
  1042. *
  1043. * @param {*} req
  1044. * @param {*} res
  1045. */
  1046. actions.api.importDataFromEsa = async(req, res) => {
  1047. const user = req.user;
  1048. let errors;
  1049. try {
  1050. errors = await importer.importDataFromEsa(user);
  1051. }
  1052. catch (err) {
  1053. errors = [err];
  1054. }
  1055. if (errors.length > 0) {
  1056. return res.json({ status: false, message: `<br> - ${errors.join('<br> - ')}` });
  1057. }
  1058. return res.json({ status: true });
  1059. };
  1060. /**
  1061. * Import all posts from qiita
  1062. *
  1063. * @param {*} req
  1064. * @param {*} res
  1065. */
  1066. actions.api.importDataFromQiita = async(req, res) => {
  1067. const user = req.user;
  1068. let errors;
  1069. try {
  1070. errors = await importer.importDataFromQiita(user);
  1071. }
  1072. catch (err) {
  1073. errors = [err];
  1074. }
  1075. if (errors.length > 0) {
  1076. return res.json({ status: false, message: `<br> - ${errors.join('<br> - ')}` });
  1077. }
  1078. return res.json({ status: true });
  1079. };
  1080. /**
  1081. * Test connection to esa and response result with json
  1082. *
  1083. * @param {*} req
  1084. * @param {*} res
  1085. */
  1086. actions.api.testEsaAPI = async(req, res) => {
  1087. try {
  1088. await importer.testConnectionToEsa();
  1089. return res.json({ status: true });
  1090. }
  1091. catch (err) {
  1092. return res.json({ status: false, message: `${err}` });
  1093. }
  1094. };
  1095. /**
  1096. * Test connection to qiita and response result with json
  1097. *
  1098. * @param {*} req
  1099. * @param {*} res
  1100. */
  1101. actions.api.testQiitaAPI = async(req, res) => {
  1102. try {
  1103. await importer.testConnectionToQiita();
  1104. return res.json({ status: true });
  1105. }
  1106. catch (err) {
  1107. return res.json({ status: false, message: `${err}` });
  1108. }
  1109. };
  1110. actions.api.searchBuildIndex = async function(req, res) {
  1111. const search = crowi.getSearcher();
  1112. if (!search) {
  1113. return res.json(ApiResponse.error('ElasticSearch Integration is not set up.'));
  1114. }
  1115. // first, delete index
  1116. try {
  1117. await search.deleteIndex();
  1118. }
  1119. catch (err) {
  1120. logger.warn('Delete index Error, but if it is initialize, its ok.', err);
  1121. }
  1122. // second, create index
  1123. try {
  1124. await search.buildIndex();
  1125. }
  1126. catch (err) {
  1127. logger.error('Error', err);
  1128. return res.json(ApiResponse.error(err));
  1129. }
  1130. searchEvent.on('addPageProgress', (total, current, skip) => {
  1131. crowi.getIo().sockets.emit('admin:addPageProgress', { total, current, skip });
  1132. });
  1133. searchEvent.on('finishAddPage', (total, current, skip) => {
  1134. crowi.getIo().sockets.emit('admin:finishAddPage', { total, current, skip });
  1135. });
  1136. // add all page
  1137. search
  1138. .addAllPages()
  1139. .then(() => {
  1140. debug('Data is successfully indexed. ------------------ ✧✧');
  1141. })
  1142. .catch(err => {
  1143. logger.error('Error', err);
  1144. });
  1145. return res.json(ApiResponse.success());
  1146. };
  1147. /**
  1148. * save settings, update config cache, and response json
  1149. *
  1150. * @param {any} req
  1151. * @param {any} res
  1152. * @param {any} form
  1153. */
  1154. function saveSetting(req, res, form) {
  1155. Config.updateNamespaceByArray('crowi', form, function(err, config) {
  1156. Config.updateConfigCache('crowi', config);
  1157. return res.json({status: true});
  1158. });
  1159. }
  1160. /**
  1161. * save settings, update config cache ONLY. (this method don't response json)
  1162. *
  1163. * @param {any} form
  1164. * @returns
  1165. */
  1166. function saveSettingAsync(form) {
  1167. return new Promise((resolve, reject) => {
  1168. Config.updateNamespaceByArray('crowi', form, (err, config) => {
  1169. if (err) {
  1170. return reject(err);
  1171. }
  1172. Config.updateConfigCache('crowi', config);
  1173. return resolve();
  1174. });
  1175. });
  1176. }
  1177. function validateMailSetting(req, form, callback) {
  1178. const mailer = crowi.mailer;
  1179. const option = {
  1180. host: form['mail:smtpHost'],
  1181. port: form['mail:smtpPort'],
  1182. };
  1183. if (form['mail:smtpUser'] && form['mail:smtpPassword']) {
  1184. option.auth = {
  1185. user: form['mail:smtpUser'],
  1186. pass: form['mail:smtpPassword'],
  1187. };
  1188. }
  1189. if (option.port === 465) {
  1190. option.secure = true;
  1191. }
  1192. const smtpClient = mailer.createSMTPClient(option);
  1193. debug('mailer setup for validate SMTP setting', smtpClient);
  1194. smtpClient.sendMail({
  1195. from: form['mail:from'],
  1196. to: req.user.email,
  1197. subject: 'Wiki管理設定のアップデートによるメール通知',
  1198. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。'
  1199. }, callback);
  1200. }
  1201. /**
  1202. * validate setting form values for SAML
  1203. *
  1204. * This validation checks, for the value of each mandatory items,
  1205. * whether it from the environment variables is empty and form value to update it is empty.
  1206. */
  1207. function validateSamlSettingForm(form, t) {
  1208. for (const key of crowi.passportService.mandatoryConfigKeysForSaml) {
  1209. const formValue = form.settingForm[key];
  1210. if (crowi.configManager.getConfigFromEnvVars('crowi', key) === null && formValue === '') {
  1211. const formItemName = t(`security_setting.form_item_name.${key}`);
  1212. form.errors.push(t('form_validation.required', formItemName));
  1213. }
  1214. }
  1215. }
  1216. return actions;
  1217. };