You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by di...@apache.org on 2023/01/12 20:42:16 UTC

[allura] 02/02: [#8484] improvements to validation and fixed project features duplication bug

This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 3178ea7374c7b0ac6ba758b49e28fe1f49444ba2
Author: Guillermo Cruz <gu...@slashdotmedia.com>
AuthorDate: Tue Jan 3 17:05:41 2023 -0600

    [#8484] improvements to validation and fixed project features duplication bug
---
 Allura/allura/controllers/auth.py                  | 19 +++++++-
 Allura/allura/ext/admin/widgets.py                 | 17 ++++---
 .../user_profile/templates/sections/social.html    |  2 +-
 Allura/allura/lib/validators.py                    | 55 +++++++++++++++-------
 Allura/allura/lib/widgets/forms.py                 |  8 +---
 .../templates/widgets/sortable_repeated_field.html | 17 +++++--
 Allura/allura/tests/functional/test_admin.py       | 39 +++++++++++++++
 Allura/development.ini                             |  1 -
 8 files changed, 118 insertions(+), 40 deletions(-)

diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 5086701d8..21b3fc346 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -24,6 +24,7 @@ import warnings
 from six.moves.urllib.parse import urlparse, urljoin
 
 import bson
+import formencode as fe
 import tg
 from tg import expose, flash, redirect, validate, config, session
 from tg.decorators import with_trailing_slash, without_trailing_slash
@@ -41,6 +42,7 @@ from allura import model as M
 from allura.lib.security import require_authenticated, has_access, is_site_admin
 from allura.lib import helpers as h
 from allura.lib import plugin
+from allura.lib import validators as V
 from allura.lib.decorators import require_post, reconfirm_auth
 from allura.lib.exceptions import InvalidRecoveryCode, MultifactorRateLimitError
 from allura.lib.repository import RepositoryApp
@@ -1053,8 +1055,21 @@ class UserContactsController(BaseController):
     def add_social_network(self, **kw):
         require_authenticated()
 
-        if kw['socialnetwork'] == 'Twitter' and not kw['accounturl'].startswith('http'):
-            kw['accounturl'] = 'http://twitter.com/%s' % kw['accounturl'].replace('@', '')
+        validator_map = {
+            'Twitter': V.TwitterValidator(),
+            'Instagram': V.InstagramValidator(),
+            'Facebook': V.FacebookValidator(),
+            'Mastodon': V.FediverseValidator(),
+            'Linkedin': V.LinkedinValidator(),
+        }
+
+        try:
+            Validator = validator_map.get(kw['socialnetwork'])
+            kw['accounturl'] = Validator().to_python(kw['accounturl'])
+        except fe.Invalid as e:
+            # c.form_errors['accounturl'] = e.msg
+            flash(e.msg, 'error')
+            redirect('.')
 
         c.user.add_multivalue_pref('socialnetworks',
                                    {'socialnetwork': kw['socialnetwork'], 'accounturl': kw['accounturl']})
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 7af817d0b..f20cfc9ac 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -164,9 +164,7 @@ class MetadataAdmin(ff.AdminForm):
     defaults = dict(
         ff.AdminForm.defaults,
         enctype='multipart/form-data')
-    allowed_social_domains = aslist(tg.config.get('allowed_social_domains',
-                                                  ['facebook', 'instagram', 'linkedin', 'twitter']),
-                                    ',')
+
     class fields(ew_core.NameList):
         name = ew.InputField(field_type='text',
                              label='Name',
@@ -224,16 +222,17 @@ class MetadataAdmin(ff.AdminForm):
             field_type="text", label="Google Analytics ID",
             attrs=(dict(placeholder='UA-123456-0', pattern='UA-[0-9]+-[0-9]+')))
         twitter_handle = ew.InputField(
-            field_type="text", label='Twitter Handle', validator=V.SocialDomainValidator('twitter.com'))
+            field_type="text", label='Twitter Handle',
+            validator=V.TwitterValidator)
+
         facebook_page = ew.InputField(field_type="text", label='Facebook page',
-                                      validator=formencode.All(fev.URL(add_http=True), V.SocialDomainValidator('facebook.com')) )
+                                      validator=V.FacebookValidator)
         instagram_page = ew.InputField(
             field_type="text", label='Instagram page',
-            validator=formencode.All(fev.URL(add_http=True), V.SocialDomainValidator('instagram.com')))
-        fediverse_address = ew.InputField(field_type="text", label="Mastodon address",
-                                          validator=V.FediverseAddressValidator)
-
+            validator=V.InstagramValidator)
 
+        fediverse_address = ew.InputField(field_type="text", label="Mastodon address",
+                                          validator=V.FediverseValidator)
 
 
 class AuditLog(ew_core.Widget):
