[Checkins] SVN: grokui.admin/trunk/ Merge changes from ulif-securitynotifications into trunk.

Uli Fouquet uli at gnufix.de
Tue Dec 23 05:52:37 EST 2008


Log message for revision 94273:
  Merge changes from ulif-securitynotifications into trunk.

Changed:
  U   grokui.admin/trunk/CHANGES.txt
  U   grokui.admin/trunk/src/grokui/admin/README.txt
  A   grokui.admin/trunk/src/grokui/admin/interfaces.py
  A   grokui.admin/trunk/src/grokui/admin/security.py
  A   grokui.admin/trunk/src/grokui/admin/tests/infoviews.py
  A   grokui.admin/trunk/src/grokui/admin/tests/releaseinfo/
  A   grokui.admin/trunk/src/grokui/admin/tests/security.py
  U   grokui.admin/trunk/src/grokui/admin/tests/server.py
  U   grokui.admin/trunk/src/grokui/admin/tests/test_grokadmin_functional.py
  U   grokui.admin/trunk/src/grokui/admin/utilities.py
  U   grokui.admin/trunk/src/grokui/admin/view.py
  U   grokui.admin/trunk/src/grokui/admin/view_templates/grokadminmacros.pt
  U   grokui.admin/trunk/src/grokui/admin/view_templates/server.pt

-=-
Modified: grokui.admin/trunk/CHANGES.txt
===================================================================
--- grokui.admin/trunk/CHANGES.txt	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/CHANGES.txt	2008-12-23 10:52:36 UTC (rev 94273)
@@ -4,6 +4,21 @@
 0.4 (unreleased)
 ================
 
+Feature changes
+---------------
+
+* Added a security notifier to inform users when security issues are
+  published on http://grok.zope.org. The notifier must be explicitly
+  enabled. You can also run your own site/directory to place security
+  notifications.
+
+* Added info views to get important information easier with tools like
+  ``curl``. Supported infos:
+
+    - Grok version used
+
+    - Current security notification (if any).
+
 0.3 (2008-12-13)
 ================
 

Modified: grokui.admin/trunk/src/grokui/admin/README.txt
===================================================================
--- grokui.admin/trunk/src/grokui/admin/README.txt	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/README.txt	2008-12-23 10:52:36 UTC (rev 94273)
@@ -38,7 +38,7 @@
 
 
 Applications
-============
+------------
 
 * List of all instanciated applications
 
@@ -60,8 +60,13 @@
 
 
 Server
-======
+------
 
+* Set security notifications. Those are by default disabled, because
+  they mean home-calling functionality you may do not want. You can
+  enable/disable those notifications or set a URL to retrieve
+  information about security related problems.
+
 * Start/Restart the server. Caution! This does not work, if the server
   was started in 'foreground mode' (with 'zopectl fg').
 
@@ -83,7 +88,7 @@
 
 
 Documentation
-=============
+-------------
 
 * From here you get starting points to the more elaborated
   documentation features of Grok, namely:
@@ -97,7 +102,26 @@
     gives documentation to classes, packages and other things, which
     are not instances.
 
+Maintaining grok installations with the admin UI
+================================================
 
+There are some special info views available especially for the use of
+system administrators that want to automate Grok administration in
+some aspects. They provide minimal information about certain topics.
+
+Currently the following infos are available this way:
+
+  - The grok version working in background::
+
+    curl -q -s -u admin:admin "http://localhost:8080/@@grokadmin/@@version"
+
+  - The security notification (if any)::
+
+    curl -q -s -u admin:admin "http://localhost:8080/@@grokadmin/@@secnote"
+
+Beside this you can pack the ZODB databases as described above.
+
+
 Bugs, Caveats and Ways to Get Help
 ==================================
 

