[IMP] [TEST] website_forum: security fixes + tests

- fixed voting, karma check could be avoided
- fixed posting comments, now correctly checking karma (not for
notifications)
- fixed bootstraping of users, now not allowed to ask questions by default;
added validation email that gives the first karma points required to
participate
- added tests
This commit is contained in:
ssh-odoo 2014-09-19 12:55:19 +05:30 committed by Thibault Delavallée
parent bf3251fd0a
commit ef8099424d
11 changed files with 556 additions and 97 deletions

View File

@ -2,3 +2,4 @@
import controllers
import models
import tests

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import werkzeug.urls
import werkzeug.wrappers
import re
import simplejson
from openerp import tools
@ -13,7 +11,6 @@ from openerp.addons.web.controllers.main import login_redirect
from openerp.addons.web.http import request
from openerp.addons.website.controllers.main import Website as controllers
from openerp.addons.website.models.website import slug
from openerp.tools.translate import _
controllers = controllers()
@ -35,12 +32,15 @@ class WebsiteForum(http.Controller):
def _prepare_forum_values(self, forum=None, **kwargs):
user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, context=request.context)
values = {'user': user,
'is_public_user': user.id == request.website.user_id.id,
'notifications': self._get_notifications(),
'header': kwargs.get('header', dict()),
'searches': kwargs.get('searches', dict()),
}
values = {
'user': user,
'is_public_user': user.id == request.website.user_id.id,
'notifications': self._get_notifications(),
'header': kwargs.get('header', dict()),
'searches': kwargs.get('searches', dict()),
'validation_email_sent': request.session.get('validation_email_sent', False),
'validation_email_done': request.session.get('validation_email_done', False),
}
if forum:
values['forum'] = forum
elif kwargs.get('forum_id'):
@ -48,6 +48,34 @@ class WebsiteForum(http.Controller):
values.update(kwargs)
return values
# User and validation
# --------------------------------------------------
@http.route('/forum/send_validation_email', type='json', auth='user', website=True)
def send_validation_email(self, forum_id=None, **kwargs):
request.registry['res.users'].send_forum_validation_email(request.cr, request.uid, request.uid, forum_id=forum_id, context=request.context)
request.session['validation_email_sent'] = True
return True
@http.route('/forum/validate_email', type='http', auth='public', website=True)
def validate_email(self, token, id, email, forum_id=None, **kwargs):
if forum_id:
try:
forum_id = int(forum_id)
except ValueError:
forum_id = None
done = request.registry['res.users'].process_forum_validation_token(request.cr, request.uid, token, int(id), email, forum_id=forum_id, context=request.context)
if done:
request.session['validation_email_done'] = True
if forum_id:
return request.redirect("/forum/%s" % int(forum_id))
return request.redirect('/forum')
@http.route('/forum/validate_email/close', type='json', auth='public', website=True)
def validate_email_done(self):
request.session['validation_email_done'] = False
return True
# Forum
# --------------------------------------------------
@ -298,10 +326,12 @@ class WebsiteForum(http.Controller):
cr, uid, context = request.cr, request.uid, request.context
if kwargs.get('comment') and post.forum_id.id == forum.id:
# TDE FIXME: check that post_id is the question or one of its answers
request.registry['forum.post']._post_comment(
cr, uid, post,
request.registry['forum.post'].message_post(
cr, uid, post.id,
body=kwargs.get('comment'),
context=context)
type='comment',
subtype='mt_comment',
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)

View File

@ -100,5 +100,36 @@
<field name="name">too localized</field>
</record>
<!-- Email template for email validation (for karma purpose) -->
<record id="validation_email" model="email.template">
<field name="name">Email Verification</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="email_from"><![CDATA[${object.company_id.name} <${(object.company_id.email or user.email)|safe}>]]></field>
<field name="email_to">${object.email|safe}</field>
<field name="subject"><![CDATA[${object.company_id.name} Forums validation]]></field>
<field name="body_html"><![CDATA[
<p>
Hello ${object.name},
</p>
<p>
You have been invited to validate your email in order to get access to "${object.company_id.name}" Q/A Forums.
</p>
<p>
To validate your email, please click on the following link:
</p>
<ul>
<li><a href="${ctx.get('token_url')}">Validate my account for "${object.company_id.name}" Q/A Forums</a></li>
</ul>
<p>
Thanks,
</p>
<pre>
--
${object.company_id.name or ''}
${object.company_id.email or ''}
${object.company_id.phone or ''}
</pre>]]></field>
</record>
</data>
</openerp>