diff --git a/Allura/allura/ext/user_profile/templates/sections/social.html b/Allura/allura/ext/user_profile/templates/sections/social.html
index d94f0bf48..ba9feb4a2 100644
--- a/Allura/allura/ext/user_profile/templates/sections/social.html
+++ b/Allura/allura/ext/user_profile/templates/sections/social.html
@@ -34,7 +34,7 @@
     <dl>
     {% for contact in user.get_pref('socialnetworks') %}
         {% if contact.socialnetwork == 'Mastodon' %}
-        <dt>{{ contact.socialnetwork }}</dt><dd><a href="{{ h.parse_fediverse_address(contact.accounturl) }}" rel="me nofollow">{{ contact.accounturl }}</a></dd>
+        <dt>{{ contact.socialnetwork }}</dt><dd><a href="{{ h.parse_fediverse_address(contact.accounturl) }}" rel="me nofollow" target="_blank">{{ contact.accounturl }}</a></dd>
         {% else %}
         <dt>{{ contact.socialnetwork }}</dt><dd>{{ contact.accounturl|urlize(nofollow=True) }}</dd>
         {% endif %}
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index aa0488705..4d9368e38 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -488,33 +488,52 @@ class IconValidator(fev.FancyValidator):
 
         return value
 
-FEDIVERSE_REGEX = r'^@[a-zA-Z_]*@[a-zA-Z_]*\.{1}[A-Za-z]{0,10}$'
+FEDIVERSE_REGEX = r'^@[\w-]+@[\w-]+(\.[\w-]+)+$'
 
-class FediverseAddressValidator(fev.FancyValidator):
+class LinkedinValidator(fev.FancyValidator):
+    def _to_python(self, value, state):
+        if value.startswith('@') and not re.match(FEDIVERSE_REGEX, value):
+            value = f'https://linkedin.com/in/{value.replace("@", "")}/'
+        elif 'linkedin.com' not in value:
+            raise fe.Invalid('Invalid Linkedin address', value, state)
+        return value
 
 
+class TwitterValidator(fev.FancyValidator):
     def _to_python(self, value, state):
-        match = re.match(FEDIVERSE_REGEX , value)
-        if not match:
-            raise fe.Invalid('Address format must be @your username@your server', value, state)
+        if value.startswith('@') and not re.match(FEDIVERSE_REGEX, value):
+            value = f'https://twitter.com/{value.replace("@", "")}'
+        elif 'twitter.com' not in value:
+            raise fe.Invalid('Invalid Twitter address', value, state)
+        return value
+
 
-        return value.lower()
+class InstagramValidator(fev.FancyValidator):
+    def _to_python(self, value, state):
+        if value.startswith('@') and not re.match(FEDIVERSE_REGEX, value):
+            value = f'https://instagram.com/{value.replace("@", "")}'
+        elif 'instagram.com' not in value:
+            raise fe.Invalid('Invalid Instagram address', value, state)
+        return value
 
 
+class FacebookValidator(fev.FancyValidator):
+    def _to_python(self, value, state):
+        if value.startswith('@') and not re.match(FEDIVERSE_REGEX, value):
+            value = f'https://facebook.com/{value.replace("@", "")}'
+        elif 'facebook.com' not in value:
+            raise fe.Invalid('Invalid Facebook address', value, state)
+        return value
 
-class SocialDomainValidator(fev.FancyValidator):
-    def __init__(self, domain='', **kw):
-        self.domain = domain
-        self.domains = kw.get('domains')
 
+class FediverseValidator(fev.FancyValidator):
     def _to_python(self, value, state):
-        if value.startswith('@') and not re.match(FEDIVERSE_REGEX , value):
-            value = f'https://twitter.com/{value.replace("@","")}'
-        url = urlsplit(value)
-        if not re.match(FEDIVERSE_REGEX , value):
-            if self.domain and not self.domain == url.netloc.replace('www.',''):
-                raise fe.Invalid('Invalid domain for this field', value, state)
-            if self.domains and not any(domain == url.netloc.replace('www.','') for domain in self.domains):
-                raise fe.Invalid('Invalid domain for this field', value, state)
+        if value.startswith('http'):
+            url = urlsplit(value)
+            value = f'{url.path.replace("/", "")}@{url.netloc}'
+            if not re.match(FEDIVERSE_REGEX, value):
+                raise fe.Invalid('Invalid Mastodon address', value, state)
+        elif not re.match(FEDIVERSE_REGEX , value):
+            raise fe.Invalid('Invalid Mastodon address', value, state)
         return value
 
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index 3444bf170..94b99871a 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -436,9 +436,6 @@ class AddSocialNetworkForm(ForgeForm):
         socialnetworks = aslist(tg.config.get('socialnetworks',
                                               ['Facebook', 'Linkedin', 'Twitter',]),
                                 ',')
-        allowed_social_domains = aslist(tg.config.get('allowed_social_domains',
-                                              ['facebook.com', 'instagram.com', 'linkedin.com', 'twitter.com']),
-                                ',')
 
         return [
             ew.SingleSelectField(
@@ -450,9 +447,8 @@ class AddSocialNetworkForm(ForgeForm):
             ew.TextField(
                 name='accounturl',
                 label='Account url',
-                validator=formencode.All(
-                    V.UnicodeString(not_empty=True), V.SocialDomainValidator(domains=allowed_social_domains)
-                ))
+                validator=V.UnicodeString(not_empty=True),
+            )
         ]
 
 