Copied: grokui.admin/trunk/src/grokui/admin/interfaces.py (from rev 94272, grokui.admin/branches/ulif-securitynotifications/src/grokui/admin/interfaces.py)
===================================================================
--- grokui.admin/trunk/src/grokui/admin/interfaces.py	                        (rev 0)
+++ grokui.admin/trunk/src/grokui/admin/interfaces.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -0,0 +1,21 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Interfaces for the admin UI."""
+
+from zope.interface import Interface
+
+class ISecurityNotifier(Interface):
+    """A notifier the looks up security warnings somewhere.
+    """
+

Copied: grokui.admin/trunk/src/grokui/admin/security.py (from rev 94272, grokui.admin/branches/ulif-securitynotifications/src/grokui/admin/security.py)
===================================================================
--- grokui.admin/trunk/src/grokui/admin/security.py	                        (rev 0)
+++ grokui.admin/trunk/src/grokui/admin/security.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -0,0 +1,189 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Security notifications for `grokui.admin`.
+
+The machinery to do home-calling security notifications.
+"""
+import grok
+import time
+import urllib2
+import urlparse
+from zope.app.appsetup.interfaces import IDatabaseOpenedWithRootEvent
+from zope.app.folder.interfaces import IRootFolder
+from zope.component import adapter, provideHandler
+from persistent import Persistent
+from grokui.admin.interfaces import ISecurityNotifier
+from grokui.admin.utilities import getVersion, TimeoutableHTTPHandler
+
+class SecurityScreen(grok.ViewletManager):
+    """A viewlet manager that keeps security related notifications.
+    """
+    grok.name('grokadmin_security')
+    grok.context(IRootFolder)
+
+class SecurityNotificationViewlet(grok.Viewlet):
+    """Viewlet displaying notifications from a local `SecurityNotifier`.
+    """
+    grok.context(IRootFolder)
+
+    @property
+    def security_notifier(self):
+        """Get a local security notifier.
+
+        The security notifier is installed as a local utility by an
+        event handler triggered on startup.
+        """
+        site = grok.getSite()
+        site_manager = site.getSiteManager()
+        return site_manager.queryUtility(ISecurityNotifier, default=None)
+
+    def render(self):
+        return self.security_notifier.getNotification()
+
+class SecurityNotifier(Persistent):
+    """A security notifier.
+
+    It can be placed in a site to store notification dates and other
+    data persistently.
+    """
+
+    grok.implements(ISecurityNotifier)
+
+    VERSION = 1 # for possibly updates/downgrades
+    MSG_DISABLED = u'Security notifications are disabled.'
+    DEFAULT_URL = 'http://grok.zope.org/releaseinfo/'
+    
+    lookup_url = DEFAULT_URL
+    last_lookup = None   # When did we do the last lookup?
+    lookup_timeout = 2   # Number of seconds to wait
+    last_display = None  # When did we display the last time?
+    enabled = False      # By default we disable the notfier.
+
+    lookup_frequency = 3600 * 3 # Lookup every three hours.
+    display_frequency = 3600 * 3 # Display warnings every three hours.
+
+    _message = u''
+    _warningstate = False
+    
+    def enable(self):
+        """Enable security notifications.
+        """
+        self.enabled = True
+        return
+
+    def disable(self):
+        """Disable security notifications.
+        """
+        self.enabled = False
+        return
+
+    def getNotification(self):
+        """Get the current security notification.
+        """
+        if self.enabled is False:
+            return self.MSG_DISABLED
+        self.updateMessage()
+        return self._message
+
+    def isWarning(self):
+        self.updateMessage()
+        return self._warningstate
+    
+    def updateMessage(self):
+        """Update the security message.
+        """
+        if self.enabled is False:
+            return
+        if self.last_lookup is not None:
+            if time.time() - self.lookup_frequency < self.last_lookup:
+                return
+        self.fetchMessage()
+        return
+    
+    def fetchMessage(self):
+        """Possibly fetch security notfications from grok.zope.org.
+        """
+        if self.enabled is False:
+            # Safety belt.
+            return
+        version = getVersion('grok')
+        filename = 'grok-%s.security.txt' % version
+        url = urlparse.urljoin(self.lookup_url, filename)
+        # We create a HTTP handler with a timeout.
+        http_handler = TimeoutableHTTPHandler(timeout=self.lookup_timeout)
+        opener = urllib2.build_opener(http_handler)
+        req = urllib2.Request(url)
+        try:
+            self._message = opener.open(req).read()
+            self._warningstate = True
+        except (urllib2.HTTPError, OSError), e:
+            if (getattr(e, 'code', None) == 404) or (
+                getattr(e, 'errno', None) == 2):
+                # No security warning found, good message.
+                self._message = u''
+                self._warningstate = False
+        except:
+            # An unexpected problem occured...
+            pass
+        if self._message == self.MSG_DISABLED:
+            self._message = u''
+        self.last_lookup = time.time()
+        return
+
+    def setLookupURL(self, url):
+        """Set the url to lookup notifications.
+        """
+        self.lookup_url = url
+        self.last_lookup = None
+        return
+    
+    def display(self):
+        """Display the message.
+
+        In fact we only keep track of timestamps of display actions.
+        """
+        self.last_display = time.time()
+        return
+
+def setupSecurityNotification(site):
+    """Setup a SecurityNotifier as persistent utility.
+
+    The utility is installed as a local and persistent utility. It is
+    local to `site` and installed under the name
+    ``grokadmin_security`` in the site manager of `site`.
+
+    It can be retrieved with a call like::
+
+      site.getSiteManager().getUtiliy(ISecurityNotifier)
+
+    See also ``security.py`` in ``tests``.
+    """
+    site_manager = site.getSiteManager()
+    if 'grokadmin_security' not in site_manager:
+        site_manager['grokadmin_security'] = SecurityNotifier()
+    utility = site_manager['grokadmin_security']
+    site_manager.registerUtility(utility, ISecurityNotifier, name=u'')
+    return
+    
+ at adapter(IDatabaseOpenedWithRootEvent)
+def securitySetupHandler(event):
+    """Call security notification setup as soon as DB is ready.
+    """
+    from zope.app.appsetup.bootstrap import getInformationFromEvent
+    
+    db, connection, root, root_folder = getInformationFromEvent(event)
+    setupSecurityNotification(root_folder)
+    
+# ...then install the event handler:
+provideHandler(securitySetupHandler)

