← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 20609: dataStore now protected with app authorities IF an app has protected its namespace. protecting na...

 

Merge authors:
  Stian Sandvold (stian-sandvold)
------------------------------------------------------------
revno: 20609 [merge]
committer: Stian Sandvold <stian.sandvold@xxxxxxxxx>
branch nick: dhis2
timestamp: Fri 2015-10-09 23:25:13 +0200
message:
  dataStore now protected with app authorities IF an app has protected its namespace. protecting namespaces can be done by defining the namespace in webapp.manifest: activities.dhis2.namespace; deleting apps now have the option to delete appData associated with the app.
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppDhis.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppManager.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValue.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/appmanager/DefaultAppManager.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/KeyJsonValueController.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/AddAppAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/DeleteAppAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/resources/org/hisp/dhis/appmanager/i18n_module.properties


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppDhis.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppDhis.java	2015-01-17 07:41:26 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppDhis.java	2015-10-09 17:01:55 +0000
@@ -45,6 +45,9 @@
     @JsonProperty( "href" )
     private String href;
 
+    @JsonProperty( "namespace" )
+    private String namespace;
+
     public String getHref()
     {
         return href;
@@ -54,4 +57,14 @@
     {
         this.href = href;
     }
+
+    public String getNamespace()
+    {
+        return namespace;
+    }
+
+    public void setNamespace( String namespace )
+    {
+        this.namespace = namespace;
+    }
 }
\ No newline at end of file

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppManager.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppManager.java	2015-10-06 22:23:53 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppManager.java	2015-10-09 21:21:12 +0000
@@ -69,7 +69,7 @@
      * @param rootPath the root path of the instance.
      * @throws IOException if the app manifest file could not be read.
      */
-    void installApp( File file, String fileName, String rootPath )
+    AppStatus installApp( File file, String fileName, String rootPath )
         throws IOException;
 
     /**
@@ -84,11 +84,12 @@
      * Deletes the app with the given name.
      *
      * @param name the app name.
+     * @param deleteAppData decide if associated data in dataStore should be deleted or not.
      * @return true if the delete was successful, false if there is no app with
      * the given name or if the app could not be removed from the file
      * system.
      */
