diff --git a/bin/dm-download b/bin/dm-download
new file mode 100755
index 0000000000000000000000000000000000000000..b0d57dfc8687484c5c3789abe942c74401a08f7e
--- /dev/null
+++ b/bin/dm-download
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Run command
+
+if [ -z $DM_ROOT_DIR ]; then
+    cd `dirname $0` && myDir=`pwd`
+    setupFile=$myDir/../setup.sh
+    if [ ! -f $setupFile ]; then
+        echo "Cannot find setup file: $setupFile"
+        exit 1
+    fi
+    source $setupFile > /dev/null
+fi
+source dm_command_setup.sh
+
+eval "$DM_ROOT_DIR/src/python/dm/ds_web_service/cli/downloadCli.py $DM_COMMAND_ARGS"
+
+
diff --git a/doc/RELEASE_NOTES.txt b/doc/RELEASE_NOTES.txt
index e19e6eca263a3f45279644f6c1b488f1cf3288fa..90d7d0d70c6593e1805b0056da2079981b518efe 100644
--- a/doc/RELEASE_NOTES.txt
+++ b/doc/RELEASE_NOTES.txt
@@ -7,6 +7,8 @@ Release 0.7 ()
   simultaneously (required changes to DAQ service REST interfaces)
 - Enhanced start/stop DAQ and upload commands to use DM_FILE_SERVER_URL
   environment variable
+- Added user interfaces and utilities that enable experiment data download
+  from machines that have SSH access to the storage host
 
 Release 0.6 (11/6/2015)
 =============================