Copied: grokui.admin/trunk/src/grokui/admin/tests/infoviews.py (from rev 94272, grokui.admin/branches/ulif-securitynotifications/src/grokui/admin/tests/infoviews.py)
===================================================================
--- grokui.admin/trunk/src/grokui/admin/tests/infoviews.py	                        (rev 0)
+++ grokui.admin/trunk/src/grokui/admin/tests/infoviews.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Information views
+*****************
+
+`grokui.admin` provides info views that can be fetched with `curl` or
+similar tools to get a quick overview over the installation status.
+
+They provide minimal information bits about for example the current
+grok version and are provided for site administrators that want to
+keep track of the site status via usual system administration tools.
+
+We must have a browser available::
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+
+We must be authenticated to fetch those infos::
+
+  >>> browser.open('http://localhost/@@grokadmin/@@version')
+  Traceback (most recent call last):
+  ...
+  HTTPError: HTTP Error 401: Unauthorized
+
+Getting the current grok version
+--------------------------------
+  
+When we are authenticated, we can retrieve the grok version used::
+  
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  >>> browser.open('http://localhost/@@grokadmin/@@version')
+  >>> print browser.contents
+  grok ...
+
+The returned string has the format 'grok <MAJ>.<MIN>[.<BUGFIX>]' with
+a major release number (<MAJ>), a minor release number (<MIN>) and an
+oiptional bugfix release number like 'grok 0.14.1'::
+
+  >>> import re
+  >>> re.match('^grok \d+\.\d+(\.\d+)?.*$', browser.contents)
+  <_sre.SRE_Match object at 0x...>
+
+Getting the current security notification
+-----------------------------------------
+
+We can get the current security notification::
+
+  >>> browser.open('http://localhost/@@grokadmin/@@secnote')
+  >>> print browser.contents
+  Security notifications are disabled.
+
+"""

Copied: grokui.admin/trunk/src/grokui/admin/tests/security.py (from rev 94272, grokui.admin/branches/ulif-securitynotifications/src/grokui/admin/tests/security.py)
===================================================================
--- grokui.admin/trunk/src/grokui/admin/tests/security.py	                        (rev 0)
+++ grokui.admin/trunk/src/grokui/admin/tests/security.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -0,0 +1,245 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Tests for security notifications.
+
+The `SecurityNotifier`
+======================
+
+A security notifier is an object that checks an URL for security
+warnings and delivers them. It keeps track of lookup-dates etc., so
+that lookups are not performed too often.
+
+Because this is a 'calling-home' feature, it is disabled by
+default. SecurityNotifiers know about their status (enabled or
+disabled) and do no lookups when disabled.
+
+Security notifications are handled by a `SecurityNotifier`::
+
+  >>> from grokui.admin.security import SecurityNotifier
+  >>> sn = SecurityNotifier()
+
+Instances provide the `ISecurityNotifier` interface.
+  
+  >>> from grokui.admin.interfaces import ISecurityNotifier
+  >>> ISecurityNotifier.providedBy(sn)
+  True
+
+
+Enabling and disabling the notifier
+-----------------------------------
+  
+By default a security notifier is not enabled::
+
+  >>> sn.enabled
+  False
+
+We enable it::
+
+  >>> sn.enable()
+  >>> sn.enabled
+  True
+
+and disable again::
+
+  >>> sn.disable()
+  >>> sn.enabled
+  False
+
+While being disabled, the notifier will do no lookups, even if
+`updateMessage` or similar methods are called.
+
+Getting notifications
+---------------------
+
+We can get a notification, of course. Asking for that will not trigger
+a lookup, while the notifier is disabled::
+
+  >>> sn.getNotification()
+  u'Security notifications are disabled.'
+
+Even an explicit lookup request will not do lookups, while the
+notifier is not enabled::
+
+  >>> sn.updateMessage()
+  >>> sn.getNotification()
+  u'Security notifications are disabled.'
+
+
+Where to look for notifications
+-------------------------------
+
+When we want to do real lookups, then by default the Grok site is
+asked::
+
+  >>> sn.lookup_url
+  'http://grok.zope.org/releaseinfo/'
+
+But we can change the place to look for security warnings. We prepared
+a local directory with some warnings, which we will use as our
+information source::
+
+  >>> import os.path
+  >>> fake_source = os.path.join(os.path.dirname(__file__), 'releaseinfo')
+  >>> fake_source_url = 'file://%s' % fake_source + os.path.sep
+  >>> sn.setLookupURL(fake_source_url)
+
+Now we can safely enable the notifier and see, whether there are infos
+for us. It is sufficient to call `getNotification()` as this will
+update the stored information automatically.
+
+Before we really start, we will have a look at the lookup timestamp,
+that stores our last tries::
+
+  >>> last_lookup = sn.last_lookup
+  >>> last_lookup is None
+  True
+
+  >>> sn.enable()
+  >>> note = sn.getNotification()
+  >>> note
+  u''
+
+Ah, there is no security warning for our version. So let us create
+one::
+
+  >>> from grokui.admin.utilities import getVersion
+  >>> version = getVersion('grok')
+  >>> fake_warning_file = 'grok-%s.security.txt' % version
+  >>> fake_warning_file = os.path.join(fake_source, fake_warning_file)
+  >>> open(fake_warning_file, 'w').write('You better smash %s' % version)
+
+When we now ask the security notifier again::
+
+  >>> sn.getNotification()
+  u''
+
+We got the same answer as before. Why? The lookups are done only in
+certain intervals to reduce the amount of outgoing traffic. When we
+fix the lookup timestamp, we get the real value::
+
+  >>> sn.last_lookup = None
+  >>> sn.getNotification()
+  'You better smash ...'
+
+To decide, whether the delivered string is actually a warning, we can
+call the `isWarning` method::
+
+  >>> sn.isWarning()
+  True
+
+  
+`SecurityNotifier` in `grokui.admin`
+====================================
+
+In `grokui.admin` the security notifier is installed at startup as
+local utility, that can be looked up by the `ISecurityNotifer`
+interface.
+
+Currently, as `grokui.admin` is merely a collection of views bound to
+root folders, also the security notification utility is normally
+managed by the local site manager of the root folder::
+
+  >>> root = getRootFolder()
+  >>> sm = root.getSiteManager()
+
+Now we can lookup the utility::
+
+  >>> from grokui.admin.interfaces import ISecurityNotifier
+  >>> notifier = sm.getUtility(ISecurityNotifier)
+  >>> notifier
+  <grokui.admin.security.SecurityNotifier object at 0x...>
+
+The utility is local, because different root folders might want
+different settings for security notifications.
+
+The utility is persistent, so that the settings are preserved when
+shutting down.
+
+Immediately after startup, the notifier exists, but is disabled::
+
+  >>> notifier.enabled
+  False
+
+We can get notifications, of course::
+
+  >>> notifier.getNotification()
+  u'Security notifications are disabled.'
+
+We can check in a formal way, whether the current notification is a
+warning::
+
+  >>> notifier.isWarning()
+  False
+
+The notifier we got here is the same as when using the UI. We log into
+the admin screen to set a new notifier URL::
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  >>> browser.open('http://localhost/@@server')
+
+On the server administration page we can see the status of our
+notifier (enabled or disabled)::
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ... Status: Security notifications are disabled
+  ...
+
+We can also see the current message which also informs us, if security
+notifications are disabled. This message is displayed on (nearly)
+every `grokui.admin` page::
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...<div id="securitynotifications">Security notifications are disabled.</div>
+  ...
+
+But we are not bound to the default URL to do lookups. We can set
+another one ourselves::
+
+  >>> browser.getControl(name='secnotesource').value=fake_source_url
+  >>> browser.getControl('Set URL').click()
+
+Now, as we set a lookup URL which we can control better, we can enable
+the security notifications::
+
+  >>> browser.getControl('Enable').click()
+
+The result of the lookup again is displayed. This time we get a 'real'
+result::
+
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...<div id="securitynotifications">You better smash ...</div>
+  ...
+
+We can of course disable security notifications at any time::
+
+  >>> browser.getControl('Disable').click()
+  >>> print browser.contents
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...<div id="securitynotifications">Security notifications are disabled.</div>
+  ...
+  
+Clean up::
+
+  >>> import os
+  >>> os.unlink(fake_warning_file)
+
+
+  
+"""

