Problem: anything named "Journal", "Terminal", "Log", or "Analyze" is not
isolated. This is the biggest security hole we have right now: it is a
trivial way for any activity to get root access.

Idea: as a short-term hack (until we have good cryptographic signatures for
activities), only turn off isolation if an activity is in
.../share/sugar/activities. Installation here is only possible for root (or
at build time).

Implementation:
This makes sense to implement in activitybundle.py, respecting a line in
activity.info like:
bitfrost_requests = P_ROOT, P_OTHER_UNIMPLEMENTED_THING, ...
That means that the data then passes up the chain: to bundleregistry, to
activityregistryservice, to sugar.activity.registry, and then to
activityfactory. Passing it up the chain meant fixing the call signatures
all the way along, and doing some refactoring along the way.

Status:
Works, not well tested (I will test more before submitting it definitively.
Also I'll have to include the patch to Journal's activity.info. Patches to
the other activities and packaging concerns will wait for round 3.) Marcopg
or others, feel free to start the review on the included patches; there are
enough bigger design decisions evident that we can get a jump on review even
before I do the solid testing on Monday.

Consequences:
- Changing the four activities named above to be installed in
share/sugar/activities. To remove them, a country would need to use a
customization key.
- If the activities above are in a country's build, they cannot be
uninstalled by user. If they are upgraded by user, they lose their
unisolated powers; if the upgrade is removed, they regain them. (Not tested)

Related issues:
- The use of version numbers to distinguish two versions of a single
activity is improved by this patch, but is still inconsistent. Erratic
behaviour is expected when two versions of the same activity are present,
although in normal use (all installation through the journal) this would
never happen as the older versions would be uninstalled automatically.
- Of course, the long-term solution is activity signatures.
- It will still be possible for a web link to claim to be activity X, but to
actually replace Browse (or other) with a trojanned version. (I know, this
is only weakly related, but it came up while I was discussing this patch
with Eben, so I mention it here.) I tracced this: #7761
<http://dev.laptop.org/ticket/7761>
From 2c114c26003d10705e3d174d47eae11311bffaaf Mon Sep 17 00:00:00 2001
From: Jameson Quinn <[EMAIL PROTECTED]>
Date: Fri, 1 Aug 2008 13:40:25 -0600
Subject: [PATCH] bug #5657: gets security requests from activitybundle, checks them, and passes them up to registry

---
 service/activityregistryservice.py |   54 ++++++------------
 service/bundleregistry.py          |  107 ++++++++++++++++++++++++++++--------
 2 files changed, 102 insertions(+), 59 deletions(-)

diff --git a/service/activityregistryservice.py b/service/activityregistryservice.py
index 6ba5598..7b3415a 100644
--- a/service/activityregistryservice.py
+++ b/service/activityregistryservice.py
@@ -24,6 +24,11 @@ _ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry'
 _ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry'
 _ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry'
 
+def log_it(s):
+    f = file("/home/chema/.sugar/default/logs/hardcoded","ab")
+    f.write(s+"\n")
+    f.close()
+
 class ActivityRegistry(dbus.service.Object):
     def __init__(self):
         bus = dbus.SessionBus()
@@ -64,11 +69,8 @@ class ActivityRegistry(dbus.service.Object):
     @dbus.service.method(_ACTIVITY_REGISTRY_IFACE,
                          in_signature='', out_signature='aa{sv}')
     def GetActivities(self):
-        result = []
         registry = bundleregistry.get_registry()
-        for bundle in registry:
-            result.append(self._bundle_to_dict(bundle))
-        return result
+        return (bundle for bundle in registry)
 
     @dbus.service.method(_ACTIVITY_REGISTRY_IFACE,
                          in_signature='s', out_signature='a{sv}')
@@ -78,7 +80,8 @@ class ActivityRegistry(dbus.service.Object):
         if not bundle:
             return {}
         
-        return self._bundle_to_dict(bundle)
+        log_it("service about to return "+str(bundle))
+        return bundle
 
     @dbus.service.method(_ACTIVITY_REGISTRY_IFACE,
                          in_signature='s', out_signature='aa{sv}')
@@ -90,18 +93,15 @@ class ActivityRegistry(dbus.service.Object):
             name = bundle.get_name().lower()
             bundle_id = bundle.get_bundle_id().lower()
             if name.find(key) != -1 or bundle_id.find(key) != -1:
