[ADD] *: CSRF protection in forms and routes
* make CSRF protection the default on all non-SAFE methods note: there currently is no way to call a CSRF-protected endpoint without a form-encoded entity-body as that's the only place we get the CSRF token from. * simple CSRF token generation: just use the HMAC'd session id, no generating a new random token per session then HMAC it * use constant-time equal function to avoid timing attacks * assert that a database secret is configured before hashing/validating the CSRF token * opt-out database manager from CSRF: The super-admin password serves the purpose of a CSRF token in the database manager screens. There is no request database to obtain the secret and generate a CSRF token.
This commit is contained in:
committed by
Olivier Dony
parent
058b33544f
commit
7636b510a2
@@ -38,6 +38,7 @@
|
||||
<template id="auth_signup.signup" name="Sign up login">
|
||||
<t t-call="web.login_layout">
|
||||
<form class="oe_signup_form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
|
||||
<t t-call="auth_signup.fields">
|
||||
<t t-set="only_passwords" t-value="bool(token)"/>
|
||||
@@ -67,6 +68,7 @@
|
||||
</div>
|
||||
|
||||
<form class="oe_reset_password_form" role="form" method="post" t-if="not message">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
|
||||
<t t-if="token">
|
||||
<t t-call="auth_signup.fields">
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
from openerp.http import Controller, route
|
||||
|
||||
class ImportController(Controller):
|
||||
@route('/base_import/set_file')
|
||||
@route('/base_import/set_file', methods=['POST'])
|
||||
def set_file(self, req, file, import_id, jsonp='callback'):
|
||||
import_id = int(import_id)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class OgoneController(http.Controller):
|
||||
new_id = acquirer.s2s_process(data)
|
||||
return new_id
|
||||
|
||||
@http.route(['/payment/ogone/s2s/create'], type='http', auth='public')
|
||||
@http.route(['/payment/ogone/s2s/create'], type='http', auth='public', methods=["POST"])
|
||||
def ogone_s2s_create(self, **post):
|
||||
acquirer_id = int(post.get('acquirer_id'))
|
||||
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
|
||||
<template id="ogone_s2s_form">
|
||||
<form method="post" t-att-action="'/payment/ogone/s2s/create' if not json else '/payment/ogone/s2s/create_json'">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div t-att-class="'row' if bootstrap_formatting else ''">
|
||||
<div t-att-class="'form-group col-md-8' if bootstrap_formatting else 'form-group'">
|
||||
<label class="control-label" for="cc_number">Card number</label>
|
||||
|
||||
@@ -14,7 +14,7 @@ class OgoneController(http.Controller):
|
||||
|
||||
@http.route([
|
||||
'/payment/transfer/feedback',
|
||||
], type='http', auth='none')
|
||||
], type='http', auth='none', csrf=False)
|
||||
def transfer_form_feedback(self, **post):
|
||||
cr, uid, context = request.cr, SUPERUSER_ID, request.context
|
||||
_logger.info('Beginning form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-'8' "-*-"
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
||||
from openerp.osv import osv
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<template id="transfer_acquirer_button">
|
||||
<form t-if="acquirer" t-att-action="tx_url" method="post" target="_self">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<t t-if="return_url">
|
||||
<input type='hidden' name='return_url' t-att-value='return_url'/>
|
||||
</t>
|
||||
@@ -11,8 +12,7 @@
|
||||
<input type='hidden' name='amount' t-att-value='amount'/>
|
||||
<input type='hidden' name='currency' t-att-value='currency.name'/>
|
||||
<!-- submit -->
|
||||
<button type="submit" width="100px"
|
||||
t-att-class="submit_class">
|
||||
<button type="submit" width="100px" t-att-class="submit_class">
|
||||
<img t-if="not submit_txt" src="/payment_transfer/static/src/img/transfer_icon.png"/>
|
||||
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
|
||||
</button>
|
||||
|
||||
@@ -108,7 +108,7 @@ class WebsiteSurvey(http.Controller):
|
||||
# Survey displaying
|
||||
@http.route(['/survey/fill/<model("survey.survey"):survey>/<string:token>',
|
||||
'/survey/fill/<model("survey.survey"):survey>/<string:token>/<string:prev>'],
|
||||
type='http', auth='public', website=True)
|
||||
type='http', auth='public', website=True, methods=['POST'])
|
||||
def fill_survey(self, survey, token, prev=None, **post):
|
||||
'''Display and validates a survey'''
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
</div>
|
||||
|
||||
<form role="form" method="post" class="js_surveyform" t-att-name="'%s_%s' % (survey.id, page.id)" t-att-action="'/survey/fill/%s/%s' % (slug(survey), token)" t-att-data-prefill="'/survey/prefill/%s/%s/%s' % (slug(survey), token, slug(page))" t-att-data-validate="'/survey/validate/%s' % (slug(survey))" t-att-data-submit="'/survey/submit/%s' % (slug(survey))">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="page_id" t-att-value="page.id" />
|
||||
<input type="hidden" name="token" t-att-value="token" />
|
||||
<t t-foreach='page.question_ids' t-as='question'>
|
||||
|
||||
@@ -736,7 +736,7 @@ class Database(http.Controller):
|
||||
def manager(self, **kw):
|
||||
return self._render_template()
|
||||
|
||||
@http.route('/web/database/create', type='http', auth="none")
|
||||
@http.route('/web/database/create', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
def create(self, master_pwd, name, lang, password, **post):
|
||||
try:
|
||||
request.session.proxy("db").create_database(master_pwd, name, bool(post.get('demo')), lang, password)
|
||||
@@ -746,7 +746,7 @@ class Database(http.Controller):
|
||||
error = "Database creation error: %s" % e
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/duplicate', type='http', auth="none")
|
||||
@http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
def duplicate(self, master_pwd, name, new_name):
|
||||
try:
|
||||
request.session.proxy("db").duplicate_database(master_pwd, name, new_name)
|
||||
@@ -755,7 +755,7 @@ class Database(http.Controller):
|
||||
error = "Database duplication error: %s" % e
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/drop', type='http', auth="none")
|
||||
@http.route('/web/database/drop', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
def drop(self, master_pwd, name):
|
||||
try:
|
||||
request.session.proxy("db").drop(master_pwd, name)
|
||||
@@ -764,7 +764,7 @@ class Database(http.Controller):
|
||||
error = "Database deletion error: %s" % e
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/backup', type='http', auth="none")
|
||||
@http.route('/web/database/backup', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
def backup(self, master_pwd, name, backup_format = 'zip'):
|
||||
try:
|
||||
openerp.service.db.check_super(master_pwd)
|
||||
@@ -782,7 +782,7 @@ class Database(http.Controller):
|
||||
error = "Database backup error: %s" % e
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/restore', type='http', auth="none")
|
||||
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
def restore(self, master_pwd, backup_file, name, copy=False):
|
||||
try:
|
||||
data = base64.b64encode(backup_file.read())
|
||||
@@ -792,7 +792,7 @@ class Database(http.Controller):
|
||||
error = "Database restore error: %s" % e
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/change_password', type='http', auth="none")
|
||||
@http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
def change_password(self, master_pwd, master_pwd_new):
|
||||
try:
|
||||
request.session.proxy("db").change_admin_password(master_pwd, master_pwd_new)
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
<t t-call="web.login_layout">
|
||||
|
||||
<form class="oe_login_form" role="form" t-attf-action="/web/login{{ '?debug' if debug else '' }}" method="post" onsubmit="this.action = this.action + location.hash">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
|
||||
<div class="form-group field-db" t-if="databases and len(databases) > 1">
|
||||
<label for="db" class="control-label">Database</label>
|
||||
|
||||
@@ -88,6 +88,8 @@
|
||||
enctype="multipart/form-data"
|
||||
target="fileframe"
|
||||
class="form-inline">
|
||||
<!-- why is this template rendered client side? -->
|
||||
<!-- input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/-->
|
||||
<div class="well">
|
||||
<div class="form-group pull-left">
|
||||
<input type="file" name="upload" t-att-accept="widget.accept" multiple="multiple" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/>
|
||||
|
||||
@@ -87,6 +87,7 @@ response = request.website.render("website.template_partner_post", values)
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<form class="form-horizontal" action="/website/action/website.action_partner_post" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Recipient</label>
|
||||
<div class="col-sm-10">
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="/web_editor/attachment/add" method="post" enctype="multipart/form-data" >
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<input name="upload" type="file" value="Choose images" multiple="multiple" accept="image/*"/>
|
||||
</div>
|
||||
|
||||
@@ -846,6 +846,7 @@
|
||||
<p>An error occured while rendering the template <code t-esc="qweb_exception.qweb['template']"/>.</p>
|
||||
<p>If this error is caused by a change of yours in the templates, you have the possibility to reset one or more templates to their <strong>factory settings</strong>.</p>
|
||||
<form action="/website/reset_templates" method="post" id="reset_templates_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<ul class="oe_template_fallback">
|
||||
<li t-foreach="views" t-as="view">
|
||||
<label>
|
||||
|
||||
@@ -314,7 +314,7 @@ class WebsiteBlog(http.Controller):
|
||||
new_blog_post = request.registry['blog.post'].browse(cr, uid, new_blog_post_id, context=context)
|
||||
return werkzeug.utils.redirect("/blog/%s/post/%s?enable_editor=1" % (slug(new_blog_post.blog_id), slug(new_blog_post)))
|
||||
|
||||
@http.route('/blog/post_duplicate', type='http', auth="public", website=True)
|
||||
@http.route('/blog/post_duplicate', type='http', auth="public", website=True, methods=['POST'])
|
||||
def blog_post_copy(self, blog_post_id, **post):
|
||||
""" Duplicate a blog.
|
||||
|
||||
|
||||
@@ -195,10 +195,11 @@
|
||||
<t t-set="object" t-value="blog_post"/>
|
||||
<t t-set="publish_edit" t-value="True"/>
|
||||
<li>
|
||||
<form class="duplicate hidden" action="/blog/post_duplicate">
|
||||
<input name="blog_post_id" t-att-value="blog_post.id"/>
|
||||
</form>
|
||||
<a href="#" class="duplicate" onclick="$(this).prev('form').submit()">Duplicate</a>
|
||||
<form class="duplicate hidden" action="/blog/post_duplicate" method="POST">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input name="blog_post_id" t-att-value="blog_post.id"/>
|
||||
</form>
|
||||
<a href="#" class="duplicate" onclick="$(this).prev('form').submit()">Duplicate</a>
|
||||
</li>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<xpath expr="//div[@name='mail_button']" position="replace">
|
||||
<div>
|
||||
<form action="/website_form/" method="post" data-model_name="crm.lead" data-success_page="/page/website_crm.contactus_thanks" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group form-field o_website_form_required_custom">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="contact_name">Your Name</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
|
||||
@@ -379,6 +379,7 @@
|
||||
<!-- Registration Templates -->
|
||||
<template id="registration_template">
|
||||
<form id="registration_form" t-attf-action="/event/#{slug(event)}/registration/new" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<table itemprop="offers" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -428,6 +429,7 @@
|
||||
<div id="modal_attendees_registration" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<form id="attendee_registration" t-attf-action="/event/#{slug(event)}/registration/confirm" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
</div>
|
||||
<section id="forms" t-if="event.show_track_proposal">
|
||||
<form class="form-horizontal mt32 js_website_submit_form" t-attf-action="/event/#{event.id}/track_proposal/post" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Name</label>
|
||||
<div class="col-md-7 col-sm-8">
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<!-- helper -->
|
||||
<template id="link_button">
|
||||
<form method="POST" t-att-action="url">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<button t-attf-class="fa btn-link #{classes} #{karma and 'karma_required text-muted' or ''}" t-attf-data-karma="#{karma}">
|
||||
<t t-esc="label"/></button>
|
||||
</form>
|
||||
@@ -479,6 +480,7 @@
|
||||
posts will be featured.
|
||||
</p>
|
||||
<form t-att-action="action_url" method="post" role="form" class="tag_text form-horizontal js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
|
||||
<input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
|
||||
<div class="form-group">
|
||||
@@ -521,6 +523,7 @@
|
||||
<b>Share</b> Something Awesome.
|
||||
</p>
|
||||
<form t-attf-action="/forum/#{slug(forum)}/new?post_type=discussion" method="post" role="form" class="tag_text js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="text" name="post_name" required="True" t-attf-value="#{post_name}"
|
||||
class="form-control mb16" placeholder="Your Discussion Title..."/>
|
||||
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
|
||||
@@ -558,6 +561,7 @@
|
||||
<li>Provide enough details and, if possible, give an example.</li>
|
||||
</ul>
|
||||
<form t-attf-action="/forum/#{slug(forum)}/new?post_type=question" method="post" role="form" class="tag_text js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="text" name="post_name" required="True" pattern=".*\S.*" t-attf-value="#{post_name}"
|
||||
class="form-control mb16" placeholder="Your Question Title..." title="Title must not be empty"/>
|
||||
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
|
||||
@@ -583,6 +587,7 @@
|
||||
<h3 t-if="not is_answer">Edit <span t-field="post.post_type"/></h3>
|
||||
<h3 t-if="is_answer">Edit reply</h3>
|
||||
<form t-attf-action="/forum/#{slug(forum)}/post/#{slug(post)}/save" method="post" role="form" class="tag_text js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div t-if="not is_answer">
|
||||
<input type="text" name="post_name" required="True" pattern=".*\S.*" t-attf-value="#{post.name}"
|
||||
class="form-control mb8" placeholder="Edit your Post" title="Title must not be empty"/>
|
||||
@@ -620,6 +625,7 @@
|
||||
them.
|
||||
</p>
|
||||
<form t-attf-action="/forum/#{ slug(forum) }/#{offensive and 'post' or 'question'}/#{slug(question)}/#{offensive and 'mark_as_offensive' or 'close'}" method="post" role="form" class="form-horizontal mt32 mb64 js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input name="post_id" t-att-value="question.id" type="hidden"/>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="reason">Post:</label>
|
||||
@@ -659,6 +665,7 @@
|
||||
<form t-attf-id="reply#{ object._name.replace('.','') + '-' + str(object.id) }" class="collapse js_website_submit_form"
|
||||
t-attf-action="/forum/#{ slug(forum) }/#{slug(object)}/reply" method="post" role="form">
|
||||
<h3 class="mt8">Your Reply</h3>
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
|
||||
<textarea name="content" t-attf-id="content-#{str(object.id)}" class="form-control load_editor" required="True"
|
||||
t-att-data-karma="forum.karma_editor"/>
|
||||
@@ -678,6 +685,7 @@
|
||||
- it really helps to select the best questions and answers!
|
||||
</p>
|
||||
<form t-attf-action="/forum/#{ slug(forum) }/#{slug(question)}/reply" method="post" class="js_website_submit_form" role="form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
|
||||
<textarea name="content" t-attf-id="content-#{str(question.id)}" class="form-control load_editor" required="True"
|
||||
t-att-data-karma="forum.karma_editor"/>
|
||||
@@ -1043,6 +1051,7 @@
|
||||
<div class="css_editable_mode_hidden">
|
||||
<form t-attf-id="comment#{ object._name.replace('.','') + '-' + str(object.id) }" class="collapse oe_comment_grey js_website_submit_form"
|
||||
t-attf-action="/forum/#{slug(forum)}/post/#{slug(object)}/comment" method="POST">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input name="post_id" t-att-value="object.id" type="hidden" class="mt8"/>
|
||||
<textarea name="comment" class="form-control" placeholder="Comment this post..."/>
|
||||
<button type="submit" class="btn btn-primary mt8">Post</button>
|
||||
@@ -1249,6 +1258,7 @@
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<form t-attf-action="/forum/#{slug(forum)}/user/#{slug(user)}/save" method="post" role="form" class="form-horizontal js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input name="user_id" t-att-value="user.id" type="hidden"/>
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label mb16" for="user_name">Real name</label>
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
<div class="container">
|
||||
<div class="well">
|
||||
<form t-attf-action="/forum/#{ slug(forum) }/promote_ok" method="post" role="form" class="form-horizontal">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input name="post_id" t-att-value="post.id" type="hidden"/>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="reason">Question:</label>
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
<div class="row">
|
||||
<section id="forms">
|
||||
<form action="/website_form/" method="post" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-model_name="hr.applicant" data-success_page="/page/website_hr_recruitment.thankyou" t-att-data-form_field_department_id="job and job.department_id.id or False" t-att-data-form_field_job_id="job and job.id or False">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group form-field o_website_form_required_custom">
|
||||
<div class="col-md-3 col-sm-4 text-right">
|
||||
<label class="control-label" for="partner_name">Your Name</label>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<t t-set="can_comment" t-value="sha_in or token or not is_user_public"/>
|
||||
<section class="mb32 hidden-print" t-if="can_comment">
|
||||
<form class="o_website_chatter_form" t-attf-action="/website_mail/post/#{chatter_mode}" method="POST">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<img class="img pull-left img-circle" t-attf-src="/web/image/res.partner/#{user_id.partner_id.id}/image_small/50x50" style="width: 50px; margin-right: 10px;"/>
|
||||
<div class="pull-left mb32" style="width: 75%%">
|
||||
<textarea rows="4" name="message" class="form-control" placeholder="Write a message..."></textarea>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<template id="unsubscribe">
|
||||
<div class="container o_unsubscribe_form">
|
||||
<div class="row">
|
||||
<form t-attf-action="/mail/mailing/unsubscribe" method="POST" id="unsubscribe_form">
|
||||
<form action="/mail/mailing/unsubscribe" method="POST" id="unsubscribe_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="email" t-att-value="email"/>
|
||||
<input type="hidden" name="mailing_id" t-att-value="mailing_id"/>
|
||||
|
||||
@@ -69,4 +70,4 @@
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post" action="/website_payment/delete/">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<select name="delete_pm_id" class="form-control" >
|
||||
<t t-foreach="pms" t-as="pm">
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
</div>
|
||||
<h1>Contact Details</h1>
|
||||
<form action="/my/account" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="row o_website_portal_details">
|
||||
<div class="col-md-8">
|
||||
<div class="row">
|
||||
|
||||
@@ -77,7 +77,7 @@ class sale_quote(http.Controller):
|
||||
context=render_ctx)
|
||||
return request.website.render('website_quote.so_quotation', values)
|
||||
|
||||
@http.route(['/quote/accept'], type='json', auth="public", website=True)
|
||||
@http.route(['/quote/accept/<int:order_id>'], type='http', auth="public", website=True, methods=['POST'])
|
||||
def accept(self, order_id, token=None, signer=None, sign=None, **post):
|
||||
order_obj = request.registry.get('sale.order')
|
||||
order = order_obj.browse(request.cr, SUPERUSER_ID, order_id)
|
||||
@@ -91,7 +91,7 @@ class sale_quote(http.Controller):
|
||||
_message_post_helper(message=message, res_id=order_id, res_model='sale.order', attachments=attachments, **({'token': token, 'token_field': 'access_token'} if token else {}))
|
||||
return True
|
||||
|
||||
@http.route(['/quote/<int:order_id>/<token>/decline'], type='http', auth="public", website=True)
|
||||
@http.route(['/quote/<int:order_id>/<token>/decline'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def decline(self, order_id, token, **post):
|
||||
order_obj = request.registry.get('sale.order')
|
||||
order = order_obj.browse(request.cr, SUPERUSER_ID, order_id)
|
||||
|
||||
@@ -229,7 +229,8 @@
|
||||
<!-- modal relative to the actions Accept/Reject/Cancel -->
|
||||
<div class="modal fade" id="modalaccept" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" t-if="not quotation.require_payment and not need_payment">
|
||||
<form id="accept" method="POST" t-attf-action="/quote/accept/#{quotation.id}/?token=#{quotation.access_token}" class="js_accept_json modal-content js_website_submit_form">
|
||||
<form id="accept" method="POST" t-attf-action="/quote/accept/#{quotation.id}/?token=#{quotation.access_token}" class="js_accept_json modal-content js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Validate Order</h4>
|
||||
@@ -305,6 +306,7 @@
|
||||
<div class="modal fade" id="modaldecline" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form id="decline" method="POST" t-attf-action="/quote/#{quotation.id}/#{quotation.access_token}/decline" class="modal-content">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Reject This Quote</h4>
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
|
||||
<template id="products_item" name="Products item">
|
||||
<form action="/shop/cart/update" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div itemscope="itemscope" itemtype="http://schema.org/Product">
|
||||
<div class="ribbon-wrapper">
|
||||
<div class="ribbon btn btn-danger">Sale</div>
|
||||
@@ -366,7 +367,7 @@
|
||||
<span itemprop="url" style="display:none;" t-esc="'/shop/product/%s' % slug(product)"/>
|
||||
|
||||
<form t-att-action="keep('/shop/cart/update')" class="js_add_cart_variants" method="POST">
|
||||
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="js_product">
|
||||
<t t-placeholder="select">
|
||||
<input type="hidden" class="product_id" name="product_id" t-att-value="int(product.product_variant_ids[0]) if len(product.product_variant_ids) == 1 else '0'"/>
|
||||
@@ -668,6 +669,7 @@
|
||||
<h1 class="mb32">Extra Step</h1>
|
||||
<div class="row">
|
||||
<form class="form-horizontal" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label for="x_test" class="col-sm-2 control-label">Field 1</label>
|
||||
<div class="col-sm-10">
|
||||
@@ -898,7 +900,8 @@
|
||||
}'/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<form action="/shop/cart/update" method="post">
|
||||
<form action="/shop/cart/update" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input name="product_id" t-att-value="product.id" type="hidden"/>
|
||||
<a class="btn btn-link a-submit"><strong>Add to Cart</strong></a>
|
||||
</form>
|
||||
@@ -925,6 +928,7 @@
|
||||
<p class="bg-warning">This promo code is not available</p>
|
||||
</t>
|
||||
<form t-if="website_sale_order and website_sale_order.website_order_line" action="/shop/pricelist" method="post" class="mb32">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="input-group">
|
||||
<input name="promo" class='form-control' type="text" placeholder="code..." t-att-value="website_sale_order.pricelist_id.code or ''"/>
|
||||
<div class="input-group-btn">
|
||||
@@ -947,7 +951,7 @@
|
||||
</t>
|
||||
<h1>Your Address</h1>
|
||||
<form action="/shop/confirm_order" method="post">
|
||||
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="row">
|
||||
<div class="col-md-8 oe_cart">
|
||||
<h3 class="page-header mt16">Billing Information
|
||||
@@ -1209,6 +1213,7 @@
|
||||
<div class="js_payment mb64 row" t-if="not website_sale_order.amount_total" id="payment_method">
|
||||
<div class="col-lg-8 col-sm-8">
|
||||
<form target="_self" action="/shop/payment/validate" method="post" class="pull-right">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<a style="width:127px;" class="btn btn-primary a-submit">
|
||||
<span t-if="order.amount_total > 0">Pay Now <span class="fa fa-long-arrow-right"></span></span>
|
||||
<span t-if="order.amount_total == 0">Confirm Order <span class="fa fa-long-arrow-right"></span></span>
|
||||
|
||||
@@ -544,6 +544,7 @@
|
||||
</div>
|
||||
<div t-att-class="comments and 'tab-pane fade active in' or 'tab-pane fade'" id="discuss">
|
||||
<form id="comment" class="js_website_submit_form" t-attf-action="/slides/slide/#{slide.id}/comment" method="POST">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="media">
|
||||
<span class="pull-left" t-if="not is_public_user">
|
||||
<img class="img img-circle media-object"
|
||||
|
||||
+66
-6
@@ -6,9 +6,9 @@ import ast
|
||||
import collections
|
||||
import contextlib
|
||||
import datetime
|
||||
import errno
|
||||
import functools
|
||||
import getpass
|
||||
import hashlib
|
||||
import hmac
|
||||
import inspect
|
||||
import logging
|
||||
import mimetypes
|
||||
@@ -17,7 +17,6 @@ import pprint
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
@@ -26,6 +25,7 @@ import warnings
|
||||
from zlib import adler32
|
||||
|
||||
import babel.core
|
||||
import passlib.utils
|
||||
import psycopg2
|
||||
import json
|
||||
import werkzeug.contrib.sessions
|
||||
@@ -43,11 +43,10 @@ except ImportError:
|
||||
psutil = None
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.service.server import memory_info
|
||||
from openerp.service import security, model as service_model
|
||||
from openerp.tools.func import lazy_property
|
||||
from openerp.tools import ustr
|
||||
from openerp.tools import ustr, consteq
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
rpc_request = logging.getLogger(__name__ + '.rpc.request')
|
||||
@@ -372,6 +371,48 @@ class WebRequest(object):
|
||||
"""
|
||||
return self.session
|
||||
|
||||
def csrf_token(self, time_limit=3600):
|
||||
""" Generates and returns a CSRF token for the current session
|
||||
|
||||
:param time_limit: the CSRF token should only be valid for the
|
||||
specified duration (in second), by default 1h,
|
||||
``None`` for the token to be valid as long as the
|
||||
current user's session is.
|
||||
:type time_limit: int | None
|
||||
:returns: ASCII token string
|
||||
"""
|
||||
token = self.session.sid
|
||||
max_ts = '' if not time_limit else int(time.time() + time_limit)
|
||||
msg = '%s%s' % (token, max_ts)
|
||||
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
|
||||
assert secret, "CSRF protection requires a configured database secret"
|
||||
hm = hmac.new(str(secret), msg, hashlib.sha1).hexdigest()
|
||||
return '%so%s' % (hm, max_ts)
|
||||
|
||||
def validate_csrf(self, csrf):
|
||||
if not csrf:
|
||||
return False
|
||||
|
||||
try:
|
||||
hm, _, max_ts = str(csrf).rpartition('o')
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
|
||||
if max_ts:
|
||||
try:
|
||||
if int(max_ts) < int(time.time()):
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
token = self.session.sid
|
||||
|
||||
msg = '%s%s' % (token, max_ts)
|
||||
secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
|
||||
assert secret, "CSRF protection requires a configured database secret"
|
||||
hm_expected = hmac.new(str(secret), msg, hashlib.sha1).hexdigest()
|
||||
return consteq(hm, hm_expected)
|
||||
|
||||
def route(route=None, **kw):
|
||||
"""
|
||||
Decorator marking the decorated method as being a handler for
|
||||
@@ -397,9 +438,21 @@ def route(route=None, **kw):
|
||||
:param methods: A sequence of http methods this route applies to. If not
|
||||
specified, all methods are allowed.
|
||||
:param cors: The Access-Control-Allow-Origin cors directive value.
|
||||
:param bool csrf: Whether CSRF protection should be enabled for the route.
|
||||
|
||||
Defaults to ``True``.
|
||||
|
||||
CSRF protection only applies to *UNSAFE* methods as
|
||||
defined by :rfc:`7231`: GET, HEAD, TRACE and OPTIONS are
|
||||
safe, all other methods are unsafe.
|
||||
|
||||
CSRF protection requires a csrf token to be sent as part
|
||||
of the request's form data, it can be obtained via
|
||||
:meth:`request.csrf_token()
|
||||
<openerp.http.WebRequest.csrf_token>`
|
||||
"""
|
||||
routing = kw.copy()
|
||||
assert not 'type' in routing or routing['type'] in ("http", "json")
|
||||
assert 'type' not in routing or routing['type'] in ("http", "json")
|
||||
def decorator(f):
|
||||
if route:
|
||||
if isinstance(route, list):
|
||||
@@ -706,6 +759,12 @@ class HttpRequest(WebRequest):
|
||||
}
|
||||
return Response(status=200, headers=headers)
|
||||
|
||||
if request.httprequest.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE') \
|
||||
and request.endpoint.routing.get('csrf', True): # csrf checked by default
|
||||
token = self.params.pop('csrf_token', None)
|
||||
if not self.validate_csrf(token):
|
||||
raise werkzeug.exceptions.BadRequest('Invalid CSRF Token')
|
||||
|
||||
r = self._call_function(**self.params)
|
||||
if not r:
|
||||
r = Response(status=204) # no content
|
||||
@@ -1295,6 +1354,7 @@ class Response(werkzeug.wrappers.Response):
|
||||
"""
|
||||
view_obj = request.registry["ir.ui.view"]
|
||||
uid = self.uid or request.uid or openerp.SUPERUSER_ID
|
||||
self.qcontext['request'] = request
|
||||
return view_obj.render(
|
||||
request.cr, uid, self.template, self.qcontext,
|
||||
context=request.context)
|
||||
|
||||
@@ -12,6 +12,7 @@ from contextlib import contextmanager
|
||||
import subprocess
|
||||
import logging
|
||||
import os
|
||||
import passlib.utils
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
@@ -1157,3 +1158,10 @@ def formatLang(env, value, digits=None, grouping=True, monetary=False, dp=False,
|
||||
elif currency_obj and currency_obj.position == 'before':
|
||||
res = '%s %s' % (currency_obj.symbol, res)
|
||||
return res
|
||||
|
||||
def _consteq(str1, str2):
|
||||
""" Constant-time string comparison. Suitable to compare bytestrings of fixed,
|
||||
known length only, because length difference is optimized. """
|
||||
return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in zip(str1, str2)) == 0
|
||||
|
||||
consteq = getattr(passlib.utils, 'consteq', _consteq)
|
||||
Reference in New Issue
Block a user