Modified: grokui.admin/trunk/src/grokui/admin/tests/server.py
===================================================================
--- grokui.admin/trunk/src/grokui/admin/tests/server.py	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/tests/server.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -55,7 +55,7 @@
 
 If we submit that message, it should appear in the page::
 
-  >>> msg_form = browser.getForm(index=2)
+  >>> msg_form = browser.getForm(index=3)
   >>> msg_form.submit()
   >>> print browser.contents
   <html xmlns="http://www.w3.org/1999/xhtml">

Modified: grokui.admin/trunk/src/grokui/admin/tests/test_grokadmin_functional.py
===================================================================
--- grokui.admin/trunk/src/grokui/admin/tests/test_grokadmin_functional.py	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/tests/test_grokadmin_functional.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -15,6 +15,10 @@
 
 def setUp(test):
     FunctionalTestSetup().setUp()
+    # In functional tests no IDatabaseOpenedWithRootEvent are fired. We
+    # therefore have to setup security notifications manually
+    from grokui.admin.security import setupSecurityNotification
+    setupSecurityNotification(getRootFolder())
 
 def tearDown(test):
     FunctionalTestSetup().tearDown()

Modified: grokui.admin/trunk/src/grokui/admin/utilities.py
===================================================================
--- grokui.admin/trunk/src/grokui/admin/utilities.py	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/utilities.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -13,8 +13,12 @@
 ##############################################################################
 """Helper functions for grok admin.
 """
