Repository: mozmeao/snippets-service

Issue:
Special Link: about:accounts
AS Router Action:
 - `SHOW_FIREFOX_ACCOUNTS`: opens the firefox accounts signup page (about:accounts?action=signup&entrypoint=snippets)

Currently blocked waiting
 - documentation on AS Router. See also #521 
 - code changes in  (bug [1478569](https://bugzilla.mozilla.org/show_bug.cgi?id=1478569))


Discussion:
Update: bug is fixed and we're waiting on docs
Example payload
https://gist.githubusercontent.com/piatra/d45d10f1aee80a5ac46c9a664cab1f44/raw/8f9252c607703fb70e1d99f8d0cc2cede06cb2c3/gistfile1.txt
Blocked by https://bugzilla.mozilla.org/show_bug.cgi?id=1496357
@piatra mentioned that this action hasn't been implemented yet. @piatra Can you please provide a link to the bug that tracks this?
Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1496352

Resolving patch:
diff --git a/snippets/base/util.py b/snippets/base/util.py
--- a/snippets/base/util.py
+++ b/snippets/base/util.py
@@ -78,9 +78,20 @@ def __call__(self, matchobj):
 
             if url_match:
                 url = url_match.group('url')
-            self.links[keyname] = {
-                'url': url,
-            }
+
+            if url == 'special:appMenu':
+                self.links[keyname] = {
+                    'action': 'OPEN_APPLICATIONS_MENU',
+                    'args': 'appMenu',
+                }
+            elif url == 'special:accounts':
+                self.links[keyname] = {
+                    'action': 'SHOW_FIREFOX_ACCOUNTS',
+                }
+            else:
+                self.links[keyname] = {
+                    'url': url,
+                }
 
             # Find the optional data-metric attrib
             metric_match = re.search('data-metric="(?P<metric>.+?)"', matchobj.group('attrs'))
diff --git a/snippets/base/validators.py b/snippets/base/validators.py
--- a/snippets/base/validators.py
+++ b/snippets/base/validators.py
@@ -13,6 +13,7 @@
 
 ALLOWED_TAGS = ['a', 'i', 'b', 'u', 'strong', 'em', 'br']
 ALLOWED_ATTRIBUTES = {'a': ['href', 'data-metric']}
+ALLOWED_PROTOCOLS = ['https', 'special']
 
 
 @deconstructible
@@ -74,13 +75,22 @@ def validate_as_router_fluent_variables(data, variables):
 
     for variable in variables:
         text = data_dict[variable]
-        bleached_text = bleach.clean(text, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
+        bleached_text = bleach.clean(
+            text,
+            tags=ALLOWED_TAGS,
+            attributes=ALLOWED_ATTRIBUTES,
+            # Allow only secure protocols and custom special links.
+            protocols=ALLOWED_PROTOCOLS,
+        )
         # Bleach escapes '&' to '&amp;'. We need to revert back to compare with
         # text
         bleached_text = bleached_text.replace('&amp;', '&')
+
         if text != bleached_text:
-            error_msg = ('Variable `{}` contains unsupported tags.'
-                         'Only {} are supported'.format(variable, ', '.join(ALLOWED_TAGS)))
+            error_msg = (
+                'Variable `{}` contains unsupported tags or insecure links.'
+                'Only {} tags and https links are supported'
+            ).format(variable, ', '.join(ALLOWED_TAGS))
             raise ValidationError(error_msg)
     return data
 


Test patch:
diff --git a/snippets/base/tests/test_util.py b/snippets/base/tests/test_util.py
--- a/snippets/base/tests/test_util.py
+++ b/snippets/base/tests/test_util.py
@@ -46,6 +46,8 @@ def test_multiple_links_with_metrics(self):
                      'and yet another that has <a foo="bar">no href attrib</a>'),
             'title': ('And this another variable with <a href="https://snippets.mozilla.org">more '
                       'links</a>'),
+            'special_account': 'With <a href="special:accounts">special accounts link</a>.',
+            'special_appMenu': 'and another <a href="special:appMenu">special menu link</a>.',
             'nolinks': 'And finally one with no links.',
         }
         final_data = {
@@ -53,7 +55,9 @@ def test_multiple_links_with_metrics(self):
                      ' <link1>link</link1> that has more complex format. One link that has '
                      'a <link2>custom metric</link2>'
                      'and yet another that has <link3>no href attrib</link3>'),
-            'title': ('And this another variable with <link4>more links</link4>'),
+            'title': 'And this another variable with <link4>more links</link4>',
+            'special_account': 'With <link5>special accounts link</link5>.',
+            'special_appMenu': 'and another <link6>special menu link</link6>.',
             'nolinks': 'And finally one with no links.',
             'links': {
                 'link0': {
@@ -73,10 +77,18 @@ def test_multiple_links_with_metrics(self):
                 },
                 'link4': {
                     'url': 'https://snippets.mozilla.org'
-                }
+                },
+                'link5': {
+                    'action': 'SHOW_FIREFOX_ACCOUNTS',
+                },
+                'link6': {
+                    'action': 'OPEN_APPLICATIONS_MENU',
+                    'args': 'appMenu',
+                },
             }
         }
-        generated_data = fluent_link_extractor(data, ['text', 'title', 'nolinks'])
+        generated_data = fluent_link_extractor(
+            data, ['text', 'title', 'special_account', 'special_appMenu', 'nolinks'])
 
         self.assertEqual(final_data['text'], generated_data['text'])
         self.assertEqual(final_data['title'], generated_data['title'])
diff --git a/snippets/base/tests/test_validators.py b/snippets/base/tests/test_validators.py
--- a/snippets/base/tests/test_validators.py
+++ b/snippets/base/tests/test_validators.py
@@ -47,16 +47,21 @@ def test_invalid_xml(self):
 class ASRouterFluentVariablesValidatorTests(TestCase):
     @patch('snippets.base.validators.ALLOWED_TAGS', ['a', 'strong'])
     def test_valid(self):
-        data = json.dumps({'text': 'Link to <a href="http://example.com">example.com</a>.',
+        data = json.dumps({'text': 'Link to <a href="https://example.com">example.com</a>.',
                            'foo': 'This is <strong>important</strong>',
+                           'special': 'This is <a href="special:accounts">special link</a>.',
                            'bar': 'This is not rich text.'})
         self.assertEqual(validate_as_router_fluent_variables(data, ['text', 'foo']), data)
 
     @patch('snippets.base.validators.ALLOWED_TAGS', 'a')
-    def test_invalid(self):
+    def test_invalid_tag(self):
         data = json.dumps({'text': '<strong>Strong</strong> text.'})
         self.assertRaises(ValidationError, validate_as_router_fluent_variables, data, ['text'])
 
+    def test_invalid_protocol(self):
+        data = json.dumps({'text': '<a href="http://example.com">Strong</strong> text.'})
+        self.assertRaises(ValidationError, validate_as_router_fluent_variables, data, ['text'])
+
 
 class RegexValidatorTests(TestCase):
     def test_valid_string(self):