-    boolean deleteApp( String name );
+    boolean deleteApp( String name, boolean deleteAppData );
 
     /**
      * Reload list of apps.
@@ -140,4 +141,12 @@
     boolean isAccessible( App app );
 
     boolean isAccessible( App app, User user );
+
+    /**
+     * Returns the app associated with the namespace, or null if no app is associated.
+     * @param namespace the namespace to check
+     * @return App or null
+     */
+    App getAppByNamespace( String namespace);
+
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValue.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValue.java	2015-10-01 09:03:27 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValue.java	2015-10-05 10:45:53 +0000
@@ -37,10 +37,19 @@
 public class KeyJsonValue
     extends BaseIdentifiableObject
 {
+    /**
+     * A namespace represents a collection of keys
+     */
     private String namespace;
 
+    /**
+     * A key belongs to a namespace, and represent a value
+     */
     private String key;
 
+    /**
+     * A value referenced by a key and namespace, json-formatted data stored as a string.
+     */
     private String value;
 
     @JsonProperty

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueService.java	2015-10-01 09:03:27 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueService.java	2015-10-05 10:45:53 +0000
@@ -35,17 +35,49 @@
  */
 public interface KeyJsonValueService
 {
+    /**
+     * Retrieves a list of existing namespaces
+     * @return a list of strings representing the existing namespaces
+     */
     List<String> getNamespaces();
 
+    /**
+     * Retrieves a list of keys from a namespace
+     * @param namespace the namespace to retrieve keys from
+     * @return a list of strings representing the keys from the namespace
+     */
     List<String> getKeysInNamespace( String namespace );
 
+    /**
+     * Deletes all keys associated with a given namespace
+     * @param namespace the namespace to delete
+     */
     void deleteNamespace( String namespace );
 
+    /**
+     * Retrieves a KeyJsonValue based on a namespace and key
+     * @param namespace the namespace where the key is associated
+     * @param key the key referencing the value
+     * @return the KeyJsonValue matching the key and namespace
+     */
     KeyJsonValue getKeyJsonValue( String namespace, String key );
 
+    /**
+     * Adds a new KeyJsonValue
+     * @param keyJsonValue the KeyJsonValue to be stored
+     * @return the id of the KeyJsonValue stored
+     */
     int addKeyJsonValue( KeyJsonValue keyJsonValue );
 
+    /**
+     * Updates a KeyJsonValue
+     * @param keyJsonValue the updated KeyJsonValue
+     */
     void updateKeyJsonValue( KeyJsonValue keyJsonValue );
 
+    /**
+     * Deletes a keyJsonValue
+     * @param keyJsonValue the KeyJsonValue to be deleted.
+     */
     void deleteKeyJsonValue( KeyJsonValue keyJsonValue );
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueStore.java	2015-10-01 10:55:33 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/keyjsonvalue/KeyJsonValueStore.java	2015-10-05 10:45:53 +0000
@@ -38,11 +38,31 @@
 public interface KeyJsonValueStore
     extends GenericIdentifiableObjectStore<KeyJsonValue>
 {
+    /**
+     * Retrieves a list of all namespaces
+     * @return a list of strings representing each existing namespace
+     */
     List<String> getNamespaces();
 
+    /**
+     * Retrieves a list of keys associated with a given namespace.
+     * @param namespace the namespace to retrieve keys from
+     * @return a list of strings representing the different keys in the namespace
+     */
     List<String> getKeysInNamespace( String namespace );
 
+    /**
+     * Retrieves a list of KeyJsonValue objects based on a given namespace
+     * @param namespace the namespace to retrieve KeyJsonValues from
+     * @return a List of KeyJsonValues
+     */
     List<KeyJsonValue> getKeyJsonValueByNamespace( String namespace );
 
+    /**
+     * Retrieves a KeyJsonValue based on the associated key and namespace
+     * @param namespace the namespace where the key is stored
+     * @param key the key referencing the value
+     * @return the KeyJsonValue retrieved
+     */
     KeyJsonValue getKeyJsonValue( String namespace, String key );
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/appmanager/DefaultAppManager.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/appmanager/DefaultAppManager.java	2015-10-06 22:23:53 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/appmanager/DefaultAppManager.java	2015-10-09 21:21:12 +0000
@@ -37,6 +37,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.datavalue.DefaultDataValueService;
+import org.hisp.dhis.keyjsonvalue.KeyJsonValueService;
 import org.hisp.dhis.setting.Setting;
 import org.hisp.dhis.setting.SystemSettingManager;
 import org.hisp.dhis.user.CurrentUserService;
@@ -50,7 +51,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -68,6 +71,11 @@
      */
     private List<App> apps = new ArrayList<>();
 
+    /**
+     * Mapping dataStore-namespaces and apps
+     */
+    private HashMap<String, App> appNamespaces = new HashMap<>();
+
     @PostConstruct
     private void init()
     {
@@ -80,6 +88,9 @@
     @Autowired
     private CurrentUserService currentUserService;
 
+    @Autowired
+    private KeyJsonValueService keyJsonValueService;
+
     // -------------------------------------------------------------------------
     // AppManagerService implementation
     // -------------------------------------------------------------------------
@@ -120,7 +131,7 @@
     }
 
     @Override
-    public void installApp( File file, String fileName, String rootPath )
+    public AppStatus installApp( File file, String fileName, String rootPath )
         throws IOException
     {
         ZipFile zip = new ZipFile( file );
@@ -133,15 +144,26 @@
         App app = mapper.readValue( inputStream, App.class );
 
         // ---------------------------------------------------------------------
+        // Check for namespace and if it's already taken by another app
+        // ---------------------------------------------------------------------
+        String appNamespace = app.getActivities().getDhis().getNamespace();
+        if ( appNamespace != null &&
+            ( this.appNamespaces.containsKey( appNamespace ) && !app.equals( appNamespaces.get( appNamespace ) ) ) )
+        {
+            return AppStatus.NAMESPACE_TAKEN;
+        }
+
+
+        // ---------------------------------------------------------------------
         // Delete if app is already installed
         // ---------------------------------------------------------------------
-
         if ( getApps().contains( app ) )
         {
             String folderPath = getAppFolderPath() + File.separator + app.getFolderName();
             FileUtils.forceDelete( new File( folderPath ) );
         }
 
+
         String dest = getAppFolderPath() + File.separator + fileName.substring( 0, fileName.lastIndexOf( '.' ) );
         Unzip unzip = new Unzip();
         unzip.setSrc( file );
@@ -167,6 +189,8 @@
         zip.close();
 
         reloadApps(); // Reload app state
+
+        return AppStatus.OK;
     }
 
     @Override
@@ -184,7 +208,7 @@
     }
 
     @Override
-    public boolean deleteApp( String name )
+    public boolean deleteApp( String name, boolean deleteAppData )
     {
         for ( App app : getApps() )
         {
@@ -195,6 +219,17 @@
                     String folderPath = getAppFolderPath() + File.separator + app.getFolderName();
                     FileUtils.forceDelete( new File( folderPath ) );
 
+                    // If deleteAppData is true and a namespace associated with the app exists, delete it.
+                    if(deleteAppData && appNamespaces.containsValue( app ))
+                    {
+                        appNamespaces.forEach( ( namespace, app1 ) -> {
+                            if( app1 == app)
+                            {
+                                keyJsonValueService.deleteNamespace( namespace );
+                            }
+                        } );
+                    }
+
                     return true;
                 }
                 catch ( IOException ex )
@@ -275,6 +310,7 @@
     public void reloadApps()
     {
         List<App> appList = new ArrayList<>();
+        HashMap<String, App> appNamespaces = new HashMap<>(  );
         ObjectMapper mapper = new ObjectMapper();
         mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
 
@@ -299,6 +335,13 @@
                                 App app = mapper.readValue( appManifest, App.class );
                                 app.setFolderName( folder.getName() );
                                 appList.add( app );
+
+                                // Add namespace
+                                String appNamespace = app.getActivities().getDhis().getNamespace();
+                                if ( appNamespace != null )
+                                {
+                                    appNamespaces.put(appNamespace, app);
+                                }
                             }
                             catch ( IOException ex )
                             {
@@ -311,6 +354,7 @@
         }
 
         this.apps = appList;
+        this.appNamespaces = appNamespaces;
 
         log.info( "Detected apps: " + apps );
     }
@@ -335,4 +379,10 @@
             userCredentials.getAllAuthorities().contains( "M_dhis-web-maintenance-appmanager" ) ||
             userCredentials.getAllAuthorities().contains( "See " + app.getName().trim() );
     }
+
+    @Override
+    public App getAppByNamespace( String namespace )
+    {
+        return appNamespaces.get( namespace );
+    }
 }

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java	2015-10-06 22:23:53 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java	2015-10-09 21:21:12 +0000
@@ -41,6 +41,7 @@
 import org.apache.commons.lang3.StringUtils;
 import org.hisp.dhis.appmanager.App;
 import org.hisp.dhis.appmanager.AppManager;
