1/* DOKUWIKI:include_once crypto_high-level.js */ 2 3 4/** 5 * Handles necessary actions before submitting the 'wikitext' edit form. 6 */ 7var currentSubmitter = null; 8 9function editFormOnSubmit(e) { 10 if (e && e.submitter) { 11 let curSub = e.submitter; 12 if (curSub.name.length==0) return false; 13 if (curSub.name.includes("cancel")) return true; 14 15 // only store the submitter if we got this far 16 currentSubmitter = curSub; 17 } 18 19 if (async_getKey_active) return false; 20 21 if(hasUnencryptedSecrets()) { 22 askForEncryptPasswordWithVerification(); 23 return false; 24 } else { 25 // there is no unencrypted content, so we can prepare submitting the form, now 26 27 // Move the original wiki_text element out of the form like we used to do in decryptEditSetup(). 28 // To prevent accidental submission of unencrypted text. 29 var wikitext=document.getElementById('wiki__text'); 30 var editform=document.getElementById('dw__editform'); 31 editform.parentNode.insertBefore(wikitext,editform); 32 33 // Now, get the wikitext content to our hidden field 34 var hiddentext=document.getElementById('wiki__text_submit'); 35 hiddentext.value=wikitext.value; 36 37 return true; 38 } 39} 40 41/** 42 * Setup the edit form: add the decrypt button and necessary functionality. 43 */ 44function decryptEditSetup(msg) { 45 //alert('setting up'); 46 var editform=null, wikitext=null, hiddentext=null, preview=null; 47 if(!(editform=document.getElementById('dw__editform'))) { 48 // alert("no form dw__editform\n"); 49 return(true); 50 } 51 if(!(wikitext=document.getElementById('wiki__text'))) { 52 // alert("no wiki__text"); 53 return(false); 54 } 55 // if there is no preview button, then assume this is a 56 // "Recover draft" page, dont do anything. 57 if(!(preview=document.getElementById('edbtn__preview'))) { 58 return(false); 59 } 60 61 if(!(save=document.getElementById('edbtn__save'))) { 62 return(false); 63 } 64 65 // Create a hidden element with id 'wiki__text_submit' and 66 // name wikitext (same as the wiki__text). 67 68 if(!(hiddentext=document.createElement('input'))) { 69 return(false); 70 } 71 72 hiddentext.setAttribute('id', 'wiki__text_submit'); 73 hiddentext.setAttribute('name', 'wikitext'); 74 hiddentext.setAttribute('type','hidden'); 75 editform.insertBefore(hiddentext,null); 76 77 if(!(decryptButton=document.createElement('input'))) { 78 return(false); 79 } 80 decryptButton.setAttribute('id', 'decryptButton'); 81 decryptButton.setAttribute('name', 'decryptButton'); 82 decryptButton.setAttribute('type','Button'); 83 decryptButton.setAttribute('value','DecryptSecret'); 84 decryptButton.onclick=decryptButtonOnClick; 85 decryptButton.setAttribute('class','button'); 86 decryptButton.setAttribute('className','button'); // required for IE 87 preview.parentNode.insertBefore(decryptButton,preview); 88 89 editform.onsubmit = function() {return editFormOnSubmit(event);}; 90 91 // The following is taken from lib/scripts/locktimer.js (state of 2018-06-08) to make drafts work. 92 // We override the locktimer refresh function to abort saving of drafts with unencrypted content. 93 dw_locktimer.refresh = function(){ 94 95 var now = new Date(), 96 params = 'call=lock&id=' + dw_locktimer.pageid + '&'; 97 98 // refresh every half minute only 99 if(now.getTime() - dw_locktimer.lasttime.getTime() <= 30*1000) { 100 return; 101 } 102 103 // POST everything necessary for draft saving 104 if(dw_locktimer.draft && jQuery('#dw__editform').find('textarea[name=wikitext]').length > 0){ 105 106 // *** BEGIN dokucrypt modified code 107 // Do not allow saving of a draft, if this page needs some content to be encrypted on save. 108 // Basically abort saving of drafts if this page has some content that needs encrypting. 109 if (hasUnencryptedSecrets()) { return(false); } 110 // *** END dokucrypt modified code 111 112 params += jQuery('#dw__editform').find(dw_locktimer.fieldsToSaveAsDraft.join(', ')).serialize(); 113 } 114 115 jQuery.post( 116 DOKU_BASE + 'lib/exe/ajax.php', 117 params, 118 dw_locktimer.refreshed, 119 'json' 120 ); 121 dw_locktimer.lasttime = now; 122 }; 123} 124 125/** 126 * Checks of there are <SECRET> blocks in the wikitext by trying to encrypt a given text 127 * with <SECRET>s contained. Works and returns synchronously. 128 * 129 * @param string x The text to be encrypted (usually that's the content of the 130 * textfield containing the wiki pages text source). 131 * 132 * @return boolean true, if there are <SECRET> blocks contained. Otherwise false. 133 */ 134function hasUnencryptedSecrets() { 135 var wikitext=null, hiddentext=null; 136 if(!(wikitext=document.getElementById('wiki__text'))) { 137 alert("failed to get wiki__text"); 138 return(false); 139 } 140 if (wikitext.value.includes("<" + tag_pt)) 141 return true; 142 else 143 return false; 144} 145 146/** 147 * Adds an input dialog to the edit page to ask the user for the encryption password. Works with callbacks and therefore represents an asynchronous workflow. 148 */ 149function askForEncryptPasswordWithVerification() { 150 var wikitext = document.getElementById('wiki__text'); 151 var hiddentext=document.getElementById('wiki__text_submit'); 152 153 lock = "default"; 154 155 // callback manages what to do and where to insert the decrypted text to 156 // call pw_prompt and let the callback call the next pw_prompt for input verification (repeat passwort) 157 do_verification = function(key) { 158 159 do_encryption = function(key2) { 160 if (key != key2) { 161 alert("Passwords do not match!"); 162 return; 163 } 164 165 // important: cache the key first, then try to do the encryption! 166 setKeyForLock(lock,key); 167 168 var encrypted_text = encryptMixedText(wikitext.value); 169 if (encrypted_text) { 170 wikitext.value=encrypted_text; 171 hiddentext.value=encrypted_text; 172 173 // retry submit 174 currentSubmitter.click(); 175 } else { 176 setKeyForLock(lock,null); 177 alert("The text could not be encrypted!"); 178 currentSubmitter = null; 179 } 180 }; 181 182 pw_prompt({ 183 lm:"Repeat passphrase key for lock " + lock, 184 elem:wikitext, 185 submit_callback:do_encryption 186 }); 187 188 }; 189 190 pw_prompt({ 191 lm:"Enter passphrase key for lock " + lock, 192 elem:wikitext, 193 submit_callback:do_verification 194 }); 195} 196 197function askForDecryptPassword() { 198 var wikitext = document.getElementById('wiki__text'); 199 var hiddentext=document.getElementById('wiki__text_submit'); 200 201 lock = "default"; 202 203 // callback manages what to do and where to insert the decrypted text to 204 do_decryption = function(key) { 205 // important: cache the key first, then try to do the decryption! 206 setKeyForLock(lock,key); 207 208 var decrypted_text = decryptMixedText(wikitext.value); 209 if (decrypted_text) { 210 wikitext.value=decrypted_text; 211 hiddentext.value=decrypted_text; 212 } else { 213 setKeyForLock(lock,null); 214 alert("The text could not be decrypted!"); 215 } 216 }; 217 218 pw_prompt({ 219 lm:"Enter passphrase key for lock " + lock, 220 elem:wikitext, 221 submit_callback:do_decryption 222 }); 223 224} 225 226/** 227 * Handles the actions after clicking the Decrypt button in the edit form. Tries to 228 * decrypt any <ENCRYPTED> blocks. 229 */ 230function decryptButtonOnClick() { 231 askForDecryptPassword(); 232 return(true); 233} 234 235function toggleElemVisibility(elemid) { 236 elem=document.getElementById(elemid); 237 if(elem.style.visibility=="visible") { 238 elem.style.visibility="hidden"; 239 elem.style.position="absolute"; 240 } else { 241 elem.style.visibility="visible"; 242 elem.style.position="relative"; 243 } 244} 245 246/* 247 this is called from <A HREF=> links to decrypt the inline html 248*/ 249function toggleCryptDiv(elemid,lock,ctext) { 250 var elem=null, atab=null, ptext=""; 251 var ctStr="Decrypt Encrypted Text", ptStr="Hide Plaintext"; 252 elem=document.getElementById(elemid); 253 atag=document.getElementById(elemid + "_atag"); 254 if(elem===null || atag===null) { 255 alert("failed to find element id " + elemid); 256 } 257 if(atag.innerHTML==ptStr) { 258 // encrypt text (set back to ctext, and forget key) 259 elem.innerHTML=ctext; 260 atag.innerHTML=ctStr; 261 elem.style.visibility="hidden"; 262 elem.style.position="absolute"; 263 setKeyForLock(lock,undefined); 264 } else if (atag.innerHTML==ctStr) { 265 // decrypt text 266 267 // callback manages what to do and where to insert the decrypted text to 268 do_decryption = function(given_key) { 269 //try the decryption 270 if(!(ptext=decryptTextString(ctext,given_key))) { 271 alert("failed to decrypt with provided key"); 272 return; 273 } 274 275 elem.textContent=ptext; 276 atag.innerHTML=ptStr; 277 // make it visible 278 elem.style.visibility="visible"; 279 elem.style.position="relative"; 280 281 //store the key that was used 282 setKeyForLock(lock,given_key); 283 284 if (JSINFO["plugin_dokucrypt3_CONFIG_copytoclipboard"] == 1) { 285 //put it into the clipboard 286 copyToClipboard(ptext).then(() => { 287 if (JSINFO['plugin_dokucrypt3_CONFIG_hidepasswordoncopytoclipboard']) { 288 elem.textContent = "{" + JSINFO['plugin_dokucrypt3_TEXT_copied_to_clipboard'] + "}"; 289 } else { 290 elem.textContent += " {" + JSINFO['plugin_dokucrypt3_TEXT_copied_to_clipboard'] + "}"; 291 }; 292 console.log('Encrypted value has been copied to the clipboard.'); 293 }).catch(() => { 294 console.log('Encrypted value could not be copied to the clipboard.'); 295 }); 296 } 297 }; 298 299 // now test if there is a key cached for the given lock - if no key can be determined, show password prompt 300 var key = getKeyForLock(lock); 301 if(key===false || key===undefined || key === null || !decryptTextString(ctext,key)) { 302 pw_prompt({ 303 lm:"Enter passphrase for lock " + lock, 304 lock:lock, 305 elem:elem, 306 submit_callback:do_decryption 307 }); 308 309 } else { 310 do_decryption(key); 311 } 312 } else { alert("Broken"); return; } 313} 314 315//copy to clipboard from: https://stackoverflow.com/questions/51805395/navigator-clipboard-is-undefined 316 317function copyToClipboard(textToCopy) { 318 // navigator clipboard api needs a secure context (https) 319 if (navigator.clipboard && window.isSecureContext) { 320 // navigator clipboard api method' 321 return navigator.clipboard.writeText(textToCopy); 322 } else { 323 // text area method 324 let textArea = document.createElement("textarea"); 325 textArea.value = textToCopy; 326 // make the textarea out of viewport 327 textArea.style.position = "fixed"; 328 textArea.style.left = "-999999px"; 329 textArea.style.top = "-999999px"; 330 document.body.appendChild(textArea); 331 textArea.focus(); 332 textArea.select(); 333 return new Promise((res, rej) => { 334 // here the magic happens 335 document.execCommand('copy') ? res() : rej(); 336 textArea.remove(); 337 }); 338 } 339} 340 341// protected password prompt adapted from: https://stackoverflow.com/a/28461750/19144619 342var promptElem = null; 343var label = null; 344var input = null; 345var submit_button = null; 346var cancel_button = null; 347var submit_event = null; 348var cancel_event = null; 349var enter_event = null; 350var async_getKey_active = false; // tracks whether the user is currently being asked for a key 351 352window.pw_prompt = function(options) { 353 var lm = options.lm || "Password:", 354 bm = options.bm || "Submit", 355 cm = options.cm || "Cancel", 356 elem = options.elem || document.body, 357 submit_callback = options.submit_callback; 358 359 if(!submit_callback) { // callback manages what to do and where to insert the decrypted text to 360 alert("No callback function for submitting provided! Please provide one - it should handle the actions after submitting the pw_prompt.") 361 }; 362 363 if (promptElem == null) { 364 promptElem = document.createElement("div"); 365 promptElem.className = "dokucrypt3pw_prompt"; 366 367 label = document.createElement("label"); 368 label.textContent = lm; 369 label.for = "pw_prompt_input"; 370 promptElem.appendChild(label); 371 372 input = document.createElement("input"); 373 input.id = "pw_prompt_input"; 374 input.type = "password"; 375 promptElem.appendChild(input); 376 377 submit_button = document.createElement("button"); 378 promptElem.appendChild(submit_button); 379 380 cancel_button = document.createElement("button"); 381 promptElem.appendChild(cancel_button); 382 } else { 383 //remove event listeners 384 submit_button.removeEventListener("click", submit_event); 385 cancel_button.removeEventListener("click", cancel_event); 386 } 387 388 submit_event = function() { 389 if (promptElem.parentNode) 390 promptElem.parentNode.removeChild(promptElem); 391 async_getKey_active = false; 392 submit_callback(input.value); 393 }; 394 cancel_event = function() { 395 if (promptElem.parentNode) 396 promptElem.parentNode.removeChild(promptElem); 397 async_getKey_active = false; 398 }; 399 400 label.textContent = lm; 401 input.value = ""; 402 input.addEventListener('keydown', function (e) { 403 if (e.key === 'Enter') { 404 submit_event(); 405 } 406 }); 407 submit_button.textContent = bm; 408 submit_button.addEventListener("click", submit_event, false); 409 cancel_button.textContent = cm; 410 cancel_button.addEventListener("click", cancel_event, false); 411 412 if(elem.nextSibling){ 413 elem.parentNode.insertBefore(promptElem,elem.nextSibling); 414 } else { 415 elem.parentNode.appendChild(promptElem); 416 } 417 418 async_getKey_active = true; 419 input.focus(); 420}; 421