-                result.append(self._bundle_to_dict(bundle))
+                result.append(bundle)
 
         return result
 
     @dbus.service.method(_ACTIVITY_REGISTRY_IFACE,
                          in_signature='s', out_signature='aa{sv}')
     def GetActivitiesForType(self, mime_type):
-        result = []
         registry = bundleregistry.get_registry()
-        for bundle in registry.get_activities_for_type(mime_type):
-            result.append(self._bundle_to_dict(bundle))
-        return result
+        return registry.get_activities_for_type(mime_type)
 
     @dbus.service.method(_ACTIVITY_REGISTRY_IFACE,
                          in_signature='sib', out_signature='')
@@ -127,32 +127,14 @@ class ActivityRegistry(dbus.service.Object):
     def ActivityChanged(self, activity_info):
         pass
 
-    def _bundle_to_dict(self, bundle):
-        registry = bundleregistry.get_registry()
-        favorite = registry.is_bundle_favorite(bundle.get_bundle_id(),
-                                               bundle.get_activity_version())
-        x, y = registry.get_bundle_position(bundle.get_bundle_id(),
-                                            bundle.get_activity_version())
-        return {'name': bundle.get_name(),
-                'icon': bundle.get_icon(),
-                'bundle_id': bundle.get_bundle_id(),
-                'version': bundle.get_activity_version(),
-                'path': bundle.get_path(),
-                'command': bundle.get_command(),
-                'show_launcher': bundle.get_show_launcher(),
-                'favorite': favorite,
-                'installation_time': bundle.get_installation_time(),
-                'position_x': x,
-                'position_y': y}
-
-    def _bundle_added_cb(self, bundle_registry, bundle):
-        self.ActivityAdded(self._bundle_to_dict(bundle))
-
-    def _bundle_removed_cb(self, bundle_registry, bundle):
-        self.ActivityRemoved(self._bundle_to_dict(bundle))
-
-    def _bundle_changed_cb(self, bundle_registry, bundle):
-        self.ActivityChanged(self._bundle_to_dict(bundle))
+    def _bundle_added_cb(self, bundle_registry, bundledict):
+        self.ActivityAdded(bundledict)
+
+    def _bundle_removed_cb(self, bundle_registry, bundledict):
+        self.ActivityRemoved(bundledict)
+
+    def _bundle_changed_cb(self, bundle_registry, bundledict):
+        self.ActivityChanged(bundledict)
 
 _instance = None
 
diff --git a/service/bundleregistry.py b/service/bundleregistry.py
index e7c30a8..8ace8d9 100644
--- a/service/bundleregistry.py
+++ b/service/bundleregistry.py
@@ -20,6 +20,7 @@ import traceback
 
 import gobject
 import simplejson
+import dbus
 
 from sugar.bundle.activitybundle import ActivityBundle
 from sugar.bundle.bundle import MalformedBundleException
@@ -27,6 +28,13 @@ from sugar import env
 
 import config
 
+VALID_BITFROST = set("P_ROOT")
+
+def log_it(s):
+    f = file("/home/chema/.sugar/default/logs/hardcoded","ab")
+    f.write(s+"\n")
+    f.close()
+
 class BundleRegistry(gobject.GObject):
     """Service that tracks the available activity bundles"""
 
@@ -42,9 +50,11 @@ class BundleRegistry(gobject.GObject):
     def __init__(self):
         gobject.GObject.__init__(self)
 
+        
+        log_it("bundleregistry: yes.\n")
         self._mime_defaults = self._load_mime_defaults()
 
-        self._bundles = []
+        self._bundles = {}
         for activity_dir in self._get_activity_directories():
             self._scan_directory(activity_dir)
 
