Skip to content

Commit de1276d

Browse files
authored
Merge branch 'master' into fix-install-scripts
2 parents 083ebcb + a9a75d0 commit de1276d

File tree

31 files changed

+382
-122
lines changed

31 files changed

+382
-122
lines changed

CHANGELOG.md

+59
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,62 @@
1+
## Version 22.09.16
2+
Fixes:
3+
- [dashboard] fixed incorrectly changing widgets with number visualisation
4+
- [core] Fix decoding of special characters in ui
5+
- [core] Fix for vulnerable password generation
6+
- [core] Sanitize file names for localisation and themes
7+
- [hooks] Fix calling of localhost
8+
- [data-manager] fixed bug in category change for events
9+
- [install] run wget without sudo during installation
10+
- [populator] Fix for empty users created for ab-testing
11+
- [settings] Fix for API settings missing from app level configuration
12+
13+
Enterprise fixes:
14+
- [data-manager] Fix bug in changing visibility for event
15+
- [cohorts] Fixed element sizes of cohort steps, inside cohort creation form
16+
- [ab-testing] Change python3 to python3.8 for CentOS 8
17+
- [ab-testing] Set default timezone for models installation scripts
18+
- [drill] Send segmentation request as POST
19+
20+
## Version 22.09.15
21+
Fixes:
22+
- [compliance-hub] use 'change' instead of 'after' for filter
23+
- [core] app user export to database (not using filesystem anymore) !!!changes export format!!!
24+
- [core] do not fetch masking config if masking is not enabled
25+
- [core] fixed parsing of special characters in event keys
26+
- [core] only use custom period when set explicitly in model file
27+
- [core] set activePeriod as current day in periodObject if single day selected
28+
- [dashboards] fixed bug with not fully loaded graphs for events and crashes for some periods
29+
- [data-manager] fixed localization for data masking toggle
30+
- [dbviewer] correct read access check fixed
31+
- [dbviewer] fixed server error on invalid queries
32+
- [events] fixed display bug in the all events view for events with ampersand in its name enterprise
33+
- [install] do not overwrite supervisord.conf in upgrades
34+
- [install] online and offline setups for CentOS/RHEL 7
35+
- [networking] support for ipv6
36+
- [period] end date was set as 00:00Am in custom period selections
37+
- [populator] added UI check for maximum time input that prevents non-number inputs
38+
- [populator] populating with template create SDK requests with template document properties
39+
- [push] fixed wrong error deserialization
40+
- [security] deepExtend manual object copy replaced with lodash merge
41+
- [security] jquery validation xss vulnerability fix
42+
- [UI] graph notes back link is fixed
43+
44+
Enterprise fixes:
45+
- [ab-testing] Fixes for setup.
46+
- [active-directory] Remove tlsKey for active directory client
47+
- [cohorts] Fixes for displaying special characters
48+
- [data-manager] Ability to mask device id
49+
- [data-manager] [users] Fixes for & in events name
50+
- [drill] Added index on eventTimeline collection for field app to have faster deletion on app delete/clear.
51+
- [drill] Fixed bug in timeline on single event deletion.
52+
- [drill] Make sure only preset values are used in meta regeneration and no new values are added.
53+
- [drill] Meta cleanup endpoint and function in drill. Clears out wrongly saved infromation in meta about user properties.
54+
- [retention] Fixes for showing cohort names in retention view.
55+
- [retention] Retention label set according to selected result type.
56+
- [revenue] Null check for revenue widgets
57+
- [users] Fixes for displaying special characters
58+
- [users] sidebar properties value change after page has loaded
59+
160
## Version 22.09.14
261
Fixes:
362
- [core] Always use random initialization vector if not provided for encryption

Dockerfile-core

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM phusion/baseimage:bionic-1.0.0
1+
FROM phusion/baseimage:focal-1.2.0
22

33
ARG COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,enterpriseinfo,logger,systemlogs,populator,reports,crashes,push,star-rating,slipping-away-users,compare,server-stats,dbviewer,assistant,times-of-day,compliance-hub,alerts,onboarding,consolidate,remote-config,hooks,dashboards
44
# Enterprise Edition:

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
<p align="right">
55