+import org.hisp.dhis.appmanager.AppStatus;
 import org.hisp.dhis.dxf2.render.DefaultRenderService;
 import org.hisp.dhis.dxf2.render.RenderService;
 import org.hisp.dhis.dxf2.webmessage.WebMessageException;
@@ -91,35 +92,37 @@
     private final ResourceLoader resourceLoader = new DefaultResourceLoader();
 
     @RequestMapping( method = RequestMethod.GET, produces = ContextUtils.CONTENT_TYPE_JSON )
-    public void getApps( @RequestParam(required=false) String key, HttpServletResponse response )
+    public void getApps( @RequestParam( required = false ) String key, HttpServletResponse response )
         throws IOException
     {
         List<App> apps = new ArrayList<>();
-        
+
         if ( key != null )
         {
             App app = appManager.getApp( key );
-            
+
             if ( app == null )
             {
                 response.sendError( HttpServletResponse.SC_NOT_FOUND );
                 return;
             }
-            
+
             apps.add( app );
         }
         else
         {
             apps = appManager.getApps();
         }
-        
+
         renderService.toJson( response.getOutputStream(), apps );
     }
 
     @RequestMapping( method = RequestMethod.POST )
     @ResponseStatus( HttpStatus.NO_CONTENT )
     @PreAuthorize( "hasRole('ALL') or hasRole('M_dhis-web-maintenance-appmanager')" )