@@ -87,7 +97,7 @@ class BundleRegistry(gobject.GObject):
         because JSON doesn't support tuples and python won't accept a list
         as a dictionary key.
         """
-        if ' ' in bundle_id:
+        if ' ' in str(bundle_id):
             raise ValueError('bundle_id cannot contain spaces')
         return '%s %s' % (bundle_id, version)
 
@@ -130,7 +140,7 @@ class BundleRegistry(gobject.GObject):
 
         for bundle_id in default_activities:
             max_version = -1
-            for bundle in self._bundles:
+            for bundle in self._bundles.itervalues():
                 if bundle.get_bundle_id() == bundle_id and \
                         max_version < bundle.get_activity_version():
                     max_version = bundle.get_activity_version()
@@ -144,14 +154,16 @@ class BundleRegistry(gobject.GObject):
         self._write_favorites_file()
 
     def get_bundle(self, bundle_id):
-        """Returns an bundle given his service name"""
-        for bundle in self._bundles:
+        """Returns an bundle given its service name"""
+        log_it("get_bundle !!!!!!! " + bundle_id)
+        for bundle in self._bundles.itervalues():
             if bundle.get_bundle_id() == bundle_id:
-                return bundle
+                log_it(str(self._bundle_to_dict(bundle)))
+                return self._bundle_to_dict(bundle)
         return None
     
     def __iter__(self):
-        return self._bundles.__iter__()
+        return (self._bundle_to_dict(bundle) for bundle in self._bundles.itervalues())
 
     def _scan_directory(self, path):
         if not os.path.isdir(path):
@@ -185,37 +197,46 @@ class BundleRegistry(gobject.GObject):
         except MalformedBundleException:
             return False
 
-        self._bundles.append(bundle)
-        self.emit('bundle-added', bundle)
+        key = self._get_favorite_key(bundle.get_bundle_id(),
+                                     bundle.get_activity_version())
+        if key in self._bundles:
+            oldpath = self._bundles[key].get_path()
+            logging.error('Adding bundle with duplicate key: %s, %s' %
+                          (bundle_path, key, oldpath))
+            #now keep the older one - more stable.
+            if os.stat(oldpath).st_ctime < os.stat(bundle_path).st_ctime:
+                return False
+        self._bundles[key] = bundle
+        self.emit('bundle-added', self._bundle_to_dict(bundle))
         return True
 
     def remove_bundle(self, bundle_path):
-        for bundle in self._bundles:
+        for key, bundle in self._bundles.iteritems():
             if bundle.get_path() == bundle_path:
-                self._bundles.remove(bundle)
-                self.emit('bundle-removed', bundle)
+                self._bundles.pop(key)
+                self.emit('bundle-removed', self._bundle_to_dict(bundle))
                 return True
         return False
 
     def get_activities_for_type(self, mime_type):
         result = []
-        for bundle in self._bundles:
+        for bundle in self._bundles.itervalues():
             if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
                 if self.get_default_for_type(mime_type) == \
                         bundle.get_bundle_id():
-                    result.insert(0, bundle)
+                    result.insert(0, self._bundle_to_dict(bundle))
                 else:
-                    result.append(bundle)
+                    result.append(self._bundle_to_dict(bundle))
         return result
 
     def get_default_for_type(self, mime_type):
         if self._mime_defaults.has_key(mime_type):
-            return self._mime_defaults[mime_type]
+            return self._bundle_to_dict(self._mime_defaults[mime_type])
         else:
             return None
 
     def _find_bundle(self, bundle_id, version):
-        for bundle in self._bundles:
+        for bundle in self._bundles.itervalues():
             if bundle.get_bundle_id() == bundle_id and \
                     bundle.get_activity_version() == version:
                 return bundle
@@ -224,6 +245,7 @@ class BundleRegistry(gobject.GObject):
 
     def set_bundle_favorite(self, bundle_id, version, favorite):
         key = self._get_favorite_key(bundle_id, version)
+        self._bundledict_cache.pop(key, None)
         if favorite and not key in self._favorite_bundles:
             self._favorite_bundles[key] = None
         elif not favorite and key in self._favorite_bundles:
@@ -233,14 +255,14 @@ class BundleRegistry(gobject.GObject):
 
         self._write_favorites_file()
         bundle = self._find_bundle(bundle_id, version)
-        self.emit('bundle-changed', bundle)
+        self.emit('bundle-changed', self._bundle_to_dict(bundle))
 
-    def is_bundle_favorite(self, bundle_id, version):
-        key = self._get_favorite_key(bundle_id, version)
+    def is_bundle_favorite(self, key):
         return key in self._favorite_bundles
 
     def set_bundle_position(self, bundle_id, version, x, y):
         key = self._get_favorite_key(bundle_id, version)
+        self._bundledict_cache.pop(key, none)
         if key not in self._favorite_bundles:
             raise ValueError('Bundle %s %s not favorite' % (bundle_id, version))
 
@@ -254,13 +276,12 @@ class BundleRegistry(gobject.GObject):
 
         self._write_favorites_file()
         bundle = self._find_bundle(bundle_id, version)
-        self.emit('bundle-changed', bundle)
+        self.emit('bundle-changed', self._bundle_to_dict(bundle))
 
-    def get_bundle_position(self, bundle_id, version):
+    def get_bundle_position(self, key):
         """Get the coordinates where the user wants the representation of this
         bundle to be displayed. Coordinates are relative to a 1000x1000 area.
         """
-        key = self._get_favorite_key(bundle_id, version)
         if key not in self._favorite_bundles or \
                 self._favorite_bundles[key] is None or \
                 'position' not in self._favorite_bundles[key]:
@@ -273,6 +294,46 @@ class BundleRegistry(gobject.GObject):
         favorites_data = {'defaults-mtime': self._last_defaults_mtime,
                           'favorites': self._favorite_bundles}
         simplejson.dump(favorites_data, open(path, 'w'), indent=1)
+        
+    def _validate_bitfrost(self, bundle):
+        requested = bundle.get_bitfrost_requests().intersection(VALID_BITFROST)
+        if "P_ROOT" in requested:
+            #only allow root for .../share/sugar/activities
+            #do not check full path for jhbuild compatibility.
+            path = os.path.split(bundle.get_path())
+            if path[-4:-1] != ["share", "sugar", "activities"]:
+                requested = requested.difference(["P_ROOT"])
+                
+            #only allow root if owner is the same as owner of this module,
+            #and permissions are same or tighter.    
+            else:
+                bundle_s = os.stat(path)
+                thismod_s = os.stat(sys.modules[__name__].__file__)
+                if (bundle_s.st_uid != thismod_s.st_uid or
+                      ((bundle_s.st_mode | thismod_s.st_mode) > 
+                         thismod_s.st_mode)):
+                    requested = requested.difference(["P_ROOT"])
+        return requested
+
+    def _bundle_to_dict(self, bundle):
+        key = self._get_favorite_key(bundle.get_bundle_id(),
+                                     bundle.get_activity_version())
+        favorite = self.is_bundle_favorite(key)
+        x, y = self.get_bundle_position(key)
+        result = {'name': bundle.get_name(),
+                  'icon': bundle.get_icon(),
+                  'bundle_id': bundle.get_bundle_id(),
+                  'version': bundle.get_activity_version(),
+                  'path': bundle.get_path(),
+                  'command': bundle.get_command(),
+                  'show_launcher': bundle.get_show_launcher(),
+                  'favorite': favorite,
+                  'installation_time': bundle.get_installation_time(),
+                  'position_x': x,
+                  'position_y': y,
+                  'bitfrost': dbus.Array(self._validate_bitfrost(bundle),
+                                         signature="as")}
+        return result
 
 _instance = None
 
-- 
1.5.2.5

From 5f6d05631b6ae5c1e379d746a632ee7a9c064e2d Mon Sep 17 00:00:00 2001
From: Jameson Quinn <[EMAIL PROTECTED]>
Date: Fri, 1 Aug 2008 13:14:28 -0600
Subject: [PATCH] bug #5657: bitfrost requests in bundle, handled in registry and activityfactory. The middle part is in sugar-base, separate patch.

---
 src/sugar/activity/activityfactory.py |   17 +++++++----
 src/sugar/activity/registry.py        |   49 ++++++++++++++++-----------------
 src/sugar/bundle/activitybundle.py    |   38 +++++++++++++++++---------
 3 files changed, 60 insertions(+), 44 deletions(-)

diff --git a/src/sugar/activity/activityfactory.py b/src/sugar/activity/activityfactory.py
index 4d6871f..9912ca5 100644
--- a/src/sugar/activity/activityfactory.py
+++ b/src/sugar/activity/activityfactory.py
@@ -29,7 +29,7 @@ from sugar.activity import registry
 from sugar import util
 from sugar import env
 
-from errno import EEXIST
+from errno import EEXIST, ENOSPC
 
 import os
 
@@ -150,6 +150,9 @@ def open_log_file(activity):
         except OSError, e:
             if e.errno == EEXIST:
                 i += 1
+            elif e.errno == ENOSPC:
+                # not the end of the world; let's try to keep going.
+                return ('/dev/null', open('/dev/null','w'))
             else:
                 raise e
 
@@ -241,11 +244,9 @@ class ActivityCreationHandler(gobject.GObject):
                                   self._handle.object_id,
                                   self._handle.uri)
 
-            if not self._use_rainbow:
-                p = subprocess.Popen(command, env=environ, cwd=activity.path,
-                                     stdout=log_file, stderr=log_file)
-                _children_pid.append(p.pid)
-            else:
+            use_rainbow = (os.path.exists('/etc/olpc-security') and
+                           "P_ROOT" in activity["bitfrost"])
+            if use_rainbow:
                 log_file.close()
                 system_bus = dbus.SystemBus()
                 factory = system_bus.get_object(_RAINBOW_SERVICE_NAME,
@@ -260,6 +261,10 @@ class ActivityCreationHandler(gobject.GObject):
                         reply_handler=self._create_reply_handler,
                         error_handler=self._create_error_handler,
                         dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE)
+            else:
+                p = subprocess.Popen(command, env=environ, cwd=activity.path,
+                                     stdout=log_file, stderr=log_file)
+                _children_pid.append(p.pid)
 
     def _no_reply_handler(self, *args):
         pass
diff --git a/src/sugar/activity/registry.py b/src/sugar/activity/registry.py
index da2eb27..51cd994 100644
--- a/src/sugar/activity/registry.py
+++ b/src/sugar/activity/registry.py
@@ -25,19 +25,20 @@ _ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry'
 _ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry'
 _ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry'
 
+def log_it(s):
+    f = file("/home/chema/.sugar/default/logs/hardcoded","ab")
+    f.write(s+"\n")
+    f.close()
+
 def _activity_info_from_dict(info_dict):
     if not info_dict:
-        return None
-    return ActivityInfo(info_dict['name'], info_dict['icon'],
-                        info_dict['bundle_id'], info_dict['version'],
-                        info_dict['path'], info_dict['show_launcher'],
-                        info_dict['command'], info_dict['favorite'],
-                        info_dict['installation_time'],
-                        info_dict['position_x'], info_dict['position_y'])
+        return {}
+    return ActivityInfo(**dict((str(key),val)
+                               for key,val in info_dict.iteritems()))
 
 class ActivityInfo(object):
-    def __init__(self, name, icon, bundle_id, version, path, show_launcher,
-                 command, favorite, installation_time, position_x, position_y):
+    """A convenience class whose members are:
+    
         self.name = name
         self.icon = icon
         self.bundle_id = bundle_id