+import httplib
+import pkg_resources
 import re
+import socket
 import urllib
+import urllib2
 from zope.tal.taldefs import attrEscape
 from urlparse import urlparse, urlunparse
 
@@ -267,3 +271,62 @@
                 or item for item in v]
         url += '?' + urllib.urlencode(data, doseq=True)
     return url
+
+def getVersion(pkgname):
+    """Determine the version of `pkgname` used in background.
+    """
+    info = pkg_resources.get_distribution(pkgname)
+    if info.has_version and info.version:
+        return info.version
+    return None
+
+class TimeoutableHTTPConnection(httplib.HTTPConnection):
+    """A customised HTTPConnection allowing a per-connection
+    timeout, specified at construction.
+    """
+
+    def __init__(self, host, port=None, strict=None, timeout=None):
+        httplib.HTTPConnection.__init__(self, host, port,
+                strict)
+        self.timeout = timeout
+
+    def connect(self):
+        """Override HTTPConnection.connect to connect to
+        host/port specified in __init__."""
+
+        msg = "getaddrinfo returns an empty list"
+        for res in socket.getaddrinfo(self.host, self.port,
+                0, socket.SOCK_STREAM):
+            af, socktype, proto, canonname, sa = res
+            try:
+                self.sock = socket.socket(af, socktype, proto)
+                if self.timeout:   # this is the new bit
+                    self.sock.settimeout(self.timeout)
+                self.sock.connect(sa)
+            except socket.error, msg:
+                if self.sock:
+                    self.sock.close()
+                self.sock = None
+                continue
+            break
+        if not self.sock:
+            raise socket.error, msg
+
+class TimeoutableHTTPHandler(urllib2.HTTPHandler):
+    """A customised HTTPHandler which times out connection
+    after the duration specified at construction.
+    """
+
+    def __init__(self, timeout=None):
+        urllib2.HTTPHandler.__init__(self)
+        self.timeout = timeout
+
+    def http_open(self, req):
+        """Override http_open."""
+
+        def makeConnection(host, port=None, strict=None):
+            return TimeoutableHTTPConnection(host, port, strict,
+                    timeout = self.timeout)
+
+        return self.do_open(makeConnection, req)
+

