You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by ke...@apache.org on 2026/05/13 17:55:41 UTC
(allura) branch master updated (640a285a9 -> 6754e9c9f)
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
from 640a285a9 bumping urllib3 2.6.3 -> 2.7.0 and gitpython 3.1.47 -> 3.1.50
new 06d41c29a [#8603] don't follow redirects on trac import
new 56e69cbb3 [#8603] disable trac importers by default
new 6defa33fb [#8603] use NonPrivateUrl on webhooks (does allow for IP addrs now also)
new bbd1d7150 [#8603] urlopen_allow_internal_hostnames setting should work for webhook and importer forms
new e154040e0 [#8603] test to know forgeblog/main.py setattr loop is ok
new bc0c96d0d [#8603] add @require_post to a few endpoints
new 46cb8e64f [#8603] change AlluraUserProperty User reference so that ShortUrl doesn't error strangely on shorturl.create_user = c.user._id (tests were always passing too, weird)
new 7013d3506 [#8603] use _verify_return_to in pwd_expired_change
new 4fe4e2808 [#8603] only permit local access to /auth/repo_permissions
new b7d28e892 [#8603] escape regexes in tag search
new 8e96d7009 [#8603] NonPrivateUrl checks all host=>IP resolutions not just one
new bb66149d5 [#8603] use Markup in SxsOutputGenerator
new 68b8d44c0 [#8603] validate URLs on clone tasks too (e.g. if task delayed from form usage, and DNS changes)
new 6754e9c9f [#8603] check multifactor login mode
The 14 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
Allura/allura/controllers/auth.py | 11 +++-
Allura/allura/controllers/discuss.py | 2 +
Allura/allura/lib/diff.py | 28 ++++-----
Allura/allura/lib/validators.py | 22 +++++--
Allura/allura/lib/widgets/discuss.py | 7 +++
Allura/allura/model/auth.py | 2 +-
Allura/allura/tasks/repo_tasks.py | 4 ++
Allura/allura/tests/functional/test_auth.py | 26 +++++++-
Allura/allura/tests/test_diff.py | 16 ++++-
Allura/allura/tests/test_webhooks.py | 69 ++++++++++++++--------
Allura/allura/tests/unit/patches.py | 14 ++++-
Allura/allura/webhooks.py | 9 ++-
Allura/development.ini | 9 +--
ForgeBlog/forgeblog/tests/functional/test_root.py | 29 +++++++++
ForgeImporters/forgeimporters/trac/__init__.py | 2 +-
.../trac/tests/functional/test_trac.py | 7 ++-
.../forgeimporters/trac/tests/test_tickets.py | 6 ++
ForgeSVN/forgesvn/tests/test_tasks.py | 14 ++++-
ForgeShortUrl/forgeshorturl/main.py | 2 +
ForgeShortUrl/forgeshorturl/model/shorturl.py | 12 ++--
ForgeTracker/forgetracker/tracker_main.py | 2 +-
21 files changed, 226 insertions(+), 67 deletions(-)
(allura) 07/14: [#8603] change AlluraUserProperty User reference so that ShortUrl doesn't error strangely on shorturl.create_user = c.user._id (tests were always passing too, weird)
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 46cb8e64ff63948921d55a3c2c902dbe8681e83f
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 13:35:57 2026 -0400
[#8603] change AlluraUserProperty User reference so that ShortUrl doesn't error strangely on shorturl.create_user = c.user._id (tests were always passing too, weird)
---
Allura/allura/model/auth.py | 2 +-
ForgeShortUrl/forgeshorturl/model/shorturl.py | 12 +++++-------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index b3eee63fc..feb9e950c 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -71,7 +71,7 @@ class AlluraUserProperty(ForeignIdProperty):
'''
def __init__(self, **kwargs):
- super().__init__('User', allow_none=True, **kwargs)
+ super().__init__(User, allow_none=True, **kwargs)
class EmailAddress(MappedClass):
diff --git a/ForgeShortUrl/forgeshorturl/model/shorturl.py b/ForgeShortUrl/forgeshorturl/model/shorturl.py
index 8da0aef9b..a4c4c9daf 100644
--- a/ForgeShortUrl/forgeshorturl/model/shorturl.py
+++ b/ForgeShortUrl/forgeshorturl/model/shorturl.py
@@ -17,11 +17,12 @@
import typing
import pymongo
+from bson import ObjectId
from tg import config
from tg import tmpl_context as c
-from ming.odm import FieldProperty, session
+from ming.odm import FieldProperty, session, RelationProperty
from datetime import datetime
-from allura.model.auth import User
+from allura.model.auth import User, AlluraUserProperty
from allura import model as M
if typing.TYPE_CHECKING:
@@ -41,14 +42,11 @@ class __mongometa__:
short_name = FieldProperty(str)
description = FieldProperty(str)
private = FieldProperty(bool)
- create_user = M.AlluraUserProperty()
+ create_user: ObjectId = AlluraUserProperty()
+ user = RelationProperty(User, via='create_user')
created = FieldProperty(datetime, if_missing=datetime.utcnow)
last_updated = FieldProperty(datetime, if_missing=datetime.utcnow)
- @property
- def user(self):
- return User.query.get(_id=self.create_user)
-
@classmethod
def upsert(cls, shortname):
u = cls.query.get(short_name=shortname, app_config_id=c.app.config._id)
(allura) 12/14: [#8603] use Markup in SxsOutputGenerator
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit bb66149d532c4bbbc55c20dc7e7aa63360a3045f
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Wed May 6 17:18:23 2026 -0400
[#8603] use Markup in SxsOutputGenerator
---
Allura/allura/lib/diff.py | 28 ++++++++++++++--------------
Allura/allura/tests/test_diff.py | 16 +++++++++++++++-
2 files changed, 29 insertions(+), 15 deletions(-)
diff --git a/Allura/allura/lib/diff.py b/Allura/allura/lib/diff.py
index 3199b28e2..e75cb0f82 100644
--- a/Allura/allura/lib/diff.py
+++ b/Allura/allura/lib/diff.py
@@ -46,7 +46,7 @@ def is_single_chg(chg_parts: ElementsHolder) -> bool:
class SxsOutputGenerator(sxsdiff.BaseGenerator):
# based on sxsdiff.generators.github.GitHubStyledGenerator
- table_tmpl_start = '''
+ table_tmpl_start = Markup('''
<table class="side-by-side-diff">
<thead>
<th class="lineno"></th>
@@ -54,9 +54,9 @@ class SxsOutputGenerator(sxsdiff.BaseGenerator):
<th class="lineno"></th>
<th>%s</th>
</thead>
-'''.strip()
+''').strip()
- table_tmpl_end = '</table>'
+ table_tmpl_end = Markup('</table>')
def __init__(self, adesc: str, bdesc: str):
self.adesc = adesc
@@ -66,9 +66,9 @@ def _spit(self, content):
self.out += content + '\n'
def run(self, diff_result: Iterable[LineChange | None]):
- self.out = ''
+ self.out = Markup('')
super().run(diff_result)
- return Markup(self.out) # noqa: S704 "safe" because we use html.escape in a few key places below
+ return self.out
def visit_row(self, line_change: LineChange | None):
if line_change is None:
@@ -86,9 +86,9 @@ def visit_row(self, line_change: LineChange | None):
@contextlib.contextmanager
def wrap_row(self, line_change):
- self._spit('<tr>')
+ self._spit(Markup('<tr>'))
yield
- self._spit('</tr>')
+ self._spit(Markup('</tr>'))
@contextlib.contextmanager
def wrap_result(self, sxs_result):
@@ -108,7 +108,7 @@ def _spit_unchanged_side(self, lineno, holder):
context = {
'mode': 'context',
'lineno': lineno,
- 'code': html.escape(str(holder)),
+ 'code': str(holder),
}
self._spit_side_from_context(context)
@@ -119,7 +119,7 @@ def _spit_changed_side(self, mode, lineno, holder):
bits = []
for elem in holder.elements:
- piece = html.escape(str(elem))
+ piece = str(elem)
if elem.is_changed and not is_single_chg(holder):
if elem.flag == diff_match_patch.DIFF_INSERT:
clss = 'diff-add'
@@ -127,10 +127,10 @@ def _spit_changed_side(self, mode, lineno, holder):
clss = 'diff-rem'
else:
clss = ''
- bits.append(f'<span class="{clss}">{piece}</span>')
+ bits.append(Markup('<span class="{clss}">{piece}</span>').format(clss=clss, piece=piece))
else:
bits.append(piece)
- code = ''.join(bits)
+ code = Markup('').join(bits)
context = {
'mode': mode,
@@ -140,10 +140,10 @@ def _spit_changed_side(self, mode, lineno, holder):
self._spit_side_from_context(context)
def _spit_side_from_context(self, context):
- self._spit(f' <td class="lineno">{context["lineno"]}</td>')
+ self._spit(Markup(' <td class="lineno">{lineno}</td>').format(lineno=context["lineno"]))
mode = context['mode']
- clss = (' class="%s"' % mode) if mode not in ['context', ''] else ''
- self._spit(f' <td{clss}><pre>{context["code"]}</pre></td>')
+ clss = Markup(' class="%s"') % mode if mode not in ['context', ''] else ''
+ self._spit(Markup(' <td{clss}><pre>{code}</pre></td>').format(clss=clss, code=context["code"]))
def sxsdiff_cleanup_trailing(input_lines: Iterable[LineChange]) -> Iterable[LineChange]:
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index 0d528af68..b22f2e809 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -58,7 +58,7 @@ def test_make_table(self):
<td class="lineno"></td>
<td><pre></pre></td>
<td class="lineno">4</td>
- <td class="diff-add"><pre> new<script>&"</pre></td>
+ <td class="diff-add"><pre> new<script>&"</pre></td>
</tr>
</table>
'''.strip()
@@ -124,3 +124,17 @@ def test_unicode_make_table(self):
b = ['измененная строка']
html = HtmlSideBySideDiff().make_table(a, b, 'file a', 'file b')
assert 'строка' in html
+
+ def test_make_table_with_html_tags(self):
+ a = ['foo<b>bar</b>']
+ b = ['<em>as</em>df']
+ html = HtmlSideBySideDiff().make_table(a, b, 'file <a>', 'file <b>')
+ assert 'foo<b>bar</b>' in html
+ assert 'foo<b>bar</b>' not in html
+ assert '<em>as</em>df' in html
+ assert '<em>as</em>df' not in html
+ assert 'file <a>' in html
+ assert 'file <a>' not in html
+ assert 'file <b>' in html
+ assert 'file <b>' not in html
+
(allura) 14/14: [#8603] check multifactor login mode
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 6754e9c9fca927a74c9a30790ada9a5efcca950f
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 12 16:15:09 2026 -0400
[#8603] check multifactor login mode
---
Allura/allura/controllers/auth.py | 2 ++
Allura/allura/tests/functional/test_auth.py | 24 ++++++++++++++++++++++++
2 files changed, 26 insertions(+)
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 7aedac3da..a500df93d 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -464,6 +464,8 @@ def do_multifactor(self, code, mode, **kwargs):
recovery = RecoveryCodeService.get()
recovery.verify_and_remove_code(user, code)
h.auditlog_user('Logged in using a multifactor recovery code', user=user)
+ else:
+ raise InvalidToken('Invalid multifactor mode')
except (InvalidToken, InvalidRecoveryCode):
request.validation.errors['code'] = 'Invalid code, please try again.'
h.auditlog_user('Multifactor login - invalid code', user=user)
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 3893a4bda..c088c0d79 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -3320,6 +3320,30 @@ def test_login_recovery_code(self):
# confirm code used up
assert recovery_code not in RecoveryCodeService().get().get_codes(user)
+ def test_login_invalid_mode(self):
+ self._init_totp()
+
+ # so test-admin isn't automatically logged in for all requests
+ self.app.extra_environ = {'disable_auth_magic': 'True'}
+
+ # regular login
+ r = self.app.get('/auth/?return_to=/p/foo')
+ encoded = self.app.antispam_field_names(r.form)
+ r.form[encoded['username']] = 'test-admin'
+ r.form[encoded['password']] = 'foo'
+ r = r.form.submit()
+
+ # check results
+ assert r.location.endswith('/auth/multifactor?return_to=%2Fp%2Ffoo'), r
+ r = r.follow()
+ assert not r.session.get('username')
+
+ # change login mode
+ r.form['mode'] = 'bogus'
+ r.form['code'] = 'invalid-code'
+ r = r.form.submit()
+ assert not r.session.get('username')
+
@patch('allura.lib.plugin.AuthenticationProvider.hibp_password_check_enabled', Mock(return_value=True))
def test_login_totp_with_hibp(self):
# this is essentially the same as regular TOTP test, just making sure that HIBP doesn't get in the way
(allura) 06/14: [#8603] add @require_post to a few endpoints
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit bc0c96d0dd64a19752ae59505819989a9d30c445
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 13:34:41 2026 -0400
[#8603] add @require_post to a few endpoints
---
Allura/allura/controllers/discuss.py | 2 ++
Allura/allura/lib/widgets/discuss.py | 7 +++++++
ForgeShortUrl/forgeshorturl/main.py | 2 ++
3 files changed, 11 insertions(+)
diff --git a/Allura/allura/controllers/discuss.py b/Allura/allura/controllers/discuss.py
index c890696e7..d4199eef0 100644
--- a/Allura/allura/controllers/discuss.py
+++ b/Allura/allura/controllers/discuss.py
@@ -103,6 +103,7 @@ def error_handler(self, *args, **kwargs):
@h.vardec
@expose()
+ @require_post()
@validate(pass_validator, error_handler=error_handler)
def subscribe(self, **kw):
threads = kw.pop('threads', [])
@@ -245,6 +246,7 @@ def tag(self, labels, **kw):
redirect(six.ensure_text(request.referer or '/'))
@expose()
+ @require_post()
def flag_as_spam(self, **kw):
require_access(self.thread, 'moderate')
self.thread.spam()
diff --git a/Allura/allura/lib/widgets/discuss.py b/Allura/allura/lib/widgets/discuss.py
index 1b8f440e6..fe7a38749 100644
--- a/Allura/allura/lib/widgets/discuss.py
+++ b/Allura/allura/lib/widgets/discuss.py
@@ -426,6 +426,13 @@ def resources(self):
if (thread_spam.length) {
if (allow_moderate.length) {
thread_spam[0].style.display='block';
+ thread_spam.click(function (e) {
+ e.preventDefault();
+ var form = $('<form method="post" action="' + this.href + '"></form>');
+ form.append('<input type="hidden" name="_csrf_token" value="' + $.cookie('_csrf_token') + '">');
+ $('body').append(form);
+ form.submit();
+ });
}
}
});
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
index adae9518d..72dbaf277 100644
--- a/ForgeShortUrl/forgeshorturl/main.py
+++ b/ForgeShortUrl/forgeshorturl/main.py
@@ -23,6 +23,7 @@
from allura.app import Application, SitemapEntry, DefaultAdminController
from allura import model as M
from allura.lib.security import require_access, has_access
+from allura.lib.decorators import require_post
from allura.lib import helpers as h
from allura.lib import validators as v
from allura.lib.search import search_app
@@ -207,6 +208,7 @@ def index(self, **kw):
@without_trailing_slash
@expose('json:')
+ @require_post()
def remove(self, shorturl, **kw):
require_access(self.app, 'update')
ShortUrl.query.remove({
(allura) 13/14: [#8603] validate URLs on clone tasks too (e.g. if task delayed from form usage, and DNS changes)
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 68b8d44c0361b6936173d497b626f905f83bcd18
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Mon May 11 13:42:09 2026 -0400
[#8603] validate URLs on clone tasks too (e.g. if task delayed from form usage, and DNS changes)
---
Allura/allura/tasks/repo_tasks.py | 4 ++++
ForgeSVN/forgesvn/tests/test_tasks.py | 14 +++++++++++---
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index e73a6d0b2..aa86ebc01 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -24,6 +24,8 @@
from allura.lib.decorators import task
from allura.lib.repository import RepositoryApp
+from allura.lib import validators as v
+
import git
from git import SymbolicReference
@@ -37,6 +39,8 @@ def init(**kwargs):
def clone(cloned_from_path, cloned_from_name, cloned_from_url):
from allura import model as M
try:
+ v.NonPrivateUrl().to_python(cloned_from_url)
+
c.app.repo.init_as_clone(
cloned_from_path,
cloned_from_name,
diff --git a/ForgeSVN/forgesvn/tests/test_tasks.py b/ForgeSVN/forgesvn/tests/test_tasks.py
index 8df3e80d1..93e4827c2 100644
--- a/ForgeSVN/forgesvn/tests/test_tasks.py
+++ b/ForgeSVN/forgesvn/tests/test_tasks.py
@@ -59,12 +59,20 @@ def test_init(self):
def test_clone(self):
ns = M.Notification.query.find().count()
- with mock.patch.object(c.app.repo, 'init_as_clone') as f:
- repo_tasks.clone('foo', 'bar', 'baz')
+ with mock.patch.object(c.app.repo, 'init_as_clone', autospec=True) as f:
+ repo_tasks.clone('foo', 'bar', 'http://example.com/baz')
M.main_orm_session.flush()
- f.assert_called_with('foo', 'bar', 'baz')
+ f.assert_called_with('foo', 'bar', 'http://example.com/baz')
assert ns + 1 == M.Notification.query.find().count()
+ def test_clone_internal(self):
+ ns = M.Notification.query.find().count()
+ with mock.patch.object(c.app.repo, 'init_as_clone', autospec=True) as f:
+ repo_tasks.clone('foo', 'bar', 'http://localhost/baz')
+ M.main_orm_session.flush()
+ f.assert_not_called()
+ assert M.MonQTask.query.get(task_name='allura.tasks.event_tasks.event', args='repo_clone_task_failed')
+
def test_refresh(self):
with mock.patch.object(c.app.repo, 'refresh') as f:
repo_tasks.refresh()
(allura) 03/14: [#8603] use NonPrivateUrl on webhooks (does allow for IP addrs now also)
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 6defa33fbc1672916216fb87e87cb25cc0a24113
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 11:42:25 2026 -0400
[#8603] use NonPrivateUrl on webhooks (does allow for IP addrs now also)
---
Allura/allura/tests/test_webhooks.py | 64 +++++++++++++++++++++++-------------
Allura/allura/webhooks.py | 9 +++--
2 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index dd373327d..922f41a88 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -291,10 +291,29 @@ def test_create_validation(self):
r = self.app.post(self.url + '/repo-push/create', data)
self.find_error(r, 'url', 'Please enter a value')
- data = {'url': 'qwer', 'secret': 'qwe'}
- r = self.app.post(self.url + '/repo-push/create', data)
- self.find_error(r, 'url',
- 'You must provide a full domain name (like qwer.com)')
+ @pytest.mark.parametrize('url', [
+ 'http://localhost/hook',
+ 'http://127.0.0.1/hook',
+ 'http://10.0.0.1/hook',
+ 'http://192.168.1.1/hook',
+ 'https://169.254.169.254/',
+ ])
+ def test_create_ssrf_private_url_rejected(self, url):
+ r = self.app.post(self.url + '/repo-push/create', {'url': url, 'secret': ''})
+ self.find_error(r, 'url', 'Invalid URL')
+
+ @pytest.mark.parametrize('url', [
+ 'http://localhost/hook',
+ 'http://127.0.0.1/hook',
+ 'https://10.0.0.1/hook',
+ ])
+ def test_edit_ssrf_private_url_rejected(self, url):
+ data = {'url': 'http://httpbin.org/post', 'secret': 'secret'}
+ self.create_webhook(data).follow()
+ wh = M.Webhook.query.get(hook_url=data['url'], type='repo-push')
+ edit_data = {'url': url, 'secret': '', 'webhook': str(wh._id)}
+ r = self.app.post(self.url + '/repo-push/edit', edit_data)
+ self.find_error(r, 'url', 'Invalid URL', 'edit')
def test_edit_validation(self):
invalid = M.Webhook(
@@ -321,11 +340,6 @@ def test_edit_validation(self):
r = self.app.post(self.url + '/repo-push/edit', data)
self.find_error(r, 'url', 'Please enter a value', 'edit')
- data = {'url': 'qwe', 'secret': 'qwe', 'webhook': str(wh._id)}
- r = self.app.post(self.url + '/repo-push/edit', data)
- self.find_error(r, 'url',
- 'You must provide a full domain name (like qwe.com)', 'edit')
-
def test_delete(self):
data = {'url': 'http://httpbin.org/post',
'secret': 'secret'}
@@ -362,9 +376,9 @@ def test_list_webhooks(self):
url2 = str(git2.admin_url + 'webhooks')
data1 = {'url': 'http://httpbin.org/post',
'secret': 'secret'}
- data2 = {'url': 'http://another-host.org/',
+ data2 = {'url': 'http://example.com/',
'secret': 'secret2'}
- data3 = {'url': 'http://another-app.org/',
+ data3 = {'url': 'http://1.2.3.4/',
'secret': 'secret3'}
self.create_webhook(data1).follow()
self.create_webhook(data2).follow()
@@ -515,6 +529,20 @@ def test_send_error_no_retries(self, log, requests, time):
requests.post.return_value.text,
requests.post.return_value.headers))
+ @pytest.mark.parametrize('url', [
+ 'http://localhost/hook',
+ 'http://127.0.0.1/hook',
+ 'http://10.0.0.1/hook',
+ 'http://192.168.1.1/hook',
+ ])
+ @patch('allura.webhooks.requests', autospec=True)
+ @patch('allura.webhooks.log', autospec=True)
+ def test_send_ssrf_private_url_blocked(self, log, requests, url):
+ result = self.h._send(url, json.dumps(self.payload), {})
+ assert result is False
+ requests.post.assert_not_called()
+ log.error.assert_called_once()
+
class TestRepoPushWebhookSender(TestWebhookBase):
@patch('allura.webhooks.send_webhook', autospec=True)
@@ -745,16 +773,6 @@ def test_create_validation(self):
'error': {'url': 'Please enter a value'},
}
assert r.json == expected
-
- data = {'url': 'qwer', 'secret': 'qwe'}
- r = self.api_post(self.url + '/repo-push', status=400, **data)
- expected = {
- 'result': 'error',
- 'error': {
- 'url': 'You must provide a full domain name (like qwer.com)'
- },
- }
- assert r.json == expected
assert M.Webhook.query.find().count() == len(self.webhooks)
def test_create(self):
@@ -806,12 +824,12 @@ def test_create_limit_reached(self):
def test_edit_validation(self):
webhook = self.webhooks[0]
url = f'{self.url}/repo-push/{webhook._id}'
- data = {'url': 'qwe', 'secret': 'qwe'}
+ data = {'url': 'http://10.0.0.1/hook', 'secret': ''}
r = self.api_post(url, status=400, **data)
expected = {
'result': 'error',
'error': {
- 'url': 'You must provide a full domain name (like qwe.com)'
+ 'url': 'Invalid URL.'
},
}
assert r.json == expected
diff --git a/Allura/allura/webhooks.py b/Allura/allura/webhooks.py
index 68098f84d..ed5a16ebb 100644
--- a/Allura/allura/webhooks.py
+++ b/Allura/allura/webhooks.py
@@ -70,7 +70,7 @@ def _convert_to_python(self, value, state):
class WebhookCreateForm(schema.Schema):
- url = fev.URL(not_empty=True)
+ url = v.NonPrivateUrl(not_empty=True)
secret = v.UnicodeString()
@@ -377,7 +377,12 @@ def send(self):
if ok:
return
- def _send(self, url, data, headers):
+ def _send(self, url, data, headers) -> bool:
+ try:
+ v.NonPrivateUrl().to_python(url)
+ except Invalid as e:
+ log.error(self.log_msg(f'Webhook send error: resolved to private address: {e!r}'))
+ return False
try:
r = requests.post(
url,
(allura) 08/14: [#8603] use _verify_return_to in pwd_expired_change
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 7013d3506ea793e738be2e3762b1376eef126fed
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 17:21:04 2026 -0400
[#8603] use _verify_return_to in pwd_expired_change
---
Allura/allura/controllers/auth.py | 4 ++--
Allura/allura/tests/functional/test_auth.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 669117200..6b7272d87 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -388,7 +388,7 @@ def logout(self, return_to=None):
redirect(config.get('auth.post_logout_url', '/'))
@staticmethod
- def _verify_return_to(return_to):
+ def _verify_return_to(return_to: str | None) -> str:
# protect against any "open redirect" attacks using an external URL
if not return_to or '\n' in return_to:
return_to = '/'
@@ -614,7 +614,7 @@ def pwd_expired(self, **kw):
@validate(F.password_change_form, error_handler=pwd_expired)
def pwd_expired_change(self, **kw):
require_authenticated()
- return_to = kw.get('return_to')
+ return_to = self._verify_return_to(kw.get('return_to'))
ap = plugin.AuthenticationProvider.get(request)
failure_redirect_url = tg.url('/auth/pwd_expired', dict(return_to=return_to))
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 5db3a7f36..3893a4bda 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -2870,7 +2870,7 @@ def test_change_pwd_validation(self):
assert 'Passwords must match' in r
r = self.check_validation('bad', 'qwerty', 'qwerty')
assert 'Incorrect password' in self.webflash(r)
- assert r.location == 'http://localhost/auth/pwd_expired?return_to='
+ assert r.location == 'http://localhost/auth/pwd_expired?return_to=%2F'
with h.push_config(config, **{'auth.min_password_len': 3}):
r = self.check_validation('foo', 'foo', 'foo')
(allura) 09/14: [#8603] only permit local access to /auth/repo_permissions
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 4fe4e280870d357be343d7d02139db0c9bb0fc16
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 17:21:27 2026 -0400
[#8603] only permit local access to /auth/repo_permissions
---
Allura/allura/controllers/auth.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 6b7272d87..7aedac3da 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -23,6 +23,7 @@
from base64 import b32encode
from datetime import datetime
import re
+import ipaddress
from urllib.parse import urlparse, urljoin, urlunparse
import bson
@@ -564,6 +565,10 @@ def repo_permissions(self, repo_path=None, username=None, **kw):
Returns JSON describing this user's permissions on that repo.
"""
+ ip = ipaddress.ip_address(utils.ip_address(request))
+ if not ip or not ip.is_private:
+ raise wexc.HTTPForbidden("Access to repo permissions is restricted to internal network connections")
+
disallow = dict(allow_read=False, allow_write=False,
allow_create=False)
# Find the user
(allura) 11/14: [#8603] NonPrivateUrl checks all host=>IP resolutions not just one
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 8e96d70099ff566055c1f82f22ea9bae03b80f08
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Wed May 6 12:40:38 2026 -0400
[#8603] NonPrivateUrl checks all host=>IP resolutions not just one
---
Allura/allura/lib/validators.py | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index e2ceffbba..95e43c219 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -61,13 +61,17 @@ def _convert_to_python(self, value, state):
url_components = urlsplit(value)
try:
- host_ip = socket.gethostbyname(url_components.hostname)
+ addr_info = socket.getaddrinfo(url_components.hostname, None)
except socket.gaierror:
raise fev.Invalid("Invalid URL.", value, state)
- parse_ip = ip_address(host_ip)
- if parse_ip and parse_ip.is_private:
- raise fev.Invalid("Invalid URL.", value, state)
- self.ip = parse_ip
+
+ for info in addr_info:
+ host_ip = info[4][0]
+ parse_ip = ip_address(host_ip)
+ if parse_ip and parse_ip.is_private:
+ raise fev.Invalid("Invalid URL.", value, state)
+
+ self.ip = ip_address(addr_info[0][4][0])
return value
(allura) 01/14: [#8603] don't follow redirects on trac import
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 06d41c29ab251f0c737f5df669b4578ee50255e2
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Wed Apr 29 17:36:25 2026 -0400
[#8603] don't follow redirects on trac import
---
ForgeImporters/forgeimporters/trac/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ForgeImporters/forgeimporters/trac/__init__.py b/ForgeImporters/forgeimporters/trac/__init__.py
index fa3ddd371..8d8b04951 100644
--- a/ForgeImporters/forgeimporters/trac/__init__.py
+++ b/ForgeImporters/forgeimporters/trac/__init__.py
@@ -41,7 +41,7 @@ def _convert_to_python(self, value, state=None):
value = value.rstrip('/') + '/'
try:
- resp = requests.head(value, allow_redirects=True, timeout=10)
+ resp = requests.head(value, timeout=10)
except OSError:
# fall through to 'raise' below
pass
(allura) 05/14: [#8603] test to know forgeblog/main.py setattr loop is ok
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit e154040e099cbad0749f7831a691c334da2245f1
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 12:01:35 2026 -0400
[#8603] test to know forgeblog/main.py setattr loop is ok
---
ForgeBlog/forgeblog/tests/functional/test_root.py | 29 +++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 7e8bc735e..b5c5ccd24 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -23,6 +23,7 @@
from allura.lib import helpers as h
from alluratest.controller import TestController
+from forgeblog import model as BM
class Test(TestController):
@@ -252,6 +253,34 @@ def test_post_bad_url_params(self):
d = self._blog_date()
self.app.get('/blog/%s/my-post/?limit=blah&page=2x' % d, status=200)
+ def test_save_ignores_non_form_fields(self):
+ """Mass assignment: extra POST params must not overwrite model fields outside the form"""
+ self._post()
+ d = self._blog_date()
+ post_url = '/blog/%s/my-post/save' % d
+
+ post = BM.BlogPost.query.find().first()
+ original_slug = post.slug
+ original_app_config_id = post.app_config_id
+
+ # Attempt to inject fields not defined in EditPostForm
+ self.app.post(post_url, params={
+ 'title': 'My Post',
+ 'text': 'Updated text',
+ 'labels': '',
+ 'state': 'published',
+ 'slug': 'hacked-slug',
+ 'deleted': 'True',
+ 'app_config_id': 'deadbeefdeadbeefdeadbeef',
+ })
+
+ post = BM.BlogPost.query.find({'slug': original_slug}).first()
+ assert post is not None, "post should still be findable by its original slug"
+ assert post.text == 'Updated text' # standard fields should be updated
+ assert post.slug == original_slug
+ assert post.deleted is False
+ assert post.app_config_id == original_app_config_id
+
def test_rate_limit_submit(self):
with h.push_config(tg.config, **{'forgeblog.rate_limits': '{"3600": 0}'}):
r = self._post()
(allura) 04/14: [#8603] urlopen_allow_internal_hostnames setting should work for webhook and importer forms
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit bbd1d71504af1060fb410905f4ca754f9a4b3ee5
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 11:54:36 2026 -0400
[#8603] urlopen_allow_internal_hostnames setting should work for webhook and importer forms
---
Allura/allura/lib/validators.py | 8 ++++++++
Allura/allura/tests/test_webhooks.py | 5 +++++
2 files changed, 13 insertions(+)
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index 1b57a9981..e2ceffbba 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -17,10 +17,14 @@
import json
import re
+
+import tg
from bson import ObjectId
import formencode as fe
from formencode import validators as fev
from tg import tmpl_context as c
+from tg.support.converters import asbool
+
from . import helpers as h
from datetime import datetime
from urllib.parse import urlsplit
@@ -51,6 +55,10 @@ class NonPrivateUrl(URL):
# prevents private IPs
def _convert_to_python(self, value, state):
value = super()._convert_to_python(value, state)
+
+ if asbool(tg.config.get('urlopen_allow_internal_hostnames', 'false')):
+ return value
+
url_components = urlsplit(value)
try:
host_ip = socket.gethostbyname(url_components.hostname)
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 922f41a88..87213094b 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -302,6 +302,11 @@ def test_create_ssrf_private_url_rejected(self, url):
r = self.app.post(self.url + '/repo-push/create', {'url': url, 'secret': ''})
self.find_error(r, 'url', 'Invalid URL')
+ @patch.dict(config, {'urlopen_allow_internal_hostnames': 'true'})
+ def test_create_private_url_allowed(self):
+ with td.audits(r'add webhook repo-push'):
+ self.app.post(self.url + '/repo-push/create', {'url': 'http://localhost/hook', 'secret': ''})
+
@pytest.mark.parametrize('url', [
'http://localhost/hook',
'http://127.0.0.1/hook',
(allura) 10/14: [#8603] escape regexes in tag search
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit b7d28e8923c164bdd6d1b6f40840d6d039ae3f33
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Tue May 5 17:32:29 2026 -0400
[#8603] escape regexes in tag search
---
ForgeTracker/forgetracker/tracker_main.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index 92d630ba8..7cc17780c 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -715,7 +715,7 @@ def tags(self, term=None, **kw):
},
{'$project': {'labels': 1}},
{'$unwind': '$labels'},
- {'$match': {'labels': {'$regex': '^%s' % term, '$options': 'i'}}},
+ {'$match': {'labels': {'$regex': '^%s' % re.escape(term), '$options': 'i'}}},
{'$group': {'_id': '$labels', 'count': {'$sum': 1}}},
{'$sort': SON([('count', -1), ('_id', 1)])}
], cursor={})
(allura) 02/14: [#8603] disable trac importers by default
Posted by ke...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git
commit 56e69cbb321faf3a1ae619f6ce6c7fc799e925a7
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Wed Apr 29 17:36:37 2026 -0400
[#8603] disable trac importers by default
---
Allura/allura/tests/unit/patches.py | 14 +++++++++++++-
Allura/development.ini | 9 +++++----
.../forgeimporters/trac/tests/functional/test_trac.py | 7 ++++++-
ForgeImporters/forgeimporters/trac/tests/test_tickets.py | 6 ++++++
4 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/Allura/allura/tests/unit/patches.py b/Allura/allura/tests/unit/patches.py
index d2b3551a3..a4c90456b 100644
--- a/Allura/allura/tests/unit/patches.py
+++ b/Allura/allura/tests/unit/patches.py
@@ -16,8 +16,9 @@
# under the License.
from mock import Mock, patch, MagicMock
-from tg import tmpl_context as c
+from tg import tmpl_context as c, config
import tg
+from tg.support.converters import aslist
from allura.tests.unit.factories import (
create_project,
@@ -67,3 +68,14 @@ def fake_request_patch(test_case):
def fake_form_request_patch(test_case):
return patch('tg.request', MagicMock(referer='.'))
+
+
+# to be used with a pytest fixture
+def enable_entry_point(config_key: str, entry_point_name: str):
+ orig_config_value = config.get(config_key, '')
+ items = aslist(orig_config_value, sep=',')
+ if entry_point_name in items:
+ items.remove(entry_point_name)
+ config[config_key] = ','.join(items)
+ yield
+ config[config_key] = orig_config_value
diff --git a/Allura/development.ini b/Allura/development.ini
index a0671e232..4cdaabeaa 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -517,13 +517,14 @@ importer_upload_path = /tmp/importer_upload/{nbhd}/{project}
; To disable any plugin, tool, importer, etc from being available, you can use the disable_entry_points config option.
; Specify the keys and values as they are declared in the tool's "setup.py" file.
-; Examples:
-;disable_entry_points.allura.importers = github-tracker, github-wiki, github-repo
-;disable_entry_points.allura.project_importers = github
+; Examples: github-tracker, github-wiki, github-repo
+disable_entry_points.allura.importers = trac-tickets
+; Examples: github, trac
+disable_entry_points.allura.project_importers = trac
disable_entry_points.allura.theme.override = responsive
; Importers specifically, can be left enabled but not linked to. You have to know the URL to use it. Example:
-;hidden_importers = trac-tickets
+;hidden_importers = tt-tickets
; GitHub importer keys. For github ticket import, it is best to set
; up an app at https://github.com/settings/developers Use the root URL
diff --git a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
index c12823fbf..4cc80ecae 100644
--- a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
+++ b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
@@ -14,16 +14,21 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-
+import pytest
from mock import patch
from tg import config
from allura.lib import helpers as h
from allura.tests import TestController
+from allura.tests.unit.patches import enable_entry_point
class TestTracImportController(TestController):
+ @pytest.fixture(autouse=True)
+ def enable_trac_entry_point(self):
+ yield from enable_entry_point('disable_entry_points.allura.project_importers', 'trac')
+
def test_index(self):
r = self.app.get('/p/import_project/trac/')
assert 'Trac URL' in r
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 5da9166a1..f9b633b05 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -22,6 +22,8 @@
import pytest
from mock import Mock, patch
+
+from allura.tests.unit.patches import enable_entry_point
from ming.odm import ThreadLocalODMSession
from tg import tmpl_context as c, config
@@ -115,6 +117,10 @@ def setup_method(self, method):
from forgetracker.tracker_main import TrackerAdminController
self.importer = TrackerAdminController._importer = TracTicketImportController(TracTicketImporter())
+ @pytest.fixture(autouse=True)
+ def enable_trac_entry_point(self):
+ yield from enable_entry_point('disable_entry_points.allura.importers', 'trac-tickets')
+
@with_tracker
def test_index(self):
r = self.app.get('/p/test/admin/bugs/_importer/')