[#7560] ticket:630 Avoid anonymous permissions for private tickets
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index 5f8256f..344af39 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -189,6 +189,17 @@
         return user
 
 
+class AnonymousValidator(fev.FancyValidator):
+
+    def _to_python(self, value, state):
+        from allura.model import User
+        if value:
+            if c.user == User.anonymous():
+                raise fe.Invalid('Log in to Mark as Private', value, state)
+            else:
+                return value
+
+
 class PathValidator(fev.FancyValidator):
 
     def _to_python(self, value, state):
diff --git a/Allura/allura/tests/test_validators.py b/Allura/allura/tests/test_validators.py
index e0b4f2c..f50b8f7 100644
--- a/Allura/allura/tests/test_validators.py
+++ b/Allura/allura/tests/test_validators.py
@@ -97,6 +97,22 @@
         self.assertEqual(str(cm.exception), "Invalid username")
 
 
+class TestAnonymousValidator(unittest.TestCase):
+    val = v.AnonymousValidator
+
+    @patch('allura.lib.validators.c')
+    def test_valid(self, c):
+        c.user = M.User.by_username('root')
+        self.assertEqual(True, self.val.to_python(True))
+
+    @patch('allura.lib.validators.c')
+    def test_invalid(self, c):
+        c.user = M.User.anonymous()
+        with self.assertRaises(fe.Invalid) as cm:
+            self.val.to_python(True)
+        self.assertEqual(str(cm.exception), "Log in to Mark as Private")
+
+
 class TestMountPointValidator(unittest.TestCase):
 
     @patch('allura.lib.validators.c')
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 7ca6176..e4e06da 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -811,9 +811,11 @@
                 ACE.allow(role._id, perm) for perm in perms]
             # maintain existing access for developers and the ticket creator,
             # but revoke all access for everyone else
-            self.acl = _allow_all(role_developer, security.all_allowed(self, role_developer)) \
-                + _allow_all(role_creator, security.all_allowed(self, role_creator)) \
-                + [DENY_ALL]
+            acl = _allow_all(role_developer, security.all_allowed(self, role_developer))
+            if role_creator != ProjectRole.anonymous():
+                acl += _allow_all(role_creator, security.all_allowed(self, role_creator))
+            acl += [DENY_ALL]
+            self.acl = acl
         else:
             self.acl = []
     private = property(_get_private, _set_private)
diff --git a/ForgeTracker/forgetracker/widgets/ticket_form.py b/ForgeTracker/forgetracker/widgets/ticket_form.py
index 677dd2a..4248b38 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_form.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_form.py
@@ -17,6 +17,7 @@
 
 from pylons import tmpl_context as c
 from formencode import validators as fev
+from webhelpers.html.builder import literal
 
 import ew as ew_core
 import ew.jinja2_ew as ew
@@ -24,6 +25,7 @@
 from allura import model as M
 from allura.lib.widgets import form_fields as ffw
 from allura.lib import helpers as h
+from allura.lib import validators as v
 
 
 class TicketCustomFields(ew.CompoundField):
@@ -76,8 +78,7 @@
 
         display = field.display(**ctx)
         if ctx['errors'] and field.show_errors and not ignore_errors:
-            display = "%s<div class='error'>%s</div>" % (display,
-                                                         ctx['errors'])
+            display += literal("<div class='error'>{0}</div>".format(ctx['errors']))
         return display
 
     def _add_current_value_to_user_field(self, field, user):
@@ -114,6 +115,7 @@
             ffw.LabelEdit(label='Labels', name='labels',
                           className='ticket_form_tags'),
             ew.Checkbox(name='private', label='Mark as Private',
+                        validator=v.AnonymousValidator(),
                         attrs={'class': 'unlabeled'}),
             ew.InputField(name='attachment', label='Attachment', field_type='file', attrs={
                           'multiple': 'True'}, validator=fev.FieldStorageUploadConverter(if_missing=None)),