@@ -48,6 +49,12 @@ class ActivityInfo(object):
         self.favorite = favorite
         self.installation_time = installation_time
         self.position = (position_x, position_y)
+        self.bitfrost = bitfrost
+    """
+    def __init__(self, **args):
+        self.position = (args["position_x"], args["position_y"])
+        del args["position_x"], args["position_y"]
+        self.__dict__.update(args)
 
 class ActivityRegistry(gobject.GObject):
     __gsignals__ = {
@@ -83,24 +90,12 @@ class ActivityRegistry(gobject.GObject):
         self._service_name_to_activity_info = {}
         self._mime_type_to_activities = {}
 
-    def _convert_info_list(self, info_list):
-        result = []
-
-        for info_dict in info_list:
-            result.append(_activity_info_from_dict(info_dict))
-
-        return result
-
     def get_activities(self):
         info_list = self._registry.GetActivities()
-        return self._convert_info_list(info_list)
+        return (_activity_info_from_dict(info) for info in info_list)
 
     def _get_activities_cb(self, reply_handler, info_list):
-        result = []
-        for info_dict in info_list:
-            result.append(_activity_info_from_dict(info_dict))
-
-        reply_handler(result)
+        reply_handler(_activity_info_from_dict(info) for info in info_list)
 
     def _get_activities_error_cb(self, error_handler, e):
         if error_handler:
@@ -121,10 +116,14 @@ class ActivityRegistry(gobject.GObject):
                     self._get_activities_error_cb(error_handler, e))
 
     def get_activity(self, service_name):
+        log_it("get_activity " + service_name)
         if self._service_name_to_activity_info.has_key(service_name):
             return self._service_name_to_activity_info[service_name]
 
+        log_it("get_activity 2 " + service_name)
         info_dict = self._registry.GetActivity(service_name)
+        
+        log_it("get_activity 3 " + str(info_dict))
         activity_info = _activity_info_from_dict(info_dict)
 
         self._service_name_to_activity_info[service_name] = activity_info
@@ -132,14 +131,14 @@ class ActivityRegistry(gobject.GObject):
 
     def find_activity(self, name):
         info_list = self._registry.FindActivity(name)
-        return self._convert_info_list(info_list)
+        return (_activity_info_from_dict(info) for info in info_list)
 
     def get_activities_for_type(self, mime_type):
         if self._mime_type_to_activities.has_key(mime_type):
             return self._mime_type_to_activities[mime_type]
 
         info_list = self._registry.GetActivitiesForType(mime_type)
-        activities = self._convert_info_list(info_list)
+        activities = (_activity_info_from_dict(info) for info in info_list)
 
         self._mime_type_to_activities[mime_type] = activities
         return activities
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
index 88ef5f1..29768e3 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -23,8 +23,7 @@ import os
 import tempfile
 
 from sugar.bundle.bundle import Bundle, MalformedBundleException, \
-    AlreadyInstalledException, RegistrationException, \
-    NotInstalledException
+    AlreadyInstalledException, RegistrationException, NotInstalledException
 
 from sugar import activity
 from sugar import env
@@ -43,18 +42,22 @@ class ActivityBundle(Bundle):
     _zipped_extension = '.xo'
     _unzipped_extension = '.activity'
     _infodir = 'activity'
+    
+    _name = None
+    _icon = None
+    _bundle_id = None
+    _mime_types = None
+    _show_launcher = True
+    _activity_version = 0
+    _bitfrost_requests = frozenset()
+    
 
     def __init__(self, path):
         Bundle.__init__(self, path)
         self.activity_class = None
         self.bundle_exec = None
         
-        self._name = None
-        self._icon = None
-        self._bundle_id = None
-        self._mime_types = None
-        self._show_launcher = True
-        self._activity_version = 0
+        self._installation_time = os.stat(path).st_mtime
 
         info_file = self.get_file('activity/activity.info')
         if info_file is None:
@@ -65,7 +68,7 @@ class ActivityBundle(Bundle):
         if linfo_file:
             self._parse_linfo(linfo_file)
 
-        self.manifest = None #This should be replaced by following function
+        self.manifest = None # This should be replaced by following function
         self.read_manifest()
 
     def _raw_manifest(self):
@@ -176,7 +179,14 @@ class ActivityBundle(Bundle):
                 raise MalformedBundleException(
                     'Activity bundle %s has invalid version number %s' %
                     (self._path, version))
-
+        
+        if cp.has_option(section, "bitfrost_requests"):
+            bitfrost = cp.get(section, "bitfrost_requests")
+            #bitfrost_requests = P_SOMETHING, P_SOMETHING_ELSE, p_some garbage, P_MAL FORMED,
+            #becomes set("P_SOMETHING","P_SOMETHING_ELSE","P_MAL FORMED")
+            bitfrost = (word.strip() for word in bitfrost.split(",") if word.strip().startswith("P_"))
+            self._bitfrost_requests = set(bitfrost)
+            
     def _get_linfo_file(self):
         lang = locale.getdefaultlocale()[0]
         if not lang:
@@ -226,7 +236,7 @@ class ActivityBundle(Bundle):
     def get_installation_time(self):
         """Get a timestamp representing the time at which this activity was
         installed."""
-        return os.stat(self._path).st_mtime
+        return self._installation_time
 
     def get_bundle_id(self):
         """Get the activity bundle id"""
@@ -259,6 +269,8 @@ class ActivityBundle(Bundle):
 
         return command
 
+    def get_bitfrost_requests(self):
+        return self._bitfrost_requests
 
     def get_mime_types(self):
         """Get the MIME types supported by the activity"""
@@ -275,10 +287,10 @@ class ActivityBundle(Bundle):
             return False
 
     def need_upgrade(self):
-        """Returns True if installing this activity bundle is meaningful - 
+        """Returns True if installing this activity bundle is meaningful -
         that is, if an identical version of this activity is not
         already installed.
-        
+
         Until we have cryptographic hashes to check identity, returns
         True always. See http://dev.laptop.org/ticket/7534.""";
         return True
-- 
1.5.2.5

_______________________________________________
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar

Reply via email to