Subversion Repositories oidplus

Rev

Rev 865 | Rev 982 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 daniel-mar 1
/*
2
 * OIDplus 2.0
511 daniel-mar 3
 * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
5 daniel-mar 4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
 
362 daniel-mar 18
// DEFAULT_LANGUAGE will be set by setup.js.php
19
// language_messages will be set by setup.js.php
20
// language_tblprefix will be set by setup.js.php
21
 
695 daniel-mar 22
// TODO: Put these settings in a "setup configuration file" (hardcoded)
635 daniel-mar 23
min_password_length = 10; // see also plugins/viathinksoft/publicPages/092_forgot_password_admin/script.js
421 daniel-mar 24
password_salt_length = 10;
695 daniel-mar 25
bcrypt_rounds = 10;
80 daniel-mar 26
 
2 daniel-mar 27
function btoa(bin) {
28
        var tableStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
29
        var table = tableStr.split("");
30
        for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) {
31
                var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++);
362 daniel-mar 32
                if ((a | b | c) > 255) throw new Error(_L('String contains an invalid character'));
2 daniel-mar 33
                base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] +
34
                                       (isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) +
35
                                       (isNaN(b + c) ? "=" : table[c & 63]);
36
        }
37
        return base64.join("");
38
};
39
 
40
function hexToBase64(str) {
41
        return btoa(String.fromCharCode.apply(null,
42
                    str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
43
}
44
 
865 daniel-mar 45
function _b64EncodeUnicode(str) {
46
        if (str == "") {
47
                return "''";
48
        } else {
49
                return "base64_decode('"+b64EncodeUnicode(str)+"')";
50
        }
51
}
52
 
2 daniel-mar 53
function b64EncodeUnicode(str) {
54
        // first we use encodeURIComponent to get percent-encoded UTF-8,
55
        // then we convert the percent encodings into raw bytes which
56
        // can be fed into btoa.
57
        return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
58
        function toSolidBytes(match, p1) {
59
                return String.fromCharCode('0x' + p1);
60
        }));
61
}
62
 
63
function generateRandomString(length) {
64
        var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
65
        retVal = "";
66
        for (var i = 0, n = charset.length; i < length; ++i) {
67
                retVal += charset.charAt(Math.floor(Math.random() * n));
68
        }
69
        return retVal;
70
}
71
 
72
String.prototype.replaceAll = function(search, replacement) {
73
        var target = this;
74
        return target.replace(new RegExp(search, 'g'), replacement);
75
};
76
 
421 daniel-mar 77
function adminGeneratePassword(password) {
78
        var salt = generateRandomString(password_salt_length);
79
        return salt+'$'+hexToBase64(sha3_512(salt+password));
80
}
81
 
456 daniel-mar 82
var bCryptWorker = null;
83
var g_prevBcryptPw = null;
84
var g_last_admPwdHash = null;
85
var g_last_pwComment = null;
86
 
2 daniel-mar 87
function rebuild() {
561 daniel-mar 88
        var pw = $("#admin_password")[0].value;
456 daniel-mar 89
 
90
        if (pw != g_prevBcryptPw) {
91
                // sync call to calculate SHA3
92
                var admPwdHash = adminGeneratePassword(pw);
93
                var pwComment = 'salted, base64 encoded SHA3-512 hash';
94
                doRebuild(admPwdHash, pwComment);
95
 
96
                // "async" call to calculate bcrypt (via web-worker)
97
                if (bCryptWorker != null) {
98
                        g_prevBcryptPw = null;
99
                        bCryptWorker.terminate();
100
                }
599 daniel-mar 101
                bCryptWorker = new Worker('../bcrypt_worker.js');
695 daniel-mar 102
                bCryptWorker.postMessage([pw, bcrypt_rounds]);
456 daniel-mar 103
                bCryptWorker.onmessage = function (event) {
104
                        var admPwdHash = event.data;
105
                        var pwComment = 'bcrypt encoded hash';
106
                        doRebuild(admPwdHash, pwComment);
107
                        g_prevBcryptPw = pw;
108
                };
109
        } else {
110
                doRebuild(g_last_admPwdHash, g_last_pwComment);
111
        }
112
}
113
 
114
function doRebuild(admPwdHash, pwComment) {
115
        g_last_admPwdHash = admPwdHash;
116
        g_last_pwComment = pwComment;
117
 
2 daniel-mar 118
        var error = false;
119
 
561 daniel-mar 120
        if ($("#config")[0] == null) return;
150 daniel-mar 121
 
81 daniel-mar 122
        // Check 1: Has the password the correct length?
561 daniel-mar 123
        if ($("#admin_password")[0].value.length < min_password_length)
81 daniel-mar 124
        {
561 daniel-mar 125
                $("#password_warn")[0].innerHTML = '<font color="red">'+_L('Password must be at least %1 characters long',min_password_length)+'</font>';
126
                $("#config")[0].innerHTML = '<b>&lt?php</b><br><br><i>// ERROR: Password must be at least '+min_password_length+' characters long</i>'; // do not translate
81 daniel-mar 127
                error = true;
128
        } else {
561 daniel-mar 129
                $("#password_warn")[0].innerHTML = '';
81 daniel-mar 130
        }
131
 
132
        // Check 2: Do the passwords match?
561 daniel-mar 133
        if ($("#admin_password")[0].value != $("#admin_password2")[0].value) {
134
                $("#password_warn2")[0].innerHTML = '<font color="red">'+_L('The passwords do not match!')+'</font>';
2 daniel-mar 135
                error = true;
136
        } else {
561 daniel-mar 137
                $("#password_warn2")[0].innerHTML = '';
2 daniel-mar 138
        }
139
 
702 daniel-mar 140
        // Check 3: Ask the database or captcha plugins for verification of their data
150 daniel-mar 141
        for (var i = 0; i < rebuild_callbacks.length; i++) {
142
                var f = rebuild_callbacks[i];
143
                if (!f()) {
144
                        error = true;
145
                }
2 daniel-mar 146
        }
81 daniel-mar 147
 
149 daniel-mar 148
        // Continue
81 daniel-mar 149
        if (!error)
150
        {
561 daniel-mar 151
                var e = $("#db_plugin")[0];
702 daniel-mar 152
                var strDatabasePlugin = e.options[e.selectedIndex].value;
153
                var e = $("#captcha_plugin")[0];
154
                var strCaptchaPlugin = e.options[e.selectedIndex].value;
150 daniel-mar 155
 
561 daniel-mar 156
                $("#config")[0].innerHTML = '<b>&lt?php</b><br><br>' +
362 daniel-mar 157
                        '<i>// To renew this file, please run setup/ in your browser.</i><br>' + // do not translate
158
                        '<i>// If you don\'t want to run setup again, you can also change most of the settings directly in this file.</i><br>' + // do not translate
2 daniel-mar 159
                        '<br>' +
261 daniel-mar 160
                        'OIDplus::baseConfig()->setValue(\'CONFIG_VERSION\',    2.1);<br>' +
2 daniel-mar 161
                        '<br>' +
150 daniel-mar 162
                        // Passwords are Base64 encoded to avoid that passwords can be read upon first sight,
294 daniel-mar 163
                        // e.g. if collegues are looking over your shoulder while you accidently open (and quickly close) userdata/baseconfig/config.inc.php
456 daniel-mar 164
                        'OIDplus::baseConfig()->setValue(\'ADMIN_PASSWORD\',    \'' + admPwdHash + '\'); // '+pwComment+'<br>' +
2 daniel-mar 165
                        '<br>' +
702 daniel-mar 166
                        'OIDplus::baseConfig()->setValue(\'DATABASE_PLUGIN\',   \''+strDatabasePlugin+'\');<br>';
150 daniel-mar 167
                for (var i = 0; i < rebuild_config_callbacks.length; i++) {
168
                        var f = rebuild_config_callbacks[i];
169
                        var cont = f();
170
                        if (cont) {
561 daniel-mar 171
                                $("#config")[0].innerHTML = $("#config")[0].innerHTML + cont;
150 daniel-mar 172
                        }
173
                }
561 daniel-mar 174
                $("#config")[0].innerHTML = $("#config")[0].innerHTML +
2 daniel-mar 175
                        '<br>' +
561 daniel-mar 176
                        'OIDplus::baseConfig()->setValue(\'TABLENAME_PREFIX\',  \''+$("#tablename_prefix")[0].value+'\');<br>' +
2 daniel-mar 177
                        '<br>' +
261 daniel-mar 178
                        'OIDplus::baseConfig()->setValue(\'SERVER_SECRET\',     \''+generateRandomString(32)+'\');<br>' +
2 daniel-mar 179
                        '<br>' +
702 daniel-mar 180
                        'OIDplus::baseConfig()->setValue(\'CAPTCHA_PLUGIN\',    \''+strCaptchaPlugin+'\');<br>';
181
                for (var i = 0; i < captcha_rebuild_config_callbacks.length; i++) {
182
                        var f = captcha_rebuild_config_callbacks[i];
183
                        var cont = f();
184
                        if (cont) {
185
                                $("#config")[0].innerHTML = $("#config")[0].innerHTML + cont;
186
                        }
187
                }
188
 
189
                $("#config")[0].innerHTML = $("#config")[0].innerHTML +
80 daniel-mar 190
                        '<br>' +
561 daniel-mar 191
                        'OIDplus::baseConfig()->setValue(\'ENFORCE_SSL\',       '+$("#enforce_ssl")[0].value+');<br>';
2 daniel-mar 192
 
561 daniel-mar 193
                $("#config")[0].innerHTML = $("#config")[0].innerHTML.replaceAll(' ', '&nbsp;');
2 daniel-mar 194
        }
195
 
196
        // In case something is not good, do not allow the user to continue with the other configuration steps:
197
        if (error) {
561 daniel-mar 198
                $("#step2")[0].style.display = "None";
199
                $("#step3")[0].style.display = "None";
200
                $("#step4")[0].style.display = "None";
2 daniel-mar 201
        } else {
561 daniel-mar 202
                $("#step2")[0].style.display = "Block";
203
                $("#step3")[0].style.display = "Block";
204
                $("#step4")[0].style.display = "Block";
2 daniel-mar 205
        }
206
}
362 daniel-mar 207
 
208
function RemoveLastDirectoryPartOf(the_url) {
209
        var the_arr = the_url.split('/');
210
        if (the_arr.pop() == '') the_arr.pop();
211
        return( the_arr.join('/') );
212
}
213
 
214
function checkAccess(dir) {
706 daniel-mar 215
        if (!dir.toLowerCase().startsWith('https:') && !dir.toLowerCase().startsWith('http:')) {
216
                var url = '../' + dir;
217
                var visibleUrl = RemoveLastDirectoryPartOf(window.location.href) + '/' + dir; // xhr.responseURL not available in IE
218
        } else {
219
                var url = dir;
220
                var visibleUrl = dir; // xhr.responseURL not available in IE
221
        }
362 daniel-mar 222
 
223
        var xhr = new XMLHttpRequest();
224
        xhr.onreadystatechange = function() {
225
                if (xhr.readyState === 4) {
226
                        if (xhr.status === 200) {
561 daniel-mar 227
                                $("#systemCheckCaption")[0].style.display = 'block';
861 daniel-mar 228
                                // TODO: At my Ubuntu VM, after fixing an issue with Apache2, the messages still appeared. I had to click the links and press F5 for each one
229
                                //       THERE MUST BE NO CACHE!!!
561 daniel-mar 230
                                $("#dirAccessWarning")[0].innerHTML = $("#dirAccessWarning")[0].innerHTML + _L('Attention: The following directory is world-readable: %1 ! You need to configure your web server to restrict access to this directory! (For Apache see <i>.htaccess</i>, for Microsoft IIS see <i>web.config</i>, for Nginx see <i>nginx.conf</i>).','<a target="_blank" href="'+url+'">'+visibleUrl+'</a>') + '<br>';
362 daniel-mar 231
                        }
232
                }
233
        };
234
 
235
        xhr.open('GET', url);
236
        xhr.send();
237
}
238
 
239
function dbplugin_changed() {
561 daniel-mar 240
        var e = $("#db_plugin")[0];
702 daniel-mar 241
        var strDatabasePlugin = e.options[e.selectedIndex].value;
362 daniel-mar 242
 
243
        for (var i = 0; i < plugin_combobox_change_callbacks.length; i++) {
244
                var f = plugin_combobox_change_callbacks[i];
702 daniel-mar 245
                f(strDatabasePlugin);
362 daniel-mar 246
        }
247
 
248
        rebuild();
249
}
250
 
702 daniel-mar 251
function captchaplugin_changed() {
252
        var e = $("#captcha_plugin")[0];
253
        var strCaptchaPlugin = e.options[e.selectedIndex].value;
254
 
255
        for (var i = 0; i < captcha_plugin_combobox_change_callbacks.length; i++) {
256
                var f = captcha_plugin_combobox_change_callbacks[i];
257
                f(strCaptchaPlugin);
258
        }
259
 
260
        rebuild();
261
}
262
 
362 daniel-mar 263
function performAccessCheck() {
561 daniel-mar 264
        $("#dirAccessWarning")[0].innerHTML = "";
362 daniel-mar 265
        checkAccess("userdata/index.html");
476 daniel-mar 266
        checkAccess("res/ATTENTION.TXT");
362 daniel-mar 267
        checkAccess("dev/index.html");
268
        checkAccess("includes/index.html");
448 daniel-mar 269
        checkAccess("setup/includes/index.html");
635 daniel-mar 270
        //checkAccess("plugins/viathinksoft/publicPages/100_whois/whois/cli/index.html");
974 daniel-mar 271
 
706 daniel-mar 272
        if (window.location.href.toLowerCase().startsWith('https://')) {
974 daniel-mar 273
                $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_YES'); // enforce SSL (because we are already SSL)
706 daniel-mar 274
        } else {
275
                // Do a SSL detection now.
276
                // This is important because on XAMPP the SSL cert is invalid (self signed) and the user might
974 daniel-mar 277
                // be very confused if the PHP detection (OIDplus::ENFORCE_SSL_AUTO) notices the open 443 port and redirects the user to a
706 daniel-mar 278
                // big warning page of the browser!
279
                var xhr = new XMLHttpRequest();
280
                xhr.onreadystatechange = function() {
281
                        if (xhr.readyState === 4) {
282
                                if (xhr.status === 200) {
974 daniel-mar 283
                                        $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_YES'); // enforce SSL (we checked that it loads correctly)
706 daniel-mar 284
                                } else {
285
                                        console.log("JS SSL detection result: "+xhr.status);
974 daniel-mar 286
                                        $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_NO'); // disable SSL (because it failed, e.g. because of invalid cert or closed port)
706 daniel-mar 287
                                }
288
                        }
289
                };
290
                var https_url = window.location.href.replace(/^http:/i, "https:");
291
                xhr.open('GET', https_url);
292
                xhr.send();
293
        }
362 daniel-mar 294
}
295
 
296
function setupOnLoad() {
297
        rebuild();
298
        dbplugin_changed();
702 daniel-mar 299
        captchaplugin_changed();
362 daniel-mar 300
        performAccessCheck();
301
}
302
 
303
function getCookie(cname) {
304
        // Source: https://www.w3schools.com/js/js_cookies.asp
305
        var name = cname + "=";
306
        var decodedCookie = decodeURIComponent(document.cookie);
307
        var ca = decodedCookie.split(';');
308
        for(var i = 0; i <ca.length; i++) {
309
                var c = ca[i];
310
                while (c.charAt(0) == ' ') {
311
                        c = c.substring(1);
312
                }
313
                if (c.indexOf(name) == 0) {
314
                        return c.substring(name.length, c.length);
315
                }
316
        }
317
        return undefined;
318
}
319
 
320
function getCurrentLang() {
321
        // Note: If the argument "?lang=" is used, PHP will automatically set a Cookie, so it is OK when we only check for the cookie
322
        var lang = getCookie('LANGUAGE');
323
        return (typeof lang != "undefined") ? lang : DEFAULT_LANGUAGE;
324
}
325
 
326
function _L() {
327
        var args = Array.prototype.slice.call(arguments);
506 daniel-mar 328
        var str = args.shift().trim();
362 daniel-mar 329
 
330
        var tmp = "";
331
        if (typeof language_messages[getCurrentLang()] == "undefined") {
332
                tmp = str;
333
        } else {
334
                var msg = language_messages[getCurrentLang()][str];
335
                if (typeof msg != "undefined") {
336
                        tmp = msg;
337
                } else {
338
                        tmp = str;
339
                }
340
        }
341
 
342
        tmp = tmp.replace('###', language_tblprefix);
343
 
344
        var n = 1;
345
        while (args.length > 0) {
346
                var val = args.shift();
347
                tmp = tmp.replace("%"+n, val);
348
                n++;
349
        }
350
 
370 daniel-mar 351
        tmp = tmp.replace("%%", "%");
352
 
362 daniel-mar 353
        return tmp;
354
}
355
 
356
window.onload = setupOnLoad;