Modified: grokui.admin/trunk/src/grokui/admin/view.py
===================================================================
--- grokui.admin/trunk/src/grokui/admin/view.py	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/view.py	2008-12-23 10:52:36 UTC (rev 94273)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2007 Zope Corporation and Contributors.
+# Copyright (c) 2007-2008 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -16,17 +16,21 @@
 import grok
 import os
 import inspect
+import time
 from urllib import urlencode
 
+from grokui.admin.interfaces import ISecurityNotifier
+
 from grokui.admin import docgrok
 from grokui.admin.docgrok import DocGrok, DocGrokPackage, DocGrokModule
 from grokui.admin.docgrok import DocGrokTextFile, DocGrokGrokApplication
 from grokui.admin.docgrok import DocGrokClass, DocGrokInterface, getItemLink
 
 from grokui.admin.objectinfo import ZopeObjectInfo
+from grokui.admin.security import SecurityNotifier
 from grokui.admin.utilities import getPathLinksForObject, getPathLinksForClass
 from grokui.admin.utilities import getPathLinksForDottedName, getParentURL
-from grokui.admin.utilities import getURLWithParams
+from grokui.admin.utilities import getURLWithParams, getVersion
 
 from ZODB.broken import Broken
 from ZODB.interfaces import IDatabase
@@ -63,6 +67,40 @@
 class ManageApplications(grok.Permission):
     grok.name('grok.ManageApplications')
 
+class GrokAdminInfoView(grok.View):
+    """A base to provide machinereadable views.
+    """
+    grok.name('grokadmin')
+    grok.require('grok.ManageApplications')
+    
+    def render(self):
+        return u'go to @@version or @@secnotes'
+
+class GrokAdminVersion(grok.View):
+    """Display grok version.
+
+    Call this view via http://localhost:8080/@@grokadmin/@@version
+    """
+    grok.name('version')
+    grok.context(GrokAdminInfoView)
+    grok.require('grok.ManageApplications')
+    def render(self):
+        return u'grok %s' % (getVersion('grok'),)
+
+class GrokAdminSecurityNotes(grok.View):
+    """Display current security notification.
+
+    Call this view via http://localhost:8080/@@grokadmin/@@secnote
+    """
+    grok.name('secnote')
+    grok.context(GrokAdminInfoView)
+    grok.require('grok.ManageApplications')
+    def render(self):
+        site = grok.getSite()
+        site_manager = site.getSiteManager()
+        notifier = site_manager.queryUtility(ISecurityNotifier, default=None)
+        return notifier.getNotification()
+    
 class Add(grok.View):
     """Add an application.
     """
