Skip to content
1 change: 1 addition & 0 deletions openreview/arr/arr.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ def create_bid_stages(self):

def create_comment_stage(self):
self.venue.comment_stage = self.comment_stage
self.venue.comment_stage.preprocess_path = '../arr/process/comment_pre_process.js'
self.venue.comment_stage.process_path = '../arr/process/comment_process.py'
return self.venue.create_comment_stage()

Expand Down
129 changes: 129 additions & 0 deletions openreview/arr/process/comment_pre_process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
async function process(client, edit, invitation) {
client.throwErrors = true

const { note } = edit
if (note.ddate) {
return
}

const [res1, res2] = await Promise.all([
client.getNotes({ id: note.forum }),
client.getGroups({ id: invitation.domain }),
])
const forum = res1.notes[0]
const domain = res2.groups[0]
const readers = note.readers || []
const authorsName = domain.content?.authors_name?.value || "Authors"
const authorGroupId = `${domain.id}/Submission${forum.number}/${authorsName}`
const commentText = note.content?.comment?.value || ""

function containsLink(text) {
const tokenChecks = [
["includes", ["http://", "https://", "www."]]
]
const popularDomainSuffixes = [
".com",
".net",
".org",
".io",
".co",
".tv",
".cn",
".de",
".uk",
".ru",
".nl",
".br"
]
const boundaryChars = "<>()[]{}.,;:!?\"'"
let lowerText = String(text || "").toLowerCase()

for (const whitespace of ["\n", "\r", "\t"]) {
lowerText = lowerText.split(whitespace).join(" ")
}

function trimToken(token) {
let start = 0
let end = token.length
while (start < end && boundaryChars.includes(token[start])) {
start += 1
}
while (end > start && boundaryChars.includes(token[end - 1])) {
end -= 1
}
return token.slice(start, end)
}

function matches(value, checks) {
for (const [operation, parts] of checks) {
for (const part of parts) {
if (value[operation](part)) {
return true
}
}
}
return false
}

for (const rawToken of lowerText.split(" ")) {
const token = trimToken(rawToken)
// Skip empty tokens, emails, and tokens that cannot be hostnames.
if (!token || token.includes("@") || !token.includes(".")) {
continue
}
// Return early for explicit URL schemes and common www-style hosts.
if (matches(token, tokenChecks)) {
return true
}

// Trim the token to a host-like prefix before matching common web suffixes.
let host = token
for (const separator of ["/", "?", "#", ":"]) {
const index = host.indexOf(separator)
if (index !== -1) {
host = host.slice(0, index)
}
}
host = trimToken(host)

// Match hosts that end in one of the common web suffixes.
const firstDot = host.indexOf(".")
if (firstDot > 0 && firstDot < host.length - 1) {
for (const suffix of popularDomainSuffixes) {
if (host.endsWith(suffix)) {
return true
}
}
}
}

return false
}

if ((readers.includes(authorGroupId) || readers.includes("everyone")) && containsLink(commentText)) {
return Promise.reject(
new OpenReviewError({
name: "Error",
message: "Links are not allowed in official comments that are visible to authors.",
})
)
}

if (readers.includes("everyone")) {
return
}

const commentMandatoryReaders =
domain.content?.comment_mandatory_readers?.value || []
for (const m of commentMandatoryReaders) {
const reader = m.replace("{number}", forum.number)
if (!readers.includes(reader)) {
return Promise.reject(
new OpenReviewError({
name: "Error",
message: reader + " must be readers of the comment",
})
)
}
}
}
177 changes: 177 additions & 0 deletions tests/test_arr_venue_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5327,6 +5327,183 @@ def test_author_response(self, client, openreview_client, helpers, test_client,
reviewer_client = openreview.api.OpenReviewClient(username = 'reviewer2@aclrollingreview.com', password=helpers.strong_password)
anon_id = reviewer_client.get_groups(prefix=f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewer_', signatory='~Reviewer_ARRTwo1')[0].id
ac_client = openreview.api.OpenReviewClient(username = 'ac1@aclrollingreview.com', password=helpers.strong_password)

with pytest.raises(openreview.OpenReviewException, match=r'Links are not allowed in official comments that are visible to authors.'):
reviewer_client.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=[anon_id],
note=openreview.api.Note(
replyto=submissions[1].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewers',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'
],
content={
"comment": { "value": "Please see https://example.com/details for more information."}
}
)
)

with pytest.raises(openreview.OpenReviewException, match=r'Links are not allowed in official comments that are visible to authors.'):
test_client.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=[f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'],
note=openreview.api.Note(
replyto=submissions[1].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewers',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'
],
content={
"comment": { "value": "Please see [this note](https://example.com/details)." }
}
)
)

Comment thread
haroldrubio marked this conversation as resolved.
with pytest.raises(openreview.OpenReviewException, match=r'Links are not allowed in official comments that are visible to authors.'):
test_client.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=[f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'],
note=openreview.api.Note(
replyto=submissions[1].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewers',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'
],
content={
"comment": { "value": "Please see www.example.com for more information." }
}
)
)

with pytest.raises(openreview.OpenReviewException, match=r'Links are not allowed in official comments that are visible to authors.'):
test_client.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=[f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'],
note=openreview.api.Note(
replyto=submissions[1].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewers',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'
],
content={
"comment": { "value": "Please see example.com for more information." }
}
)
)

with pytest.raises(openreview.OpenReviewException, match=r'Links are not allowed in official comments that are visible to authors.'):
test_client.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=[f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'],
note=openreview.api.Note(
replyto=submissions[1].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewers',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'
],
content={
"comment": { "value": "Please see example.co.uk for more information." }
}
)
)

comment_edit = pc_client_v2.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=['aclweb.org/ACL/ARR/2023/August/Program_Chairs'],
note=openreview.api.Note(
replyto=submissions[0].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Authors'
],
content={
"comment": { "value": "Please email pc@example.com for more information." }
}
)
)
helpers.await_queue_edit(openreview_client, edit_id=comment_edit['id'])

comment_edit = pc_client_v2.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=['aclweb.org/ACL/ARR/2023/August/Program_Chairs'],
note=openreview.api.Note(
replyto=submissions[0].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Authors'
],
content={
"comment": { "value": "Please see example.museum for more information." }
}
)
)
helpers.await_queue_edit(openreview_client, edit_id=comment_edit['id'])

with pytest.raises(openreview.OpenReviewException, match=r'Links are not allowed in official comments that are visible to authors.'):
test_client.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=[f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'],
note=openreview.api.Note(
replyto=submissions[1].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Reviewers',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors'
],
content={
"comment": { "value": "Please see example.io for more information." }
}
)
)

comment_edit = pc_client_v2.post_note_edit(
invitation=f"aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/-/Official_Comment",
writers=['aclweb.org/ACL/ARR/2023/August'],
signatures=['aclweb.org/ACL/ARR/2023/August/Program_Chairs'],
note=openreview.api.Note(
replyto=submissions[0].id,
readers=[
'aclweb.org/ACL/ARR/2023/August/Program_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Senior_Area_Chairs',
f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[0].number}/Area_Chairs'
],
content={
"comment": { "value": "Internal discussion: https://example.com/internal-doc" }
}
)
)
helpers.await_queue_edit(openreview_client, edit_id=comment_edit['id'])

clients = [reviewer_client, test_client]
signatures = [anon_id, f'aclweb.org/ACL/ARR/2023/August/Submission{submissions[1].number}/Authors']
root_note_id = None
Expand Down