Mercurial > p > roundup > code
view website/issues/html/issue.item.html @ 6693:9a1f5e496e6c
issue2551203 - Add support for CORS preflight request
Add support for unauthenticated CORS preflight and fix headers for
CORS.
client.py:
pass through unauthenticated CORS preflight to rest backend. Normal
rest OPTION handlers (including tracker defined extensions) can
see and handle the request.
make some error cases return error json with crrect mime type rather
than plain text tracebacks.
create new functions to verify origin and referer that filter using
allowed origins setting.
remove tracker base url from error message is referer is not at an
allowed origin.
rest.py:
fix up OPTION methods handlers to include
Access-Control-Allow-Methods that are the same as the Allow
header.
set cache to one week for all Access-Control headers for CORS
preflight only.
remove self.client.setHeader("Access-Control-Allow-Origin", "*") and
set Access-Control-Allow-Origin to the client supplied origin if
it passes allowed origin checks. Required for CORS otherwise data
isn't available to caller. Set for all responses.
set Vary header now includes Origin as responses can differ based on
Origin for all responses.
set Access-Control-Allow-Credentials to true on all responses.
test_liveserver.py:
run server with setting to enforce origin csrf header check
run server with setting to enforce x-requested-with csrf header check
run server with setting for allowed_api_origins
requests now set required csrf headers
test preflight request on collections
check new headers and Origin is no longer '*'
rewrite all compression checks to use a single method with argument
to use different compression methods. Reduce a lot of code
duplication and makes updating for new headers easier.
test_cgi:
test new error messages in client.py
account for new headers
test preflight and new code paths
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 07 Jun 2022 09:39:35 -0400 |
| parents | 12c5ddf865b1 |
| children | 63390dcfcfe9 |
line wrap: on
line source
<tal:block metal:use-macro="templates/page/macros/icing"> <title metal:fill-slot="head_title"> <tal:block condition="context/id" i18n:translate="" >Issue <span tal:replace="context/id" i18n:name="id" />: <span tal:replace="context/title" i18n:name="title" /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" /></tal:block> <tal:block condition="not:context/id" i18n:translate="" >New Issue - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker" /></tal:block> </title> <tal:block metal:fill-slot="body_title"> <span tal:condition="python: not (context.id or context.is_edit_ok())" tal:omit-tag="python:1" i18n:translate="">New Issue</span> <span tal:condition="python: not context.id and context.is_edit_ok()" tal:omit-tag="python:1" i18n:translate="">New Issue Editing</span> <span tal:condition="python: context.id and not context.is_edit_ok()" tal:omit-tag="python:1" i18n:translate="">Issue <tal:x replace="context/id" i18n:name="id" /></span> <span tal:condition="python: context.id and context.is_edit_ok()" tal:omit-tag="python:1" i18n:translate="">Issue<tal:x replace="context/id" i18n:name="id" /> Editing</span> </tal:block> <td class="content" metal:fill-slot="content"> <p tal:condition="python:not (context.is_view_ok() or request.user.hasRole('Anonymous'))" i18n:translate=""> You are not allowed to view this page.</p> <p tal:condition="python:not context.is_view_ok() and request.user.hasRole('Anonymous')" i18n:translate=""> Please login with your username and password.</p> <div tal:condition="context/is_view_ok"> <form method="POST" name="itemSynopsis" onSubmit="return submit_once()" enctype="multipart/form-data" tal:attributes="action context/designator"> <fieldset><legend>classification</legend> <table class="form"> <tr> <th class="required"><label for="title" i18n:translate="">Title:</label></th> <td colspan="3" tal:condition="context/title/is_edit_ok" tal:content="structure python:context.title.field(id='title', size=60)">title</td> <td colspan="3" tal:condition="not:context/title/is_edit_ok"> <span tal:content="structure context/title/plain"/> <input type="hidden" name="title" tal:attributes="value context/title"> </td> </tr> <tr> <th class="required" i18n:translate=""> <span tal:replace="structure python:db.issue_type.classhelp('id,name,description',label='Type')" />: </th> <td tal:content="structure context/type/menu">type</td> <th i18n:translate=""> <span tal:replace="structure python:db.severity.classhelp('id,name,description',label='Severity')" />: </th> <td tal:content="structure context/severity/menu">components</td> </tr> <tr> <th i18n:translate=""> <span tal:replace="structure python:db.component.classhelp('id,name,description',label='Components')" />: </th> <td tal:content="structure context/components/menu">components</td> <th i18n:translate=""> <span tal:replace="structure python:db.version.classhelp('id,name,description',label='Versions')" />: </th> <td tal:content="structure context/versions/menu">versions</td> </tr> </table> </fieldset> <fieldset><legend>process</legend> <table class="form"> <tr tal:condition="context/id"> <th i18n:translate=""> <span tal:replace="structure python:db.status.classhelp('id,name,description',label='Status')" />: </th> <td tal:content="structure context/status/menu">status</td> <th><label for="resolution" i18n:translate="">Resolution:</label></th> <td tal:content="structure python:context.resolution.menu(html_kwargs={'id':'resolution'})">resolution</td> </tr> <tr tal:condition="context/id"> <th> <label for="dependencies" i18n:translate="">Dependencies</label> <span tal:condition="context/dependencies/is_edit_ok" tal:replace="structure python:db.issue.classhelp('id,title', filter='status=0,1', property='dependencies')" /> </th> <td> <span tal:replace="structure python:context.dependencies.field(id='dependencies',showid=1,size=20)" /> <span tal:condition="context/dependencies" tal:repeat="d python:context.dependencies.sorted('creation')"> <br/>View: <a tal:attributes="href string:issue${d/id}" tal:content="d/id"></a> </span> </td> <th> <tal:block><label for="superseder" i18n:translate="">Superseder</label></tal:block>: <span tal:condition="context/superseder/is_edit_ok" tal:replace="structure python:db.issue.classhelp('id,title', filter='status=0,1', property='superseder')" /> </th> <td> <span tal:replace="structure python:context.superseder.field(id='superseder', showid=1, size=20)" /> <span tal:condition="context/superseder"> <!-- <br><span i18n:translate="">View</span>: <a tal:repeat="sup context/superseder" tal:content="python:sup['id'] + ', '*(not repeat['sup'].end)" tal:attributes="href string:issue${sup/id}; title sup/title;"></a> --> <br><span i18n:translate="">View</span>: <a tal:content="context/superseder/id" tal:attributes="href string:issue${context/superseder/id}; title context/superseder/title;"></a> </span> </td> </tr> <tr> <th><label for="assignee" i18n:translate="">Assigned To</label>:</th> <td tal:condition="context/status/is_edit_ok"> <select id="assignee" name="assignee"> <option value="-1">nobody</option> <tal:block tal:repeat="userdata python:db._db.user.filter_sql('select id,_username from _user where _roles like \'%Developer%\' order by _username')"> <option tal:attributes="value python:userdata[0]; selected python:str(userdata[0]) == context.assignee._value" tal:content="python:userdata[1]"></option> </tal:block> </select> </td> <td tal:condition="not:context/assignee/is_edit_ok"> <span tal:replace="structure context/assignee/plain" /> </td> <th><label for="nosy" i18n:translate="">Nosy List</label>: <span tal:condition="context/nosy/is_edit_ok" tal:replace="structure python:db.user.classhelp('username,realname,address', property='nosy')" /> </th> <td> <span tal:replace="structure python:context.nosy.field(id='nosy')" /> </td> </tr> <tr> <th> <span tal:replace="structure python:db.priority.classhelp('id,name,description',label='Priority')" />: </th> <td tal:content="structure context/priority/menu">priority</td> <th><label for="keywords" i18n:translate="">Keywords</label>:</th> <td tal:content="structure python:context['keywords'].menu(height=5,html_kwargs={'id': 'keywords'})">keywords</td> </tr> <tr tal:condition="context/is_edit_ok"> <th><label for="@note" i18n:translate="">Comment</label>:</th> <td colspan="3"> <textarea tal:content="request/form/@note/value | default" id="@note" name="@note" wrap="hard" rows="10" cols="72"></textarea> </td> </tr> <tr tal:condition="context/is_edit_ok"> <th><label for="file-1@content" i18n:translate="">File</label>:</th> <td> <input type="hidden" name="@link@files" value="file-1"> <input type="file" id="file-1@content" name="file-1@content" size="40"> </td> <th><label for="file-1@description" i18n:translate="">File Description</label>:</th> <td colspan=3><input type="text" class="fileDesc" id="file-1@description" name="file-1@description" size="40"></td> </tr> <tr tal:condition="context/is_edit_ok"> <td colspan=4> <textarea readonly id="DropZone" aria-labeledby="DropZone"> paste images or drag and drop files here.... </textarea> </td> </tr> </table> </fieldset> <table class="form"> <tr tal:condition="context/is_edit_ok"> <td> <input type="hidden" name="@template" value="item"> <input type="hidden" name="@required" value="title"> </td> <td colspan=3> <span tal:replace="structure context/submit">submit button</span> <a tal:condition="context/id" tal:attributes="href context/copy_url" i18n:translate="">Make a copy</a> </td> </tr> </table> </form> <!-- drag and drop code --> <script tal:attributes="nonce request/client/client_nonce"> /* multiple file drops cause issues with redefined file-X@content issues. input multiple assumes it can start numbering from 1 for each of the multiple files. However numbering here does the same leading to duplicate file-2@content. layout needs some work, alignnment of new file input's isn't great. Need a way to delete or reset file inputs so file assigned to them isn't uploaded. Clicking on button in chrome and then canceling unsets the file. But this sequence does nothing in firefox. Pasting always uses image.<type> can't name file. Need to query user during paste for name/description. */ let newInput=null; let NextInputNum = 100; /* file input 1 is hardcoded in form. It is a multiple file input control. To prevent collision, we start dynamic file controls at file-100@content. 100 is larger than we expect the number of files uploaded using file input 1.*/ let target = document.getElementById('DropZone'); target.style.display = "block"; let body = document.body; let fileInput = document.getElementById('file-1@content'); let fileDesc = document.getElementById('file-1@description'); function make_clear_fileInput_closure(input) { return function(ev) { input.value = ""; ev.preventDefault();} } function make_new_clear_button() { newClearInput=document.createElement('button'); newClearInput.textContent = "X"; newClearInput.setAttribute("aria-label", "Clear next file input."); newClearInput.setAttribute("title", "Clear next file input."); newClearInput.classList.add("clearButton"); return newClearInput; } function make_new_file_input() { newInput=document.createElement('input'); newInput.type="file"; newInput.id="file-" + NextInputNum +"@content"; newInput.name=newInput.id; return newInput; } function add_file_input () { // Only allow one change listener on newest input. fileInput.removeEventListener('change', add_file_input, false); newClearInput = fileInput.insertAdjacentElement( 'beforebegin', make_new_clear_button()); // add change handler to file clear button newClearInput.addEventListener('click', make_clear_fileInput_closure(fileInput), false); /* Insert break so next input is on new line */ br = fileInput.insertAdjacentElement('afterend', document.createElement('br')); /* create new file input to get next dragged file */ /* <input type="file" name="file-2@content"> for 2, 3, 4, ... */ fileInput = br.insertAdjacentElement('afterend', make_new_file_input()); // add change hander to newest file input fileInput.addEventListener('change', add_file_input, // create new input for more files false); /* link file-N to list of files on issue. also link to msg-1 */ addLink=document.createElement('input'); addLink.type="hidden"; addLink.id="@link@file=file-" + NextInputNum; addLink.name="@link@files" addLink.value="file-" + NextInputNum; fileInput.insertAdjacentElement('afterend', addLink); addLink=document.createElement('input'); addLink.type="hidden"; addLink.id="msg-1@link@files=file-" + NextInputNum; addLink.name="msg-1@link@files" addLink.value="file-" + NextInputNum fileInput.insertAdjacentElement('afterend', addLink); /* break line before description field to prevent wrapping multiple descriptions onto one line when zoomed out or large display.*/ br = fileDesc.insertAdjacentElement('afterend', document.createElement('br')); fileDesc=document.createElement('input'); fileDesc.type="text"; fileDesc.id="file-" + NextInputNum + "@description"; fileDesc.name=fileDesc.id fileDesc.size = 40 fileDesc.classList.add("fileDesc"); fileDesc=br.insertAdjacentElement('afterend', fileDesc); NextInputNum = NextInputNum+1; } function MarkDropZone(e, active) { active == true ? e.style.backgroundColor = "goldenrod" : e.style.backgroundColor = ""; } fileInput.addEventListener('change', add_file_input, // create new input for more files false); target.addEventListener('dragover', (e) => { e.preventDefault(); body.classList.add('dragging'); }); target.addEventListener('dragenter', (e) => { e.preventDefault(); MarkDropZone(target, true); }); target.addEventListener('dragleave', (e) => { e.preventDefault(); MarkDropZone(target, false); }); target.addEventListener('dragleave', () => { body.classList.remove('dragging'); }); target.addEventListener('drop', (e) => { body.classList.remove('dragging'); MarkDropZone(target, false); // Only allow single file drop unless // fileInput name is @file that can support // multiple file drop and file drop is multiple. if (( fileInput.name != "@file" || ! fileInput.hasAttribute('multiple')) && e.dataTransfer.files.length != 1 ) { alert("File input can only accept one file.") e.preventDefault(); return } // set file input files to the dragged files fileInput.files = e.dataTransfer.files; add_file_input(); // create new input for more files // run last otherwise firefox empties e.dataTransfer e.preventDefault(); }); target.addEventListener('mouseover', (e) => { e.preventDefault(); MarkDropZone(target, true); }); target.addEventListener('mouseout', (e) => { e.preventDefault(); MarkDropZone(target, false); }); target.addEventListener('paste', (event) => { // https://mobiarch.wordpress.com/2013/09/25/upload-image-by-copy-and-paste/ // add paste event listener to the page // https://stackoverflow.com/questions/50427513/ // html-paste-clipboard-image-to-file-input if ( event.clipboardData.files.length == 0) { // if not file data alert alert("No image found for pasting"); event.preventDefault(); return; } fileInput.files = event.clipboardData.files; /* Possible enhancement if file check fails. iterate over all items 0 ...: event.clipboardData.items.length look at all items[i].kind for 'string' and items[i].type looking for a text/plain item. If found, event.clipboardData.items[1].getAsString( callback_fcn(s)) where callback function that creates a new dataTransfer object with a file and insert the content s and assigns it to the input. https://gist.github.com/guest271314/7eac2c21911f5e40f489\33ac78e518bd */ add_file_input(); // create new input for more files // do not paste contents to dropzone event.preventDefault(); }, false); </script> <style tal:attributes="nonce request/client/client_nonce"> #FileArea button.clearButton ~ input[type=file] {display:inline-block;} #DropZone { /* don't display dropzone by default. Displayed as block by javascript. */ display:none; width: 100%; /* override textarea inset */ border-style: dashed; padding: 3ex 0; /* larger dropzone */ /* add space below inputs */ margin-block-start: 1em; /* lighter color */ background: rgba(255,255,255,0.4); } input[id$=\@content], input.fileDesc {margin-block-end: 0.5em} </style> <p tal:condition="context/id" i18n:translate=""> Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b> by <b><tal:x replace="context/creator" i18n:name="creator" /></b>, last changed <b><tal:x replace="python:context.activity.pretty('%Y-%m-%d %H:%M')" i18n:name="activity" /></b> by <b><tal:x replace="context/actor" i18n:name="actor" /></b>. </p> <table class="files" tal:condition="context/files"> <tr><th colspan="5" class="header" i18n:translate="">Files</th></tr> <tr> <th i18n:translate="">File name</th> <th i18n:translate="">Uploaded</th> <th i18n:translate="">Description</th> <th i18n:translate="">Edit</th> <th i18n:translate="">Remove</th> </tr> <tr tal:repeat="file python:context.files.sorted('creation')"> <td> <a tal:attributes="href file/download_url" tal:content="file/name">dld link</a> </td> <td> <span tal:content="file/creator">creator's name</span>, <span tal:content="python:file.creation.pretty('%Y-%m-%d %H:%M')">creation date</span> </td> <td tal:content="file/description" /> <td><a tal:condition="file/is_edit_ok" tal:attributes="href string:file${file/id}">edit</a> </td> <td> <form style="padding:0" method="POST" tal:condition="file/is_edit_ok" tal:attributes="action string:issue${context/id}"> <input type="hidden" name="@remove@files" tal:attributes="value file/id"> <input type="hidden" name="@action" value="edit"> <input type="submit" value="remove" i18n:attributes="value"> </form> </td> </tr> </table> <table class="messages" tal:condition="context/messages"> <tr><th colspan="4" class="header" i18n:translate="">Messages</th></tr> <tal:block tal:repeat="msg context/messages"> <tr> <th><a tal:attributes="href string:msg${msg/id}" i18n:translate="">msg<tal:x replace="msg/id" i18n:name="id" /></a></th> <th i18n:translate="">Author: <tal:x replace="python:msg.author.realname.plain()" i18n:name="author" /> (<tal:x replace="msg/author"/>)</th> <th i18n:translate="">Date: <tal:x replace="python:msg.date.pretty('%Y-%m-%d %H:%M')" i18n:name="date" /></th> <th> <form style="padding:0" method="POST" tal:condition="msg/is_edit_ok" tal:attributes="action string:issue${context/id}"> <input type="hidden" name="@remove@messages" tal:attributes="value msg/id"> <input type="hidden" name="@action" value="edit"> <input type="submit" value="remove" i18n:attributes="value"> </form> </th> </tr> <tr> <td colspan="4" class="content"> <pre tal:condition="python:msg.content.is_view_ok()" tal:content="structure python:utils.localReplace(msg.content.hyperlinked())">content</pre> </td> </tr> </tal:block> </table> <tal:block tal:condition="context/id" tal:replace="structure context/history" /> </div> </td> </tal:block> <!-- SHA: ad841842c0da5f9d1a7f69a1e0c847a549b75bf2 -->