View File

@ -1,16 +1,18 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import uuid
from werkzeug.exceptions import Forbidden
import openerp
from openerp import tools
from openerp import api, tools
from openerp import SUPERUSER_ID
from openerp.addons.website.models.website import slug
from openerp.exceptions import Warning
from openerp.osv import osv, fields
from openerp.tools import html2plaintext
from openerp.tools.translate import _
from werkzeug.exceptions import Forbidden
class KarmaError(Forbidden):
""" Karma-related error, used for forum and posts. """
@ -23,42 +25,48 @@ class Forum(osv.Model):
_description = 'Forums'
_inherit = ['mail.thread', 'website.seo.metadata']
def init(self, cr):
""" Add forum uuid for user email validation. """
forum_uuids = self.pool['ir.config_parameter'].search(cr, SUPERUSER_ID, [('key', '=', 'website_forum.uuid')])
if not forum_uuids:
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'website_forum.uuid', str(uuid.uuid4()), ['base.group_system'])
_columns = {
'name': fields.char('Name', required=True, translate=True),
'faq': fields.html('Guidelines'),
'description': fields.html('Description'),
# karma generation
'karma_gen_question_new': fields.integer('Karma earned for new questions'),
'karma_gen_question_upvote': fields.integer('Karma earned for upvoting a question'),
'karma_gen_question_downvote': fields.integer('Karma earned for downvoting a question'),
'karma_gen_answer_upvote': fields.integer('Karma earned for upvoting an answer'),
'karma_gen_answer_downvote': fields.integer('Karma earned for downvoting an answer'),
'karma_gen_answer_accept': fields.integer('Karma earned for accepting an anwer'),
'karma_gen_answer_accepted': fields.integer('Karma earned for having an answer accepted'),
'karma_gen_answer_flagged': fields.integer('Karma earned for having an answer flagged'),
'karma_gen_question_new': fields.integer('Asking a question'),
'karma_gen_question_upvote': fields.integer('Question upvoted'),
'karma_gen_question_downvote': fields.integer('Question downvoted'),
'karma_gen_answer_upvote': fields.integer('Answer upvoted'),
'karma_gen_answer_downvote': fields.integer('Answer downvoted'),
'karma_gen_answer_accept': fields.integer('Accepting an answer'),
'karma_gen_answer_accepted': fields.integer('Answer accepted'),
'karma_gen_answer_flagged': fields.integer('Answer flagged'),
# karma-based actions
'karma_ask': fields.integer('Karma to ask a new question'),
'karma_answer': fields.integer('Karma to answer a question'),
'karma_edit_own': fields.integer('Karma to edit its own posts'),
'karma_edit_all': fields.integer('Karma to edit all posts'),
'karma_close_own': fields.integer('Karma to close its own posts'),
'karma_close_all': fields.integer('Karma to close all posts'),
'karma_unlink_own': fields.integer('Karma to delete its own posts'),
'karma_unlink_all': fields.integer('Karma to delete all posts'),
'karma_upvote': fields.integer('Karma to upvote'),
'karma_downvote': fields.integer('Karma to downvote'),
'karma_answer_accept_own': fields.integer('Karma to accept an answer on its own questions'),
'karma_answer_accept_all': fields.integer('Karma to accept an answers to all questions'),
'karma_editor_link_files': fields.integer('Karma for linking files (Editor)'),
'karma_editor_clickable_link': fields.integer('Karma for clickable links (Editor)'),
'karma_comment_own': fields.integer('Karma to comment its own posts'),
'karma_comment_all': fields.integer('Karma to comment all posts'),
'karma_comment_convert_own': fields.integer('Karma to convert its own answers to comments and vice versa'),
'karma_comment_convert_all': fields.integer('Karma to convert all answers to answers and vice versa'),
'karma_comment_unlink_own': fields.integer('Karma to unlink its own comments'),
'karma_comment_unlink_all': fields.integer('Karma to unlinnk all comments'),
'karma_retag': fields.integer('Karma to change question tags'),
'karma_flag': fields.integer('Karma to flag a post as offensive'),
'karma_ask': fields.integer('Ask a question'),
'karma_answer': fields.integer('Answer a question'),
'karma_edit_own': fields.integer('Edit its own posts'),
'karma_edit_all': fields.integer('Edit all posts'),
'karma_close_own': fields.integer('Close its own posts'),
'karma_close_all': fields.integer('Close all posts'),
'karma_unlink_own': fields.integer('Delete its own posts'),
'karma_unlink_all': fields.integer('Delete all posts'),
'karma_upvote': fields.integer('Upvote'),
'karma_downvote': fields.integer('Downvote'),
'karma_answer_accept_own': fields.integer('Accept an answer on its own questions'),
'karma_answer_accept_all': fields.integer('Accept an answer to all questions'),
'karma_editor_link_files': fields.integer('Linking files (Editor)'),
'karma_editor_clickable_link': fields.integer('Clickable links (Editor)'),
'karma_comment_own': fields.integer('Comment its own posts'),
'karma_comment_all': fields.integer('Comment all posts'),
'karma_comment_convert_own': fields.integer('Convert its own answers to comments and vice versa'),
'karma_comment_convert_all': fields.integer('Convert all answers to comments and vice versa'),
'karma_comment_unlink_own': fields.integer('Unlink its own comments'),
'karma_comment_unlink_all': fields.integer('Unlink all comments'),
'karma_retag': fields.integer('Change question tags'),
'karma_flag': fields.integer('Flag a post as offensive'),
}
def _get_default_faq(self, cr, uid, context=None):
@ -70,7 +78,7 @@ class Forum(osv.Model):
_defaults = {
'description': 'This community is for professionals and enthusiasts of our products and services.',
'faq': _get_default_faq,
'karma_gen_question_new': 2,
'karma_gen_question_new': 0, # set to null for anti spam protection
'karma_gen_question_upvote': 5,
'karma_gen_question_downvote': -2,
'karma_gen_answer_upvote': 10,
@ -78,8 +86,8 @@ class Forum(osv.Model):
'karma_gen_answer_accept': 2,
'karma_gen_answer_accepted': 15,
'karma_gen_answer_flagged': -100,
'karma_ask': 0,
'karma_answer': 0,
'karma_ask': 3, # set to not null for anti spam protection
'karma_answer': 3, # set to not null for anti spam protection
'karma_edit_own': 1,
'karma_edit_all': 300,
'karma_close_own': 100,
@ -92,8 +100,8 @@ class Forum(osv.Model):
'karma_answer_accept_all': 500,
'karma_editor_link_files': 20,
'karma_editor_clickable_link': 20,
'karma_comment_own': 1,
'karma_comment_all': 1,
'karma_comment_own': 3,
'karma_comment_all': 5,
'karma_comment_convert_own': 50,
'karma_comment_convert_all': 500,
'karma_comment_unlink_own': 50,
@ -393,13 +401,6 @@ class Post(osv.Model):
return super(Post, self).unlink(cr, uid, ids, context=context)
def vote(self, cr, uid, ids, upvote=True, context=None):
posts = self.browse(cr, uid, ids, context=context)
if upvote and any(not post.can_upvote for post in posts):
raise KarmaError('Not enough karma to upvote.')
elif not upvote and any(not post.can_downvote for post in posts):
raise KarmaError('Not enough karma to downvote.')
Vote = self.pool['forum.post.vote']
vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
new_vote = '1' if upvote else '-1'
@ -509,15 +510,18 @@ class Post(osv.Model):
res_id = post.parent_id and "%s#answer-%s" % (post.parent_id.id, post.id) or post.id
return "/forum/%s/question/%s" % (post.forum_id.id, res_id)
def _post_comment(self, cr, uid, post, body, context=None):
context = dict(context or {}, mail_create_nosubcribe=True)
if not post.can_comment:
raise KarmaError('Not enough karma to comment')
return self.message_post(cr, uid, post.id,
body=body,
type='comment',
subtype='mt_comment',
context=context)
@api.cr_uid_ids_context
def message_post(self, cr, uid, thread_id, type='notification', subtype=None, context=None, **kwargs):
if thread_id and type == 'comment': # user comments have a restriction on karma
if isinstance(thread_id, (list, tuple)):
post_id = thread_id[0]
else:
post_id = thread_id
post = self.browse(cr, uid, post_id, context=context)
if not post.can_comment:
raise KarmaError('Not enough karma to comment')
return super(Post, self).message_post(cr, uid, thread_id, type=type, subtype=subtype, context=context, **kwargs)
class PostReason(osv.Model):
_name = "forum.post.reason"
@ -557,6 +561,17 @@ class Vote(osv.Model):
def create(self, cr, uid, vals, context=None):
vote_id = super(Vote, self).create(cr, uid, vals, context=context)
vote = self.browse(cr, uid, vote_id, context=context)
# own post check
if vote.user_id.id == vote.post_id.create_uid.id:
raise Warning('Not allowed to vote for its own post')
# karma check
if vote.vote == '1' and not vote.post_id.can_upvote:
raise KarmaError('Not enough karma to upvote.')
elif vote.vote == '-1' and not vote.post_id.can_downvote:
raise KarmaError('Not enough karma to downvote.')
# karma update
if vote.post_id.parent_id:
karma_value = self._get_karma_value('0', vote.vote, vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
else:
@ -567,6 +582,16 @@ class Vote(osv.Model):
def write(self, cr, uid, ids, values, context=None):
if 'vote' in values:
for vote in self.browse(cr, uid, ids, context=context):
# own post check
if vote.user_id.id == vote.post_id.create_uid.id:
raise Warning('Not allowed to vote for its own post')
# karma check
if (values['vote'] == '1' or vote.vote == '-1' and values['vote'] == '0') and not vote.post_id.can_upvote:
raise KarmaError('Not enough karma to upvote.')
elif (values['vote'] == '-1' or vote.vote == '1' and values['vote'] == '0') and not vote.post_id.can_downvote:
raise KarmaError('Not enough karma to downvote.')
# karma update
if vote.post_id.parent_id:
karma_value = self._get_karma_value(vote.vote, values['vote'], vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
else:

View File

@ -1,14 +1,22 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from urllib import urlencode
import hashlib
from openerp import SUPERUSER_ID
from openerp.osv import osv, fields
class Users(osv.Model):
_inherit = 'res.users'
def __init__(self, pool, cr):
init_res = super(Users, self).__init__(pool, cr)
self.SELF_WRITEABLE_FIELDS = list(set(
self.SELF_WRITEABLE_FIELDS + \
init_res = super(Users, self).__init__(pool, cr)
self.SELF_WRITEABLE_FIELDS = list(
set(
self.SELF_WRITEABLE_FIELDS +
['country_id', 'city', 'website', 'website_description', 'website_published']))
return init_res
@ -37,6 +45,50 @@ class Users(osv.Model):
'karma': 0,
}
def _generate_forum_token(self, cr, uid, user_id, email):
"""Return a token for email validation. This token is valid for the day
and is a hash based on a (secret) uuid generated by the forum module,
the user_id, the email and currently the day (to be updated if necessary). """
forum_uuid = self.pool.get('ir.config_parameter').get_param(cr, SUPERUSER_ID, 'website_forum.uuid')
return hashlib.sha256('%s-%s-%s-%s' % (
datetime.now().replace(hour=0, minute=0, second=0, microsecond=0),
forum_uuid,
user_id,
email)).hexdigest()
def send_forum_validation_email(self, cr, uid, user_id, forum_id=None, context=None):
user = self.pool['res.users'].browse(cr, uid, user_id, context=context)
token = self._generate_forum_token(cr, uid, user_id, user.email)
activation_template_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.validation_email')
if activation_template_id:
params = {
'token': token,
'id': user_id,
'email': user.email}
if forum_id:
params['forum_id'] = forum_id
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
token_url = base_url + '/forum/validate_email?%s' % urlencode(params)
tpl_ctx = dict(context, token_url=token_url)
self.pool['email.template'].send_mail(cr, SUPERUSER_ID, activation_template_id, user_id, force_send=True, context=tpl_ctx)
return True
def process_forum_validation_token(self, cr, uid, token, user_id, email, forum_id=None, context=None):
validation_token = self.pool['res.users']._generate_forum_token(cr, uid, user_id, email)
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, user_id, context=context)
if token == validation_token and user.karma == 0:
karma = 3
if not forum_id:
forum_ids = self.pool['forum.forum'].search(cr, uid, [], limit=1, context=context)
if forum_ids:
forum_id = forum_ids[0]
if forum_id:
forum = self.pool['forum.forum'].browse(cr, uid, forum_id, context=context)
# karma gained: karma to ask a question and have 2 downvotes
karma = forum.karma_ask + (-2 * forum.karma_gen_question_downvote)
return user.write({'karma': karma})
return False
def add_karma(self, cr, uid, ids, karma, context=None):
for user in self.browse(cr, uid, ids, context=context):
self.write(cr, uid, [user.id], {'karma': user.karma + karma}, context=context)

View File

@ -6,8 +6,8 @@ $(document).ready(function () {
ev.preventDefault();
var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="karma_alert">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
karma + ' karma is required to perform this action. You can earn karma by answering questions or having '+
'your answers upvoted by the community.</div>');
karma + ' karma is required to perform this action. You can earn karma by having '+
'your answers upvoted by the community.</div>');
var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
if (vote_alert.length == 0) {
$(ev.currentTarget).parent().append($warning);
@ -50,7 +50,6 @@ $(document).ready(function () {
}
}
});
return true;
});
$('.accept_answer').not('.karma_required').on('click', function (ev) {
@ -76,7 +75,6 @@ $(document).ready(function () {
}
}
});
return true;
});
$('.favourite_question').on('click', function (ev) {
@ -89,7 +87,6 @@ $(document).ready(function () {
$link.removeClass("forum_favourite_question")
}
});
return true;
});
$('.comment_delete').on('click', function (ev) {
@ -98,15 +95,29 @@ $(document).ready(function () {
openerp.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
$link.parents('.comment').first().remove();
});
return true;
});
$('.notification_close').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
openerp.jsonRpc("/forum/notification_read", 'call', {
'notification_id': $link.attr("id")})
return true;
'notification_id': $link.attr("id")});
});
$('.send_validation_email').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
openerp.jsonRpc("/forum/send_validation_email", 'call', {
'forum_id': $link.attr('forum-id'),
}).then(function (data) {
if (data) {
$('button.validation_email_close').click();
}
});
});
$('.validated_email_close').on('click', function (ev) {
openerp.jsonRpc("/forum/validate_email/close", 'call', {});
});
if($('input.load_tags').length){

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
import common
import test_forum

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
from openerp.tests import common
KARMA = {
'ask': 5, 'ans': 10,
'com_own': 5, 'com_all': 10,
'com_conv_all': 50,
'upv': 5, 'dwv': 10,
'edit_own': 10, 'edit_all': 20,
'close_own': 10, 'close_all': 20,
'unlink_own': 10, 'unlink_all': 20,
'gen_que_new': 1, 'gen_que_upv': 5, 'gen_que_dwv': -10,
'gen_ans_upv': 10, 'gen_ans_dwv': -20,
}
class TestForumCommon(common.TransactionCase):
def setUp(self):
super(TestForumCommon, self).setUp()
Forum = self.env['forum.forum']
Post = self.env['forum.post']
# Test users
TestUsersEnv = self.env['res.users'].with_context({'no_reset_password': True})
group_employee_id = self.ref('base.group_user')
group_portal_id = self.ref('base.group_portal')
group_public_id = self.ref('base.group_public')
self.user_employee = TestUsersEnv.create({
'name': 'Armande Employee',
'login': 'Armande',
'alias_name': 'armande',
'email': 'armande.employee@example.com',
'karma': 0,
'groups_id': [(6, 0, [group_employee_id])]
})
self.user_portal = TestUsersEnv.create({
'name': 'Beatrice Portal',
'login': 'Beatrice',
'alias_name': 'beatrice',
'email': 'beatrice.employee@example.com',
'karma': 0,
'groups_id': [(6, 0, [group_portal_id])]
})
self.user_public = TestUsersEnv.create({
'name': 'Cedric Public',
'login': 'Cedric',
'alias_name': 'cedric',
'email': 'cedric.employee@example.com',
'karma': 0,
'groups_id': [(6, 0, [group_public_id])]
})
# Test forum
self.forum = Forum.create({
'name': 'TestForum',
'karma_ask': KARMA['ask'],
'karma_answer': KARMA['ans'],
'karma_comment_own': KARMA['com_own'],
'karma_comment_all': KARMA['com_all'],
'karma_answer_accept_own': 9999,
'karma_answer_accept_all': 9999,
'karma_upvote': KARMA['upv'],
'karma_downvote': KARMA['dwv'],
'karma_edit_own': KARMA['edit_own'],
'karma_edit_all': KARMA['edit_all'],
'karma_close_own': KARMA['close_own'],
'karma_close_all': KARMA['close_all'],
'karma_unlink_own': KARMA['unlink_own'],
'karma_unlink_all': KARMA['unlink_all'],
'karma_comment_convert_all': KARMA['com_conv_all'],
'karma_gen_question_new': KARMA['gen_que_new'],
'karma_gen_question_upvote': KARMA['gen_que_upv'],
'karma_gen_question_downvote': KARMA['gen_que_dwv'],
'karma_gen_answer_upvote': KARMA['gen_ans_upv'],
'karma_gen_answer_downvote': KARMA['gen_ans_dwv'],
'karma_gen_answer_accept': 9999,
'karma_gen_answer_accepted': 9999,
})
self.post = Post.create({
'name': 'TestQuestion',
'content': 'I am not a bird.',
'forum_id': self.forum.id,
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': self.forum.id})]
})
self.answer = Post.create({
'name': 'TestAnswer',
'content': 'I am an anteater.',
'forum_id': self.forum.id,
'parent_id': self.post.id,
})

View File

@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
from openerp.addons.website_forum.tests.common import KARMA, TestForumCommon
from openerp.addons.website_forum.models.forum import KarmaError
from openerp.exceptions import Warning, AccessError
from openerp.tools import mute_logger
class TestForum(TestForumCommon):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
def test_ask(self):
Post = self.env['forum.post']
# Public user asks a question: not allowed
with self.assertRaises(AccessError):
Post.sudo(self.user_public).create({
'name': " Question ?",
'forum_id': self.forum.id,
})
# Portal user asks a question with tags: not allowed, unsufficient karma
with self.assertRaises(KarmaError):
Post.sudo(self.user_portal).create({
'name': " Q_0",
'forum_id': self.forum.id,
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': self.forum.id})]
})
# Portal user asks a question with tags: ok if enough karma
self.user_portal.karma = KARMA['ask']
Post.sudo(self.user_portal).create({
'name': " Q0",
'forum_id': self.forum.id,
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': self.forum.id})]
})
self.assertEqual(self.user_portal.karma, KARMA['ask'] + KARMA['gen_que_new'], 'website_forum: wrong karma generation when asking question')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
def test_answer(self):
Post = self.env['forum.post']
# Answers its own question: not allowed, unsufficient karma
with self.assertRaises(KarmaError):
Post.sudo(self.user_employee).create({
'name': " A0",
'forum_id': self.forum.id,
'parent_id': self.post.id,
})
# Answers on question: ok if enough karma
self.user_employee.karma = KARMA['ans']
Post.sudo(self.user_employee).create({
'name': " A0",
'forum_id': self.forum.id,
'parent_id': self.post.id,
})
self.assertEqual(self.user_employee.karma, KARMA['ans'], 'website_forum: wrong karma generation when answering question')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
def test_vote_crash(self):
Post = self.env['forum.post']
self.user_employee.karma = KARMA['ans']
emp_answer = Post.sudo(self.user_employee).create({
'name': 'TestAnswer',
'forum_id': self.forum.id,
'parent_id': self.post.id})
# upvote its own post
with self.assertRaises(Warning):
emp_answer.vote(upvote=True)
# not enough karma
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).vote(upvote=True)
def test_vote(self):
self.post.create_uid.karma = KARMA['ask']
self.user_portal.karma = KARMA['upv']
self.post.sudo(self.user_portal).vote(upvote=True)
self.assertEqual(self.post.create_uid.karma, KARMA['ask'] + KARMA['gen_que_upv'], 'website_forum: wrong karma generation of upvoted question author')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
def test_downvote_crash(self):
Post = self.env['forum.post']
self.user_employee.karma = KARMA['ans']
emp_answer = Post.sudo(self.user_employee).create({
'name': 'TestAnswer',
'forum_id': self.forum.id,
'parent_id': self.post.id})
# downvote its own post
with self.assertRaises(Warning):
emp_answer.vote(upvote=False)
# not enough karma
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).vote(upvote=False)
def test_downvote(self):
self.post.create_uid.karma = 50
self.user_portal.karma = KARMA['dwv']
self.post.sudo(self.user_portal).vote(upvote=False)
self.assertEqual(self.post.create_uid.karma, 50 + KARMA['gen_que_dwv'], 'website_forum: wrong karma generation of downvoted question author')
def test_comment_crash(self):
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).message_post(body='Should crash', type='comment')
def test_comment(self):
self.post.sudo(self.user_employee).message_post(body='Test0', type='notification')
self.user_employee.karma = KARMA['com_all']
self.post.sudo(self.user_employee).message_post(body='Test1', type='comment')
self.assertEqual(len(self.post.message_ids), 4, 'website_forum: wrong behavior of message_post')
def test_convert_answer_to_comment_crash(self):
Post = self.env['forum.post']
# converting a question does nothing
msg_ids = self.post.sudo(self.user_portal).convert_answer_to_comment()
self.assertEqual(msg_ids[0], False, 'website_forum: question to comment conversion failed')
self.assertEqual(Post.search([('name', '=', 'TestQuestion')])[0].forum_id.name, 'TestForum', 'website_forum: question to comment conversion failed')
with self.assertRaises(KarmaError):
self.answer.sudo(self.user_portal).convert_answer_to_comment()
def test_convert_answer_to_comment(self):
self.user_portal.karma = KARMA['com_conv_all']
post_author = self.answer.create_uid.partner_id
msg_ids = self.answer.sudo(self.user_portal).convert_answer_to_comment()
self.assertEqual(len(msg_ids), 1, 'website_forum: wrong answer to comment conversion')
msg = self.env['mail.message'].browse(msg_ids[0])
self.assertEqual(msg.author_id, post_author, 'website_forum: wrong answer to comment conversion')
self.assertIn('I am an anteater', msg.body, 'website_forum: wrong answer to comment conversion')
def test_edit_post_crash(self):
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).write({'name': 'I am not your father.'})
def test_edit_post(self):
self.post.create_uid.karma = KARMA['edit_own']
self.post.write({'name': 'Actually I am your dog.'})
self.user_portal.karma = KARMA['edit_all']
self.post.sudo(self.user_portal).write({'name': 'Actually I am your cat.'})
def test_close_post_crash(self):
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).close(None)
def test_close_post_own(self):
self.post.create_uid.karma = KARMA['close_own']
self.post.close(None)
def test_close_post_all(self):
self.user_portal.karma = KARMA['close_all']
self.post.sudo(self.user_portal).close(None)
def test_deactivate_post_crash(self):
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).write({'active': False})
def test_deactivate_post_own(self):
self.post.create_uid.karma = KARMA['unlink_own']
self.post.write({'active': False})
def test_deactivate_post_all(self):
self.user_portal.karma = KARMA['unlink_all']
self.post.sudo(self.user_portal).write({'active': False})
def test_unlink_post_crash(self):
with self.assertRaises(KarmaError):
self.post.sudo(self.user_portal).unlink()
def test_unlink_post_own(self):
self.post.create_uid.karma = KARMA['unlink_own']
self.post.unlink()
def test_unlink_post_all(self):
self.user_portal.karma = KARMA['unlink_all']
self.post.sudo(self.user_portal).unlink()

