Repository: gravitee-io/graviteeio-access-management
Pull request #603 (2019-06-13 09:02:06)

discovery scope management
This solve a part of issue [#2326](https://github.com/gravitee-io/issues/issues/2326).
Now scopes displayed into the discovery will not include admin "private" scopes.


Patch:
diff --git a/Makefile b/Makefile
index 6f9050b7c2..3a27cfd922 100644
--- a/Makefile
+++ b/Makefile
@@ -160,7 +160,10 @@ postman: ## Run postman non regression test (require newman npm module)
 	@newman run postman/collections/graviteeio-am-openid-core-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 	@newman run postman/collections/graviteeio-am-openid-dcr-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 	@newman run postman/collections/graviteeio-am-scim-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
+	@newman run postman/collections/graviteeio-am-scope-management-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 	@newman run postman/collections/graviteeio-am-client-management-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
+	@newman run postman/collections/graviteeio-am-policies-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
+	@newman run postman/collections/graviteeio-am-login-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 	@newman run postman/collections/graviteeio-am-user-management-collection.json -e postman/environment/dev.json --ignore-redirects --insecure --bail
 
 oidctest-run: oidctest-install oidctest-start ## Run openid-certification tools, using same docker network
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/oidc/Scope.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/oidc/Scope.java
index b775d2198c..745bfe7943 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/oidc/Scope.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/oidc/Scope.java
@@ -34,30 +34,32 @@ public enum Scope {
             StandardClaims.NAME, StandardClaims.FAMILY_NAME, StandardClaims.GIVEN_NAME, StandardClaims.MIDDLE_NAME,
             StandardClaims.NICKNAME, StandardClaims.PREFERRED_USERNAME, StandardClaims.PROFILE, StandardClaims.PICTURE,
             StandardClaims.WEBSITE, StandardClaims.GENDER, StandardClaims.BIRTHDATE, StandardClaims.ZONEINFO, StandardClaims.LOCALE,
-            StandardClaims.UPDATED_AT)),
-    EMAIL("email","Email","Access to the email and email_verified Claims", Arrays.asList(StandardClaims.EMAIL, StandardClaims.EMAIL_VERIFIED)),
-    ADDRESS("address", "Address","Access to the address Claim", Arrays.asList(StandardClaims.ADDRESS)),
-    PHONE("phone", "Phone", "Access to the phone_number and phone_number_verified Claims", Arrays.asList(StandardClaims.PHONE_NUMBER, StandardClaims.PHONE_NUMBER_VERIFIED)),
-    OPENID("openid","Openid","Used to perform Openid requests", Collections.emptyList()),
-    OFFLINE_ACCESS("offline_access", "Offline_access","Access to End-User UserInfo even when he is not logged in.", Collections.emptyList()),
-    DCR("dcr", "Client_registration", "Access to client information through openid register endpoint.", Collections.emptyList()),
-    DCR_ADMIN("dcr_admin", "Client_registration_admin", "Access to Dynamic Client Registration endpoint.", Collections.emptyList()),
-    SCIM("scim", "SCIM", "Access to System for Cross-domain Identity Management endpoint.", Collections.emptyList()),
-    CONSENT_ADMIN("consent_admin", "Consent_admin", "Access to End-User consents", Collections.emptyList());
+            StandardClaims.UPDATED_AT),true),
+    EMAIL("email","Email","Access to the email and email_verified Claims", Arrays.asList(StandardClaims.EMAIL, StandardClaims.EMAIL_VERIFIED),true),
+    ADDRESS("address", "Address","Access to the address Claim", Arrays.asList(StandardClaims.ADDRESS),true),
+    PHONE("phone", "Phone", "Access to the phone_number and phone_number_verified Claims", Arrays.asList(StandardClaims.PHONE_NUMBER, StandardClaims.PHONE_NUMBER_VERIFIED),true),
+    OPENID("openid","Openid","Used to perform Openid requests", Collections.emptyList(),true),
+    OFFLINE_ACCESS("offline_access", "Offline_access","Access to End-User UserInfo even when he is not logged in.", Collections.emptyList(),true),
+    DCR("dcr", "Client_registration", "Access to client information through openid register endpoint.", Collections.emptyList(),false),
+    DCR_ADMIN("dcr_admin", "Client_registration_admin", "Access to Dynamic Client Registration endpoint.", Collections.emptyList(),false),
+    SCIM("scim", "SCIM", "Access to System for Cross-domain Identity Management endpoint.", Collections.emptyList(),false),
+    CONSENT_ADMIN("consent_admin", "Consent_admin", "Access to End-User consents", Collections.emptyList(),false);
 
     private final String key;
     private final String label;
     private final String description;
     private final List<String> claims;
+    private final boolean discovery;
 
     //See https://tools.ietf.org/html/rfc6749#section-3.3
     public final static String SCOPE_DELIMITER = " ";
 
