Repository: gravitee-io/graviteeio-access-management
Pull request #749 (2020-04-06 14:05:24)

fix(permissions): patch domain allowed according to user's permissions
Closes gravitee-io/issues#3433


Patch:
diff --git a/docs/scripts/migration-v3.js b/docs/scripts/migration-v3.js
index e5680b8ee5..d6a2314803 100644
--- a/docs/scripts/migration-v3.js
+++ b/docs/scripts/migration-v3.js
@@ -72,7 +72,7 @@ if (adminDomain != null) {
 
 // Migrate all other domains to default environment and remove useless loginForm field
 db.getCollection("domains").updateMany({}, {
-    "$unset": {"loginForm": ""},
+    "$unset": {"loginForm": "", "master": "", "identities": ""},
     "$set": {"referenceId": "DEFAULT", "referenceType": "ENVIRONMENT"}
 });
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/src/main/java/io/gravitee/am/gateway/services/sync/SyncManager.java b/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/src/main/java/io/gravitee/am/gateway/services/sync/SyncManager.java
index d7f34cb5be..4a9d76cf0d 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/src/main/java/io/gravitee/am/gateway/services/sync/SyncManager.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-services/gravitee-am-gateway-services-sync/src/main/java/io/gravitee/am/gateway/services/sync/SyncManager.java
@@ -135,12 +135,12 @@ public class SyncManager implements InitializingBean {
     private void deployDomains() {
         logger.info("Starting security domains initialization ...");
         Set<Domain> domains = domainRepository.findAll()
-                // remove master domains and disabled domains
+                // remove disabled domains
                 .map(registeredDomains -> {
                     if (registeredDomains != null) {
                         return registeredDomains
                                 .stream()
-                                .filter(domain -> !domain.isMaster() && domain.isEnabled())
+                                .filter(Domain::isEnabled)
                                 .collect(Collectors.toSet());
                     }
                     return Collections.<Domain>emptySet();
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ApplicationResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ApplicationResource.java
index 463fd2279c..0c545d8527 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ApplicationResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ApplicationResource.java
@@ -22,6 +22,8 @@ import io.gravitee.am.management.service.permissions.Permissions;
 import io.gravitee.am.model.Acl;
 import io.gravitee.am.model.Application;
 import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.application.ApplicationSettings;
+import io.gravitee.am.model.application.ApplicationType;
 import io.gravitee.am.model.permissions.Permission;
 import io.gravitee.am.service.ApplicationService;
 import io.gravitee.am.service.DomainService;
@@ -45,10 +47,7 @@ import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.container.Suspended;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -100,7 +99,7 @@ public class ApplicationResource extends AbstractResource {
                     }
                     return Response.ok(application1).build();
                 })
-                .subscribe(response::resume, t-> response.resume(t));
+                .subscribe(response::resume, t -> response.resume(t));
     }
 
     @PATCH
@@ -172,10 +171,10 @@ public class ApplicationResource extends AbstractResource {
     @POST
     @Path("secret/_renew")
     @ApiOperation(value = "Renew application secret",
-            notes = "User must have APPLICATION[UPDATE] permission on the specified application " +
-                    "or APPLICATION_OAUTH2[UPDATE] permission on the specified domain " +
-                    "or APPLICATION_OAUTH2[UPDATE] permission on the specified environment " +
-                    "or APPLICATION_OAUTH2[UPDATE] permission on the specified organization")
+            notes = "User must have APPLICATION_OPENID[UPDATE] permission on the specified application " +
+                    "or APPLICATION_OPENID[UPDATE] permission on the specified domain " +
+                    "or APPLICATION_OPENID[UPDATE] permission on the specified environment " +
+                    "or APPLICATION_OPENID[UPDATE] permission on the specified organization")
     @Produces(MediaType.APPLICATION_JSON)
     @ApiResponses({
             @ApiResponse(code = 200, message = "Application secret successfully updated", response = Application.class),
@@ -213,36 +212,68 @@ public class ApplicationResource extends AbstractResource {
     public void updateInternal(String organizationId, String environmentId, String domain, String application, PatchApplication patchApplication, final AsyncResponse response) {
 
         final User authenticatedUser = getAuthenticatedUser();
+        Set<Permission> requiredPermissions = patchApplication.getRequiredPermissions();
 
-        Completable.merge(patchApplication.getRequiredPermissions().stream()
-                .map(permission -> checkAnyPermission(organizationId, environmentId, domain, application, permission, Acl.UPDATE))
-                .collect(Collectors.toList()))
-                .andThen(domainService.findById(domain)
-                        .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
-                        .flatMapSingle(patch -> applicationService.patch(domain, application, patchApplication, authenticatedUser)))
-                .subscribe(response::resume, response::resume);
+        if (requiredPermissions.isEmpty()) {
+            // If there is no require permission, it means there is nothing to update. This is not a valid request.
+            response.resume(new BadRequestException("You need to specify at least one value to update."));
+        } else {
+            Completable.merge(patchApplication.getRequiredPermissions().stream()
+                    .map(permission -> checkAnyPermission(organizationId, environmentId, domain, application, permission, Acl.UPDATE))
+                    .collect(Collectors.toList()))
+                    .andThen(domainService.findById(domain)
+                            .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                            .flatMapSingle(patch -> applicationService.patch(domain, application, patchApplication, authenticatedUser)
+                                    .flatMap(updatedApplication -> findAllPermissions(authenticatedUser, organizationId, environmentId, domain, application)
+                                            .map(userPermissions -> filterApplicationInfos(updatedApplication, userPermissions)))))
+                    .subscribe(response::resume, response::resume);
+        }
     }
 
-    private Application filterApplicationInfos(Application app, Map<ReferenceType, Map<Permission, Set<Acl>>> userPermissions) {
+    private Application filterApplicationInfos(Application application, Map<ReferenceType, Map<Permission, Set<Acl>>> userPermissions) {
+
+        Application filteredApplication = new Application();
+
+        if (hasAnyPermission(userPermissions, Permission.APPLICATION, Acl.READ)) {
+            filteredApplication.setId(application.getId());
+            filteredApplication.setName(application.getName());
+            filteredApplication.setType(application.getType());
+            filteredApplication.setDescription(application.getDescription());
+            filteredApplication.setDomain(application.getDomain());
+            filteredApplication.setEnabled(application.isEnabled());
+            filteredApplication.setTemplate(application.isTemplate());
+            filteredApplication.setCreatedAt(application.getCreatedAt());
+            filteredApplication.setUpdatedAt(application.getUpdatedAt());
+        }
+
+        if(hasAnyPermission(userPermissions, Permission.APPLICATION_FACTOR, Acl.READ)){
+            filteredApplication.setFactors(application.getFactors());
+        }
 
-        if (!hasAnyPermission(userPermissions, Permission.APPLICATION_IDENTITY_PROVIDER, Acl.READ)) {
-            app.setIdentities(null);
+        if (hasAnyPermission(userPermissions, Permission.APPLICATION_IDENTITY_PROVIDER, Acl.READ)) {
+            filteredApplication.setIdentities(application.getIdentities());
         }
-        if (!hasAnyPermission(userPermissions, Permission.APPLICATION_CERTIFICATE, Acl.READ)) {
-            app.setCertificate(null);
+
+        if (hasAnyPermission(userPermissions, Permission.APPLICATION_CERTIFICATE, Acl.READ)) {
+            filteredApplication.setCertificate(application.getCertificate());
         }
-        if (app.getSettings() != null) {
-            if (!hasAnyPermission(userPermissions, Permission.APPLICATION_SETTINGS, Acl.READ)) {
-                app.setMetadata(null);
-                app.getSettings().setAdvanced(null);
-                app.getSettings().setAccount(null);
+
+        if (application.getSettings() != null) {
+
+            ApplicationSettings filteredApplicationSettings = new ApplicationSettings();
+            filteredApplication.setSettings(filteredApplicationSettings);
+
+            if (hasAnyPermission(userPermissions, Permission.APPLICATION_SETTINGS, Acl.READ)) {
+                filteredApplication.setMetadata(application.getMetadata());
+                filteredApplicationSettings.setAdvanced(application.getSettings().getAdvanced());
+                filteredApplicationSettings.setAccount(application.getSettings().getAccount());
             }
 
-            if (!hasAnyPermission(userPermissions, Permission.APPLICATION_OPENID, Acl.READ)) {
-                app.getSettings().setOauth(null);
+            if (hasAnyPermission(userPermissions, Permission.APPLICATION_OPENID, Acl.READ)) {
+                filteredApplicationSettings.setOauth(application.getSettings().getOauth());
             }
         }
 
-        return app;
+        return filteredApplication;
     }
 }
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ClientResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ClientResource.java
index e5b5df30de..48e12f67f1 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ClientResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/ClientResource.java
@@ -180,10 +180,10 @@ public class ClientResource extends AbstractResource {
     @POST
     @Path("secret/_renew")
     @ApiOperation(value = "Renew client secret",
-            notes = "User must have the APPLICATION_OAUTH2[UPDATE] permission on the specified client " +
-                    "or APPLICATION_OAUTH2[UPDATE] permission on the specified domain " +
-                    "or APPLICATION_OAUTH2[UPDATE] permission on the specified environment " +
-                    "or APPLICATION_OAUTH2[UPDATE] permission on the specified organization.")
+            notes = "User must have the APPLICATION_OPENID[UPDATE] permission on the specified client " +
+                    "or APPLICATION_OPENID[UPDATE] permission on the specified domain " +
+                    "or APPLICATION_OPENID[UPDATE] permission on the specified environment " +
+                    "or APPLICATION_OPENID[UPDATE] permission on the specified organization.")
     @Produces(MediaType.APPLICATION_JSON)
     @ApiResponses({
             @ApiResponse(code = 200, message = "Client secret successfully updated", response = Client.class),
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
index b406e82696..831dba3468 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
@@ -26,6 +26,7 @@ import io.gravitee.am.service.DomainService;
 import io.gravitee.am.service.exception.DomainNotFoundException;
 import io.gravitee.am.service.model.PatchDomain;
 import io.gravitee.common.http.MediaType;
+import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -43,6 +44,7 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
@@ -231,32 +233,55 @@ public class DomainResource extends AbstractResource {
         return resourceContext.getResource(FactorsResource.class);
     }
 
-    private void updateInternal(String organizationId, String environmentId, String domainId, final PatchDomain domainToPatch, final AsyncResponse response) {
+    private void updateInternal(String organizationId, String environmentId, String domainId, final PatchDomain patchDomain, final AsyncResponse response) {
 
         final User authenticatedUser = getAuthenticatedUser();
-
-        checkAnyPermission(organizationId, environmentId, domainId, Permission.DOMAIN_SETTINGS, Acl.UPDATE)
-                .andThen(domainService.patch(domainId, domainToPatch, authenticatedUser))
-                .subscribe(domain -> response.resume(Response.ok(domain).build()), response::resume);
+        Set<Permission> requiredPermissions = patchDomain.getRequiredPermissions();
+
+        if (requiredPermissions.isEmpty()) {
+            // If there is no require permission, it means there is nothing to update. This is not a valid request.
+            response.resume(new BadRequestException("You need to specify at least one value to update."));
+        } else {
+            Completable.merge(requiredPermissions.stream()
+                    .map(permission -> checkAnyPermission(organizationId, environmentId, domainId, permission, Acl.UPDATE))
+                    .collect(Collectors.toList()))
+                    .andThen(domainService.patch(domainId, patchDomain, authenticatedUser)
+                            .flatMap(domain -> findAllPermissions(authenticatedUser, organizationId, environmentId, domainId)
+                                    .map(userPermissions -> filterDomainInfos(domain, userPermissions))))
+                    .subscribe(response::resume, response::resume);
+        }
     }
 
     private Domain filterDomainInfos(Domain domain, Map<ReferenceType, Map<Permission, Set<Acl>>> userPermissions) {
 
-        if (!hasAnyPermission(userPermissions, Permission.DOMAIN_IDENTITY_PROVIDER, Acl.READ)) {
-            domain.setIdentities(null);
+        Domain filteredDomain = new Domain();
+
+        if (hasAnyPermission(userPermissions, Permission.DOMAIN, Acl.READ)) {
+            filteredDomain.setId(domain.getId());
+            filteredDomain.setName(domain.getName());
+            filteredDomain.setDescription(domain.getDescription());
+            filteredDomain.setEnabled(domain.isEnabled());
+            filteredDomain.setCreatedAt(domain.getCreatedAt());
+            filteredDomain.setUpdatedAt(domain.getUpdatedAt());
+            filteredDomain.setPath(domain.getPath());
+            filteredDomain.setReferenceType(domain.getReferenceType());
+            filteredDomain.setReferenceId(domain.getReferenceId());
         }
-        if (!hasAnyPermission(userPermissions, Permission.DOMAIN_OPENID, Acl.READ)) {
-            domain.setOidc(null);
+
+        if (hasAnyPermission(userPermissions, Permission.DOMAIN_OPENID, Acl.READ)) {
+            filteredDomain.setOidc(domain.getOidc());
         }
-        if (!hasAnyPermission(userPermissions, Permission.DOMAIN_SCIM, Acl.READ)) {
-            domain.setScim(null);
+
+        if (hasAnyPermission(userPermissions, Permission.DOMAIN_SCIM, Acl.READ)) {
+            filteredDomain.setScim(domain.getScim());
         }
-        if (!hasAnyPermission(userPermissions, Permission.DOMAIN_SETTINGS, Acl.READ)) {
-            domain.setTags(null);
-            domain.setAccountSettings(null);
-            domain.setLoginSettings(null);
+
+        if (hasAnyPermission(userPermissions, Permission.DOMAIN_SETTINGS, Acl.READ)) {
+            filteredDomain.setLoginSettings(domain.getLoginSettings());
+            filteredDomain.setAccountSettings(domain.getAccountSettings());
+            filteredDomain.setTags(domain.getTags());
         }
 
-        return domain;
+        return filteredDomain;
     }
 }
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java
index b8e9ac9bf4..dc3ac4afac 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Domain.java
@@ -49,11 +49,6 @@ public class Domain implements Resource {
      */
     private boolean enabled;
 
-    /**
-     * Domain master flag.
-     */
-    private boolean master;
-
     /**
      * Domain creation date
      */
@@ -69,8 +64,6 @@ public class Domain implements Resource {
      */
     private String path;
 
-    private Set<String> identities;
-
     private OIDCSettings oidc;
 
     private LoginSettings loginSettings;
@@ -99,11 +92,9 @@ public class Domain implements Resource {
         this.name = other.name;
         this.description = other.description;
         this.enabled = other.enabled;
-        this.master = other.master;
         this.createdAt = other.createdAt;
         this.updatedAt = other.updatedAt;
         this.path = other.path;
-        this.identities = other.identities;
         this.oidc = other.oidc;
         this.loginSettings = other.loginSettings;
         this.scim = other.scim;
@@ -145,14 +136,6 @@ public class Domain implements Resource {
         this.enabled = enabled;
     }
 
-    public boolean isMaster() {
-        return master;
-    }
-
-    public void setMaster(boolean master) {
-        this.master = master;
-    }
-
     public Date getCreatedAt() {
         return createdAt;
     }
@@ -177,14 +160,6 @@ public class Domain implements Resource {
         this.path = path;
     }
 
-    public Set<String> getIdentities() {
-        return identities;
-    }
-
-    public void setIdentities(Set<String> identities) {
-        this.identities = identities;
-    }
-
     public OIDCSettings getOidc() {
         return oidc;
     }
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java
index 3b46a35fdf..446d051eb7 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoDomainRepository.java
@@ -112,8 +112,6 @@ public class MongoDomainRepository extends AbstractManagementMongoRepository imp
         domain.setName(domainMongo.getName());
         domain.setDescription(domainMongo.getDescription());
         domain.setEnabled(domainMongo.isEnabled());
-        domain.setMaster(domainMongo.isMaster());
-        domain.setIdentities(domainMongo.getIdentities());
         domain.setOidc(convert(domainMongo.getOidc()));
         domain.setScim(convert(domainMongo.getScim()));
         domain.setLoginSettings(convert(domainMongo.getLoginSettings()));
@@ -137,8 +135,6 @@ public class MongoDomainRepository extends AbstractManagementMongoRepository imp
         domainMongo.setName(domain.getName());
         domainMongo.setDescription(domain.getDescription());
         domainMongo.setEnabled(domain.isEnabled());
-        domainMongo.setMaster(domain.isMaster());
-        domainMongo.setIdentities(domain.getIdentities());
         domainMongo.setOidc(convert(domain.getOidc()));
         domainMongo.setScim(convert(domain.getScim()));
         domainMongo.setLoginSettings(convert(domain.getLoginSettings()));
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/DomainMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/DomainMongo.java
index 486b4a2333..004419ac3a 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/DomainMongo.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/DomainMongo.java
@@ -38,12 +38,8 @@ public class DomainMongo extends Auditable {
 
     private boolean enabled;
 
-    private boolean master;
-
     private String path;
 
-    private Set<String> identities;
-
     private OIDCSettingsMongo oidc;
 
     private SCIMSettingsMongo scim;
@@ -82,14 +78,6 @@ public class DomainMongo extends Auditable {
         this.description = description;
     }
 
-    public boolean isMaster() {
-        return master;
-    }
-
-    public void setMaster(boolean master) {
-        this.master = master;
-    }
-
     public boolean isEnabled() {
         return enabled;
     }
@@ -106,14 +94,6 @@ public class DomainMongo extends Auditable {
         this.path = path;
     }
 
-    public Set<String> getIdentities() {
-        return identities;
-    }
-
-    public void setIdentities(Set<String> identities) {
-        this.identities = identities;
-    }
-
     public OIDCSettingsMongo getOidc() {
         return oidc;
     }
@@ -184,4 +164,4 @@ public class DomainMongo extends Auditable {
     public void setReferenceId(String referenceId) {
         this.referenceId = referenceId;
     }
-}
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/DomainService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/DomainService.java
index 04b09ad0f5..4d9ddb1204 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/DomainService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/DomainService.java
@@ -51,8 +51,6 @@ public interface DomainService {
 
     Single<Domain> patch(String domainId, PatchDomain domain, User principal);
 
-    Single<Domain> setMasterDomain(String domainId, boolean isMaster);
-
     Completable delete(String domain, User principal);
 
     default Single<Domain> create(String organizationId, String environmentId, NewDomain domain) {
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/DomainDeleteMasterException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/DomainDeleteMasterException.java
deleted file mode 100644
index 381c75b908..0000000000
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/DomainDeleteMasterException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.gravitee.am.service.exception;
-
-import io.gravitee.common.http.HttpStatusCode;
-
-/**
- * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class DomainDeleteMasterException extends AbstractManagementException {
-
-    private final String domain;
-
-    public DomainDeleteMasterException(String domain) {
-        this.domain = domain;
-    }
-
-    @Override
-    public int getHttpStatusCode() {
-        return HttpStatusCode.BAD_REQUEST_400;
-    }
-
-    @Override
-    public String getMessage() {
-        return "Domain [" + domain + "] is master and cannot be deleted.";
-    }
-}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/DomainMasterNotFoundException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/DomainMasterNotFoundException.java
deleted file mode 100644
index bb7ec7d5ad..0000000000
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/DomainMasterNotFoundException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.gravitee.am.service.exception;
-
-/**
- * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class DomainMasterNotFoundException extends AbstractNotFoundException {
-
-    @Override
-    public String getMessage() {
-        return "Master domain can not be found.";
-    }
-}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java
index 1a0500cb1a..7cfcabec3b 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/DomainServiceImpl.java
@@ -235,9 +235,6 @@ public class DomainServiceImpl implements DomainService {
                     domain.setName(updateDomain.getName());
                     domain.setDescription(updateDomain.getDescription());
                     domain.setEnabled(updateDomain.isEnabled());
-                    domain.setIdentities(updateDomain.getIdentities());
-                    // master flag is set programmatically (keep old value)
-                    domain.setMaster(oldDomain.isMaster());
                     domain.setCreatedAt(oldDomain.getCreatedAt());
                     domain.setUpdatedAt(new Date());
                     //As it is not managed by UpdateDomain, we keep old value
@@ -316,37 +313,11 @@ public class DomainServiceImpl implements DomainService {
                 });
     }
 
-    @Override
-    public Single<Domain> setMasterDomain(String domainId, boolean isMaster) {
-        LOGGER.debug("Set master flag for domain: {}", domainId);
-        return domainRepository.findById(domainId)
-                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domainId)))
-                .flatMapSingle(oldDomain -> {
-                    oldDomain.setMaster(isMaster);
-                    oldDomain.setUpdatedAt(new Date());
-                    return domainRepository.update(oldDomain);
-                })
-                .onErrorResumeNext(ex -> {
-                    if (ex instanceof AbstractManagementException) {
-                        return Single.error(ex);
-                    }
-
-                    LOGGER.error("An error occurs while trying to set master flag for domain {}", domainId, ex);
-                    return Single.error(new TechnicalManagementException("An error occurs while trying to set master flag for a domain", ex));
-                });
-    }
-
     @Override
     public Completable delete(String domainId, User principal) {
         LOGGER.debug("Delete security domain {}", domainId);
         return domainRepository.findById(domainId)
                 .switchIfEmpty(Maybe.error(new DomainNotFoundException(domainId)))
-                .flatMapSingle(domain -> {
-                    if (domain.isMaster()) {
-                        throw new DomainDeleteMasterException(domainId);
-                    }
-                    return Single.just(domain);
-                })
                 .flatMapCompletable(domain -> {
                     // delete applications
                     return applicationService.findByDomain(domainId)
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchApplication.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchApplication.java
index 79706ca9a0..36b1e6ee8d 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchApplication.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchApplication.java
@@ -142,7 +142,7 @@ public class PatchApplication {
     /**
      * Returns the list of required permission depending on what fields are filled.
      *
-     * Ex: if settings.oauth is filled, {@link Permission#APPLICATION_OPENID} will be added to the list of required permission cause it means the user want to update this information.
+     * Ex: if settings.oauth is filled, {@link Permission#APPLICATION_OPENID} will be added to the list of required permissions cause it means the user want to update this information.
      *
      * @return the list of required permissions.
      */
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchDomain.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchDomain.java
index c08be07048..5f816a2895 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchDomain.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchDomain.java
@@ -20,10 +20,12 @@ import io.gravitee.am.model.Domain;
 import io.gravitee.am.model.account.AccountSettings;
 import io.gravitee.am.model.login.LoginSettings;
 import io.gravitee.am.model.oidc.OIDCSettings;
+import io.gravitee.am.model.permissions.Permission;
 import io.gravitee.am.model.scim.SCIMSettings;
 import io.gravitee.am.service.model.openid.PatchOIDCSettings;
 import io.gravitee.am.service.utils.SetterUtils;
 
+import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 
@@ -40,7 +42,6 @@ public class PatchDomain {
     private Optional<String> description;
     private Optional<Boolean> enabled;
     private Optional<String> path;
-    private Optional<Set<String>> identities;
     @JsonProperty("oidc")
     private Optional<PatchOIDCSettings> oidc;
     private Optional<SCIMSettings> scim;
@@ -80,14 +81,6 @@ public class PatchDomain {
         this.path = path;
     }
 
-    public Optional<Set<String>> getIdentities() {
-        return identities;
-    }
-
-    public void setIdentities(Optional<Set<String>> identities) {
-        this.identities = identities;
-    }
-
     public Optional<PatchOIDCSettings> getOidc() {
         return oidc;
     }
@@ -136,7 +129,6 @@ public class PatchDomain {
         SetterUtils.safeSet(toPatch::setDescription, this.getDescription());
         SetterUtils.safeSet(toPatch::setEnabled, this.getEnabled(), boolean.class);
         SetterUtils.safeSet(toPatch::setPath, this.getPath());
-        SetterUtils.safeSet(toPatch::setIdentities, this.getIdentities());
         SetterUtils.safeSet(toPatch::setScim, this.getScim());
         SetterUtils.safeSet(toPatch::setLoginSettings, this.getLoginSettings());
         SetterUtils.safeSet(toPatch::setAccountSettings, this.getAccountSettings());
@@ -153,4 +145,38 @@ public class PatchDomain {
 
         return toPatch;
     }
+
+
+    /**
+     * Returns the list of required permission depending on what fields are filled.
+     *
+     * Ex: if settings.oauth is filled, {@link Permission#APPLICATION_OPENID} will be added to the list of required permissions cause it means the user want to update this information.
+     *
+     * @return the list of required permissions.
+     */
+    public Set<Permission> getRequiredPermissions() {
+
+        Set<Permission> requiredPermissions = new HashSet<>();
+
+        if (name != null && name.isPresent()
+                || description != null && description.isPresent()
+                || enabled != null && enabled.isPresent()
+                || path != null && path.isPresent()
+                || loginSettings != null && loginSettings.isPresent()
+                || accountSettings != null && accountSettings.isPresent()
+                || tags != null && tags.isPresent()) {
+
+            requiredPermissions.add(Permission.DOMAIN_SETTINGS);
+        }
+
+        if (oidc != null && oidc.isPresent()) {
+            requiredPermissions.addAll(oidc.get().getRequiredPermissions());
+        }
+
+        if (scim != null && scim.isPresent()) {
+            requiredPermissions.add(Permission.DOMAIN_SCIM);
+        }
+
+        return requiredPermissions;
+    }
 }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateDomain.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateDomain.java
index 78226866b3..efab5584d0 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateDomain.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateDomain.java
@@ -50,10 +50,6 @@ public class UpdateDomain {
      */
     private String path;
 
-    private Set<String> identities;
-
-    private Set<String> oauth2Identities;
-
     private SCIMSettings scim;
 
     private LoginSettings loginSettings;
@@ -92,22 +88,6 @@ public class UpdateDomain {
         this.path = path;
     }
 
-    public Set<String> getIdentities() {
-        return identities;
-    }
-
-    public void setIdentities(Set<String> identities) {
-        this.identities = identities;
-    }
-
-    public Set<String> getOauth2Identities() {
-        return oauth2Identities;
-    }
-
-    public void setOauth2Identities(Set<String> oauth2Identities) {
-        this.oauth2Identities = oauth2Identities;
-    }
-
     public SCIMSettings getScim() {
         return scim;
     }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchOIDCSettings.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchOIDCSettings.java
index 1560f0bc87..f5998e72d4 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchOIDCSettings.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/openid/PatchOIDCSettings.java
@@ -18,9 +18,12 @@ package io.gravitee.am.service.model.openid;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.gravitee.am.model.oidc.ClientRegistrationSettings;
 import io.gravitee.am.model.oidc.OIDCSettings;
+import io.gravitee.am.model.permissions.Permission;
 import io.gravitee.am.service.utils.SetterUtils;
 
+import java.util.HashSet;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * @author Alexandre FARIA (contact at alexandrefaria.net)
@@ -72,4 +75,17 @@ public class PatchOIDCSettings {
 
         return toPatch;
     }
+
+
+    public Set<Permission> getRequiredPermissions() {
+
+        Set<Permission> requiredPermissions = new HashSet<>();
+
+        if (clientRegistrationSettings != null && clientRegistrationSettings.isPresent()
+                || redirectUriStrictMatching != null && redirectUriStrictMatching.isPresent()) {
+            requiredPermissions.add(Permission.DOMAIN_OPENID);
+        }
+
+        return requiredPermissions;
+    }
 }


Test patch:
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
index 0cafdb27a7..686dd1ba7f 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.management.handlers.management.api;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.gravitee.am.identityprovider.api.DefaultUser;
 import io.gravitee.am.identityprovider.api.User;
@@ -44,12 +45,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 
 import javax.annotation.Priority;
+import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerRequestFilter;
-import javax.ws.rs.core.Application;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.*;
 import java.io.IOException;
 import java.security.Principal;
 
@@ -161,7 +161,7 @@ public abstract class JerseySpringTest {
 
     @Before
     public void init() {
-       when(permissionService.hasPermission(any(User.class), any(PermissionAcls.class))).thenReturn(Single.just(true));
+        when(permissionService.hasPermission(any(User.class), any(PermissionAcls.class))).thenReturn(Single.just(true));
     }
 
     @Configuration
@@ -319,12 +319,12 @@ public abstract class JerseySpringTest {
         }
 
         @Bean
-        public PermissionService permissionService(){
+        public PermissionService permissionService() {
             return mock(PermissionService.class);
         }
 
         @Bean
-        public MembershipService membershipService(){
+        public MembershipService membershipService() {
             return mock(MembershipService.class);
         }
     }
@@ -333,7 +333,7 @@ public abstract class JerseySpringTest {
 
     public final WebTarget target(final String path) {
 
-        if("domains".equals(path)) {
+        if ("domains".equals(path)) {
             return _jerseyTest.target("organizations").path("DEFAULT").path("environments").path("DEFAULT").path(path);
         }
 
@@ -395,7 +395,7 @@ public abstract class JerseySpringTest {
 
     /**
      * Allows to read response entity using object mapper instead of jersey's own implementation.
-     * It is especially useful to ensure objects are deserialize from json using same feature (ex: lower cased enum, ...).
+     * It is especially useful to ensure objects are deserialized from json using same feature (ex: lower cased enum, ...).
      *
      * @param response the jaxrs response.
      * @param clazz the type of entity
@@ -406,7 +406,7 @@ public abstract class JerseySpringTest {
     protected <T> T readEntity(Response response, Class<T> clazz) {
 
         try {
-            if(clazz == String.class) {
+            if (clazz == String.class) {
                 return (T) response.readEntity(String.class);
             }
             return objectMapper.readValue(response.readEntity(String.class), clazz);
@@ -414,4 +414,23 @@ public abstract class JerseySpringTest {
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Allows to put entity using object mapper instead of jersey's own implementation.
+     * It is especially useful to ensure objects are serialized to json using same feature (ex: lower cased enum, ...).
+     *
+     * @param webTarget the jersey web target.
+     * @param value the entity to put.
+     * @param <T> the type of entity.
+     *
+     * @return the resulted {@link Response}.
+     */
+    protected <T> Response put(WebTarget webTarget, T value) {
+
+        try {
+            return webTarget.request().put(Entity.entity(objectMapper.writeValueAsString(value), MediaType.APPLICATION_JSON_TYPE));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/ApplicationResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/ApplicationResourceTest.java
index d5c134ad30..05f4cdb1b9 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/ApplicationResourceTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/ApplicationResourceTest.java
@@ -18,11 +18,19 @@ package io.gravitee.am.management.handlers.management.api.resources;
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
 import io.gravitee.am.management.service.permissions.PermissionAcls;
+import io.gravitee.am.model.Acl;
 import io.gravitee.am.model.Application;
 import io.gravitee.am.model.Domain;
 import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.account.AccountSettings;
+import io.gravitee.am.model.application.ApplicationAdvancedSettings;
+import io.gravitee.am.model.application.ApplicationOAuthSettings;
+import io.gravitee.am.model.application.ApplicationSettings;
+import io.gravitee.am.model.application.ApplicationType;
 import io.gravitee.am.model.permissions.Permission;
 import io.gravitee.am.service.exception.ApplicationNotFoundException;
+import io.gravitee.am.service.model.PatchApplication;
+import io.gravitee.am.service.model.PatchDomain;
 import io.gravitee.common.http.HttpStatusCode;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
@@ -30,7 +38,11 @@ import org.junit.Test;
 
 import javax.ws.rs.core.Response;
 
-import static org.junit.Assert.assertEquals;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Optional;
+
+import static org.junit.Assert.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.doReturn;
 
@@ -46,23 +58,70 @@ public class ApplicationResourceTest extends JerseySpringTest {
         final Domain mockDomain = new Domain();
         mockDomain.setId(domainId);
 
-        final String clientId = "client-id";
-        final Application mockClient = new Application();
-        mockClient.setId(clientId);
-        mockClient.setName("client-name");
-        mockClient.setDomain(domainId);
+        final Application mockApplication = buildApplicationMock();
 
         doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
         doReturn(Single.just(Permission.allPermissionAcls(ReferenceType.APPLICATION))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString());
         doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
-        doReturn(Maybe.just(mockClient)).when(applicationService).findById(clientId);
+        doReturn(Maybe.just(mockApplication)).when(applicationService).findById(mockApplication.getId());
 
-        final Response response = target("domains").path(domainId).path("applications").path(clientId).request().get();
+        // Check all data are returned when having all permissions.
+        final Response response = target("domains").path(domainId).path("applications").path(mockApplication.getId()).request().get();
         assertEquals(HttpStatusCode.OK_200, response.getStatus());
 
         final Application application = readEntity(response, Application.class);
-        assertEquals(domainId, application.getDomain());
-        assertEquals(clientId, application.getId());
+        assertEquals(mockApplication.getId(), application.getId());
+        assertEquals(mockApplication.getName(), application.getName());
+        assertEquals(mockApplication.getDomain(), application.getDomain());
+        assertEquals(mockApplication.getType(), application.getType());
+        assertEquals(mockApplication.getDescription(), application.getDescription());
+        assertEquals(mockApplication.isEnabled(), application.isEnabled());
+        assertEquals(mockApplication.isTemplate(), application.isTemplate());
+        assertEquals(mockApplication.getFactors(), application.getFactors());
+        assertEquals(mockApplication.getCreatedAt(), application.getCreatedAt());
+        assertEquals(mockApplication.getUpdatedAt(), application.getUpdatedAt());
+        assertEquals(mockApplication.getIdentities(), application.getIdentities());
+        assertEquals(mockApplication.getCertificate(), application.getCertificate());
+        assertNotNull(application.getSettings());
+        assertNotNull(application.getSettings().getAdvanced());
+        assertNotNull(application.getSettings().getAccount());
+        assertNotNull(application.getSettings().getOauth());
+    }
+
+    @Test
+    public void shouldGetFilteredApp() {
+        final String domainId = "domain-id";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        final Application mockApplication = buildApplicationMock();
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.of(Permission.APPLICATION, Acl.READ))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString()); // only application read permission
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Maybe.just(mockApplication)).when(applicationService).findById(mockApplication.getId());
+
+        // Check data are filtered according to permissions.
+        final Response response = target("domains").path(domainId).path("applications").path(mockApplication.getId()).request().get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Application application = readEntity(response, Application.class);
+        assertEquals(mockApplication.getId(), application.getId());
+        assertEquals(mockApplication.getName(), application.getName());
+        assertEquals(mockApplication.getDomain(), application.getDomain());
+        assertEquals(mockApplication.getType(), application.getType());
+        assertEquals(mockApplication.getDescription(), application.getDescription());
+        assertEquals(mockApplication.isEnabled(), application.isEnabled());
+        assertEquals(mockApplication.isTemplate(), application.isTemplate());
+        assertNull(application.getFactors());
+        assertEquals(mockApplication.getCreatedAt(), application.getCreatedAt());
+        assertEquals(mockApplication.getUpdatedAt(), application.getUpdatedAt());
+        assertNull(application.getIdentities());
+        assertNull(application.getCertificate());
+        assertNotNull(application.getSettings());
+        assertNull(application.getSettings().getAdvanced());
+        assertNull(application.getSettings().getAccount());
+        assertNull(application.getSettings().getOauth());
     }
 
     @Test
@@ -80,7 +139,6 @@ public class ApplicationResourceTest extends JerseySpringTest {
         assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
     }
 
-
     @Test
     public void shouldGetClient_domainNotFound() {
         final String domainId = "domain-id";
@@ -113,6 +171,131 @@ public class ApplicationResourceTest extends JerseySpringTest {
         assertEquals(HttpStatusCode.BAD_REQUEST_400, response.getStatus());
     }
 
+    @Test
+    public void shouldUpdateApplication() {
+
+        final String domainId = "domain-id";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        final Application mockApplication = buildApplicationMock();
+        PatchApplication patchApplication = new PatchApplication();
+        patchApplication.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.allPermissionAcls(ReferenceType.APPLICATION))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString());
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Single.just(mockApplication)).when(applicationService).patch(eq(domainId), eq(mockApplication.getId()), any(PatchApplication.class), any(User.class));
+
+        // heck all data are returned when having all permissions.
+        final Response response = put(target("domains").path(domainId).path("applications").path(mockApplication.getId()), patchApplication);
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Application application = readEntity(response, Application.class);
+        assertEquals(mockApplication.getId(), application.getId());
+        assertEquals(mockApplication.getName(), application.getName());
+        assertEquals(mockApplication.getDomain(), application.getDomain());
+        assertEquals(mockApplication.getType(), application.getType());
+        assertEquals(mockApplication.getDescription(), application.getDescription());
+        assertEquals(mockApplication.isEnabled(), application.isEnabled());
+        assertEquals(mockApplication.isTemplate(), application.isTemplate());
+        assertEquals(mockApplication.getFactors(), application.getFactors());
+        assertEquals(mockApplication.getCreatedAt(), application.getCreatedAt());
+        assertEquals(mockApplication.getUpdatedAt(), application.getUpdatedAt());
+        assertEquals(mockApplication.getIdentities(), application.getIdentities());
+        assertEquals(mockApplication.getCertificate(), application.getCertificate());
+        assertNotNull(application.getSettings());
+        assertNotNull(application.getSettings().getAdvanced());
+        assertNotNull(application.getSettings().getAccount());
+        assertNotNull(application.getSettings().getOauth());
+    }
+
+    @Test
+    public void shouldUpdateFilteredApplication() {
+
+        final String domainId = "domain-id";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        final Application mockApplication = buildApplicationMock();
+        PatchApplication patchApplication = new PatchApplication();
+        patchApplication.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.of(Permission.APPLICATION, Acl.READ))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString()); // only application read permission
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Single.just(mockApplication)).when(applicationService).patch(eq(domainId), eq(mockApplication.getId()), any(PatchApplication.class), any(User.class));
+
+        // Check all data are returned when having all permissions.
+        final Response response = put(target("domains").path(domainId).path("applications").path(mockApplication.getId()), patchApplication);
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Application application = readEntity(response, Application.class);
+        assertEquals(mockApplication.getId(), application.getId());
+        assertEquals(mockApplication.getName(), application.getName());
+        assertEquals(mockApplication.getDomain(), application.getDomain());
+        assertEquals(mockApplication.getType(), application.getType());
+        assertEquals(mockApplication.getDescription(), application.getDescription());
+        assertEquals(mockApplication.isEnabled(), application.isEnabled());
+        assertEquals(mockApplication.isTemplate(), application.isTemplate());
+        assertNull(application.getFactors());
+        assertEquals(mockApplication.getCreatedAt(), application.getCreatedAt());
+        assertEquals(mockApplication.getUpdatedAt(), application.getUpdatedAt());
+        assertNull(application.getIdentities());
+        assertNull(application.getCertificate());
+        assertNotNull(application.getSettings());
+        assertNull(application.getSettings().getAdvanced());
+        assertNull(application.getSettings().getAccount());
+        assertNull(application.getSettings().getOauth());
+    }
+
+    @Test
+    public void shouldUpdateApplication_domainNotFound() {
+
+        final String domainId = "domain-id";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        final Application mockApplication = buildApplicationMock();
+        PatchApplication patchApplication = new PatchApplication();
+        patchApplication.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.of(Permission.APPLICATION, Acl.READ))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString()); // only application read permission
+        doReturn(Maybe.empty()).when(domainService).findById(domainId);
+
+        final Response response = put(target("domains").path(domainId).path("applications").path(mockApplication.getId()), patchApplication);
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+
+    @Test
+    public void shouldUpdateApplication_forbidden() {
+
+        final String domainId = "domain-id";
+        final Domain mockDomain = new Domain();
+        mockDomain.setId(domainId);
+
+        final Application mockApplication = buildApplicationMock();
+        PatchApplication patchApplication = new PatchApplication();
+        patchApplication.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(false)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+
+        final Response response = put(target("domains").path(domainId).path("applications").path(mockApplication.getId()), patchApplication);
+        assertEquals(HttpStatusCode.FORBIDDEN_403, response.getStatus());
+    }
+
+    @Test
+    public void shouldUpdateApplication_badRequest() {
+
+        // Empty patch should result in 400 bad request.
+        PatchApplication patchApplication = new PatchApplication();
+
+        final Response response = put(target("domains").path("domain-id").path("applications").path("application-id"), patchApplication);
+        assertEquals(HttpStatusCode.BAD_REQUEST_400, response.getStatus());
+    }
+
     @Test
     public void shouldRenewClientSecret() {
         final String domainId = "domain-id";
@@ -163,4 +346,31 @@ public class ApplicationResourceTest extends JerseySpringTest {
                 .post(null);
         assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
     }
+
+    private Application buildApplicationMock() {
+
+        Application mockApplication = new Application();
+        mockApplication.setId("client-id");
+        mockApplication.setName("client-name");
+        mockApplication.setDomain("domain-id");
+        mockApplication.setType(ApplicationType.SERVICE);
+        mockApplication.setDescription("description");
+        mockApplication.setEnabled(true);
+        mockApplication.setTemplate(true);
+        mockApplication.setFactors(Collections.singleton("factor"));
+        mockApplication.setCreatedAt(new Date());
+        mockApplication.setUpdatedAt(new Date());
+        mockApplication.setIdentities(Collections.singleton("identity"));
+        mockApplication.setCertificate("certificate");
+
+        ApplicationSettings filteredApplicationSettings = new ApplicationSettings();
+        filteredApplicationSettings.setAdvanced(new ApplicationAdvancedSettings());
+        filteredApplicationSettings.setAccount(new AccountSettings());
+        filteredApplicationSettings.setOauth(new ApplicationOAuthSettings());
+
+        mockApplication.setSettings(filteredApplicationSettings);
+        mockApplication.setMetadata(Collections.singletonMap("key", "value"));
+
+        return mockApplication;
+    }
 }
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java
index fcfba30ae4..c655c186a5 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java
@@ -18,18 +18,29 @@ package io.gravitee.am.management.handlers.management.api.resources;
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
 import io.gravitee.am.management.service.permissions.PermissionAcls;
+import io.gravitee.am.model.Acl;
 import io.gravitee.am.model.Domain;
 import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.account.AccountSettings;
+import io.gravitee.am.model.login.LoginSettings;
+import io.gravitee.am.model.oidc.OIDCSettings;
 import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.model.scim.SCIMSettings;
 import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.model.PatchDomain;
 import io.gravitee.common.http.HttpStatusCode;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import org.junit.Test;
 
+import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Response;
 
-import static org.junit.Assert.assertEquals;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Optional;
+
+import static org.junit.Assert.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.doReturn;
 
@@ -41,20 +52,60 @@ public class DomainResourceTest extends JerseySpringTest {
 
     @Test
     public void shouldGetDomain() {
-        final String domainId = "domain-id";
-        final Domain mockDomain = new Domain();
-        mockDomain.setId(domainId);
-        mockDomain.setName("domain-name");
+
+        final Domain mockDomain = buildDomainMock();
 
         doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
         doReturn(Single.just(Permission.allPermissionAcls(ReferenceType.DOMAIN))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString());
-        doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId);
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(mockDomain.getId());
 
-        final Response response = target("domains").path(domainId).request().get();
+        final Response response = target("domains").path(mockDomain.getId()).request().get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Domain domain = readEntity(response, Domain.class);
+        assertEquals(mockDomain.getId(), domain.getId());
+        assertEquals(mockDomain.getName(), domain.getName());
+        assertEquals(mockDomain.getDescription(), domain.getDescription());
+        assertEquals(mockDomain.isEnabled(), domain.isEnabled());
+        assertEquals(mockDomain.getCreatedAt(), domain.getCreatedAt());
+        assertEquals(mockDomain.getUpdatedAt(), domain.getUpdatedAt());
+        assertEquals(mockDomain.getPath(), domain.getPath());
+        assertEquals(mockDomain.getReferenceType(), domain.getReferenceType());
+        assertEquals(mockDomain.getReferenceId(), domain.getReferenceId());
+        assertNotNull(domain.getOidc());
+        assertNotNull(domain.getScim());
+        assertNotNull(domain.getLoginSettings());
+        assertNotNull(domain.getAccountSettings());
+        assertEquals(mockDomain.getTags(), domain.getTags());
+    }
+
+    @Test
+    public void shouldGetFilteredDomain() {
+
+        final Domain mockDomain = buildDomainMock();
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.of(Permission.DOMAIN, Acl.READ))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString()); // only domain read permission
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(mockDomain.getId());
+
+        final Response response = target("domains").path(mockDomain.getId()).request().get();
         assertEquals(HttpStatusCode.OK_200, response.getStatus());
 
         final Domain domain = readEntity(response, Domain.class);
-        assertEquals("domain-name", domain.getName());
+        assertEquals(mockDomain.getId(), domain.getId());
+        assertEquals(mockDomain.getName(), domain.getName());
+        assertEquals(mockDomain.getDescription(), domain.getDescription());
+        assertEquals(mockDomain.isEnabled(), domain.isEnabled());
+        assertEquals(mockDomain.getCreatedAt(), domain.getCreatedAt());
+        assertEquals(mockDomain.getUpdatedAt(), domain.getUpdatedAt());
+        assertEquals(mockDomain.getPath(), domain.getPath());
+        assertEquals(mockDomain.getReferenceType(), domain.getReferenceType());
+        assertEquals(mockDomain.getReferenceId(), domain.getReferenceId());
+        assertNull(domain.getOidc());
+        assertNull(domain.getScim());
+        assertNull(domain.getLoginSettings());
+        assertNull(domain.getAccountSettings());
+        assertNull(domain.getTags());
     }
 
     @Test
@@ -66,6 +117,17 @@ public class DomainResourceTest extends JerseySpringTest {
         assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
     }
 
+    @Test
+    public void shouldGetDomain_forbidden() {
+        final String domainId = "domain-id";
+
+        doReturn(Single.just(false)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Maybe.empty()).when(domainService).findById(domainId);
+
+        final Response response = target("domains").path(domainId).request().get();
+        assertEquals(HttpStatusCode.FORBIDDEN_403, response.getStatus());
+    }
+
     @Test
     public void shouldGetDomain_technicalManagementException() {
         final String domainId = "domain-id";
@@ -74,4 +136,109 @@ public class DomainResourceTest extends JerseySpringTest {
         final Response response = target("domains").path(domainId).request().get();
         assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR_500, response.getStatus());
     }
+
+    @Test
+    public void shouldUpdateDomain() {
+
+        Domain mockDomain = buildDomainMock();
+        PatchDomain patchDomain = new PatchDomain();
+        patchDomain.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.allPermissionAcls(ReferenceType.DOMAIN))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString());
+        doReturn(Single.just(mockDomain)).when(domainService).patch(eq(mockDomain.getId()), any(PatchDomain.class), any(User.class));
+
+        final Response response = put(target("domains").path(mockDomain.getId()), patchDomain);
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Domain domain = readEntity(response, Domain.class);
+        assertEquals(mockDomain.getId(), domain.getId());
+        assertEquals(mockDomain.getName(), domain.getName());
+        assertEquals(mockDomain.getDescription(), domain.getDescription());
+        assertEquals(mockDomain.isEnabled(), domain.isEnabled());
+        assertEquals(mockDomain.getCreatedAt(), domain.getCreatedAt());
+        assertEquals(mockDomain.getUpdatedAt(), domain.getUpdatedAt());
+        assertEquals(mockDomain.getPath(), domain.getPath());
+        assertEquals(mockDomain.getReferenceType(), domain.getReferenceType());
+        assertEquals(mockDomain.getReferenceId(), domain.getReferenceId());
+        assertNotNull(domain.getOidc());
+        assertNotNull(domain.getScim());
+        assertNotNull(domain.getLoginSettings());
+        assertNotNull(domain.getAccountSettings());
+        assertEquals(mockDomain.getTags(), domain.getTags());
+    }
+
+    @Test
+    public void shouldUpdateFilteredDomain() {
+
+        Domain mockDomain = buildDomainMock();
+        PatchDomain patchDomain = new PatchDomain();
+        patchDomain.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(true)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(Permission.of(Permission.DOMAIN, Acl.READ))).when(permissionService).findAllPermissions(any(User.class), any(ReferenceType.class), anyString()); // only domain read permission
+        doReturn(Single.just(mockDomain)).when(domainService).patch(eq(mockDomain.getId()), any(PatchDomain.class), any(User.class));
+
+        final Response response = put(target("domains").path(mockDomain.getId()), patchDomain);
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Domain domain = readEntity(response, Domain.class);
+        assertEquals(mockDomain.getId(), domain.getId());
+        assertEquals(mockDomain.getName(), domain.getName());
+        assertEquals(mockDomain.getDescription(), domain.getDescription());
+        assertEquals(mockDomain.isEnabled(), domain.isEnabled());
+        assertEquals(mockDomain.getCreatedAt(), domain.getCreatedAt());
+        assertEquals(mockDomain.getUpdatedAt(), domain.getUpdatedAt());
+        assertEquals(mockDomain.getPath(), domain.getPath());
+        assertEquals(mockDomain.getReferenceType(), domain.getReferenceType());
+        assertEquals(mockDomain.getReferenceId(), domain.getReferenceId());
+        assertNull(domain.getOidc());
+        assertNull(domain.getScim());
+        assertNull(domain.getLoginSettings());
+        assertNull(domain.getAccountSettings());
+        assertNull(domain.getTags());
+    }
+
+    @Test
+    public void shouldUpdateDomain_forbidden() {
+        final String domainId = "domain-id";
+
+        PatchDomain patchDomain = new PatchDomain();
+        patchDomain.setDescription(Optional.of("New description"));
+
+        doReturn(Single.just(false)).when(permissionService).hasPermission(any(User.class), any(PermissionAcls.class));
+        doReturn(Single.just(new Domain())).when(domainService).patch(anyString(), any(PatchDomain.class), any(User.class));
+
+        final Response response = put(target("domains").path(domainId), patchDomain);
+        assertEquals(HttpStatusCode.FORBIDDEN_403, response.getStatus());
+    }
+
+    @Test
+    public void shouldUpdateDomain_badRequest() {
+
+        // Empty patch should result in 400 bad request.
+        PatchDomain patchDomain = new PatchDomain();
+
+        final Response response = put(target("domains").path("domain-id"), patchDomain);
+        assertEquals(HttpStatusCode.BAD_REQUEST_400, response.getStatus());
+    }
+
+    private Domain buildDomainMock() {
+        final Domain mockDomain = new Domain();
+        mockDomain.setId("domain-id");
+        mockDomain.setName("domain-name");
+        mockDomain.setDescription("description");
+        mockDomain.setEnabled(true);
+        mockDomain.setCreatedAt(new Date());
+        mockDomain.setUpdatedAt(new Date());
+        mockDomain.setPath("path");
+        mockDomain.setReferenceType(ReferenceType.ENVIRONMENT);
+        mockDomain.setReferenceId("referenceId");
+        mockDomain.setOidc(new OIDCSettings());
+        mockDomain.setScim(new SCIMSettings());
+        mockDomain.setLoginSettings(new LoginSettings());
+        mockDomain.setAccountSettings(new AccountSettings());
+        mockDomain.setTags(Collections.singleton("tag"));
+        return mockDomain;
+    }
 }
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java
index 9b77fc51fe..e79be60dfa 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/DomainServiceTest.java
@@ -19,12 +19,10 @@ import io.gravitee.am.identityprovider.api.DefaultUser;
 import io.gravitee.am.model.*;
 import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.model.oauth2.Scope;
-import io.gravitee.am.model.permissions.RoleScope;
 import io.gravitee.am.model.permissions.SystemRole;
 import io.gravitee.am.repository.exceptions.TechnicalException;
 import io.gravitee.am.repository.management.api.DomainRepository;
 import io.gravitee.am.service.exception.DomainAlreadyExistsException;
-import io.gravitee.am.service.exception.DomainDeleteMasterException;
 import io.gravitee.am.service.exception.DomainNotFoundException;
 import io.gravitee.am.service.exception.TechnicalManagementException;
 import io.gravitee.am.service.impl.DomainServiceImpl;
@@ -552,17 +550,6 @@ public class DomainServiceTest {
         testObserver.assertNotComplete();
     }
 
-
-    @Test
-    public void shouldNotDeleteMasterDomain() throws TechnicalException {
-        when(domain.isMaster()).thenReturn(true);
-        when(domainRepository.findById(DOMAIN_ID)).thenReturn(Maybe.just(domain));
-
-        TestObserver testObserver = domainService.delete(DOMAIN_ID).test();
-        testObserver.assertError(DomainDeleteMasterException.class);
-        testObserver.assertNotComplete();
-    }
-
     @Test
     public void shouldDelete_technicalException() {
         when(domainRepository.findById(DOMAIN_ID)).thenReturn(Maybe.error(TechnicalException::new));
@@ -583,42 +570,4 @@ public class DomainServiceTest {
         testObserver.assertError(TechnicalManagementException.class);
         testObserver.assertNotComplete();
     }
-
-    @Test
-    public void shouldSetMasterDomain() {
-        when(domainRepository.findById(DOMAIN_ID)).thenReturn(Maybe.just(new Domain()));
-        when(domainRepository.update(any(Domain.class))).thenReturn(Single.just(new Domain()));
-
-        TestObserver testObserver = domainService.setMasterDomain(DOMAIN_ID, true).test();
-        testObserver.awaitTerminalEvent();
-
-        testObserver.assertComplete();
-        testObserver.assertNoErrors();
-
-        verify(domainRepository, times(1)).update(any(Domain.class));
-    }
-
-    @Test
-    public void shouldSetMasterDomain_domainNotFound() {
-        when(domainRepository.findById(DOMAIN_ID)).thenReturn(Maybe.empty());
-
-        TestObserver testObserver = domainService.setMasterDomain(DOMAIN_ID, true).test();
-
-        testObserver.assertError(DomainNotFoundException.class);
-        testObserver.assertNotComplete();
-
-        verify(domainRepository, never()).update(any(Domain.class));
-    }
-
-    @Test
-    public void shouldSetMasterDomain_technicalException() {
-        when(domainRepository.findById(DOMAIN_ID)).thenReturn(Maybe.error(TechnicalException::new));
-
-        TestObserver testObserver = domainService.setMasterDomain(DOMAIN_ID, true).test();
-
-        testObserver.assertError(TechnicalManagementException.class);
-        testObserver.assertNotComplete();
-
-        verify(domainRepository, never()).update(any(Domain.class));
-    }
 }
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchApplicationTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchApplicationTest.java
new file mode 100644
index 0000000000..ca101661c0
--- /dev/null
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchApplicationTest.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.model;
+
+import io.gravitee.am.model.account.AccountSettings;
+import io.gravitee.am.model.login.LoginSettings;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.model.scim.SCIMSettings;
+import io.gravitee.am.service.model.openid.PatchClientRegistrationSettings;
+import io.gravitee.am.service.model.openid.PatchOIDCSettings;
+import org.junit.Test;
+
+import javax.swing.text.html.Option;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class PatchApplicationTest {
+
+    @Test
+    public void getRequiredPermissions() {
+
+        PatchApplication patchApplication = new PatchApplication();
+        assertEquals(Collections.emptySet(), patchApplication.getRequiredPermissions());
+
+        patchApplication.setName(Optional.of("patchName"));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setDescription(Optional.of("patchDescription"));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setEnabled(Optional.of(true));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setTemplate(Optional.of(true));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setIdentities(Optional.of(Collections.singleton("patchIdentity")));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_IDENTITY_PROVIDER)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setFactors(Optional.of(Collections.singleton("patchFactor")));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_FACTOR)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setCertificate(Optional.of("patchCertificate"));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_CERTIFICATE)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        patchApplication.setMetadata(Optional.of(Collections.emptyMap()));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+
+        patchApplication = new PatchApplication();
+        PatchApplicationSettings patchApplicationSettings = new PatchApplicationSettings();
+        patchApplication.setSettings(Optional.of(patchApplicationSettings));
+        assertEquals(Collections.emptySet(), patchApplication.getRequiredPermissions());
+        patchApplicationSettings.setOauth(Optional.of(new PatchApplicationOAuthSettings()));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_OPENID)), patchApplication.getRequiredPermissions());
+        patchApplicationSettings.setOauth(Optional.empty());
+        patchApplicationSettings.setAccount(Optional.of(new AccountSettings()));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+        patchApplicationSettings.setAccount(Optional.empty());
+        patchApplicationSettings.setAdvanced(Optional.of(new PatchApplicationAdvancedSettings()));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS)), patchApplication.getRequiredPermissions());
+
+        // Check multiple permissions.
+        patchApplication = new PatchApplication();
+        patchApplication.setTemplate(Optional.of(true));
+        patchApplication.setIdentities(Optional.of(Collections.singleton("patchIdentity")));
+        patchApplication.setFactors(Optional.of(Collections.singleton("patchFactor")));
+        patchApplication.setCertificate(Optional.of("patchCertificate"));
+        patchApplicationSettings = new PatchApplicationSettings();
+        patchApplicationSettings.setOauth(Optional.of(new PatchApplicationOAuthSettings()));
+        patchApplication.setSettings(Optional.of(patchApplicationSettings));
+
+        assertEquals(new HashSet<>(Arrays.asList(Permission.APPLICATION_SETTINGS, Permission.APPLICATION_OPENID, Permission.APPLICATION_IDENTITY_PROVIDER,
+                Permission.APPLICATION_CERTIFICATE, Permission.APPLICATION_FACTOR)), patchApplication.getRequiredPermissions());
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchDomainTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchDomainTest.java
index e5f88188c5..158d56a847 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchDomainTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/model/PatchDomainTest.java
@@ -16,14 +16,21 @@
 package io.gravitee.am.service.model;
 
 import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.account.AccountSettings;
+import io.gravitee.am.model.login.LoginSettings;
 import io.gravitee.am.model.oidc.ClientRegistrationSettings;
 import io.gravitee.am.model.oidc.OIDCSettings;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.model.scim.SCIMSettings;
 import io.gravitee.am.service.model.openid.PatchClientRegistrationSettings;
 import io.gravitee.am.service.model.openid.PatchOIDCSettings;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Optional;
 
 import static org.junit.Assert.*;
@@ -52,10 +59,10 @@ public class PatchDomainTest {
         Domain result = patch.patch(toPatch);
 
         //check.
-        assertNotNull("was expecting a domain",result);
-        assertEquals("description should have been updated","expectedDescription", result.getDescription());
-        assertNull("name should have been set to null",result.getName());
-        assertEquals("path should not be updated","expectedPath", result.getPath());
+        assertNotNull("was expecting a domain", result);
+        assertEquals("description should have been updated", "expectedDescription", result.getDescription());
+        assertNull("name should have been set to null", result.getName());
+        assertEquals("path should not be updated", "expectedPath", result.getPath());
     }
 
     @Test
@@ -77,10 +84,10 @@ public class PatchDomainTest {
         Domain result = patch.patch(toPatch);
 
         //check.
-        assertNotNull("was expecting a domain",result);
+        assertNotNull("was expecting a domain", result);
         assertNotNull(result.getOidc());
         assertNotNull(result.getOidc().getClientRegistrationSettings());
-        assertFalse("should have been disabled",result.getOidc().getClientRegistrationSettings().isDynamicClientRegistrationEnabled());
+        assertFalse("should have been disabled", result.getOidc().getClientRegistrationSettings().isDynamicClientRegistrationEnabled());
     }
 
     @Test
@@ -101,9 +108,63 @@ public class PatchDomainTest {
         Domain result = patch.patch(toPatch);
 
         //check.
-        assertNotNull("was expecting a domain",result);
+        assertNotNull("was expecting a domain", result);
         assertNotNull(result.getOidc());
         assertNotNull(result.getOidc().getClientRegistrationSettings());
-        assertTrue("should have been enabled",result.getOidc().getClientRegistrationSettings().isDynamicClientRegistrationEnabled());
+        assertTrue("should have been enabled", result.getOidc().getClientRegistrationSettings().isDynamicClientRegistrationEnabled());
+    }
+
+    @Test
+    public void testGetRequiredPermissions() {
+
+        PatchDomain patchDomain = new PatchDomain();
+        assertEquals(Collections.emptySet(), patchDomain.getRequiredPermissions());
+
+        patchDomain.setName(Optional.of("patchName"));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setDescription(Optional.of("patchDescription"));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setEnabled(Optional.of(true));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setPath(Optional.of("patchPath"));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setLoginSettings(Optional.of(new LoginSettings()));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setAccountSettings(Optional.of(new AccountSettings()));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setTags(Optional.of(Collections.singleton("patchTag")));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        PatchOIDCSettings oidcSettings = new PatchOIDCSettings();
+        patchDomain.setOidc(Optional.of(oidcSettings));
+        assertEquals(Collections.emptySet(), patchDomain.getRequiredPermissions());
+        oidcSettings.setClientRegistrationSettings(Optional.of(new PatchClientRegistrationSettings()));
+        oidcSettings.setRedirectUriStrictMatching(Optional.of(true));
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_OPENID)), patchDomain.getRequiredPermissions());
+
+        patchDomain = new PatchDomain();
+        patchDomain.setScim(Optional.of(new SCIMSettings()));
+        assertEquals(new HashSet<>(Arrays.asList( Permission.DOMAIN_SCIM)), patchDomain.getRequiredPermissions());
+
+        // Check multiple permissions.
+        patchDomain = new PatchDomain();
+        patchDomain.setPath(Optional.of("patchPath"));
+        patchDomain.setOidc(Optional.of(oidcSettings));
+        patchDomain.setScim(Optional.of(new SCIMSettings()));
+
+        assertEquals(new HashSet<>(Arrays.asList(Permission.DOMAIN_SETTINGS, Permission.DOMAIN_OPENID, Permission.DOMAIN_SCIM)), patchDomain.getRequiredPermissions());
     }
 }