diff --git a/Allura/allura/templates/widgets/sortable_repeated_field.html b/Allura/allura/templates/widgets/sortable_repeated_field.html
index e81249fb0..8390d0e22 100644
--- a/Allura/allura/templates/widgets/sortable_repeated_field.html
+++ b/Allura/allura/templates/widgets/sortable_repeated_field.html
@@ -26,13 +26,24 @@
   {% if show_button %}{{ widget.button.display() }}{% endif %}
   <br style="clear:both"/>
   <div class="{{ flist_cls }}">
+  {% set id = 0 %}
     {% for i in range(repetitions) %}
       {% set ctx = widget.context_for(i) %}
-      {{ widget.field.display(css_class=field_cls, **ctx) }}
+        {% if c.form_values %}
+            {% if 'features-' ~ i ~ '.feature' in c.form_values %}
+                {% set ctx = widget.context_for(i) %}
+                {% do ctx.update({'value': {'feature': c.form_values.get('features-' ~ i ~ '.feature')} }) %}
+                {{ widget.field.display(css_class=field_cls, **ctx) }}
+            {% endif %}
+        {% else %}
+            {{ widget.field.display(css_class=field_cls, **ctx) }}
+        {% endif %}
     {% endfor %}
     {% if extra_field_on_focus_name %}
-      {% set ctx = widget.context_for(repetitions) %}
-      {{ widget.field.display(css_class=field_cls, **ctx) }}
+        {% if not c.form_values %}
+          {% set ctx = widget.context_for(repetitions) %}
+          {{ widget.field.display(css_class=field_cls, **ctx) }}
+        {% endif %}
     {% endif %}
     {{ widget.field.display(name=name+'#', css_class=stub_cls) }}
   </div>
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index c1d71bd72..11deb1ea0 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -956,6 +956,45 @@ class TestProjectAdmin(TestController):
         r = self.app.get('/admin/invitations')
         r.mustcontain('Neighborhood Invitation(s) for test')
 
+    def test_social_networks(self):
+        #Invalid Twitter
+        resp = self.app.post('/admin/update', params={'twitter_handle':'https://twit.com/tests'})
+        assert resp.status_int == 200
+        resp = self.app.post('/admin/update', params={'twitter_handle': 'https://google.com'})
+        assert resp.status_int == 200
+        #invalid Facebook
+        resp = self.app.post('/admin/update', params={'facebook_page': 'https://facebok.com'})
+        assert resp.status_int == 200
+        resp = self.app.post('/admin/update', params={'facebook_page': 'https://spam.com'})
+        assert resp.status_int == 200
+        assert 'Invalid Facebook address' in resp
+        #invalid instagram
+        resp = self.app.post('/admin/update', params={'instagram_page': 'https://instagrams.com'})
+        assert resp.status_int == 200
+        #invalid fediverse
+        resp = self.app.post('/admin/update', params={'fediverse_address': '@test12@indieweb.social'})
+        assert resp.status_int == 200
+
+        #valid Twitter
+        resp = self.app.post('/admin/update', params={'twitter_handle': 'https://twitter.com/sourceforge'})
+        assert resp.status_int == 302
+        resp = self.app.post('/admin/update', params={'twitter_handle': '@sourceforge'})
+        assert resp.status_int == 302
+        #valid Facebook
+        resp = self.app.post('/admin/update', params={'facebook_page': 'https://www.facebook.com/sourceforgenet/'})
+        assert resp.status_int == 302
+        #valid instagram
+        resp = self.app.post('/admin/update', params={'instagram_page': 'https://instagram.com/test'})
+        assert resp.status_int == 302
+        resp = self.app.post('/admin/update', params={'instagram_page': '@test'})
+        assert resp.status_int == 302
+        # valid fediverse
+        resp = self.app.post('/admin/update', params={'fediverse_address': '@test@indieweb.social'})
+        assert resp.status_int == 302
+        resp = self.app.post('/admin/update', params={'fediverse_address': 'https://indieweb.social/@test'})
+        assert resp.status_int == 302
+
+
 
 class TestExport(TestController):
 
diff --git a/Allura/development.ini b/Allura/development.ini
index 8499ef58a..7872c3d92 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -199,7 +199,6 @@ auth.allow_non_primary_email_password_reset = true
 auth.require_email_addr = true
 ; List of social network options to use on user account settings
 socialnetworks = Facebook, Linkedin, Twitter, Instagram, Mastodon
-allowed_social_domains = facebook.com, instagram.com, linkedin.com, twitter.com
 
 ; Allow uploading ssh key, optionally set ssh preferences url
 auth.allow_upload_ssh_key = false