-    Scope(String key, String label, String description, List<String> claims) {
+    Scope(String key, String label, String description, List<String> claims, boolean discovery) {
         this.key = key;
         this.label = label;
         this.description = description;
         this.claims = claims;
+        this.discovery = discovery;
     }
 
     public String getKey() {
@@ -84,4 +86,8 @@ public enum Scope {
             return false;
         }
     }
+
+    public boolean isDiscovery() {
+        return discovery;
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeService.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeService.java
index 8e059e518d..a29325c962 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeService.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeService.java
@@ -18,6 +18,7 @@ package io.gravitee.am.gateway.handler.oauth2.service.scope;
 import io.gravitee.am.model.oauth2.Scope;
 import io.reactivex.Single;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -27,4 +28,6 @@ import java.util.Set;
 public interface ScopeService {
 
     Single<Set<Scope>> getAll();
+
+    List<String> getDiscoveryScope();
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/impl/ScopeServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/impl/ScopeServiceImpl.java
index 4a8173661f..9944183511 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/impl/ScopeServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oauth2/service/scope/impl/ScopeServiceImpl.java
@@ -21,7 +21,10 @@ import io.gravitee.am.model.oauth2.Scope;
 import io.reactivex.Single;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -36,4 +39,14 @@ public class ScopeServiceImpl implements ScopeService {
     public Single<Set<Scope>> getAll() {
         return Single.just(scopeManager.findAll());
     }
+
+    @Override
+    public List<String> getDiscoveryScope() {
+        return scopeManager.findAll()
+                .stream()
+                .filter(scope -> scope.isDiscovery())
+                .map(Scope::getKey)
+                .sorted()
+                .collect(Collectors.toList());
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java
index 59fc6b8034..26e7fa5ad1 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/main/java/io/gravitee/am/gateway/handler/oidc/service/discovery/impl/OpenIDDiscoveryServiceImpl.java
@@ -16,12 +16,11 @@
 package io.gravitee.am.gateway.handler.oidc.service.discovery.impl;
 
 import io.gravitee.am.common.oauth2.CodeChallengeMethod;
-import io.gravitee.am.common.oauth2.GrantType;
-import io.gravitee.am.common.oauth2.ResponseType;
 import io.gravitee.am.common.oidc.ClaimType;
 import io.gravitee.am.common.oidc.ClientAuthenticationMethod;
 import io.gravitee.am.common.oidc.Scope;
 import io.gravitee.am.gateway.handler.common.jwa.utils.JWAlgorithmUtils;
+import io.gravitee.am.gateway.handler.oauth2.service.scope.ScopeService;
 import io.gravitee.am.gateway.handler.oidc.service.discovery.OpenIDDiscoveryService;
 import io.gravitee.am.gateway.handler.oidc.service.discovery.OpenIDProviderMetadata;
 import io.gravitee.am.gateway.handler.oidc.service.utils.SubjectTypeUtils;
@@ -55,6 +54,9 @@ public class OpenIDDiscoveryServiceImpl implements OpenIDDiscoveryService {
     @Autowired
     private Domain domain;
 
+    @Autowired
+    private ScopeService scopeService;
+
     @Override
     public OpenIDProviderMetadata getConfiguration(String basePath) {
         OpenIDProviderMetadata openIDProviderMetadata = new OpenIDProviderMetadata();
@@ -73,7 +75,7 @@ public class OpenIDDiscoveryServiceImpl implements OpenIDDiscoveryService {
         openIDProviderMetadata.setRegistrationEndpoint(getEndpointAbsoluteURL(basePath, REGISTRATION_ENDPOINT));
 
         // supported parameters
-        openIDProviderMetadata.setScopesSupported(Stream.of(Scope.values()).map(Scope::getKey).collect(Collectors.toList()));
+        openIDProviderMetadata.setScopesSupported(scopeService.getDiscoveryScope());
         openIDProviderMetadata.setResponseTypesSupported(ResponseTypeUtils.getSupportedResponseTypes());
         openIDProviderMetadata.setGrantTypesSupported(GrantTypeUtils.getSupportedGrantTypes());
         openIDProviderMetadata.setIdTokenSigningAlgValuesSupported(JWAlgorithmUtils.getSupportedIdTokenSigningAlg());
diff --git a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/ScopeResource.java b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/ScopeResource.java
index 4b9eebb478..a22c5bcd49 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/ScopeResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-handlers/gravitee-am-management-api-handlers-management/src/main/java/io/gravitee/am/management/handlers/management/api/resources/ScopeResource.java
@@ -22,6 +22,7 @@ import io.gravitee.am.service.DomainService;
 import io.gravitee.am.service.ScopeService;
 import io.gravitee.am.service.exception.DomainNotFoundException;
 import io.gravitee.am.service.exception.ScopeNotFoundException;
+import io.gravitee.am.service.model.PatchScope;
 import io.gravitee.am.service.model.UpdateScope;
 import io.gravitee.common.http.MediaType;
 import io.reactivex.Maybe;
@@ -43,6 +44,7 @@ import javax.ws.rs.core.Response;
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
  * @author GraviteeSource Team
  */
 public class ScopeResource extends AbstractResource {
@@ -81,12 +83,35 @@ public class ScopeResource extends AbstractResource {
                         error -> response.resume(error));
     }
 
+    @PATCH
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Patch a scope")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Scope successfully patched", response = Scope.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void patch(
+            @PathParam("domain") String domain,
+            @PathParam("scope") String scope,
+            @ApiParam(name = "scope", required = true) @Valid @NotNull PatchScope patchScope,
+            @Suspended final AsyncResponse response) {
+        final User authenticatedUser = getAuthenticatedUser();
+
+        domainService.findById(domain)
+                .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain)))
+                .flatMapSingle(irrelevant -> scopeService.patch(domain, scope, patchScope, authenticatedUser))
+                .map(scope1 -> Response.ok(scope1).build())
+                .subscribe(
+                        result -> response.resume(result),
+                        error -> response.resume(error));
+    }
+
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     @ApiOperation(value = "Update a scope")
     @ApiResponses({
-            @ApiResponse(code = 201, message = "Scope successfully updated", response = Client.class),
+            @ApiResponse(code = 200, message = "Scope successfully updated", response = Scope.class),
             @ApiResponse(code = 500, message = "Internal server error")})
     public void update(
             @PathParam("domain") String domain,
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/OpenIDScopeUpgrader.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/OpenIDScopeUpgrader.java
index 70339f2d1b..bbcb34fd4e 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/OpenIDScopeUpgrader.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/OpenIDScopeUpgrader.java
@@ -67,9 +67,10 @@ public class OpenIDScopeUpgrader implements Upgrader, Ordered {
     }
 
     private Single<Scope> createSystemScope(String domain, io.gravitee.am.common.oidc.Scope systemScope) {
-        return scopeService.findByDomain(domain)
-                .flatMap(scopes -> {
-                    Optional<Scope> optScope = scopes.stream().filter(scope -> scope.getKey().equalsIgnoreCase(systemScope.getKey())).findFirst();
+        return scopeService.findByDomainAndKey(domain, systemScope.getKey())
+                .map(scope -> Optional.of(scope))
+                .defaultIfEmpty(Optional.empty())
+                .flatMapSingle(optScope -> {
                     if (!optScope.isPresent()) {
                         logger.info("Create a new system scope key[{}] for domain[{}]", systemScope.getKey(), domain);
                         NewSystemScope scope = new NewSystemScope();
@@ -77,8 +78,9 @@ public class OpenIDScopeUpgrader implements Upgrader, Ordered {
                         scope.setClaims(systemScope.getClaims());
                         scope.setName(systemScope.getLabel());
                         scope.setDescription(systemScope.getDescription());
+                        scope.setDiscovery(systemScope.isDiscovery());
                         return scopeService.create(domain, scope);
-                    } else if (!optScope.get().isSystem()){
+                    } else if (shouldUpdateSystemScope(optScope, systemScope)){
                         logger.info("Update a system scope key[{}] for domain[{}]", systemScope.getKey(), domain);
                         final Scope existingScope = optScope.get();
                         UpdateSystemScope scope = new UpdateSystemScope();
@@ -86,11 +88,24 @@ public class OpenIDScopeUpgrader implements Upgrader, Ordered {
                         scope.setDescription(existingScope.getDescription() != null ? existingScope.getDescription() : systemScope.getDescription());
                         scope.setClaims(systemScope.getClaims());
                         scope.setExpiresIn(existingScope.getExpiresIn());
+                        scope.setDiscovery(systemScope.isDiscovery());
                         return scopeService.update(domain, optScope.get().getId(), scope);
                     }
                     return Single.just(optScope.get());
                 });
     }
+
+    /**
+     * Update System scope if it is not currently set as system or if discovery property does not match.
+     * @param optScope
+     * @param systemScope
+     * @return
+     */
+    private boolean shouldUpdateSystemScope(Optional<Scope> optScope, io.gravitee.am.common.oidc.Scope systemScope) {
+        //If not currently set as system or if discovery property does not match
+        return !optScope.get().isSystem() || optScope.get().isDiscovery() != systemScope.isDiscovery();
+    }
+
     @Override
     public int getOrder() {
         return 162;
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/oauth2/Scope.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/oauth2/Scope.java
index 0703e23efe..0c94ab061e 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/oauth2/Scope.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/oauth2/Scope.java
@@ -63,6 +63,8 @@ public class Scope {
      */
     private Integer expiresIn;
 
+    private boolean discovery;
+
     public Scope() {
     }
 
@@ -81,6 +83,7 @@ public class Scope {
         this.system = other.system;
         this.claims = other.claims;
         this.expiresIn = other.expiresIn;
+        this.discovery = other.discovery;
     }
 
     public String getId() {
@@ -163,6 +166,14 @@ public class Scope {
         this.expiresIn = expiresIn;
     }
 
+    public boolean isDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(boolean discovery) {
+        this.discovery = discovery;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java
index 1108000cd9..35e8137908 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoScopeRepository.java
@@ -104,6 +104,7 @@ public class MongoScopeRepository extends AbstractManagementMongoRepository impl
         scope.setExpiresIn(scopeMongo.getExpiresIn());
         scope.setCreatedAt(scopeMongo.getCreatedAt());
         scope.setUpdatedAt(scopeMongo.getUpdatedAt());
+        scope.setDiscovery(scopeMongo.isDiscovery());
 
         return scope;
     }
@@ -124,6 +125,7 @@ public class MongoScopeRepository extends AbstractManagementMongoRepository impl
         scopeMongo.setExpiresIn(scope.getExpiresIn());
         scopeMongo.setCreatedAt(scope.getCreatedAt());
         scopeMongo.setUpdatedAt(scope.getUpdatedAt());
+        scopeMongo.setDiscovery(scope.isDiscovery());
 
         return scopeMongo;
     }
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ScopeMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ScopeMongo.java
index 474f4cef81..1d9879731a 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ScopeMongo.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/ScopeMongo.java
@@ -53,6 +53,8 @@ public class ScopeMongo extends Auditable {
 
     private Integer expiresIn;
 
+    private boolean discovery;
+
     public String getId() {
         return id;
     }
@@ -117,6 +119,14 @@ public class ScopeMongo extends Auditable {
         this.expiresIn = expiresIn;
     }
 
+    public boolean isDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(boolean discovery) {
+        this.discovery = discovery;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/ScopeService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/ScopeService.java
index ca988488e4..e3c7c05939 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/ScopeService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/ScopeService.java
@@ -19,6 +19,7 @@ import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.model.oauth2.Scope;
 import io.gravitee.am.service.model.NewScope;
 import io.gravitee.am.service.model.NewSystemScope;
+import io.gravitee.am.service.model.PatchScope;
 import io.gravitee.am.service.model.UpdateScope;
 import io.gravitee.am.service.model.UpdateSystemScope;
 import io.reactivex.Completable;
@@ -47,6 +48,8 @@ public interface ScopeService {
 
     Maybe<Scope> findByDomainAndKey(String domain, String scopeKey);
 
+    Single<Scope> patch(String domain, String id, PatchScope patchScope, User principal);
+
     Single<Scope> update(String domain, String id, UpdateScope updateScope, User principal);
 
     Single<Scope> update(String domain, String id, UpdateSystemScope updateScope);
@@ -63,6 +66,10 @@ public interface ScopeService {
         return create(domain, scope, null);
     }
 
+    default Single<Scope> patch(String domain, String id, PatchScope patchScope) {
+        return patch(domain, id, patchScope, null);
+    }
+
     default Single<Scope> update(String domain, String id, UpdateScope updateScope) {
         return update(domain, id, updateScope, null);
     }
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 dac284e7c0..86a39d0f1b 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
@@ -430,6 +430,7 @@ public class DomainServiceImpl implements DomainService {
                     scope.setClaims(systemScope.getClaims());
                     scope.setName(systemScope.getLabel());
                     scope.setDescription(systemScope.getDescription());
+                    scope.setDiscovery(systemScope.isDiscovery());
                     return scopeService.create(domain.getId(), scope);
                 })
                 .lastOrError()
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ScopeServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ScopeServiceImpl.java
index 72358c1a7b..ee85a1b1f4 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ScopeServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/ScopeServiceImpl.java
@@ -106,6 +106,7 @@ public class ScopeServiceImpl implements ScopeService {
                     scope.setName(newScope.getName());
                     scope.setDescription(newScope.getDescription());
                     scope.setExpiresIn(newScope.getExpiresIn());
+                    scope.setDiscovery(newScope.isDiscovery());
                     scope.setCreatedAt(new Date());
                     scope.setUpdatedAt(new Date());
 
@@ -147,6 +148,7 @@ public class ScopeServiceImpl implements ScopeService {
                     scope.setName(newScope.getName());
                     scope.setDescription(newScope.getDescription());
                     scope.setExpiresIn(newScope.getExpiresIn());
+                    scope.setDiscovery(newScope.isDiscovery());
                     scope.setCreatedAt(new Date());
                     scope.setUpdatedAt(new Date());
                     return scopeRepository.create(scope);
@@ -165,6 +167,24 @@ public class ScopeServiceImpl implements ScopeService {
                 });
     }
 
+    @Override
+    public Single<Scope> patch(String domain, String id, PatchScope patchScope, User principal) {
+        LOGGER.debug("Patching a scope {} for domain {}", id, domain);
+        return scopeRepository.findById(id)
+                .switchIfEmpty(Maybe.error(new ScopeNotFoundException(id)))
+                .flatMapSingle(oldScope -> {
+                    Scope scopeToUpdate = patchScope.patch(oldScope);
+                    return update(domain, scopeToUpdate, oldScope, principal);
+                })
+                .onErrorResumeNext(ex -> {
+                    if (ex instanceof AbstractManagementException) {
+                        return Single.error(ex);
+                    }
+                    LOGGER.error("An error occurs while trying to patch a scope", ex);
+                    return Single.error(new TechnicalManagementException("An error occurs while trying to patch a scope", ex));
+                });
+    }
+
     @Override
     public Single<Scope> update(String domain, String id, UpdateScope updateScope, User principal) {
         LOGGER.debug("Update a scope {} for domain {}", id, domain);
@@ -175,16 +195,11 @@ public class ScopeServiceImpl implements ScopeService {
                     scopeToUpdate.setName(updateScope.getName());
                     scopeToUpdate.setDescription(updateScope.getDescription());
                     scopeToUpdate.setExpiresIn(updateScope.getExpiresIn());
-                    scopeToUpdate.setUpdatedAt(new Date());
-
-                    return scopeRepository.update(scopeToUpdate)
-                            .flatMap(scope1 -> {
-                                // Reload domain to take care about scope update
-                                Event event = new Event(Type.SCOPE, new Payload(scope1.getId(), scope1.getDomain(), Action.UPDATE));
-                                return domainService.reload(domain, event).flatMap(domain1 -> Single.just(scope1));
-                            })
-                            .doOnSuccess(scope1 -> auditService.report(AuditBuilder.builder(ScopeAuditBuilder.class).principal(principal).type(EventType.SCOPE_UPDATED).oldValue(oldScope).scope(scope1)))
-                            .doOnError(throwable -> auditService.report(AuditBuilder.builder(ScopeAuditBuilder.class).principal(principal).type(EventType.SCOPE_UPDATED).throwable(throwable)));
+                    if(!oldScope.isSystem() && updateScope.getDiscovery()!=null) {
+                        scopeToUpdate.setDiscovery(updateScope.isDiscovery());
+                    }
+
+                    return update(domain, scopeToUpdate, oldScope, principal);
                 })
                 .onErrorResumeNext(ex -> {
                     if (ex instanceof AbstractManagementException) {
@@ -195,6 +210,19 @@ public class ScopeServiceImpl implements ScopeService {
                 });
     }
 
+    private Single<Scope> update(String domain, Scope toUpdate, Scope oldValue, User principal) {
+
+        toUpdate.setUpdatedAt(new Date());
+        return scopeRepository.update(toUpdate)
+                .flatMap(scope1 -> {
+                    // Reload domain to take care about scope update
+                    Event event = new Event(Type.SCOPE, new Payload(scope1.getId(), scope1.getDomain(), Action.UPDATE));
+                    return domainService.reload(domain, event).flatMap(domain1 -> Single.just(scope1));
+                })
+                .doOnSuccess(scope1 -> auditService.report(AuditBuilder.builder(ScopeAuditBuilder.class).principal(principal).type(EventType.SCOPE_UPDATED).oldValue(oldValue).scope(scope1)))
+                .doOnError(throwable -> auditService.report(AuditBuilder.builder(ScopeAuditBuilder.class).principal(principal).type(EventType.SCOPE_UPDATED).throwable(throwable)));
+    }
+
     @Override
     public Single<Scope> update(String domain, String id, UpdateSystemScope updateScope) {
         LOGGER.debug("Update a system scope {} for domain {}", id, domain);
@@ -207,6 +235,7 @@ public class ScopeServiceImpl implements ScopeService {
                     scope.setSystem(true);
                     scope.setClaims(updateScope.getClaims());
                     scope.setExpiresIn(updateScope.getExpiresIn());
+                    scope.setDiscovery(updateScope.isDiscovery());
                     return scopeRepository.update(scope);
                 })
                 .flatMap(scope -> {
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewScope.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewScope.java
index 53b2ff54a6..59327edb8f 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewScope.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewScope.java
@@ -34,6 +34,8 @@ public class NewScope {
 
     private Integer expiresIn;
 
+    private boolean discovery;
+
     public String getKey() {
         return key;
     }
@@ -65,4 +67,12 @@ public class NewScope {
     public void setExpiresIn(Integer expiresIn) {
         this.expiresIn = expiresIn;
     }
+
+    public boolean isDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(boolean discovery) {
+        this.discovery = discovery;
+    }
 }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewSystemScope.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewSystemScope.java
index c7d5f44099..32b34e1939 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewSystemScope.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewSystemScope.java
@@ -37,6 +37,8 @@ public class NewSystemScope {
 
     private Integer expiresIn;
 
+    private boolean discovery;
+
     public String getKey() {
         return key;
     }
@@ -76,4 +78,12 @@ public class NewSystemScope {
     public void setExpiresIn(Integer expiresIn) {
         this.expiresIn = expiresIn;
     }
+
+    public boolean isDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(boolean discovery) {
+        this.discovery = discovery;
+    }
 }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchScope.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchScope.java
new file mode 100644
index 0000000000..91d8ece1d7
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/PatchScope.java
@@ -0,0 +1,79 @@
+/**
+ * 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.oauth2.Scope;
+import io.gravitee.am.service.utils.SetterUtils;
+
+import java.util.Optional;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+public class PatchScope {
+
+    private Optional<String> name;
+
+    private Optional<String> description;
+
+    private Optional<Integer> expiresIn;
+
+    private Optional<Boolean> discovery;
+
+    public Optional<String> getName() {
+        return name;
+    }
+
+    public void setName(Optional<String> name) {
+        this.name = name;
+    }
+
+    public Optional<String> getDescription() {
+        return description;
+    }
+
+    public void setDescription(Optional<String> description) {
+        this.description = description;
+    }
+
+    public Optional<Integer> getExpiresIn() {
+        return expiresIn;
+    }
+
+    public void setExpiresIn(Optional<Integer> expiresIn) {
+        this.expiresIn = expiresIn;
+    }
+
+    public Optional<Boolean> getDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(Optional<Boolean> discovery) {
+        this.discovery = discovery;
+    }
+
+    public Scope patch(Scope toPatch) {
+        Scope patched = new Scope(toPatch);
+        SetterUtils.safeSet(patched::setName,this.getName());
+        SetterUtils.safeSet(patched::setDescription, this.getDescription());
+        SetterUtils.safeSet(patched::setExpiresIn, this.getExpiresIn());
+        if(!toPatch.isSystem()){
+            SetterUtils.safeSet(patched::setDiscovery, this.getDiscovery(), boolean.class);
+        }
+        return patched;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateScope.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateScope.java
index 54e86d7db5..6a2e65ee1e 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateScope.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateScope.java
@@ -32,6 +32,8 @@ public class UpdateScope {
 
     private Integer expiresIn;
 
+    private Boolean discovery;
+
     public String getName() {
         return name;
     }
@@ -55,4 +57,16 @@ public class UpdateScope {
     public void setExpiresIn(Integer expiresIn) {
         this.expiresIn = expiresIn;
     }
+
+    public boolean isDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(boolean discovery) {
+        this.discovery = discovery;
+    }
+
+    public Boolean getDiscovery() {
+        return discovery;
+    }
 }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateSystemScope.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateSystemScope.java
index de08615535..97d975394e 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateSystemScope.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateSystemScope.java
@@ -34,6 +34,8 @@ public class UpdateSystemScope {
 
     private Integer expiresIn;
 
+    private boolean discovery;
+
     public String getName() {
         return name;
     }
@@ -65,4 +67,12 @@ public class UpdateSystemScope {
     public void setExpiresIn(Integer expiresIn) {
         this.expiresIn = expiresIn;
     }
+
+    public boolean isDiscovery() {
+        return discovery;
+    }
+
+    public void setDiscovery(boolean discovery) {
+        this.discovery = discovery;
+    }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.html b/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.html
index 7517a8009e..65d1b684d8 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.html
@@ -63,6 +63,14 @@
           </mat-form-field>
         </div>
       </div>
+
+      <mat-slide-toggle
+        (change)="enableScopeDiscovery($event)"
+        [checked]="isDiscovery()">
+        Display scope into openid discovery endpoint
+      </mat-slide-toggle>
+      <mat-hint style="font-size: 75%;">Enable scope to be displayed into the openid discovery endpoint under <i>:domain/oidc/.well-known/openid-configuration</i></mat-hint>
+
       <div fxLayout="row" fxLayoutAlign="end" style="margin-top: 50px;">
         <a mat-button [routerLink]="['..']" style="margin-right: 20px;">CANCEL</a>
         <button mat-raised-button [disabled]="(!scopeForm.valid || scopeForm.pristine) || formIsInvalid()" type="submit">CREATE</button>
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.scss b/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.scss
index a4dc5f12a7..74f29891e3 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.scss
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.scss
@@ -21,6 +21,7 @@
         padding: 10px;
         background-color: #f1f1f1;
         margin-top: 20px;
+        margin-bottom: 20px;
         h4 {
           margin: 5px 0;
         }
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.ts b/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.ts
index 944fa2c0a6..45ca10a1bf 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/creation/scope-creation.component.ts
@@ -65,4 +65,12 @@ export class ScopeCreationComponent implements OnInit {
   getScopeExpiry() {
     return (this.scope.expiresIn) ? moment.duration(this.scope.expiresIn, 'seconds').humanize() : 'no time set';
   }
+
+  enableScopeDiscovery(event) {
+    this.scope.discovery = event.checked;
+  }
+
+  isDiscovery() {
+    return this.scope.discovery;
+  }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.html b/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.html
index e822f32289..b6f1606d6a 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.html
@@ -65,6 +65,13 @@
           </div>
         </div>
 
+        <mat-slide-toggle
+          (change)="enableScopeDiscovery($event)"
+          [checked]="isDiscovery()">
+          Display scope into openid discovery endpoint
+        </mat-slide-toggle>
+        <mat-hint style="font-size: 75%;">Enable scope to be displayed into the openid discovery endpoint under <i>:domain/oidc/.well-known/openid-configuration</i></mat-hint>
+
         <div fxLayout="row" class="scope-form-actions">
           <button mat-raised-button [disabled]="(!scopeForm.valid || scopeForm.pristine || formIsInvalid()) && !formChanged" type="submit">SAVE</button>
         </div>
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.scss b/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.scss
index 7dc54239fa..a21385ffe5 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.scss
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.scss
@@ -21,6 +21,7 @@
         padding: 10px;
         background-color: #f1f1f1;
         margin-top: 20px;
+        margin-bottom: 20px;
         h4 {
           margin: 5px 0;
         }
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.ts b/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.ts
index 2ed46dd591..e12b2b5d69 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/scope/scope.component.ts
@@ -83,6 +83,15 @@ export class ScopeComponent implements OnInit {
     this.formChanged = true;
   }
 
+  enableScopeDiscovery(event) {
+    this.scope.discovery = event.checked;
+    this.formChanged = true;
+  }
+
+  isDiscovery() {
+    return this.scope.discovery;
+  }
+
   delete(event) {
     event.preventDefault();
     this.dialogService
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.html b/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.html
index 338c478ed1..bfc1989a04 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.html
@@ -38,6 +38,17 @@
           {{row.name}}
         </ng-template>
       </ngx-datatable-column>
+      <ngx-datatable-column name="Discovery" [flexGrow]="1">
+        <ng-template let-row="row" ngx-datatable-cell-template>
+          <span>
+            <mat-slide-toggle
+              (change)="enableScopeDiscovery(row.id, $event)"
+              [checked]="row.discovery"
+              [disabled]="row.system">
+            </mat-slide-toggle>
+          </span>
+        </ng-template>
+      </ngx-datatable-column>
       <ngx-datatable-column name="User consent expiry period" [flexGrow]="1">
         <ng-template let-row="row" ngx-datatable-cell-template>
           <span *ngIf="!row.expiresIn" style="color: grey; font-style: italic;">No time set</span>
diff --git a/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.ts b/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.ts
index ca727370fc..4cc701738e 100644
--- a/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/scopes/scopes.component.ts
@@ -43,7 +43,7 @@ export class DomainSettingsScopesComponent implements OnInit {
   }
 
   get isEmpty() {
-    return !this.scopes || this.scopes.length == 0;
+    return !this.scopes || this.scopes.length === 0;
   }
 
   delete(id, event) {
@@ -64,4 +64,10 @@ export class DomainSettingsScopesComponent implements OnInit {
     return expiresIn ? moment.duration(expiresIn, 'seconds').humanize() : 'no time set';
   }
 
+  enableScopeDiscovery(id, event) {
+    this.scopeService.patchDiscovery(this.domainId, id, event.checked).subscribe(response => {
+      this.snackbarService.open('Scope updated');
+      this.loadScopes();
+    });
+  }
 }
diff --git a/gravitee-am-ui/src/app/services/scope.service.ts b/gravitee-am-ui/src/app/services/scope.service.ts
index 3a6d72accf..6e948f7a2b 100644
--- a/gravitee-am-ui/src/app/services/scope.service.ts
+++ b/gravitee-am-ui/src/app/services/scope.service.ts
@@ -40,7 +40,14 @@ export class ScopeService {
     return this.http.put<any>(this.scopes + domainId + "/scopes/" + id, {
       'name' : scope.name,
       'description' : scope.description,
-      'expiresIn' : scope.expiresIn
+      'expiresIn' : scope.expiresIn,
+      'discovery' : scope.discovery
+    });
+  }
+
+  patchDiscovery(domainId, id, discovery): Observable<any> {
+    return this.http.patch<any>(this.scopes + domainId + "/scopes/" + id, {
+      'discovery' : discovery
     });
   }
 
diff --git a/postman/collections/graviteeio-am-openid-discovery-collection.json b/postman/collections/graviteeio-am-openid-discovery-collection.json
index ceb5a14a4d..0389a0e016 100644
--- a/postman/collections/graviteeio-am-openid-discovery-collection.json
+++ b/postman/collections/graviteeio-am-openid-discovery-collection.json
@@ -286,6 +286,7 @@
 									"    pm.expect(body).to.have.property(\"require_request_uri_registration\");",
 									"    ",
 									"    pm.expect(body.code_challenge_methods_supported).to.eql([ 'plain','S256' ]);",
+									"    pm.expect(body.scopes_supported).to.eql([ 'address','email','offline_access','openid','phone','profile']);",
 									"});"
 								],
 								"type": "text/javascript"
diff --git a/postman/collections/graviteeio-am-scope-management-collection.json b/postman/collections/graviteeio-am-scope-management-collection.json
new file mode 100644
index 0000000000..b9a68893d4
--- /dev/null
+++ b/postman/collections/graviteeio-am-scope-management-collection.json
@@ -0,0 +1,1223 @@
+{
+	"info": {
+		"_postman_id": "84ad4c35-c898-4975-b32e-9fcda726b1d6",
+		"name": "Gravitee.io - AM - Scope Management",
+		"description": "Test openid connect discovery specifications: https://openid.net/specs/openid-connect-discovery-1_0.html",
+		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+	},
+	"item": [
+		{
+			"name": "Prepare",
+			"item": [
+				{
+					"name": "Generate admin token",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "571e9b59-b9e7-452c-9469-9786ded290a6",
+								"exec": [
+									"",
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"var token = JSON.parse(responseBody);",
+									"pm.environment.set('token', token.access_token);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Authorization",
+								"value": "Basic YWRtaW46YWRtaW5hZG1pbg=="
+							}
+						],
+						"body": {
+							"mode": "urlencoded",
+							"urlencoded": [
+								{
+									"key": "grant_type",
+									"value": "password",
+									"type": "text"
+								},
+								{
+									"key": "username",
+									"value": "admin",
+									"type": "text"
+								},
+								{
+									"key": "password",
+									"value": "adminadmin",
+									"type": "text"
+								}
+							]
+						},
+						"url": {
+							"raw": "{{management_url}}/admin/token",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"admin",
+								"token"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create scope domain",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "b286f0fa-cfcc-45b9-863a-1dbc87fdf835",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"var jsonData = pm.response.json();",
+									"pm.environment.set('domain', jsonData.id);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n\t\"name\": \"scope\", \n\t\"description\": \"test scope management\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								""
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Start domain",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "bfe78ac1-144a-4bbd-abf9-55e160e723bf",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"enabled\": true\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}"
+							]
+						}
+					},
+					"response": []
+				}
+			]
+		},
+		{
+			"name": "Invalid Case",
+			"item": [
+				{
+					"name": "Create - Missing property",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 400\", function () {",
+									"    pm.response.to.have.status(400);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.message).to.eql('[description: may not be null]');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"key\": \"admin\",\n  \"name\": \"admin\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create already existing scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 400\", function () {",
+									"    pm.response.to.have.status(400);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.message).to.contains('A scope [email] already exists for domain');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"key\": \"email\",\n  \"name\": \"should not be created\",\n  \"description\": \"should not be created\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Update non existing scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 404\", function () {",
+									"    pm.response.to.have.status(404);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.message).to.contains('can not be found');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PUT",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"name\": \"shouldNotBeUpdated\",\n  \"description\": \"shouldNotBeUpdated\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/nonExistingScope",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"nonExistingScope"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Update - Missing name property",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 400\", function () {",
+									"    pm.response.to.have.status(400);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.message).to.eql('[name: may not be null]');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PUT",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"discovery\": true,\n  \"description\": \"wallet information updated\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/nonExistingScope",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"nonExistingScope"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Delete non existing scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 404\", function () {",
+									"    pm.response.to.have.status(404);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.message).to.contains('can not be found');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"name\": \"shouldNotBeUpdated\",\n  \"description\": \"shouldNotBeUpdated\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/nonExistingScope",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"nonExistingScope"
+							]
+						}
+					},
+					"response": []
+				}
+			]
+		},
+		{
+			"name": "Nominal Case",
+			"item": [
+				{
+					"name": "Get scopes",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body[0].key).to.eql('address');",
+									"    pm.expect(body[0].system).to.eql(true);",
+									"    ",
+									"    pm.environment.set('addressScopeId', body[0].id);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.key).to.eql('admin');",
+									"    pm.expect(body.discovery).to.eql(false);",
+									"    ",
+									"    pm.environment.set('adminScopeId', body.id);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"key\": \"admin\",\n  \"name\": \"admin\",\n  \"description\": \"admin non public scope\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Get scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.discovery).to.eql(false);",
+									"    pm.expect(body.name).to.eql('admin');",
+									"    pm.expect(body.description).to.eql('admin non public scope');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{adminScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{adminScopeId}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Patch scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.discovery).to.eql(true);",
+									"    pm.expect(body.name).to.eql('admin name patched');",
+									"    pm.expect(body.description).to.eql('admin information patched');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"discovery\": true,\n  \"name\": \"admin name patched\",\n  \"description\": \"admin information patched\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{adminScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{adminScopeId}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Update scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.discovery).to.eql(true);//should not be overrided when missing in the body",
+									"    pm.expect(body.name).to.eql('admin name updated');",
+									"    pm.expect(body.description).to.eql('admin information updated');",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PUT",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"name\": \"admin name updated\",\n  \"description\": \"admin information updated\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{adminScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{adminScopeId}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Delete scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 204\", function () {",
+									"    pm.response.to.have.status(204);",
+									"});",
+									""
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "DELETE",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"discovery\": true,\n  \"description\": \"wallet information updated\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{adminScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{adminScopeId}}"
+							]
+						}
+					},
+					"response": []
+				}
+			]
+		},
+		{
+			"name": "System scope",
+			"item": [
+				{
+					"name": "System scope discovery",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.discovery).to.eql(true);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"discovery\": false,\n  \"description\": \"Discovery should stay to true\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{addressScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{addressScopeId}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "System scope discovery",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.discovery).to.eql(true);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PUT",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"discovery\": false,\n  \"name\": \"Address\",\n  \"description\": \"Discovery should stay to true\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{addressScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{addressScopeId}}"
+							]
+						}
+					},
+					"response": []
+				}
+			]
+		},
+		{
+			"name": "Scope Discovery",
+			"item": [
+				{
+					"name": "Create admin scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.key).to.eql('admin');",
+									"    pm.expect(body.discovery).to.eql(false);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"key\": \"admin\",\n  \"name\": \"admin\",\n  \"description\": \"admin non public scope\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create body scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.key).to.eql('body');",
+									"    pm.expect(body.discovery).to.eql(true);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"key\": \"body\",\n  \"name\": \"body\",\n  \"description\": \"body information\",\n  \"discovery\": true\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Create wallet scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 201\", function () {",
+									"    pm.response.to.have.status(201);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.key).to.eql('wallet');",
+									"    pm.expect(body.discovery).to.eql(false);",
+									"    ",
+									"    pm.environment.set('walletScopeId', body.id);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "POST",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"key\": \"wallet\",\n  \"name\": \"wallet\",\n  \"description\": \"wallet information\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "Patch wallet scope",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "a9d1a08f-42e1-48a8-a551-aeda99bc04e1",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"",
+									"    pm.expect(body.discovery).to.eql(true);",
+									"    pm.expect(body.name).to.eql('wallet');",
+									"    pm.expect(body.description).to.eql('wallet information updated');",
+									"});",
+									"",
+									"// wait for sync process",
+									"setTimeout(function(){}, 5000);"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "PATCH",
+						"header": [
+							{
+								"key": "Content-Type",
+								"value": "application/json"
+							},
+							{
+								"key": "Authorization",
+								"value": "Bearer {{token}}"
+							}
+						],
+						"body": {
+							"mode": "raw",
+							"raw": "{\n  \"discovery\": true,\n  \"description\": \"wallet information updated\"\n}"
+						},
+						"url": {
+							"raw": "{{management_url}}/management/domains/{{domain}}/scopes/{{walletScopeId}}",
+							"host": [
+								"{{management_url}}"
+							],
+							"path": [
+								"management",
+								"domains",
+								"{{domain}}",
+								"scopes",
+								"{{walletScopeId}}"
+							]
+						}
+					},
+					"response": []
+				},
+				{
+					"name": "well-known/openid-configuration",
+					"event": [
+						{
+							"listen": "test",
+							"script": {
+								"id": "5332b6f5-7419-4792-a30f-cce968d3d67e",
+								"exec": [
+									"pm.test(\"Status code is 200\", function () {",
+									"    pm.response.to.have.status(200);",
+									"});",
+									"",
+									"pm.test(\"Check scope discovery properties\", function () {",
+									"    pm.response.to.be.header('Content-Type', 'application/json');",
+									"    var body = pm.response.json();",
+									"    pm.expect(body.scopes_supported).to.eql([ 'address','body','email','offline_access','openid','phone','profile','wallet']);",
+									"});"
+								],
+								"type": "text/javascript"
+							}
+						}
+					],
+					"request": {
+						"method": "GET",
+						"header": [],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{gateway_url}}/{{domain}}/oidc/.well-known/openid-configuration",
+							"host": [
+								"{{gateway_url}}"
+							],
+							"path": [
+								"{{domain}}",
+								"oidc",
+								".well-known",
+								"openid-configuration"
+							]
+						}
+					},
+					"response": []
+				}
+			]
+		},
+		{
+			"name": "Delete domain",
+			"event": [
+				{
+					"listen": "test",
+					"script": {
+						"id": "abfa8366-3ee2-45b0-b658-0040b79d565c",
+						"exec": [
+							"pm.test(\"Status code is 204\", function () {",
+							"    pm.response.to.have.status(204);",
+							"});"
+						],
+						"type": "text/javascript"
+					}
+				}
+			],
+			"request": {
+				"method": "DELETE",
+				"header": [
+					{
+						"key": "Content-Type",
+						"value": "application/json"
+					},
+					{
+						"key": "Authorization",
+						"value": "Bearer {{token}}"
+					}
+				],
+				"body": {
+					"mode": "raw",
+					"raw": ""
+				},
+				"url": {
+					"raw": "{{management_url}}/management/domains/{{domain}}",
+					"host": [
+						"{{management_url}}"
+					],
+					"path": [
+						"management",
+						"domains",
+						"{{domain}}"
+					]
+				}
+			},
+			"response": []
+		}
+	]
+}
\ No newline at end of file


Test patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeServiceTest.java
new file mode 100644
index 0000000000..a97f14b4ad
--- /dev/null
+++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-oidc/src/test/java/io/gravitee/am/gateway/handler/oauth2/service/scope/ScopeServiceTest.java
@@ -0,0 +1,74 @@
+/**
+ * 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.gateway.handler.oauth2.service.scope;
+
+import io.gravitee.am.gateway.handler.oauth2.service.scope.impl.ScopeServiceImpl;
+import io.gravitee.am.model.oauth2.Scope;
+import io.reactivex.observers.TestObserver;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ScopeServiceTest {
+
+    @InjectMocks
+    private ScopeService scopeService = new ScopeServiceImpl();
+
+    @Mock
+    private ScopeManager scopeManager;
+
+    @Before
+    public void setUp() {
+        Scope one = new Scope();
+        one.setKey("one");
+
+        Scope two = new Scope();
+        two.setKey("two");
+        two.setDiscovery(true);
+
+        when(scopeManager.findAll()).thenReturn(new HashSet<>(Arrays.asList(one,two)));
+    }
+
+    @Test
+    public void getAll() {
+        TestObserver testObserver = scopeService.getAll().test();
+
+        verify(scopeManager, times(1)).findAll();
+        testObserver.assertNoErrors();
+        testObserver.assertComplete();
+        testObserver.assertValue(list -> ((Set)list).size()==2);
+    }
+
+    @Test
+    public void getDiscoveryScope() {
+        assertEquals(1,scopeService.getDiscoveryScope().size());
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/OpenIDScopeUpgraderTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/OpenIDScopeUpgraderTest.java
new file mode 100644
index 0000000000..3542721328
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/OpenIDScopeUpgraderTest.java
@@ -0,0 +1,106 @@
+/**
+ * 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.management.service;
+
+import io.gravitee.am.management.service.impl.upgrades.OpenIDScopeUpgrader;
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.oauth2.Scope;
+import io.gravitee.am.service.DomainService;
+import io.gravitee.am.service.ScopeService;
+import io.gravitee.am.service.model.NewSystemScope;
+import io.gravitee.am.service.model.UpdateSystemScope;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Alexandre FARIA (contact at alexandrefaria.net)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class OpenIDScopeUpgraderTest {
+
+    @InjectMocks
+    private OpenIDScopeUpgrader openIDScopeUpgrader = new OpenIDScopeUpgrader();
+
+    @Mock
+    private DomainService domainService;
+
+    @Mock
+    private ScopeService scopeService;
+
+    @Mock
+    private Domain domain;
+
+    private static final String DOMAIN_ID = "domainId";
+
+    @Before
+    public void setUp() {
+        when(domain.getId()).thenReturn(DOMAIN_ID);
+        when(domainService.findAll()).thenReturn(Single.just(new HashSet<>(Arrays.asList(domain))));
+    }
+
+    @Test
+    public void shouldCreateSystemScope() {
+        when(scopeService.findByDomainAndKey(eq(DOMAIN_ID), anyString())).thenReturn(Maybe.empty());
+        when(scopeService.create(anyString(),any(NewSystemScope.class))).thenReturn(Single.just(new Scope()));
+
+        assertTrue(openIDScopeUpgrader.upgrade());
+        verify(scopeService, times(io.gravitee.am.common.oidc.Scope.values().length)).create(anyString(), any(NewSystemScope.class));
+    }
+
+    @Test
+    public void shouldUpdateSystemScope() {
+        Scope openId = new Scope();
+        openId.setId("1");
+        openId.setSystem(false);//expect to be updated because not set as system
+        openId.setKey("openid");
+
+        Scope phone = new Scope();
+        phone.setId("2");
+        phone.setSystem(true);
+        phone.setKey("phone");
+        phone.setDiscovery(false);//expect to be updated because not same discovery value
+
+        Scope email = new Scope();
+        email.setId("3");
+        email.setSystem(true);//expect not to be updated
+        email.setKey("email");
+        email.setDiscovery(true);
+
+        when(scopeService.findByDomainAndKey(eq(DOMAIN_ID), anyString())).thenReturn(Maybe.empty());
+        when(scopeService.findByDomainAndKey(DOMAIN_ID, "openid")).thenReturn(Maybe.just(openId));
+        when(scopeService.findByDomainAndKey(DOMAIN_ID, "phone")).thenReturn(Maybe.just(phone));
+        when(scopeService.findByDomainAndKey(DOMAIN_ID, "email")).thenReturn(Maybe.just(email));
+        when(scopeService.create(anyString(),any(NewSystemScope.class))).thenReturn(Single.just(new Scope()));
+        when(scopeService.update(anyString(), anyString(), any(UpdateSystemScope.class))).thenReturn(Single.just(new Scope()));
+
+        assertTrue(openIDScopeUpgrader.upgrade());
+        verify(scopeService, times(io.gravitee.am.common.oidc.Scope.values().length-3)).create(anyString(), any(NewSystemScope.class));
+        verify(scopeService, times(2)).update(anyString(), anyString(), any(UpdateSystemScope.class));
+    }
+}
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/ScopeServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/ScopeServiceTest.java
index 331f5d0a34..f398309aef 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/ScopeServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/ScopeServiceTest.java
@@ -26,24 +26,30 @@ import io.gravitee.am.service.exception.*;
 import io.gravitee.am.service.impl.ScopeServiceImpl;
 import io.gravitee.am.service.model.NewScope;
 import io.gravitee.am.service.model.PatchClient;
+import io.gravitee.am.service.model.PatchScope;
 import io.gravitee.am.service.model.UpdateRole;
+import io.gravitee.am.service.model.UpdateScope;
+import io.gravitee.am.service.model.UpdateSystemScope;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import io.reactivex.observers.TestObserver;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
+import java.util.Optional;
 import java.util.Set;
 
+import static org.junit.Assert.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.*;
 
@@ -228,6 +234,236 @@ public class ScopeServiceTest {
         verify(scopeRepository, never()).create(any(Scope.class));
     }
 
+    @Test
+    public void shouldPatch_systemScope_discoveryNotReplaced() {
+        PatchScope patch = new PatchScope();
+        patch.setDiscovery(Optional.of(true));
+        patch.setName(Optional.of("name"));
+
+        final String scopeId = "toPatchId";
+
+        Scope toPatch = new Scope();
+        toPatch.setId(scopeId);
+        toPatch.setSystem(true);
+        toPatch.setDiscovery(false);
+        toPatch.setName("oldName");
+        toPatch.setDescription("oldDescription");
+
+        ArgumentCaptor<Scope> argument = ArgumentCaptor.forClass(Scope.class);
+
+        when(scopeRepository.findById(scopeId)).thenReturn(Maybe.just(toPatch));
+        when(scopeRepository.update(argument.capture())).thenReturn(Single.just(new Scope()));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver testObserver = scopeService.patch(DOMAIN,scopeId, patch).test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(scopeRepository, times(1)).update(any(Scope.class));
+        assertNotNull(argument.getValue());
+        assertEquals("name",argument.getValue().getName());
+        assertEquals("oldDescription",argument.getValue().getDescription());
+        assertFalse(argument.getValue().isDiscovery());
+    }
+
+    @Test
+    public void shouldPatch_nonSystemScope_discoveryNotReplaced() {
+        PatchScope patch = new PatchScope();
+        patch.setDiscovery(Optional.of(true));
+        patch.setName(Optional.of("name"));
+
+        final String scopeId = "toPatchId";
+
+        Scope toPatch = new Scope();
+        toPatch.setId(scopeId);
+        toPatch.setSystem(false);
+        toPatch.setDiscovery(false);
+        toPatch.setName("oldName");
+        toPatch.setDescription("oldDescription");
+
+        ArgumentCaptor<Scope> argument = ArgumentCaptor.forClass(Scope.class);
+
+        when(scopeRepository.findById(scopeId)).thenReturn(Maybe.just(toPatch));
+        when(scopeRepository.update(argument.capture())).thenReturn(Single.just(new Scope()));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver testObserver = scopeService.patch(DOMAIN,scopeId, patch).test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(scopeRepository, times(1)).update(any(Scope.class));
+        assertNotNull(argument.getValue());
+        assertEquals("name",argument.getValue().getName());
+        assertEquals("oldDescription",argument.getValue().getDescription());
+        assertTrue(argument.getValue().isDiscovery());
+    }
+
+    @Test
+    public void shouldNotPatch() {
+        Scope toPatch = new Scope();
+        toPatch.setId("toPatchId");
+
+        when(scopeRepository.findById("toPatchId")).thenReturn(Maybe.error(TechnicalException::new));
+
+        TestObserver testObserver = scopeService.patch(DOMAIN,"toPatchId", new PatchScope()).test();
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+    }
+
+    @Test
+    public void shouldUpdate_systemScope_discoveryNotReplaced() {
+        UpdateScope updateScope = new UpdateScope();
+        updateScope.setDiscovery(true);
+        updateScope.setName("name");
+
+        final String scopeId = "toUpdateId";
+
+        Scope toUpdate = new Scope();
+        toUpdate.setId(scopeId);
+        toUpdate.setSystem(true);
+        toUpdate.setDiscovery(false);
+        toUpdate.setName("oldName");
+        toUpdate.setDescription("oldDescription");
+
+        ArgumentCaptor<Scope> argument = ArgumentCaptor.forClass(Scope.class);
+
+        when(scopeRepository.findById(scopeId)).thenReturn(Maybe.just(toUpdate));
+        when(scopeRepository.update(argument.capture())).thenReturn(Single.just(new Scope()));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver testObserver = scopeService.update(DOMAIN,scopeId, updateScope).test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(scopeRepository, times(1)).update(any(Scope.class));
+        assertNotNull(argument.getValue());
+        assertEquals("name",argument.getValue().getName());
+        assertNull(argument.getValue().getDescription());
+        assertFalse(argument.getValue().isDiscovery());
+    }
+
+    @Test
+    public void shouldUpdate_nonSystemScope_discoveryNotReplaced() {
+        UpdateScope updateScope = new UpdateScope();
+        updateScope.setName("name");
+
+        final String scopeId = "toUpdateId";
+
+        Scope toUpdate = new Scope();
+        toUpdate.setId(scopeId);
+        toUpdate.setSystem(false);
+        toUpdate.setDiscovery(true);
+        toUpdate.setName("oldName");
+        toUpdate.setDescription("oldDescription");
+
+        ArgumentCaptor<Scope> argument = ArgumentCaptor.forClass(Scope.class);
+
+        when(scopeRepository.findById(scopeId)).thenReturn(Maybe.just(toUpdate));
+        when(scopeRepository.update(argument.capture())).thenReturn(Single.just(new Scope()));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver testObserver = scopeService.update(DOMAIN,scopeId, updateScope).test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(scopeRepository, times(1)).update(any(Scope.class));
+        assertNotNull(argument.getValue());
+        assertEquals("name",argument.getValue().getName());
+        assertNull(argument.getValue().getDescription());
+        assertTrue(argument.getValue().isDiscovery());
+    }
+
+    @Test
+    public void shouldUpdate_nonSystemScope_discoveryReplaced() {
+        UpdateScope updateScope = new UpdateScope();
+        updateScope.setDiscovery(true);
+        updateScope.setName("name");
+
+        final String scopeId = "toUpdateId";
+
+        Scope toUpdate = new Scope();
+        toUpdate.setId(scopeId);
+        toUpdate.setSystem(false);
+        toUpdate.setDiscovery(false);
+        toUpdate.setName("oldName");
+        toUpdate.setDescription("oldDescription");
+
+        ArgumentCaptor<Scope> argument = ArgumentCaptor.forClass(Scope.class);
+
+        when(scopeRepository.findById(scopeId)).thenReturn(Maybe.just(toUpdate));
+        when(scopeRepository.update(argument.capture())).thenReturn(Single.just(new Scope()));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver testObserver = scopeService.update(DOMAIN,scopeId, updateScope).test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(scopeRepository, times(1)).update(any(Scope.class));
+        assertNotNull(argument.getValue());
+        assertEquals("name",argument.getValue().getName());
+        assertNull(argument.getValue().getDescription());
+        assertTrue(argument.getValue().isDiscovery());
+    }
+
+    @Test
+    public void shouldNotUpdate() {
+        Scope toUpdate = new Scope();
+        toUpdate.setId("toUpdateId");
+
+        when(scopeRepository.findById("toUpdateId")).thenReturn(Maybe.error(TechnicalException::new));
+
+        TestObserver testObserver = scopeService.update(DOMAIN,"toUpdateId", new UpdateScope()).test();
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+    }
+
+    @Test
+    public void shouldUpdateSystemScope() {
+        UpdateSystemScope updateScope = new UpdateSystemScope();
+        updateScope.setDiscovery(true);
+        updateScope.setName("name");
+
+        final String scopeId = "toUpdateId";
+
+        Scope toUpdate = new Scope();
+        toUpdate.setId(scopeId);
+        toUpdate.setSystem(true);
+        toUpdate.setDiscovery(false);
+        toUpdate.setName("oldName");
+        toUpdate.setDescription("oldDescription");
+
+        ArgumentCaptor<Scope> argument = ArgumentCaptor.forClass(Scope.class);
+
+        when(scopeRepository.findById(scopeId)).thenReturn(Maybe.just(toUpdate));
+        when(scopeRepository.update(argument.capture())).thenReturn(Single.just(new Scope()));
+        when(domainService.reload(any(), any())).thenReturn(Single.just(new Domain()));
+
+        TestObserver testObserver = scopeService.update(DOMAIN,scopeId, updateScope).test();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        verify(scopeRepository, times(1)).update(any(Scope.class));
+        assertNotNull(argument.getValue());
+        assertEquals("name",argument.getValue().getName());
+        assertNull(argument.getValue().getDescription());
+        assertTrue(argument.getValue().isDiscovery());
+    }
+
+    @Test
+    public void shouldNotUpdateSystemScope() {
+        Scope toUpdate = new Scope();
+        toUpdate.setId("toUpdateId");
+
+        when(scopeRepository.findById("toUpdateId")).thenReturn(Maybe.error(TechnicalException::new));
+
+        TestObserver testObserver = scopeService.update(DOMAIN,"toUpdateId", new UpdateSystemScope()).test();
+
+        testObserver.assertError(TechnicalManagementException.class);
+        testObserver.assertNotComplete();
+    }
+
     @Test
     public void shouldDelete_notExistingScope() {
         when(scopeRepository.findById("my-scope")).thenReturn(Maybe.empty());