-    public void installApp( @RequestParam( "file" ) MultipartFile file, HttpServletRequest request, HttpServletResponse response ) throws IOException, WebMessageException
+    public void installApp( @RequestParam( "file" ) MultipartFile file, HttpServletRequest request,
+        HttpServletResponse response )
+        throws IOException, WebMessageException
     {
         File tempFile = File.createTempFile( "IMPORT_", "_ZIP" );
         file.transferTo( tempFile );
@@ -128,7 +131,14 @@
 
         try
         {
-            appManager.installApp( tempFile, file.getOriginalFilename(), contextPath );
+            AppStatus appStatus = appManager.installApp( tempFile, file.getOriginalFilename(), contextPath );
+
+            if ( appStatus.equals( AppStatus.NAMESPACE_TAKEN ) )
+            {
+                throw new WebMessageException(
+                    WebMessageUtils.conflict( "The namespace defined in manifest.webapp is already protected." ) );
+            }
+
         }
         catch ( JsonParseException ex )
         {
@@ -136,7 +146,8 @@
         }
         catch ( IOException ex )
         {
-            throw new WebMessageException( WebMessageUtils.conflict( "App could not not be installed on file system, check permissions" ) );
+            throw new WebMessageException(
+                WebMessageUtils.conflict( "App could not not be installed on file system, check permissions" ) );
         }
     }
 
@@ -149,7 +160,8 @@
     }
 
     @RequestMapping( value = "/{app}/**", method = RequestMethod.GET )