diff --git a/etc/dm.sudo-rules.template b/etc/dm.sudo-rules.template
index ad7f67d906b04175e34f6306244ffa6403d40b89..afc042be96a25874f2808d914800812065a58d29 100644
--- a/etc/dm.sudo-rules.template
+++ b/etc/dm.sudo-rules.template
@@ -6,7 +6,7 @@ Cmnd_Alias SETFACL=/usr/bin/setfacl -m group\:*\:rx DM_STORAGE_DIR/*
 Cmnd_Alias USERMOD=/usr/sbin/usermod -a -G * *
 Cmnd_Alias GROUPADD=/usr/sbin/groupadd *
 Cmnd_Alias CHOWN=/bin/chown -R \:* *
-Cmnd_Alias GPASSWD=/usr/bin/gpasswd -M * *
+Cmnd_Alias GPASSWD=/usr/bin/gpasswd * * *
 
 USER HOST = (root) NOPASSWD: SETFACL,USERMOD,GROUPADD,CHOWN,GPASSWD
 
diff --git a/src/python/dm/common/plugins/__init__.py b/src/python/dm/common/plugins/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/python/dm/common/plugins/fileProcessorInterface.py b/src/python/dm/common/plugins/fileProcessorInterface.py
deleted file mode 100755
index f9aef1cedf77655088cdfe436fc0d0a2538e5b92..0000000000000000000000000000000000000000
--- a/src/python/dm/common/plugins/fileProcessorInterface.py
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env python
-
-import abc
-class FileProcessorInterface:
-
-    @abc.abstractmethod
-    def processFile(self, filePath, daqPath, experiment):
-        return NotImplemented
-        
diff --git a/src/python/dm/common/plugins/fileTransferPlugin.py b/src/python/dm/common/plugins/fileTransferPlugin.py
deleted file mode 100755
index 1b369025fd72713d282e96cfd036912c0186bff0..0000000000000000000000000000000000000000
--- a/src/python/dm/common/plugins/fileTransferPlugin.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env python
-
-import os
-from dm.common.utility.loggingManager import LoggingManager
-from dm.common.utility.dmSubprocess import DmSubprocess
-from dm.common.exceptions.invalidArgument import InvalidArgument
-from dm.common.exceptions.invalidRequest import InvalidRequest
-from fileProcessorInterface import FileProcessorInterface
-
-class FileTransferPlugin(FileProcessorInterface):
-
-    def __init__(self, command, src=None, dest=None):
-        self.src = src
-        self.dest = dest
-        self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__)
-        if command is None or not len(command):
-            raise InvalidArgument('File transfer command must be non-empty string.')
-        self.command = command
-        self.subprocess = None
-
-    def processFile(self, filePath, daqPath, experiment):
-        storageHost = experiment.get('storageHost')
-        storageDirectory = experiment.get('storageDirectory')
-        dest = '%s:%s' % (storageHost, storageDirectory)
-        # Use relative path with respect to daq directory as a source
-        os.chdir(daqPath)
-        src = os.path.relpath(filePath, daqPath)
-        self.start(src, dest)
-
-    def getFullCommand(self, src, dest):
-        return '%s %s %s' % (self.command, src, dest)
-
-    def setSrc(self, src):
-        self.src = src
-
-    def setDest(self, dest):
-        self.dest = dest
-
-    def start(self, src=None, dest=None):
-        # Use preconfigured source if provided source is None
-        fileSrc = src
-        if src is None:
-            fileSrc = self.src
-        # Use provided destination only if preconfigured destination is None
-        # Plugins may have desired destination preconfigured for all files
-        fileDest = self.dest
-        if self.dest is None:
-            fileDest = dest
-
-        if not fileSrc or not fileDest:
-            raise InvalidRequest('Both source and destination must be non-empty strings.')
-        self.subprocess = DmSubprocess.getSubprocess(self.getFullCommand(fileSrc, fileDest))
-        return self.subprocess.run()
-
-    def wait(self):
-        if self.subprocess:
-            return self.subprocess.wait()
-        return None
-
-    def poll(self):
-        if self.subprocess:
-            return self.subprocess.poll()
-        return None
-
-    def getStdOut(self):
-        if self.subprocess:
-            return self.subprocess.getStdOut()
-        return None
-
-
-    def getStdErr(self):
-        if self.subprocess:
-            return self.subprocess.getStdErr()
-        return None
-
-    def getExitStatus(self):
-        if self.subprocess:
-            return self.subprocess.getExitStatus()
-        return None
-
-    def reset(self):
-        self.subprocess = None
-
-#######################################################################
-# Testing.
-if __name__ == '__main__':
-    ft = FileTransfer('rsync -arv', '/tmp/xyz', '/tmp/xyz2')
-    ft.start()
-    print 'StdOut: ', ft.getStdOut()
-    print 'StdErr: ', ft.getStdErr()
-    print 'Exit Status: ', ft.getExitStatus()
diff --git a/src/python/dm/common/plugins/rsyncFileTransferPlugin.py b/src/python/dm/common/plugins/rsyncFileTransferPlugin.py
deleted file mode 100755
index 3550d0cf22b5ec2cca5671f56e488182d0cb05c5..0000000000000000000000000000000000000000
--- a/src/python/dm/common/plugins/rsyncFileTransferPlugin.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python
-
-from fileTransferPlugin import FileTransferPlugin
-class RsyncFileTransferPlugin(FileTransferPlugin):
-
-    COMMAND = 'rsync -arvlPR'
-
-    def __init__(self, src=None, dest=None):
-        FileTransferPlugin.__init__(self, self.COMMAND, src, dest)
-
-#######################################################################
-# Testing.
-if __name__ == '__main__':
-    ft = RsyncFileTransferPlugin('/tmp/xyz', '/tmp/xyz2')
-    ft.start()
-    print 'StdOut: ', ft.getStdOut()
-    print 'StdErr: ', ft.getStdErr()
-    print 'Exit Status: ', ft.getExitStatus()
diff --git a/src/python/dm/common/utility/configurationManager.py b/src/python/dm/common/utility/configurationManager.py
index 55cf8e3c7181eb27062923d76c9cc44d883745ba..075e9c9e56677ba3d1e8eda1a9de6e54488df0ab 100755
--- a/src/python/dm/common/utility/configurationManager.py
+++ b/src/python/dm/common/utility/configurationManager.py
@@ -816,3 +816,4 @@ class ConfigurationManager(UserDict.UserDict):
 if __name__ == '__main__':
     cm = ConfigurationManager.getInstance()
     print cm
+    print cm.getUsername()
diff --git a/src/python/dm/common/utility/ldapLinuxPlatformUtility.py b/src/python/dm/common/utility/ldapLinuxPlatformUtility.py
index 354a6b7097aa901be264097c13a8f38649dfeb9e..4db4845577f4b25d4d44ae666ed51701a4b01f98 100755
--- a/src/python/dm/common/utility/ldapLinuxPlatformUtility.py
+++ b/src/python/dm/common/utility/ldapLinuxPlatformUtility.py
@@ -18,6 +18,7 @@ class LdapLinuxPlatformUtility:
     USERMOD_CMD = '/usr/sbin/usermod'
     SETFACL_CMD = '/usr/bin/setfacl'
     CHOWN_CMD = '/bin/chown'
+    GPASSWD_CMD = '/usr/bin/gpasswd'
 
     def __init__(self, serverUrl, adminDn, adminPasswordFile, groupDnFormat, minGidNumber=None):
         self.serverUrl = serverUrl
@@ -164,12 +165,21 @@ class LdapLinuxPlatformUtility:
             logger.error('Could not add user %s to group %s: %s' % (username, groupName, ex))
             raise InternalError(exception=ex)
 
+
     @classmethod
     def addLocalUserToGroup(cls, username, groupName):
         """ Add local user to group. """
         logger = cls.getLogger()
         logger.debug('Adding local user %s to group %s' % (username, groupName))
-        cmd = '%s -a -G %s %s' % (cls.USERMOD_CMD, groupName, username)
+        cmd = '%s -a %s %s' % (cls.GPASSWD_CMD, username, groupName)
+        cls.executeSudoCommand(cmd)
+
+    @classmethod
+    def deleteLocalUserFromGroup(cls, username, groupName):
+        """ Remove local user from group. """
+        logger = cls.getLogger()
+        logger.debug('Removing local user %s from group %s' % (username, groupName))
+        cmd = '%s -d %s %s' % (cls.GPASSWD_CMD, username, groupName)
         cls.executeSudoCommand(cmd)
 
     def getGroupInfo(self, groupName):
diff --git a/src/python/dm/common/utility/linuxUtility.py b/src/python/dm/common/utility/linuxUtility.py
index afe5c2a1714eb1cc86f5c9364a4fb728626e994b..97acf654df2bcce81bfd05918ff09bb95c7bd357 100755
--- a/src/python/dm/common/utility/linuxUtility.py
+++ b/src/python/dm/common/utility/linuxUtility.py
@@ -49,7 +49,18 @@ class LinuxUtility:
     @classmethod
     def addLocalUserToGroup(cls, username, groupName):
         """ Add local user to group. """
-        cls.addUserToGroup(username, groupName)
+        logger = cls.getLogger()
+        logger.debug('Adding local user %s to group %s' % (username, groupName))
+        cmd = '%s -a %s %s' % (cls.GPASSWD_CMD, username, groupName)
+        cls.executeSudoCommand(cmd)
+
+    @classmethod
+    def deleteLocalUserFromGroup(cls, username, groupName):
+        """ Remove local user from group. """
+        logger = cls.getLogger()
+        logger.debug('Removing local user %s from group %s' % (username, groupName))
+        cmd = '%s -d %s %s' % (cls.GPASSWD_CMD, username, groupName)
+        cls.executeSudoCommand(cmd)
 
     @classmethod
     def setGroupUsers(cls, groupName, usernameList):
diff --git a/src/python/dm/common/utility/rsyncFileTransfer.py b/src/python/dm/common/utility/rsyncFileTransfer.py
new file mode 100755
index 0000000000000000000000000000000000000000..c553aa5f9ded636c38c288db5df09de2e30d61f7
--- /dev/null
+++ b/src/python/dm/common/utility/rsyncFileTransfer.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+from dm.common.utility.dmSubprocess import DmSubprocess
+
+class RsyncFileTransfer:
+
+    COMMAND = 'rsync'
+
+    def __init__(self, src, dest, flags='-arvlP'):
+        self.src = src
+        self.dest = dest
+        self.flags = flags
+        self.command = '%s %s %s %s' % (self.COMMAND, self.flags, self.src, self.dest)
+        self.subprocess = DmSubprocess.getSubprocess(self.command)
+
+    def execute(self):
+        return self.subprocess.run()
+
+    def getStdOut(self):
+        return self.subprocess.getStdOut()
+
+    def getStdErr(self):
+        return self.subprocess.getStdErr()
+
+    def getExitStatus(self):
+        return self.subprocess.getExitStatus()
+
+#######################################################################
+# Testing.
+if __name__ == '__main__':
+    ft = RsyncFileTransfer('/tmp/abc', '/tmp/abc2')
+    ft.execute()
+    print 'StdOut: ', ft.getStdOut()
+    print 'StdErr: ', ft.getStdErr()
+    print 'Exit Status: ', ft.getExitStatus()
diff --git a/src/python/dm/ds_web_service/api/fileRestApi.py b/src/python/dm/ds_web_service/api/fileRestApi.py
index 597d886b61f19bf02c6c5a5468d9dc927a1cb8bc..6d365118a4bfdeaa719b8f86bae6292da0685c03 100755
--- a/src/python/dm/ds_web_service/api/fileRestApi.py
+++ b/src/python/dm/ds_web_service/api/fileRestApi.py
@@ -3,10 +3,14 @@
 import os
 import urllib
 import json
+import getpass
 
 from dm.common.utility.encoder import Encoder
 from dm.common.exceptions.dmException import DmException
+from dm.common.exceptions.invalidRequest import InvalidRequest
 from dm.common.objects.fileMetadata import FileMetadata
+from dm.common.objects.experiment import Experiment
+from dm.common.utility.rsyncFileTransfer import RsyncFileTransfer
 from dsRestApi import DsRestApi
 
 class FileRestApi(DsRestApi):
@@ -40,6 +44,33 @@ class FileRestApi(DsRestApi):
         responseDict = self.sendSessionRequest(url=url, method='POST')
         return FileMetadata(responseDict)
 
+    @DsRestApi.execute
+    def download(self, experimentName, experimentFilePath='',  destDirectory='.'):
+        username = getpass.getuser()
+
+        # Initialize download
+        url = '%s/downloadAuthorizations/%s/%s' % (self.getContextRoot(), username, experimentName)
+        if not experimentName:
+            raise InvalidRequest('Experiment name must be provided.')
+        self.logger.info('Authorizing download for user %s (experiment: %s)' % (username, experimentName))
+        responseDict = self.sendSessionRequest(url=url, method='POST')
+        experiment = Experiment(responseDict)
+        storageDirectory = experiment.get('storageDirectory')
+        storageHost = experiment.get('storageHost')
+        src = '%s@%s:%s' % (username, storageHost, storageDirectory)
+        if experimentFilePath:
+            src = '%s/%s' % (src, experimentFilePath)
+        dest = destDirectory
+             
+        # Download
+        fileTransfer = RsyncFileTransfer(src=src, dest=dest)
+        self.logger.info('Executing file download on behalf of %s (experiment: %s)' % (username, experimentName))
+        fileTransfer.execute()
+
+        # Finalize download
+        self.logger.info('Deleting download authorization for user %s (experiment: %s)' % (username, experimentName))
+        self.sendSessionRequest(url=url, method='DELETE')
+
 #######################################################################
 # Testing.
 
diff --git a/src/python/dm/ds_web_service/cli/addExperimentCli.py b/src/python/dm/ds_web_service/cli/addExperimentCli.py
index 566a41d007ada37b1782854e95b8e24d6b6a2af7..91a0dab5a9cd27d51ee224aad9c4392bf9e9e061 100755
--- a/src/python/dm/ds_web_service/cli/addExperimentCli.py
+++ b/src/python/dm/ds_web_service/cli/addExperimentCli.py
@@ -10,8 +10,8 @@ class AddExperimentCli(DsWebServiceSessionCli):
         self.addOption('', '--experiment', dest='experimentName', help='Experiment name.')
         self.addOption('', '--type-id', dest='typeId', help='Experiment type id.')
         self.addOption('', '--description', dest='description', help='Experiment description.')
-        self.addOption('', '--start-date', dest='startDate', help='Experiment start date in format 31-AUG-15.')
-        self.addOption('', '--end-date', dest='endDate', help='Experiment end date in format 31-AUG-15.')
+        self.addOption('', '--start-date', dest='startDate', help='Experiment start date in format DD-MMM-YY.')
+        self.addOption('', '--end-date', dest='endDate', help='Experiment end date in format DD-MMM-YY.')
 
     def checkArgs(self):
         if self.options.experimentName is None:
@@ -19,7 +19,6 @@ class AddExperimentCli(DsWebServiceSessionCli):
         if self.options.typeId is None:
             raise InvalidRequest('Experiment type id must be provided.')
 
-
     def getExperimentName(self):
         return self.options.experimentName
 
diff --git a/src/python/dm/ds_web_service/cli/downloadCli.py b/src/python/dm/ds_web_service/cli/downloadCli.py
new file mode 100755
index 0000000000000000000000000000000000000000..fe9650e6b9d4ed2dc60b8c3a7de025ba3f2234e6
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/downloadCli.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dm.ds_web_service.api.fileRestApi import FileRestApi
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class DownloadCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+        self.addOption('', '--experiment', dest='experimentName', help='Experiment name.')
+        self.addOption('', '--relative-path', dest='experimentFilePath', default='', help='Experiment (relative) file path. If omitted, all experiment data will be downloaded.')
+        self.addOption('', '--destination-directory', dest='destinationDirectory', default='.', help='Destination directory. If omitted, files will be downloaded to current directory.')
+
+    def checkArgs(self):
+        if self.options.experimentName is None:
+            raise InvalidRequest('Experiment name must be provided.')
+
+    def getExperimentName(self):
+        return self.options.experimentName
+
+    def getExperimentFilePath(self):
+        return self.options.experimentFilePath
+
+    def getDestinationDirectory(self):
+        return self.options.destinationDirectory
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-download --experiment=EXPERIMENTNAME 
+        [--relative-path=EXPERIMENTFILEPATH]
+        [--destination-directory=DESTINATIONDIRECTORY]
+
+Description:
+    Downloads experiment files.
+        """)
+        self.checkArgs()
+        api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        api.download(self.getExperimentName(), self.getExperimentFilePath(), self.getDestinationDirectory())
+        #print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = DownloadCli()
+    cli.run()
+
diff --git a/src/python/dm/ds_web_service/cli/statExperimentFileCli.py b/src/python/dm/ds_web_service/cli/statExperimentFileCli.py
index 6e0565a33db06362121ec5d1a1130f7e80754a2c..f56798dfcf7c0837535b7f00a0569ac268cef46e 100755
--- a/src/python/dm/ds_web_service/cli/statExperimentFileCli.py
+++ b/src/python/dm/ds_web_service/cli/statExperimentFileCli.py
@@ -24,7 +24,7 @@ class StatExperimentFileCli(DsWebServiceSessionCli):
 
     def runCommand(self):
         self.parseArgs(usage="""
-    dm-get-experiment --experiment=EXPERIMENTNAME --relative-path=EXPERIMENTFILEPATH
+    dm-stat-file --experiment=EXPERIMENTNAME --relative-path=EXPERIMENTFILEPATH
 
 Description:
     Retrieves stat information for a given file.
diff --git a/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py b/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py
index b599c4a6db76533d8da394eab3d96dd371e798c4..f68d0f595118591e651808de392fc0a91f83f434 100755
--- a/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py
+++ b/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py
@@ -91,6 +91,24 @@ class ExperimentRouteDescriptor:
                 'method' : ['PUT']
             },
 
+            # Authorize download for a given UNIX username
+            {
+                'name' : 'authorizeDownload',
+                'path' : '%s/downloadAuthorizations/:(username)/:(experimentName)' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'authorizeDownload',
+                'method' : ['POST']
+            },
+
+            # Revoke authorization download for a given UNIX username
+            {
+                'name' : 'deauthorizeDownload',
+                'path' : '%s/downloadAuthorizations/:(username)/:(experimentName)' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'deauthorizeDownload',
+                'method' : ['DELETE']
+            },
+
         ]
        
         return routes
diff --git a/src/python/dm/ds_web_service/service/experimentSessionController.py b/src/python/dm/ds_web_service/service/experimentSessionController.py
index 5192b3f7dda0444f22e1de0301270a11444f9491..64d983ec79b184295f1f8a24b77fd0a2b226493e 100755
--- a/src/python/dm/ds_web_service/service/experimentSessionController.py
+++ b/src/python/dm/ds_web_service/service/experimentSessionController.py
@@ -103,3 +103,25 @@ class ExperimentSessionController(DmSessionController):
         self.logger.debug('Returning: %s' % response)
         return response
 
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isAdministrator())
+    @DmSessionController.execute
+    def authorizeDownload(self, username, experimentName, **kwargs):
+        if not username:
+            raise InvalidRequest('Invalid username provided.')
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        response = self.experimentSessionControllerImpl.authorizeDownload(username, experimentName).getFullJsonRep()
+        return response
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isAdministrator())
+    @DmSessionController.execute
+    def deauthorizeDownload(self, username, experimentName, **kwargs):
+        if not username:
+            raise InvalidRequest('Invalid username provided.')
+        if not experimentName:
+            raise InvalidRequest('Invalid experiment name provided.')
+        response = self.experimentSessionControllerImpl.deauthorizeDownload(username, experimentName).getFullJsonRep()
+        return response
+
diff --git a/src/python/dm/ds_web_service/service/impl/experimentManager.py b/src/python/dm/ds_web_service/service/impl/experimentManager.py
index c44acf938e30fc45a1ac9f295267122d8196808c..108b62ff5d01959dafffc3962d75504dbd4ed626 100755
--- a/src/python/dm/ds_web_service/service/impl/experimentManager.py
+++ b/src/python/dm/ds_web_service/service/impl/experimentManager.py
@@ -15,6 +15,7 @@ from dm.common.utility.fileUtility import FileUtility
 from dm.common.db.api.experimentDbApi import ExperimentDbApi
 from dm.common.processing.fileProcessingManager import FileProcessingManager
 from dm.common.exceptions.objectNotFound import ObjectNotFound
+from dm.common.exceptions.invalidRequest import InvalidRequest
 
 class ExperimentManager(Singleton):
 
@@ -52,7 +53,7 @@ class ExperimentManager(Singleton):
         cm = ConfigurationManager.getInstance()
         configItems = cm.getConfigItems(ExperimentManager.CONFIG_SECTION_NAME)
         self.logger.debug('Got config items: %s' % configItems)
-        self.storageDirectory =cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.STORAGE_DIRECTORY_KEY) 
+        self.storageDirectory = cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.STORAGE_DIRECTORY_KEY) 
         self.manageStoragePermissions = ValueUtility.toBoolean(cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.MANAGE_STORAGE_PERMISSIONS_KEY))
         platformUtility = cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.PLATFORM_UTILITY_KEY)
         if platformUtility:
@@ -90,6 +91,24 @@ class ExperimentManager(Singleton):
             experimentUsers = experiment.get('experimentUsernameList', [])
             self.platformUtility.setGroupUsers(experimentName, experimentUsers)
 
+    def authorizeDownload(self, username, experimentName):
+        experiment = self.experimentDbApi.getExperimentByName(experimentName)
+        storageDirectory = self.updateExperimentWithStorageDataDirectory(experiment)
+        if os.path.exists(storageDirectory):
+            self.platformUtility.addLocalUserToGroup(username, experimentName)
+        else:
+            raise InvalidRequest('Experiment %s has not been started.' % experimentName)
+        return experiment
+
+    def deauthorizeDownload(self, username, experimentName):
+        experiment = self.experimentDbApi.getExperimentByName(experimentName)
+        storageDirectory = self.updateExperimentWithStorageDataDirectory(experiment)
+        if os.path.exists(storageDirectory):
+            self.platformUtility.deleteLocalUserFromGroup(username, experimentName)
+        else:
+            raise InvalidRequest('Experiment %s has not been started.' % experimentName)
+        return experiment
+
     def createExperimentGroup(self, experiment):
         experimentName = experiment.get('name')
         storageDirectory = experiment.get('storageDirectory')
diff --git a/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py b/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py
index 05af7916b994e7192afd89fa2551ed05103f73b2..24b69ee995b25bffca10a2e44c66e8d196aebb34 100755
--- a/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py
+++ b/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py
@@ -62,3 +62,8 @@ class ExperimentSessionControllerImpl(DmObjectManager):
         ExperimentManager.getInstance().updateExperimentWithStorageDataDirectory(experiment)
         return experiment
 
+    def authorizeDownload(self, username, experimentName):
+        return ExperimentManager.getInstance().authorizeDownload(username, experimentName)
+
+    def deauthorizeDownload(self, username, experimentName):
+        return ExperimentManager.getInstance().deauthorizeDownload(username, experimentName)
diff --git a/src/python/dm/ds_web_service/service/userRouteDescriptor.py b/src/python/dm/ds_web_service/service/userRouteDescriptor.py
index cb1e7595fc925ed5c97d41787ffebc90f9188152..004b4739d4fbc7c8453f0ff665a0c11a10845867 100755
--- a/src/python/dm/ds_web_service/service/userRouteDescriptor.py
+++ b/src/python/dm/ds_web_service/service/userRouteDescriptor.py
@@ -63,6 +63,7 @@ class UserRouteDescriptor:
                 'action' : 'deleteUserExperimentRole',
                 'method' : ['DELETE']
             },
+
         ]
        
         return routes