@@ -157,6 +195,14 @@
 
     """
 
+    @property
+    def grok_version(self):
+        return getVersion('grok')
+
+    @property
+    def grokuiadmin_version(self):
+        return getVersion('grokui.admin')
+    
     def root_url(self, name=None):
         obj = self.context
         result = ""
@@ -441,6 +487,36 @@
     grok.require('grok.ManageApplications')
 
     @property
+    def security_notifier_url(self):
+        """Get the URL to look up for security warnings.
+        """
+        return self.security_notifier.lookup_url
+    
+    @property
+    def security_notifier(self):
+        """Get a local security notifier.
+
+        The security notifier is installed as a local utility by an
+        event handler in the security module.
+        """
+        site = grok.getSite()
+        site_manager = site.getSiteManager()
+        return site_manager.queryUtility(ISecurityNotifier, default=None)
+    
+    @property
+    def secnotes_enabled(self):
+        if self.security_notifier is None:
+            # Safety belt if installation of notifier failed
+            return False
+        return self.security_notifier.enabled
+
+    @property
+    def secnotes_message(self):
+        if self.security_notifier is None:
+            return u'Security notifier is not installed.'
+        return self.security_notifier.getNotification()
+    
+    @property
     def server_control(self):
         return zope.component.queryUtility(IServerControl, '', None)
 
@@ -458,14 +534,35 @@
         if messages:
             return messages[0]
 
+    def updateSecurityNotifier(self, setsecnotes=None, setsecnotesource=None,
+                               secnotesource=None):
+        if self.security_notifier is None:
+            return
+        if setsecnotesource is not None:
+            self.security_notifier.setLookupURL(secnotesource)
+        if setsecnotes is not None:
+            if self.security_notifier.enabled is True:
+                self.security_notifier.disable()
+            else:
+                self.security_notifier.enable()
+        if self.secnotes_enabled is False:
+            return
+        return
+        
     def update(self, time=None, restart=None, shutdown=None,
-               admin_message=None, submitted=False,
-               dbName="", pack=None, days=0):
+               setsecnotes=None, secnotesource=None, setsecnotesource=None,
+               admin_message=None, submitted=False, dbName="", pack=None,
+               days=0):
 
         # Packing control
         if pack is not None:
             return self.pack(dbName, days)
 
+        # Security notification control
+        self.updateSecurityNotifier(setsecnotes, setsecnotesource,
+                                    secnotesource)
+
+        
         if not submitted:
             return
 

Modified: grokui.admin/trunk/src/grokui/admin/view_templates/grokadminmacros.pt
===================================================================
--- grokui.admin/trunk/src/grokui/admin/view_templates/grokadminmacros.pt	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/view_templates/grokadminmacros.pt	2008-12-23 10:52:36 UTC (rev 94273)
@@ -71,13 +71,8 @@
             Server Control
           </span>
         </span>
-<!--
         &nbsp;&nbsp;
         <a href=""
-          tal:attributes="href view/root_url">Debug</a>
--->
-        &nbsp;&nbsp;
-        <a href=""
           tal:attributes="href string:${view/root_url}/docgrok/">
           <span tal:attributes="class python:view.in_docgrok() and 'emph'"
             >Documentation</span>
@@ -88,6 +83,10 @@
 
           <div id="messages" tal:content="structure context/@@messages" />
 
+	  <div id="securitynotifications"
+	       tal:content="structure provider:grokadmin_security"
+	       tal:on-error="nothing" />
+
           <div metal:define-slot="content">
 
             <h1>Welcome to Grok!</h1>

Modified: grokui.admin/trunk/src/grokui/admin/view_templates/server.pt
===================================================================
--- grokui.admin/trunk/src/grokui/admin/view_templates/server.pt	2008-12-23 10:39:05 UTC (rev 94272)
+++ grokui.admin/trunk/src/grokui/admin/view_templates/server.pt	2008-12-23 10:52:36 UTC (rev 94273)
@@ -2,6 +2,51 @@
   <div metal:fill-slot="content">
     <h1>Manage your Zope 3 instance</h1>
 
+    <fieldset>
+      <legend>Security notifications</legend>
+      <form method="post" action=""
+	    tal:attributes="action
+	    string:${context/@@absolute_url}/server">
+	<div>
+	  Running Grok <span tal:replace="view/grok_version" /> /
+	  grokui.admin <span tal:replace="view/grokuiadmin_version" />
+	</div>
+	<div>&nbsp;</div>
+	<div>
+	  You can be notified if serious security-related issues were
+	  discovered that concern your installation.
+	</div>
+	<div>
+	  Note, that if you enable this, HTTP-lookups of the grok site
+	  will happen to check, whether there are any published
+	  security issues for the installed version of grok.
+	</div>
+	<div>&nbsp;</div>
+	<div>
+	  <label for="secnotesource">URL to lookup:</label>
+	  <input type="text" size="30" name="secnotesource"
+		 tal:attributes="value view/security_notifier_url" />
+	  <input class="button" type="submit" name="setsecnotesource"
+		 value="Set URL" />
+	</div>
+	<div>&nbsp;</div>
+	<div tal:condition="view/secnotes_enabled">
+	  <span class="emph">
+	    Status: Security notifications are enabled
+	  </span>
+	  <input class="button" type="submit" name="setsecnotes"
+		 value="Disable" />
+	</div>
+	<div tal:condition="not: view/secnotes_enabled">
+	  <span class="emph">
+	    Status: Security notifications are disabled
+	  </span>
+	  <input class="button" type="submit" name="setsecnotes"
+		 value="Enable" />
+	</div>
+      </form>
+    </fieldset>
+
     <form method="post" action=""
       tal:attributes="action string:${context/@@absolute_url}/server">
       <fieldset>



More information about the Checkins mailing list