View File

@ -24,28 +24,45 @@
<sheet>
<group>
<field name="name"/>
<field name="karma_ask"/>
<field name="karma_edit_own"/>
<field name="karma_edit_all"/>
<field name="karma_close_own"/>
<field name="karma_close_all"/>
<field name="karma_unlink_own"/>
<field name="karma_unlink_all"/>
</group>
<group>
<field name="karma_upvote"/>
<field name="karma_downvote"/>
<field name="karma_answer_accept_own"/>
<field name="karma_answer_accept_all"/>
<field name="karma_editor_link_files"/>
<field name="karma_editor_clickable_link"/>
<field name="karma_comment_own"/>
<field name="karma_comment_all"/>
<field name="karma_comment_convert_own"/>
<field name="karma_comment_convert_all"/>
<field name="karma_comment_unlink_own"/>
<field name="karma_comment_unlink_all"/>
</group>
<notebook>
<page string='Karma Gains'>
<group>
<field name="karma_gen_question_new"/>
<field name="karma_gen_question_upvote"/>
<field name="karma_gen_question_downvote"/>
<field name="karma_gen_answer_upvote"/>
<field name="karma_gen_answer_downvote"/>
<field name="karma_gen_answer_accept"/>
<field name="karma_gen_answer_accepted"/>
</group>
</page>
<page string='Karma Requirements'>
<group>
<group>
<field name="karma_ask"/>
<field name="karma_upvote"/>
<field name="karma_downvote"/>
<field name="karma_edit_own"/>
<field name="karma_edit_all"/>
<field name="karma_close_own"/>
<field name="karma_close_all"/>
<field name="karma_unlink_own"/>
<field name="karma_unlink_all"/>
</group>
<group>
<field name="karma_answer_accept_own"/>
<field name="karma_answer_accept_all"/>
<field name="karma_comment_own"/>
<field name="karma_comment_all"/>
<field name="karma_comment_convert_own"/>
<field name="karma_comment_convert_all"/>
<field name="karma_comment_unlink_own"/>
<field name="karma_comment_unlink_all"/>
</group>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>

View File

@ -96,6 +96,21 @@
<div t-field="notification.body"/>
<a t-attf-href="/forum/#{ slug(forum) }/user/#{ user.id }#badges" class="fa fa-arrow-right">View Your Badges</a>
</div>
<div t-if="not validation_email_sent and not is_public_user and user.karma == 0" class="alert alert-danger alert-dismissable">
<button type="button" class="close validation_email_close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<div>
<p>
It appears your email has not been verified.
<a class="send_validation_email" href="#" t-att-forum-id="forum.id">Click here to send a verification email allowing you to participate to the forum.</a>
</p>
</div>
</div>
<div t-if="validation_email_done" class="alert alert-success alert-dismissable">
<button type="button" class="close validated_email_close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<div>
<p>Congratulations! Your email has just been validated. You may now participate to our forums.</p>
</div>
</div>
<t t-raw="0"/>
</div>
<div class="col-sm-3" id="right-column">