From a7267df404b0fbc4bcb5f5b913feeec66cb91375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 28 May 2018 11:29:37 -0400 Subject: [PATCH] closes #6464 --- install/data/defaults.json | 1 - install/package.json | 6 +- .../language/en-GB/admin/settings/guest.json | 5 +- public/src/app.js | 2 +- public/src/modules/helpers.js | 5 - src/controllers/api.js | 2 - src/controllers/search.js | 113 +++++++++--------- src/controllers/tags.js | 17 ++- src/controllers/users.js | 14 ++- src/install.js | 3 +- src/privileges/global.js | 9 ++ src/socket.io/topics/tags.js | 19 ++- src/socket.io/user/search.js | 12 +- src/upgrades/1.9.4/search_privileges.js | 30 +++++ src/views/admin/settings/guest.tpl | 22 ---- test/categories.js | 6 + test/controllers.js | 19 ++- test/mocks/databasemock.js | 2 +- test/search.js | 28 +++-- test/user.js | 4 +- 20 files changed, 194 insertions(+), 125 deletions(-) create mode 100644 src/upgrades/1.9.4/search_privileges.js diff --git a/install/data/defaults.json b/install/data/defaults.json index 93850b9c01..1a901fb3c9 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -11,7 +11,6 @@ "maximumTagsPerTopic": 5, "minimumTagLength": 3, "maximumTagLength": 15, - "allowGuestSearching": 0, "allowTopicsThumbnail": 0, "registrationType": "normal", "allowLocalLogin": 1, diff --git a/install/package.json b/install/package.json index 863c89520c..7deca8851a 100644 --- a/install/package.json +++ b/install/package.json @@ -75,9 +75,9 @@ "nodebb-plugin-spam-be-gone": "0.5.3", "nodebb-rewards-essentials": "0.0.11", "nodebb-theme-lavender": "5.0.4", - "nodebb-theme-persona": "9.0.6", - "nodebb-theme-slick": "1.2.1", - "nodebb-theme-vanilla": "10.0.6", + "nodebb-theme-persona": "9.0.7", + "nodebb-theme-slick": "1.2.2", + "nodebb-theme-vanilla": "10.0.7", "nodebb-widget-essentials": "4.0.4", "nodemailer": "4.6.4", "passport": "^0.4.0", diff --git a/public/language/en-GB/admin/settings/guest.json b/public/language/en-GB/admin/settings/guest.json index 6b2ac2c8b2..a8b9d458f0 100644 --- a/public/language/en-GB/admin/settings/guest.json +++ b/public/language/en-GB/admin/settings/guest.json @@ -1,8 +1,5 @@ { "handles": "Guest Handles", "handles.enabled": "Allow guest handles", - "handles.enabled-help": "This option exposes a new field that allows guests to pick a name to associate with each post they make. If disabled, they will simply be called \"Guest\"", - "privileges": "Guest Privileges", - "privileges.can-search": "Allow guests to search without logging in", - "privileges.can-search-users": "Allow guests to search users without logging in" + "handles.enabled-help": "This option exposes a new field that allows guests to pick a name to associate with each post they make. If disabled, they will simply be called \"Guest\"" } \ No newline at end of file diff --git a/public/src/app.js b/public/src/app.js index 080063cc89..84ad8a0fb6 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -521,7 +521,7 @@ app.cacheBuster = null; } searchButton.on('click', function (e) { - if (!config.loggedIn && !config.allowGuestSearching) { + if (!config.loggedIn && !app.user.privileges['search:content']) { app.alert({ message: '[[error:search-requires-login]]', timeout: 3000, diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 0a3eea6b26..2171eedfba 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -20,7 +20,6 @@ generateCategoryBackground: generateCategoryBackground, generateChildrenCategories: generateChildrenCategories, generateTopicClass: generateTopicClass, - displayUserSearch: displayUserSearch, membershipBtn: membershipBtn, spawnPrivilegeStates: spawnPrivilegeStates, localeToHTML: localeToHTML, @@ -159,10 +158,6 @@ return style.join(' '); } - function displayUserSearch(data, allowGuestUserSearching) { - return data.loggedIn || allowGuestUserSearching === 'true'; - } - // Groups helpers function membershipBtn(groupObj) { if (groupObj.isMember && groupObj.name !== 'administrators') { diff --git a/src/controllers/api.js b/src/controllers/api.js index 5e3cb27942..c01b5aa828 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -32,8 +32,6 @@ apiController.loadConfig = function (req, callback) { config.minimumTagLength = meta.config.minimumTagLength || 3; config.maximumTagLength = meta.config.maximumTagLength || 15; config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1; - config.allowGuestSearching = parseInt(meta.config.allowGuestSearching, 10) === 1; - config.allowGuestUserSearching = parseInt(meta.config.allowGuestUserSearching, 10) === 1; config.allowGuestHandles = parseInt(meta.config.allowGuestHandles, 10) === 1; config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1; config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1; diff --git a/src/controllers/search.js b/src/controllers/search.js index 4032357ffd..979a71ee19 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -9,6 +9,7 @@ var plugins = require('../plugins'); var search = require('../search'); var categories = require('../categories'); var pagination = require('../pagination'); +var privileges = require('../privileges'); var helpers = require('./helpers'); var searchController = module.exports; @@ -17,64 +18,68 @@ searchController.search = function (req, res, next) { if (!plugins.hasListeners('filter:search.query')) { return next(); } - - if (!req.loggedIn && parseInt(meta.config.allowGuestSearching, 10) !== 1) { - return helpers.notAllowed(req, res); - } - var page = Math.max(1, parseInt(req.query.page, 10)) || 1; - if (req.query.categories && !Array.isArray(req.query.categories)) { - req.query.categories = [req.query.categories]; - } - var data = { - query: req.query.term, - searchIn: req.query.in || 'posts', - matchWords: req.query.matchWords || 'all', - postedBy: req.query.by, - categories: req.query.categories, - searchChildren: req.query.searchChildren, - hasTags: req.query.hasTags, - replies: req.query.replies, - repliesFilter: req.query.repliesFilter, - timeRange: req.query.timeRange, - timeFilter: req.query.timeFilter, - sortBy: req.query.sortBy || meta.config.searchDefaultSortBy || '', - sortDirection: req.query.sortDirection, - page: page, - uid: req.uid, - qs: req.query, - }; + async.waterfall([ + function (next) { + privileges.global.can('search:content', req.uid, next); + }, + function (allowed, next) { + if (!allowed) { + return helpers.notAllowed(req, res); + } + + if (req.query.categories && !Array.isArray(req.query.categories)) { + req.query.categories = [req.query.categories]; + } - async.parallel({ - categories: async.apply(categories.buildForSelect, req.uid, 'read'), - search: async.apply(search.search, data), - }, function (err, results) { - if (err) { - return next(err); - } + var data = { + query: req.query.term, + searchIn: req.query.in || 'posts', + matchWords: req.query.matchWords || 'all', + postedBy: req.query.by, + categories: req.query.categories, + searchChildren: req.query.searchChildren, + hasTags: req.query.hasTags, + replies: req.query.replies, + repliesFilter: req.query.repliesFilter, + timeRange: req.query.timeRange, + timeFilter: req.query.timeFilter, + sortBy: req.query.sortBy || meta.config.searchDefaultSortBy || '', + sortDirection: req.query.sortDirection, + page: page, + uid: req.uid, + qs: req.query, + }; - results.categories = results.categories.filter(function (category) { - return category && !category.link; - }); + async.parallel({ + categories: async.apply(categories.buildForSelect, req.uid, 'read'), + search: async.apply(search.search, data), + }, next); + }, + function (results) { + results.categories = results.categories.filter(function (category) { + return category && !category.link; + }); - var categoriesData = [ - { value: 'all', text: '[[unread:all_categories]]' }, - { value: 'watched', text: '[[category:watched-categories]]' }, - ].concat(results.categories); + var categoriesData = [ + { value: 'all', text: '[[unread:all_categories]]' }, + { value: 'watched', text: '[[category:watched-categories]]' }, + ].concat(results.categories); - var searchData = results.search; - searchData.categories = categoriesData; - searchData.categoriesCount = Math.max(10, Math.min(20, categoriesData.length)); - searchData.pagination = pagination.create(page, searchData.pageCount, req.query); - searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts'; - searchData.showAsTopics = req.query.showAs === 'topics'; - searchData.title = '[[global:header.search]]'; - searchData.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[global:search]]' }]); - searchData.expandSearch = !req.query.term; - searchData.searchDefaultSortBy = meta.config.searchDefaultSortBy || ''; - searchData.search_query = validator.escape(String(req.query.term || '')); - searchData.term = req.query.term; - res.render('search', searchData); - }); + var searchData = results.search; + searchData.categories = categoriesData; + searchData.categoriesCount = Math.max(10, Math.min(20, categoriesData.length)); + searchData.pagination = pagination.create(page, searchData.pageCount, req.query); + searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts'; + searchData.showAsTopics = req.query.showAs === 'topics'; + searchData.title = '[[global:header.search]]'; + searchData.breadcrumbs = helpers.buildBreadcrumbs([{ text: '[[global:search]]' }]); + searchData.expandSearch = !req.query.term; + searchData.searchDefaultSortBy = meta.config.searchDefaultSortBy || ''; + searchData.search_query = validator.escape(String(req.query.term || '')); + searchData.term = req.query.term; + res.render('search', searchData); + }, + ], next); }; diff --git a/src/controllers/tags.js b/src/controllers/tags.js index f672bd9bfe..a35c00a72e 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -5,6 +5,7 @@ var validator = require('validator'); var user = require('../user'); var topics = require('../topics'); +var privileges = require('../privileges'); var pagination = require('../pagination'); var helpers = require('./helpers'); @@ -71,12 +72,20 @@ tagsController.getTag = function (req, res, next) { tagsController.getTags = function (req, res, next) { async.waterfall([ function (next) { - topics.getTags(0, 99, next); + async.parallel({ + canSearch: function (next) { + privileges.global.can('search:tags', req.uid, next); + }, + tags: function (next) { + topics.getTags(0, 99, next); + }, + }, next); }, - function (tags) { - tags = tags.filter(Boolean); + function (results) { + results.tags = results.tags.filter(Boolean); var data = { - tags: tags, + tags: results.tags, + displayTagSearch: results.canSearch, nextStart: 100, breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[tags:tags]]' }]), title: '[[pages:tags]]', diff --git a/src/controllers/users.js b/src/controllers/users.js index cf3707d873..5c441be72e 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -4,8 +4,9 @@ var async = require('async'); var user = require('../user'); var meta = require('../meta'); -var pagination = require('../pagination'); var db = require('../database'); +var pagination = require('../pagination'); +var privileges = require('../privileges'); var helpers = require('./helpers'); var usersController = module.exports; @@ -33,6 +34,12 @@ usersController.index = function (req, res, next) { usersController.search = function (req, res, next) { async.waterfall([ function (next) { + privileges.global.can('search:users', req.uid, next); + }, + function (allowed, next) { + if (!allowed) { + return next(new Error('[[error:no-privileges]]')); + } async.parallel({ search: function (next) { user.search({ @@ -56,6 +63,7 @@ usersController.search = function (req, res, next) { results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod; results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query); results.search['section_' + section] = true; + results.displayUserSearch = true; render(req, res, results.search, next); }, ], next); @@ -171,6 +179,9 @@ usersController.getUsers = function (set, uid, query, callback) { isAdminOrGlobalMod: function (next) { user.isAdminOrGlobalMod(uid, next); }, + canSearch: function (next) { + privileges.global.can('search:users', uid, next); + }, usersData: function (next) { usersController.getUsersAndCount(set, uid, start, stop, next); }, @@ -185,6 +196,7 @@ usersController.getUsers = function (set, uid, query, callback) { title: setToData[set].title || '[[pages:users/latest]]', breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs), isAdminOrGlobalMod: results.isAdminOrGlobalMod, + displayUserSearch: results.canSearch, }; userData['section_' + (query.section || 'joindate')] = true; next(null, userData); diff --git a/src/install.js b/src/install.js index 6365e561cf..25cb213488 100644 --- a/src/install.js +++ b/src/install.js @@ -380,7 +380,8 @@ function createGlobalModeratorsGroup(next) { function giveGlobalPrivileges(next) { var privileges = require('./privileges'); - privileges.global.give(['chat', 'upload:post:image', 'signature'], 'registered-users', next); + var defaultPrivileges = ['chat', 'upload:post:image', 'signature', 'search:content', 'search:users', 'search:tags']; + privileges.global.give(defaultPrivileges, 'registered-users', next); } function createCategories(next) { diff --git a/src/privileges/global.js b/src/privileges/global.js index 49ee66632c..cabfd69d59 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -18,6 +18,9 @@ module.exports = function (privileges) { { name: 'Upload Files' }, { name: 'Signature' }, { name: 'Ban' }, + { name: 'Search Content' }, + { name: 'Search Users' }, + { name: 'Search Tags' }, ]; privileges.global.userPrivilegeList = [ @@ -26,6 +29,9 @@ module.exports = function (privileges) { 'upload:post:file', 'signature', 'ban', + 'search:content', + 'search:users', + 'search:tags', ]; privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) { @@ -81,6 +87,9 @@ module.exports = function (privileges) { chat: privData.chat || isAdminOrMod, 'upload:post:image': privData['upload:post:image'] || isAdminOrMod, 'upload:post:file': privData['upload:post:file'] || isAdminOrMod, + 'search:content': privData['search:content'] || isAdminOrMod, + 'search:users': privData['search:users'] || isAdminOrMod, + 'search:tags': privData['search:tags'] || isAdminOrMod, }, next); }, ], callback); diff --git a/src/socket.io/topics/tags.js b/src/socket.io/topics/tags.js index 2c50999b70..8b08c9dfda 100644 --- a/src/socket.io/topics/tags.js +++ b/src/socket.io/topics/tags.js @@ -3,6 +3,7 @@ var async = require('async'); var db = require('../../database'); var topics = require('../../topics'); +var privileges = require('../../privileges'); var utils = require('../../utils'); module.exports = function (SocketTopics) { @@ -25,13 +26,27 @@ module.exports = function (SocketTopics) { }; SocketTopics.searchTags = function (socket, data, callback) { - topics.searchTags(data, callback); + searchTags(socket.uid, topics.searchTags, data, callback); }; SocketTopics.searchAndLoadTags = function (socket, data, callback) { - topics.searchAndLoadTags(data, callback); + searchTags(socket.uid, topics.searchAndLoadTags, data, callback); }; + function searchTags(uid, method, data, callback) { + async.waterfall([ + function (next) { + privileges.global.can('search:tags', uid, next); + }, + function (allowed, next) { + if (!allowed) { + return next(new Error('[[error:no-privileges]]')); + } + method(data, next); + }, + ], callback); + } + SocketTopics.loadMoreTags = function (socket, data, callback) { if (!data || !utils.isNumber(data.after)) { return callback(new Error('[[error:invalid-data]]')); diff --git a/src/socket.io/user/search.js b/src/socket.io/user/search.js index 87463e3ed1..5d1b52b068 100644 --- a/src/socket.io/user/search.js +++ b/src/socket.io/user/search.js @@ -3,8 +3,8 @@ var async = require('async'); var user = require('../../user'); -var meta = require('../../meta'); var pagination = require('../../pagination'); +var privileges = require('../../privileges'); module.exports = function (SocketUser) { SocketUser.search = function (socket, data, callback) { @@ -12,12 +12,14 @@ module.exports = function (SocketUser) { return callback(new Error('[[error:invalid-data]]')); } - if (!socket.uid && parseInt(meta.config.allowGuestUserSearching, 10) !== 1) { - return callback(new Error('[[error:not-logged-in]]')); - } - async.waterfall([ function (next) { + privileges.global.can('search:users', socket.uid, next); + }, + function (allowed, next) { + if (!allowed) { + return next(new Error('[[error:no-privileges]]')); + } user.search({ query: data.query, page: data.page, diff --git a/src/upgrades/1.9.4/search_privileges.js b/src/upgrades/1.9.4/search_privileges.js new file mode 100644 index 0000000000..0259da62d2 --- /dev/null +++ b/src/upgrades/1.9.4/search_privileges.js @@ -0,0 +1,30 @@ +'use strict'; + +var async = require('async'); + +module.exports = { + name: 'Give global search privileges', + timestamp: Date.UTC(2018, 4, 28), + method: function (callback) { + var meta = require('../../meta'); + var privileges = require('../../privileges'); + var allowGuestSearching = parseInt(meta.config.allowGuestSearching, 10) === 1; + var allowGuestUserSearching = parseInt(meta.config.allowGuestUserSearching, 10) === 1; + async.waterfall([ + function (next) { + privileges.global.give(['search:content', 'search:users', 'search:tags'], 'registered-users', next); + }, + function (next) { + var guestPrivs = []; + if (allowGuestSearching) { + guestPrivs.push('search:content'); + } + if (allowGuestUserSearching) { + guestPrivs.push('search:users'); + } + guestPrivs.push('search:tags'); + privileges.global.give(guestPrivs, 'guests', next); + }, + ], callback); + }, +}; diff --git a/src/views/admin/settings/guest.tpl b/src/views/admin/settings/guest.tpl index eba32a4329..e6a9672093 100644 --- a/src/views/admin/settings/guest.tpl +++ b/src/views/admin/settings/guest.tpl @@ -17,26 +17,4 @@ - -
-
[[admin/settings/guest:privileges]]
-
-
-
- -
- -
- -
-
-
-
- \ No newline at end of file diff --git a/test/categories.js b/test/categories.js index ba6bf248e9..9e570d43b2 100644 --- a/test/categories.js +++ b/test/categories.js @@ -667,6 +667,9 @@ describe('Categories', function () { assert.deepEqual(data, { ban: false, chat: false, + 'search:content': false, + 'search:users': false, + 'search:tags': false, 'upload:post:image': false, 'upload:post:file': false, signature: false, @@ -705,6 +708,9 @@ describe('Categories', function () { assert.deepEqual(data, { 'groups:ban': false, 'groups:chat': true, + 'groups:search:content': true, + 'groups:search:users': true, + 'groups:search:tags': true, 'groups:upload:post:image': true, 'groups:upload:post:file': false, 'groups:signature': true, diff --git a/test/controllers.js b/test/controllers.js index 7f0e5fdb29..f23c5a2b40 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -630,15 +630,28 @@ describe('Controllers', function () { }); }); - it('should load users search page', function (done) { - request(nconf.get('url') + '/users?term=bar§ion=sort-posts', function (err, res, body) { + it('should error if guests do not have search privilege', function (done) { + request(nconf.get('url') + '/api/users?term=bar§ion=sort-posts', { json: true }, function (err, res, body) { assert.ifError(err); - assert.equal(res.statusCode, 200); + assert.equal(res.statusCode, 500); assert(body); + assert.equal(body.error, '[[error:no-privileges]]'); done(); }); }); + it('should load users search page', function (done) { + privileges.global.give(['search:users'], 'guests', function (err) { + assert.ifError(err); + request(nconf.get('url') + '/users?term=bar§ion=sort-posts', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + privileges.global.rescind(['search:users'], 'guests', done); + }); + }); + }); + it('should load groups page', function (done) { request(nconf.get('url') + '/groups', function (err, res, body) { assert.ifError(err); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 6d3f858998..0aaab1647e 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -205,7 +205,7 @@ function setupDefaultConfigs(meta, next) { function giveDefaultGlobalPrivileges(next) { var privileges = require('../../src/privileges'); - privileges.global.give(['chat', 'upload:post:image', 'signature'], 'registered-users', next); + privileges.global.give(['chat', 'upload:post:image', 'signature', 'search:content', 'search:users', 'search:tags'], 'registered-users', next); } function enableDefaultPlugins(callback) { diff --git a/test/search.js b/test/search.js index 9610b3b0c2..b4c5150a7d 100644 --- a/test/search.js +++ b/test/search.js @@ -11,6 +11,7 @@ var topics = require('../src/topics'); var categories = require('../src/categories'); var user = require('../src/user'); var search = require('../src/search'); +var privileges = require('../src/privileges'); describe('Search', function () { var phoebeUid; @@ -96,21 +97,22 @@ describe('Search', function () { it('should search term in titles and posts', function (done) { var meta = require('../src/meta'); - meta.config.allowGuestSearching = 1; var qs = '/api/search?term=cucumber&in=titlesposts&categories[]=' + cid1 + '&by=phoebe&replies=1&repliesFilter=atleast&sortBy=timestamp&sortDirection=desc&showAs=posts'; - - request({ - url: nconf.get('url') + qs, - json: true, - }, function (err, response, body) { + privileges.global.give(['search:content'], 'guests', function (err) { assert.ifError(err); - assert(body); - assert.equal(body.matchCount, 1); - assert.equal(body.posts.length, 1); - assert.equal(body.posts[0].pid, post1Data.pid); - assert.equal(body.posts[0].uid, phoebeUid); - - done(); + request({ + url: nconf.get('url') + qs, + json: true, + }, function (err, response, body) { + assert.ifError(err); + assert(body); + assert.equal(body.matchCount, 1); + assert.equal(body.posts.length, 1); + assert.equal(body.posts[0].pid, post1Data.pid); + assert.equal(body.posts[0].uid, phoebeUid); + + privileges.global.rescind(['search:content'], 'guests', done); + }); }); }); diff --git a/test/user.js b/test/user.js index 021007c315..116fe2811c 100644 --- a/test/user.js +++ b/test/user.js @@ -293,10 +293,8 @@ describe('User', function () { }); it('should error for guest', function (done) { - meta.config.allowGuestUserSearching = 0; socketUser.search({ uid: 0 }, { query: 'john' }, function (err) { - assert.equal(err.message, '[[error:not-logged-in]]'); - meta.config.allowGuestUserSearching = 1; + assert.equal(err.message, '[[error:no-privileges]]'); done(); }); });