6-
[![Build Status](https://api.travis-ci.org/Countly/countly-server.png?branch=master)](https://travis-ci.org/Countly/countly-server) [![Install Countly on DigitalOcean](https://count.ly/github/install-on-digital-ocean.svg)](http://do.count.ly)
6+
![CI](https://github.com/countly/countly-server/actions/workflows/main.yml/badge.svg)
7+
![CodeQL Analysis](https://github.com/countly/countly-server/actions/workflows/codeql-analysis.yml/badge.svg)
8+
79

810
</p>
911

api/lib/countly.common.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ countlyCommon.extractChartData = function(db, clearFunction, chartData, dataProp
699699
* @param {object} data - countly metric model data
700700
* @param {object} props - object where key is output property name and value could be string as key from data object or function to create new value based on existing ones
701701
* @param {function} clearObject - function to prefill all expected properties as u, t, n, etc with 0, so you would not have null in the result which won't work when drawing graphs
702+
* @param {object} periodObject - period object override
702703
* @returns {object} object with sparkleline data for each property
703704
* @example
704705
* var sparkLines = countlyCommon.getSparklineData(countlySession.getDb(), {
@@ -723,8 +724,8 @@ countlyCommon.extractChartData = function(db, clearFunction, chartData, dataProp
723724
* "avg-events":"1.6222222222222222,1.5555555555555556,1.6,1.6363636363636365,1.6486486486486487,1,1,1,1,1,1.8333333333333333,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1.4137931034482758,1,1,1,1"
724725
* }
725726
*/
726-
countlyCommon.getSparklineData = function(data, props, clearObject) {
727-
var _periodObj = countlyCommon.periodObj;
727+
countlyCommon.getSparklineData = function(data, props, clearObject, periodObject) {
728+
var _periodObj = periodObject || countlyCommon.periodObj;
728729
var sparkLines = {};
729730
for (let p in props) {
730731
sparkLines[p] = [];
@@ -1530,6 +1531,7 @@ countlyCommon.timeString = function(timespent) {
15301531
* @param {array} unique - array of all properties that are unique from properties array. We need to apply estimation to them
15311532
* @param {object} totalUserOverrideObj - using unique property as key and total_users estimation property as value for all unique metrics that we want to have total user estimation overridden
15321533
* @param {object} prevTotalUserOverrideObj - using unique property as key and total_users estimation property as value for all unique metrics that we want to have total user estimation overridden for previous period
1534+
* @param {object} periodObject period object override for calculation
15331535
* @returns {object} dashboard data object
15341536
* @example
15351537
* countlyCommon.getDashboardData(countlySession.getDb(), ["t", "n", "u", "d", "e", "p", "m"], ["u", "p", "m"], {u:"users"});
@@ -1544,7 +1546,7 @@ countlyCommon.timeString = function(timespent) {
15441546
* "m":{"total":86,"prev-total":0,"change":"NA","trend":"u","isEstimate":true}
15451547
* }
15461548
*/
1547-
countlyCommon.getDashboardData = function(data, properties, unique, totalUserOverrideObj, prevTotalUserOverrideObj) {
1549+
countlyCommon.getDashboardData = function(data, properties, unique, totalUserOverrideObj, prevTotalUserOverrideObj, periodObject) {
15481550
/**
15491551
* Clear object, bu nulling out predefined properties, that does not exist
15501552
* @param {object} obj - object to clear
@@ -1568,7 +1570,7 @@ countlyCommon.getDashboardData = function(data, properties, unique, totalUserOve
15681570
return obj;
15691571
}
15701572

1571-
var _periodObj = countlyCommon.periodObj,
1573+
var _periodObj = periodObject || countlyCommon.periodObj,
15721574
dataArr = {},
15731575
tmp_x,
15741576
tmp_y,

api/lib/countly.model.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ countlyModel.create = function(fetchValue) {
7171
};
7272
//Private Properties
7373
var _Db = {},
74-
_period = "30days",
74+
_period = null,
7575
_metas = {},
7676
_uniques = ["u"],
7777
_metrics = ["t", "u", "n"],
@@ -507,6 +507,10 @@ countlyModel.create = function(fetchValue) {
507507
* @returns {array} object to use when displaying number {value: 123, change: 12, sparkline: [1,2,3,4,5,6,7]}
508508
*/
509509
countlyMetric.getNumber = function(metric, isSparklineNotRequired) {
510+
var periodObject = null;
511+
if (this.getPeriod()) { // only set custom period if it was explicitly set on the model object
512+
periodObject = countlyCommon.getPeriodObj({qstring: {}}, this.getPeriod());
513+
}
510514
metric = metric || _metrics[0];
511515
var metrics = [metric];
512516
//include other default metrics for data correction
@@ -517,7 +521,7 @@ countlyModel.create = function(fetchValue) {
517521
if (metric === "n") {
518522
metrics.push("u");
519523
}
520-
var data = countlyCommon.getDashboardData(this.getDb(), metrics, _uniques, { u: this.getTotalUsersObj().users }, { u: this.getTotalUsersObj(true).users });
524+
var data = countlyCommon.getDashboardData(this.getDb(), metrics, _uniques, { u: this.getTotalUsersObj().users }, { u: this.getTotalUsersObj(true).users }, periodObject);
521525
if (isSparklineNotRequired) {
522526
return data[metric];
523527
}
@@ -535,7 +539,7 @@ countlyModel.create = function(fetchValue) {
535539
}
536540

537541
return obj;
538-
});
542+
}, periodObject);
539543
for (let i in data) {
540544
if (sparkLines[i]) {
541545
data[i].sparkline = sparkLines[i].split(",").map(function(item) {
@@ -553,10 +557,13 @@ countlyModel.create = function(fetchValue) {
553557
*/
554558
countlyMetric.getTimelineData = function() {
555559
var dataProps = [];
560+
var periodObject = null;
556561
for (let i = 0; i < _metrics.length; i++) {
557562
dataProps.push({ name: _metrics[i] });
558563
}
559-
var periodObject = countlyCommon.getPeriodObj({qstring: {}}, this.getPeriod());
564+
if (this.getPeriod()) { // only set custom period if it was explicitly set on the model object
565+
periodObject = countlyCommon.getPeriodObj({qstring: {}}, this.getPeriod());
566+
}
560567
var data = countlyCommon.extractData(this.getDb(), this.clearObject, dataProps, periodObject);
561568
var ret = {};
562569
for (let i = 0; i < data.length; i++) {

api/parts/data/usage.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -392,11 +392,11 @@ usage.returnAllProcessedMetrics = function(params) {
392392
}
393393

394394
// We check if country data logging is on and user's country is the configured country of the app
395-
if (tmpMetric.name === "country" && (plugins.getConfig("api").country_data === false || params.app_cc !== params.user.country)) {
395+
if (tmpMetric.name === "country" && (plugins.getConfig("api", params.app && params.app.plugins, true).country_data === false || params.app_cc !== params.user.country)) {
396396
continue;
397397
}
398398
// We check if city data logging is on and user's country is the configured country of the app
399-
if (tmpMetric.name === "city" && (plugins.getConfig("api").city_data === false || params.app_cc !== params.user.country)) {
399+
if (tmpMetric.name === "city" && (plugins.getConfig("api", params.app && params.app.plugins, true).city_data === false || params.app_cc !== params.user.country)) {
400400
continue;
401401
}
402402

api/utils/common.js

+69-14
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,32 @@ common.validateArgs = function(args, argProperties, returnErrors) {
13221322
}
13231323
}
13241324

1325+
if (argProperties[arg].regex) {
1326+
try {
1327+
var re = new RegExp(argProperties[arg].regex);
1328+
if (!re.test(args[arg])) {
1329+
if (returnErrors) {
1330+
returnObj.errors.push(arg + " is not correct format");
1331+
returnObj.result = false;
1332+
argState = false;
1333+
}
1334+
else {
1335+
return false;
1336+
}
1337+
}
1338+
}
1339+
catch (ex) {
1340+
if (returnErrors) {
1341+
returnObj.errors.push('Incorrect regex: ' + args[arg]);
1342+
returnObj.result = false;
1343+
argState = false;
1344+
}
1345+
else {
1346+
return false;
1347+
}
1348+
}
1349+
}
1350+
13251351
if (argState && returnErrors && !argProperties[arg]['exclude-from-ret-obj']) {
13261352
returnObj.obj[arg] = parsed === undefined ? args[arg] : parsed;
13271353
}
@@ -2584,6 +2610,44 @@ common.reviver = (key, value) => {
25842610
}
25852611
};
25862612

2613+
/**
2614+
* Shuffle string using crypto.getRandomValues
2615+
* @param {string} text - text to be shuffled
2616+
* @returns {string} shuffled password
2617+
*/
2618+
common.shuffleString = function(text) {
2619+
var j, x, i;
2620+
for (i = text.length; i; i--) {
2621+
j = Math.floor(Math.random() * i);
2622+
x = text[i - 1];
2623+
text[i - 1] = text[j];
2624+
text[j] = x;
2625+
}
2626+
2627+
return text.join("");
2628+
};
2629+
2630+
/**
2631+
* Gets a random string from given character set string with given length
2632+
* @param {string} charSet - charSet string
2633+
* @param {number} length - length of the random string. default 1
2634+
* @returns {string} random string from charset
2635+
*/
2636+
common.getRandomValue = function(charSet, length = 1) {
2637+
const randomValues = crypto.getRandomValues(new Uint8Array(charSet.length));
2638+
let randomValue = "";
2639+
2640+
if (length > charSet.length) {
2641+
length = charSet.length;
2642+
}
2643+
2644+
for (let i = 0; i < length; i++) {
2645+
randomValue += charSet[randomValues[i] % charSet.length];
2646+
}
2647+
2648+
return randomValue;
2649+
};
2650+
25872651
/**
25882652
* Generate random password
25892653
* @param {number} length - length of the password
@@ -2605,29 +2669,20 @@ common.generatePassword = function(length, no_special) {
26052669
}
26062670

26072671
//1 char
2608-
text.push(upchars.charAt(Math.floor(Math.random() * upchars.length)));
2672+
text.push(this.getRandomValue(upchars));
26092673
//1 number
2610-
text.push(numbers.charAt(Math.floor(Math.random() * numbers.length)));
2674+
text.push(this.getRandomValue(numbers));
26112675
//1 special char
26122676
if (!no_special) {
2613-
text.push(specials.charAt(Math.floor(Math.random() * specials.length)));
2677+
text.push(this.getRandomValue(specials));
26142678
length--;
26152679
}
26162680

2617-
var j, x, i;
26182681
//5 any chars
2619-
for (i = 0; i < Math.max(length - 2, 5); i++) {
2620-
text.push(all.charAt(Math.floor(Math.random() * all.length)));
2621-
}
2682+
text.push(this.getRandomValue(all, Math.max(length - 2, 5)));
26222683

26232684
//randomize order
2624-
for (i = text.length; i; i--) {
2625-
j = Math.floor(Math.random() * i);
2626-
x = text[i - 1];
2627-
text[i - 1] = text[j];
2628-
text[j] = x;
2629-
}
2630-
return text.join("");
2685+
return this.shuffleString(text);
26312686
};
26322687

26332688
/**

api/utils/localization.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ var locale = {},
88
fs = require('fs'),
99
path = require('path'),
1010
parser = require('properties-parser'),
11-
log = require('./log')('api:localization');
11+
log = require('./log')('api:localization'),
12+
common = require('./common');
1213

1314
var dir = path.resolve(__dirname, '../../frontend/express/public/localization/min');
1415
var file = "locale";
@@ -72,7 +73,7 @@ locale.getProperty = function(lang, name, callback) {
7273
}
7374
else if (!localized[lang]) {
7475
localized[lang] = JSON.parse(JSON.stringify(orig));
75-
fs.readFile(dir + '/' + file + '_' + lang + '.properties', 'utf8', function(err, local_properties) {
76+
fs.readFile(dir + '/' + common.sanitizeFilename(file) + '_' + common.sanitizeFilename(lang) + '.properties', 'utf8', function(err, local_properties) {
7677
if (!err && local_properties) {
7778
local_properties = parser.parse(local_properties);
7879
for (var i in local_properties) {
@@ -103,7 +104,7 @@ locale.getProperties = function(lang, callback) {
103104
}
104105
else if (!localized[lang]) {
105106
localized[lang] = JSON.parse(JSON.stringify(orig));
106-
fs.readFile(dir + '/' + file + '_' + lang + '.properties', 'utf8', function(err, local_properties) {
107+
fs.readFile(dir + '/' + common.sanitizeFilename(file) + '_' + common.sanitizeFilename(lang) + '.properties', 'utf8', function(err, local_properties) {
107108
if (!err && local_properties) {
108109
local_properties = parser.parse(local_properties);
109110
for (var i in local_properties) {

bin/countly.install.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ fi
1313

1414
sudo bash "$DIR/scripts/init_countly_user.sh"
1515
cd "$DIR/../"
16+
1617
sudo su countly -c "/bin/bash $DIR/scripts/check_countly_user_permissions.sh > /dev/null 2>&1"
1718

1819
if [ ! -f ./permission_test_file.txt ]; then
@@ -23,7 +24,6 @@ else
2324
sudo rm -f ./permission_test_file.txt
2425
fi
2526

26-
2727
if [ "$totalm" -lt "1800" ]; then
2828
echo "Countly requires at least 2Gb of RAM"
2929
if [ "$COUNTLY_OVERWRITE_MEM_REQUIREMENT" != "1" ]; then

bin/docker/modify.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ if [ "${COUNTLY_CONTAINER}" != "frontend" ]; then
3131
# install monngodb tools to have mongoexport
3232
if [ "${ID}" == "debian" ] || [ "${ID}" == "ubuntu" ]; then
3333
wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian10-x86_64-100.5.4.deb
34-
apt install ./mongodb-database-tools-*-100.5.4.deb
34+
apt install -y --allow-downgrades ./mongodb-database-tools-*-100.5.4.deb
3535
rm ./mongodb-database-tools-*-100.5.4.deb
3636
else
3737
wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel70-x86_64-100.5.4.rpm

frontend/express/app.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_
354354
if (!loadedThemes[theme]) {
355355
var tempThemeFiles = {css: [], js: []};
356356
if (theme && theme.length) {
357-
var themeDir = path.resolve(__dirname, "public/themes/" + theme + "/");
357+
var themeDir = path.resolve(__dirname, "public/themes/" + common.sanitizeFilename(theme) + "/");
358358
fs.readdir(themeDir, function(err, list) {
359359
if (err) {
360360
if (callback) {
@@ -978,14 +978,14 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_
978978
}
979979
plgns.forEach(plugin => {
980980
try {
981-
let contents = fs.readdirSync(__dirname + `/../../plugins/${plugin}/frontend/public/javascripts`) || [];
981+
let contents = fs.readdirSync(__dirname + `/../../plugins/${common.sanitizeFilename(plugin)}/frontend/public/javascripts`) || [];
982982
toDashboard.javascripts.push.apply(toDashboard.javascripts, contents.filter(n => typeof n === 'string' && n.includes('.js') && n.length > 3 && n.indexOf('.js') === n.length - 3).map(n => `${plugin}/javascripts/${n}`));
983983
}
984984
catch (e) {
985985
console.log('Error while reading folder of plugin %s: %j', plugin, e.stack);
986986
}
987987
try {
988-
let contents = fs.readdirSync(__dirname + `/../../plugins/${plugin}/frontend/public/stylesheets`) || [];
988+
let contents = fs.readdirSync(__dirname + `/../../plugins/${common.sanitizeFilename(plugin)}/frontend/public/stylesheets`) || [];
989989
toDashboard.stylesheets.push.apply(toDashboard.stylesheets, contents.filter(n => typeof n === 'string' && n.includes('.css') && n.length > 4 && n.indexOf('.css') === n.length - 4).map(n => `${plugin}/stylesheets/${n}`));
990990
}
991991
catch (e) {

0 commit comments

Comments
 (0)