-    public void renderApp( @PathVariable( "app" ) String app, HttpServletRequest request, HttpServletResponse response ) throws IOException
+    public void renderApp( @PathVariable( "app" ) String app, HttpServletRequest request, HttpServletResponse response )
+        throws IOException
     {
         Iterable<Resource> locations = Lists.newArrayList(
             resourceLoader.getResource( "file:" + appManager.getAppFolderPath() + "/" + app + "/" ),
@@ -215,14 +227,15 @@
 
     @RequestMapping( value = "/{app}", method = RequestMethod.DELETE )
     @PreAuthorize( "hasRole('ALL') or hasRole('M_dhis-web-maintenance-appmanager')" )
-    public void deleteApp( @PathVariable( "app" ) String app, HttpServletRequest request, HttpServletResponse response ) throws WebMessageException
+    public void deleteApp( @PathVariable( "app" ) String app, @RequestParam(value = "deleteappdata", required = false, defaultValue = "false") boolean deleteAppData, HttpServletRequest request, HttpServletResponse response )
+        throws WebMessageException
     {
         if ( !appManager.exists( app ) )
         {
             throw new WebMessageException( WebMessageUtils.notFound( "App does not exist: " + app ) );
         }
 
-        if ( !appManager.deleteApp( app ) )
+        if ( !appManager.deleteApp( app, deleteAppData ) )
         {
             throw new WebMessageException( WebMessageUtils.conflict( "There was an error deleting app: " + app ) );
         }
@@ -231,7 +244,8 @@
     @SuppressWarnings( "unchecked" )
     @RequestMapping( value = "/config", method = RequestMethod.POST, consumes = ContextUtils.CONTENT_TYPE_JSON )
     @PreAuthorize( "hasRole('ALL') or hasRole('M_dhis-web-maintenance-appmanager')" )
-    public void setConfig( HttpServletRequest request, HttpServletResponse response ) throws IOException, WebMessageException
+    public void setConfig( HttpServletRequest request, HttpServletResponse response )
+        throws IOException, WebMessageException
     {
         Map<String, String> config = renderService.fromJson( request.getInputStream(), Map.class );
 
@@ -277,7 +291,8 @@
     // Helpers
     //--------------------------------------------------------------------------
 
-    private Resource findResource( Iterable<Resource> locations, String resourceName ) throws IOException
+    private Resource findResource( Iterable<Resource> locations, String resourceName )
+        throws IOException
     {
         for ( Resource location : locations )
         {

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/KeyJsonValueController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/KeyJsonValueController.java	2015-10-01 10:02:47 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/KeyJsonValueController.java	2015-10-09 17:01:55 +0000
@@ -34,6 +34,8 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.hisp.dhis.appmanager.App;
+import org.hisp.dhis.appmanager.AppManager;
 import org.hisp.dhis.dxf2.render.RenderService;
 import org.hisp.dhis.dxf2.webmessage.WebMessage;
 import org.hisp.dhis.dxf2.webmessage.WebMessageException;
@@ -57,6 +59,17 @@
     @Autowired
     private RenderService renderService;
 
+    @Autowired
+    private AppManager appManager;
+
+    /**
+     * Returns a json-array of strings representing the different namespaces used.
+     * If no namespace exists, an empty array is returned.
+     *
+     * @param response
+     * @return the list of namespaces
+     * @throws IOException
+     */
     @RequestMapping( value = "", method = RequestMethod.GET, produces = "application/json" )
     public
     @ResponseBody
@@ -66,6 +79,15 @@
         return keyJsonValueService.getNamespaces();
     }
 
+    /**
+     * Returns a list of strings representing keys in the given namespace.
+     *
+     * @param namespace the namespace requested
+     * @param response
+     * @return a list of keys
+     * @throws IOException
+     * @throws WebMessageException
+     */
     @RequestMapping( value = "/{namespace}", method = RequestMethod.GET, produces = "application/json" )
     public
     @ResponseBody
@@ -83,6 +105,14 @@
         return keyJsonValueService.getKeysInNamespace( namespace );
     }
 
+    /**
+     * Deletes all keys with the given namespace.
+     *
+     * @param namespace the namespace to be deleted.
+     * @param response
+     * @return
+     * @throws WebMessageException
+     */
     @RequestMapping( value = "/{namespace}", method = RequestMethod.DELETE )
     public
     @ResponseBody
@@ -91,6 +121,11 @@
         HttpServletResponse response )
         throws WebMessageException
     {
+        if ( !hasAccess( namespace ) )
+        {
+            throw new WebMessageException( WebMessageUtils.forbidden( "The namespace '" + namespace +
+                "' is protected, and you don't have the right authority to access it." ) );
+        }
 
         if ( !keyJsonValueService.getNamespaces().contains( namespace ) )
         {
@@ -103,6 +138,16 @@
         return WebMessageUtils.ok( "Namespace '" + namespace + "' deleted." );
     }
 
+    /**
+     * Retrieves the KeyJsonValue represented by the given key from the given namespace.
+     *
+     * @param namespace where the key is associated
+     * @param key       representing the json stored
+     * @param response
+     * @return a KeyJsonValue object
+     * @throws IOException
+     * @throws WebMessageException
+     */
     @RequestMapping( value = "/{namespace}/{key}", method = RequestMethod.GET, produces = "application/json" )
     public
     @ResponseBody
@@ -112,6 +157,12 @@
         HttpServletResponse response )
         throws IOException, WebMessageException
     {
+        if ( !hasAccess( namespace ) )
+        {
+            throw new WebMessageException( WebMessageUtils.forbidden( "The namespace '" + namespace +
+                "' is protected, and you don't have the right authority to access it." ) );
+        }
+
         KeyJsonValue keyJsonValue = keyJsonValueService.getKeyJsonValue( namespace, key );
 
         if ( keyJsonValue == null )
@@ -123,6 +174,17 @@
         return keyJsonValue;
     }
 
+    /**
+     * Creates a new KeyJsonValue Object on the given namespace with the key and value supplied.
+     *
+     * @param namespace where the key is associated
+     * @param key       representing the value
+     * @param body      the value to be stored (json format)
+     * @param response
+     * @return the object created
+     * @throws IOException
+     * @throws WebMessageException
+     */
     @RequestMapping( value = "/{namespace}/{key}", method = RequestMethod.POST, produces = "application/json", consumes = "application/json" )
     public
     @ResponseBody
@@ -133,6 +195,12 @@
         HttpServletResponse response )
         throws IOException, WebMessageException
     {
+        if ( !hasAccess( namespace ) )
+        {
+            throw new WebMessageException( WebMessageUtils.forbidden( "The namespace '" + namespace +
+                "' is protected, and you don't have the right authority to access it." ) );
+        }
+
         if ( keyJsonValueService.getKeyJsonValue( namespace, key ) != null )
         {
             throw new WebMessageException( WebMessageUtils
@@ -156,6 +224,18 @@
         return keyJsonValue;
     }
 
+    /**
+     * Update a key in the given namespace
+     *
+     * @param namespace namespace where the key is associated
+     * @param key       key to be updated
+     * @param body      the new value to be stored
+     * @param request
+     * @param response
+     * @return The updated object
+     * @throws WebMessageException
+     * @throws IOException
+     */
     @RequestMapping( value = "/{namespace}/{key}", method = RequestMethod.PUT, produces = "application/json", consumes = "application/json" )
     public
     @ResponseBody
@@ -167,6 +247,13 @@
         HttpServletResponse response )
         throws WebMessageException, IOException
     {
+
+        if ( !hasAccess( namespace ) )
+        {
+            throw new WebMessageException( WebMessageUtils.forbidden( "The namespace '" + namespace +
+                "' is protected, and you don't have the right authority to access it." ) );
+        }
+
         KeyJsonValue keyJsonValue = keyJsonValueService.getKeyJsonValue( namespace, key );
 
         if ( keyJsonValue == null )
@@ -187,6 +274,15 @@
         return keyJsonValue;
     }
 
+    /**
+     * Delete a key from the given namespace
+     *
+     * @param namespace namespace where the key is associated
+     * @param key       key to be deleted
+     * @param response
+     * @return the success of the deletion
+     * @throws WebMessageException
+     */
     @RequestMapping( value = "/{namespace}/{key}", method = RequestMethod.DELETE, produces = "application/json" )
     public
     @ResponseBody
@@ -196,6 +292,12 @@
         HttpServletResponse response )
         throws WebMessageException
     {
+        if ( !hasAccess( namespace ) )
+        {
+            throw new WebMessageException( WebMessageUtils.forbidden( "The namespace '" + namespace +
+                "' is protected, and you don't have the right authority to access it." ) );
+        }
+
         KeyJsonValue keyJsonValue = keyJsonValueService.getKeyJsonValue( namespace, key );
 
         if ( keyJsonValue == null )
@@ -208,4 +310,10 @@
 
         return WebMessageUtils.ok( "Key '" + key + "' deleted from namespace '" + namespace + "'." );
     }
+
+    private boolean hasAccess( String namespace )
+    {
+        App app = appManager.getAppByNamespace( namespace );
+        return app == null || appManager.isAccessible( app );
+    }
 }

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/AddAppAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/AddAppAction.java	2015-06-15 13:44:20 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/AddAppAction.java	2015-10-09 17:01:55 +0000
@@ -41,6 +41,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.struts2.ServletActionContext;
 import org.hisp.dhis.appmanager.AppManager;
+import org.hisp.dhis.appmanager.AppStatus;
 import org.hisp.dhis.i18n.I18n;
 import org.hisp.dhis.commons.util.StreamUtils;
 import org.hisp.dhis.webapi.utils.ContextUtils;
@@ -138,8 +139,14 @@
             {
                 String contextPath = ContextUtils.getContextPath( request );
                 
-                appManager.installApp( file, fileName, contextPath );
-                
+                AppStatus appStatus = appManager.installApp( file, fileName, contextPath );
+
+                if(appStatus == AppStatus.NAMESPACE_TAKEN)
+                {
+                    message = i18n.getString( "appmanager_namespace_taken" );
+                    log.warn( "Namespace in the app's manifest already taken." );
+                    return FAILURE;
+                }
                 message = i18n.getString( "appmanager_install_success" );
                 
                 return SUCCESS;

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/DeleteAppAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/DeleteAppAction.java	2015-08-18 20:08:20 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/java/org/hisp/dhis/appmanager/action/DeleteAppAction.java	2015-10-09 21:21:12 +0000
@@ -76,7 +76,7 @@
     public String execute()
         throws Exception
     {
-        if ( appName != null && appManager.deleteApp( appName ) )
+        if ( appName != null && appManager.deleteApp( appName, false ) )
         {
             message = i18n.getString( "appmanager_delete_success" );
         }

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/resources/org/hisp/dhis/appmanager/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/resources/org/hisp/dhis/appmanager/i18n_module.properties	2015-08-18 20:08:20 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-appmanager/src/main/resources/org/hisp/dhis/appmanager/i18n_module.properties	2015-10-09 17:01:55 +0000
@@ -20,4 +20,5 @@
 appmanager_you_have_no_apps_installed=You have no apps installed at the moment
 appmanager_author=Author
 appmanager_version=Version
-appmanager_set_to_default=Set to default
\ No newline at end of file
+appmanager_set_to_default=Set to default
+appmanager_namespace_taken=The namespace defined in manifest.webapp is already protected by another app
\ No newline at end of file