Repository: gravitee-io/graviteeio-access-management
Pull request #962 (2020-11-13 16:42:28)

fix(auth): attach user to the proper organization when using social idp
Closes gravitee-io/issues#4528


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000..558804d71f
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,30 @@
+version: 2.1
+
+parameters:
+  gio_action:
+    type: enum
+    enum: [release, pr_build]
+    default: pr_build
+  dry_run:
+    type: boolean
+    default: true
+    description: "Run in dry run mode?"
+
+orbs:
+  gravitee: gravitee-io/gravitee@1.0
+
+workflows:
+  version: 2.1
+  pull_requests:
+    when:
+      equal: [ pr_build, << pipeline.parameters.gio_action >> ]
+    jobs:
+      - gravitee/pr-build:
+          context: cicd-orchestrator
+  release:
+    when:
+      equal: [ release, << pipeline.parameters.gio_action >> ]
+    jobs:
+      - gravitee/release:
+          context: cicd-orchestrator
+          dry_run: << pipeline.parameters.dry_run >>
diff --git a/docs/upgrades/3.x/3.5.0/mongodb/1-add-environment-permissions.js b/docs/upgrades/3.x/3.5.0/mongodb/1-add-environment-permissions.js
new file mode 100644
index 0000000000..1f6f80a119
--- /dev/null
+++ b/docs/upgrades/3.x/3.5.0/mongodb/1-add-environment-permissions.js
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+function createUUID() {
+    var dt = new Date().getTime();
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+        var r = (dt + Math.random() * 16) % 16 | 0;
+        dt = Math.floor(dt / 16);
+        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
+    });
+}
+
+// Set 'default' hrid on the default organization.
+organization = db.getCollection('organizations').findOne({"_id": "DEFAULT"});
+
+if (!organization.hrids || organization.hrids.length === 0) {
+    organization.hrids = ['default'];
+    db.getCollection('organizations').update({"_id": "DEFAULT"}, organization);
+}
+
+// Set 'default' hrid on the default environment.
+environment = db.getCollection('environments').findOne({"_id": "DEFAULT"});
+
+if (!environment.hrids || environment.hrids.length === 0) {
+    environment.hrids = ['default'];
+    db.getCollection('environments').update({"_id": "DEFAULT"}, environment);
+}
+
+// Update ORGANIZATION_USER role adding the ENVIRONMENT['LIST'] permission.
+organizationUserRole = db.getCollection("roles")
+    .findOne({"name": "ORGANIZATION_USER", "referenceType": "ORGANIZATION", "referenceId": "DEFAULT"});
+
+if (organizationUserRole != null) {
+    if (!organizationUserRole.permissionAcls['ENVIRONMENT']) {
+        organizationUserRole.permissionAcls['ENVIRONMENT'] = ['LIST']
+    } else if (!organizationUserRole.permissionAcls['ENVIRONMENT'].includes('LIST')) {
+        organizationUserRole.permissionAcls['ENVIRONMENT'].push('LIST');
+    }
+    db.getCollection("roles").update({'_id': organizationUserRole._id}, organizationUserRole);
+}
+
+// Create ENVIRONMENT_USER role if not already exists.
+environmentUserRole = db.getCollection("roles")
+    .findOne({"name": "ENVIRONMENT_USER", "referenceType": "ORGANIZATION", "referenceId": "DEFAULT"});
+
+if (environmentUserRole == null) {
+    let environmentUserRoleId = createUUID();
+    db.getCollection("roles").insert({
+        _id: environmentUserRoleId,
+        assignableType: "ENVIRONMENT",
+        createdAt: ISODate(),
+        defaultRole: true,
+        name: "ENVIRONMENT_USER",
+        permissionAcls: {
+            DOMAIN: ["LIST"],
+            ENVIRONMENT: ["READ"],
+        },
+        referenceId: "DEFAULT",
+        referenceType: "ORGANIZATION",
+        system: false,
+        updatedAt: ISODate(),
+    });
+
+    // Assign ENVIRONMENT_USER role to all existing users.
+    db.getCollection('users')
+        .find({"referenceType": "ORGANIZATION", "referenceId": "DEFAULT"})
+        .forEach(function (user) {
+            db.getCollection("memberships").insert({
+                "_id": createUUID(),
+                "createdAt": ISODate(),
+                "memberId": user._id,
+                "memberType": "USER",
+                "referenceId": "DEFAULT",
+                "referenceType": "ENVIRONMENT",
+                "role": environmentUserRoleId,
+                "updatedAt": ISODate()
+            });
+        });
+}
+
+// Create ENVIRONMENT_OWNER role if not already exists.
+environmentOwnerRole = db.getCollection("roles")
+    .findOne({"name": "ENVIRONMENT_OWNER", "referenceType": "ORGANIZATION", "referenceId": "DEFAULT"});
+
+if (environmentOwnerRole == null) {
+    db.getCollection("roles").insert({
+        _id: createUUID(),
+        assignableType: "ENVIRONMENT",
+        createdAt: ISODate(),
+        defaultRole: true,
+        name: "ENVIRONMENT_OWNER",
+        permissionAcls: {
+            DOMAIN_MEMBER: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_SCIM: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_IDENTITY_PROVIDER: [
+                "UPDATE",
+                "LIST",
+                "READ",
+                "DELETE",
+                "CREATE",
+            ],
+            APPLICATION_SETTINGS: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            ENVIRONMENT: ["READ"],
+            APPLICATION_EMAIL_TEMPLATE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_REPORTER: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_FORM: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_EXTENSION_GRANT: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_FORM: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_SCOPE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_SETTINGS: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_ROLE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_IDENTITY_PROVIDER: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_ANALYTICS: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_UMA_SCOPE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_EXTENSION_POINT: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_CERTIFICATE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_GROUP: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_USER: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_OPENID: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_AUDIT: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_FACTOR: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_MEMBER: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_OPENID: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_CERTIFICATE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_UMA: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_FACTOR: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            DOMAIN_EMAIL_TEMPLATE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+            APPLICATION_RESOURCE: ["UPDATE", "LIST", "READ", "DELETE", "CREATE"],
+        },
+        referenceId: "DEFAULT",
+        referenceType: "ORGANIZATION",
+        system: false,
+        updatedAt: ISODate(),
+    });
+}
+
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/CockpitAuthenticationFilter.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/CockpitAuthenticationFilter.java
index 9a3d9e7b4f..d8fbd7455c 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/CockpitAuthenticationFilter.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/CockpitAuthenticationFilter.java
@@ -17,7 +17,6 @@ package io.gravitee.am.management.handlers.management.api.authentication.filter;
 
 import io.gravitee.am.common.jwt.Claims;
 import io.gravitee.am.common.jwt.JWT;
-import io.gravitee.am.common.oidc.CustomClaims;
 import io.gravitee.am.common.oidc.StandardClaims;
 import io.gravitee.am.identityprovider.api.DefaultUser;
 import io.gravitee.am.identityprovider.api.User;
@@ -35,9 +34,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.GenericFilterBean;
 
@@ -57,7 +54,6 @@ import java.security.cert.Certificate;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.stream.Collectors;
 
 public class CockpitAuthenticationFilter extends GenericFilterBean {
 
@@ -97,9 +93,6 @@ public class CockpitAuthenticationFilter extends GenericFilterBean {
     @Autowired
     private AuthenticationService authenticationService;
 
-    @Autowired
-    private RoleService roleService;
-
     private JWTParser jwtParser;
 
     @Override
@@ -163,44 +156,15 @@ public class CockpitAuthenticationFilter extends GenericFilterBean {
         return authentication;
     }
 
-    private List<String> convertToRoleIds(List<String> jwtRoles, String organizationId) {
-
-        List<String> roleIds = new ArrayList<>();
-        if (jwtRoles != null && !jwtRoles.isEmpty()) {
-
-            for (String roleName : jwtRoles) {
-                Role role = null;
-                SystemRole systemRole = SystemRole.fromName(roleName);
-
-                // First try to map to a system role.
-                if (systemRole != null) {
-                    role = roleService.findSystemRole(systemRole, ReferenceType.ORGANIZATION).blockingGet();
-                } else {
-                    // Then try to find a default role.
-                    DefaultRole defaultRole = DefaultRole.fromName(roleName);
-
-                    if (defaultRole != null) {
-                        role = roleService.findDefaultRole(organizationId, defaultRole, ReferenceType.ORGANIZATION).blockingGet();
-                    }
-                }
-
-                if (role != null) {
-                    roleIds.add(role.getId());
-                }
-            }
-        }
-        return roleIds;
-    }
-
     private Key getPublicKey() throws Exception {
 
-        final KeyStore trustStore = loadTrustStore();
+        final KeyStore trustStore = loadKeyStore();
         final Certificate cert = trustStore.getCertificate(keyAlias);
 
         return cert.getPublicKey();
     }
 
-    private KeyStore loadTrustStore() throws Exception {
+    private KeyStore loadKeyStore() throws Exception {
 
         final KeyStore keystore = KeyStore.getInstance(keyStoreType);
 
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java
index ae61977ec3..067a77428f 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/filter/SocialAuthenticationFilter.java
@@ -15,6 +15,7 @@
  */
 package io.gravitee.am.management.handlers.management.api.authentication.filter;
 
+import io.gravitee.am.common.jwt.Claims;
 import io.gravitee.am.identityprovider.api.AuthenticationProvider;
 import io.gravitee.am.identityprovider.api.SimpleAuthenticationContext;
 import io.gravitee.am.identityprovider.api.User;
@@ -23,6 +24,8 @@ import io.gravitee.am.management.handlers.management.api.authentication.manager.
 import io.gravitee.am.management.handlers.management.api.authentication.provider.generator.JWTGenerator;
 import io.gravitee.am.management.handlers.management.api.authentication.provider.security.EndUserAuthentication;
 import io.gravitee.am.management.handlers.management.api.authentication.service.AuthenticationService;
+import io.gravitee.am.model.IdentityProvider;
+import io.gravitee.am.model.ReferenceType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -86,9 +89,10 @@ public class SocialAuthenticationFilter extends AbstractAuthenticationProcessing
     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
         // get oauth2 provider
         String providerId = request.getParameter(PROVIDER_PARAMETER);
-        AuthenticationProvider authenticationProvider = identityProviderManager.get(providerId);
+        final IdentityProvider identityProvider = identityProviderManager.getIdentityProvider(providerId);
+        final AuthenticationProvider authenticationProvider = identityProviderManager.get(providerId);
 
-        if (authenticationProvider == null) {
+        if (authenticationProvider == null || identityProvider == null || identityProvider.getReferenceType() != ReferenceType.ORGANIZATION) {
             throw new ProviderNotFoundException("Social Provider " + providerId + " not found");
         }
 
@@ -106,6 +110,7 @@ public class SocialAuthenticationFilter extends AbstractAuthenticationProcessing
             // set user identity provider source
             Map<String, String> details = new LinkedHashMap<>();
             details.put(SOURCE, providerId);
+            details.put(Claims.organization, identityProvider.getReferenceId());
             UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, provAuthentication.getCredentials(), AuthorityUtils.NO_AUTHORITIES);
             usernamePasswordAuthenticationToken.setDetails(details);
             return usernamePasswordAuthenticationToken;
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/model/InstallationEntity.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/model/InstallationEntity.java
new file mode 100644
index 0000000000..126f11685b
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/model/InstallationEntity.java
@@ -0,0 +1,77 @@
+/**
+ * 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.handlers.management.api.model;
+
+import io.gravitee.am.model.Installation;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class InstallationEntity {
+
+    private String id;
+    private Map<String, String> additionalInformation = new HashMap<>();
+    private Date createdAt;
+    private Date updatedAt;
+
+    public InstallationEntity() {
+    }
+
+    public InstallationEntity(Installation other) {
+
+        this.id = other.getId();
+        this.additionalInformation = other.getAdditionalInformation();
+        this.createdAt = other.getCreatedAt();
+        this.updatedAt = other.getUpdatedAt();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Map<String, String> getAdditionalInformation() {
+        return additionalInformation;
+    }
+
+    public void setAdditionalInformation(Map<String, String> additionalInformation) {
+        this.additionalInformation = additionalInformation;
+    }
+
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/CurrentUserResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/CurrentUserResource.java
index c1f3b0d0f9..5ab2f2b4c6 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/CurrentUserResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/CurrentUserResource.java
@@ -20,21 +20,23 @@ import io.gravitee.am.common.oidc.CustomClaims;
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
 import io.gravitee.am.model.Organization;
+import io.gravitee.am.model.Platform;
 import io.gravitee.am.model.ReferenceType;
 import io.gravitee.am.model.permissions.Permission;
 import io.gravitee.common.http.MediaType;
+import io.reactivex.Single;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.tuple.Pair;
 
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.container.AsyncResponse;
 import javax.ws.rs.container.Suspended;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 /**
  * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
@@ -56,8 +58,19 @@ public class CurrentUserResource extends AbstractResource {
         // Get the organization the current user is logged on.
         String organizationId = (String) authenticatedUser.getAdditionalInformation().getOrDefault(Claims.organization, Organization.DEFAULT);
 
-        permissionService.findAllPermissions(authenticatedUser, ReferenceType.ORGANIZATION, organizationId)
-                .map(Permission::flatten)
+        final Single<List<String>> organizationPermissions = permissionService.findAllPermissions(authenticatedUser, ReferenceType.ORGANIZATION, organizationId)
+                .map(Permission::flatten);
+
+        final Single<List<String>> platformPermissions = permissionService.findAllPermissions(authenticatedUser, ReferenceType.PLATFORM, Platform.DEFAULT)
+                .map(Permission::flatten);
+
+        Single.zip(platformPermissions, organizationPermissions, Pair::of)
+                .map(p -> {
+                    Set<String> allPermissions = new HashSet<>();
+                    allPermissions.addAll(p.getLeft());
+                    allPermissions.addAll(p.getRight());
+                    return allPermissions;
+                })
                 .map(permissions -> {
                     // prepare profile information with role permissions
                     Map<String, Object> profile = new HashMap<>(authenticatedUser.getAdditionalInformation());
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/InstallationResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/InstallationResource.java
new file mode 100644
index 0000000000..088030ad79
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/InstallationResource.java
@@ -0,0 +1,71 @@
+/**
+ * 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.handlers.management.api.resources.platform;
+
+import io.gravitee.am.management.handlers.management.api.model.ErrorEntity;
+import io.gravitee.am.management.handlers.management.api.model.InstallationEntity;
+import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
+import io.gravitee.am.model.Acl;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.Platform;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.service.InstallationService;
+import io.gravitee.common.http.MediaType;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class InstallationResource extends AbstractResource {
+
+    protected static final String DEFAULT_COCKPIT_URL = "https://cockpit.gravitee.io";
+
+    @Autowired
+    private Environment environment;
+
+    @Autowired
+    private InstallationService installationService;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Get installation information",
+            notes = "User must have the INSTALLATION[READ] permission on the platform")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Installation successfully fetched", response = InstallationEntity.class),
+            @ApiResponse(code = 404, message = "No installation has been found", response = ErrorEntity.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void get(
+            @Suspended final AsyncResponse response) {
+
+        checkPermission(ReferenceType.PLATFORM, Platform.DEFAULT, Permission.INSTALLATION, Acl.READ)
+                .andThen(installationService.get()
+                        .map(InstallationEntity::new))
+                .doOnSuccess(installationEntity -> installationEntity.getAdditionalInformation()
+                        .put(Installation.COCKPIT_URL, environment.getProperty("cockpit.url", DEFAULT_COCKPIT_URL)))
+                .subscribe(response::resume, response::resume);
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java
index 0be589ba9d..782c8ecbc5 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/platform/PlatformResource.java
@@ -61,4 +61,10 @@ public class PlatformResource {
     public SystemRoleResource getSystemRoleResource() {
         return resourceContext.getResource(SystemRoleResource.class);
     }
+
+
+    @Path("installation")
+    public InstallationResource getInstallationResource() {
+        return resourceContext.getResource(InstallationResource.class);
+    }
 }
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/EnvironmentCommandHandler.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/EnvironmentCommandHandler.java
index 07974a2bf1..2a1871b080 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/EnvironmentCommandHandler.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/EnvironmentCommandHandler.java
@@ -63,8 +63,8 @@ public class EnvironmentCommandHandler implements CommandHandler<EnvironmentComm
 
         return environmentService.createOrUpdate(environmentPayload.getOrganizationId(), environmentPayload.getId(), newEnvironment, null)
                 .map(organization -> new EnvironmentReply(command.getId(), CommandStatus.SUCCEEDED))
-                .doOnSuccess(reply -> logger.info("Environment [{}] created with id [{}].", environmentPayload.getName(), environmentPayload.getId()))
-                .doOnError(error -> logger.error("Error occurred when creating environment [{}] with id [{}].", environmentPayload.getName(), environmentPayload.getId(), error))
+                .doOnSuccess(reply -> logger.info("Environment [{}] handled with id [{}].", environmentPayload.getName(), environmentPayload.getId()))
+                .doOnError(error -> logger.error("Error occurred when handling environment [{}] with id [{}].", environmentPayload.getName(), environmentPayload.getId(), error))
                 .onErrorReturn(throwable -> new EnvironmentReply(command.getId(), CommandStatus.ERROR));
     }
 }
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/HelloCommandProducer.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/HelloCommandProducer.java
new file mode 100644
index 0000000000..be5bd04e5a
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/HelloCommandProducer.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.management.service.impl.commands;
+
+import io.gravitee.am.model.Environment;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.Organization;
+import io.gravitee.am.service.InstallationService;
+import io.gravitee.cockpit.api.command.Command;
+import io.gravitee.cockpit.api.command.CommandProducer;
+import io.gravitee.cockpit.api.command.CommandStatus;
+import io.gravitee.cockpit.api.command.hello.HelloCommand;
+import io.gravitee.cockpit.api.command.hello.HelloReply;
+import io.gravitee.node.api.Node;
+import io.reactivex.Single;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component("cockpitHelloCommandProducer")
+public class HelloCommandProducer implements CommandProducer<HelloCommand, HelloReply> {
+
+    private final Node node;
+    private final InstallationService installationService;
+
+    public HelloCommandProducer(Node node, InstallationService installationService) {
+        this.node = node;
+        this.installationService = installationService;
+    }
+
+    @Override
+    public Command.Type produceType() {
+        return Command.Type.HELLO_COMMAND;
+    }
+
+    @Override
+    public Single<HelloCommand> prepare(HelloCommand command) {
+
+        return installationService.getOrInitialize().map(installation -> {
+            command.getPayload().getNode().setInstallationId(installation.getId());
+            command.getPayload().getNode().setHostname(node.hostname());
+            command.getPayload().getAdditionalInformation().putAll(installation.getAdditionalInformation());
+            command.getPayload().setDefaultOrganizationId(Organization.DEFAULT);
+            command.getPayload().setDefaultEnvironmentId(Environment.DEFAULT);
+
+            return command;
+        });
+    }
+
+    @Override
+    public Single<HelloReply> handleReply(HelloReply reply) {
+
+        if (reply.getCommandStatus() == CommandStatus.SUCCEEDED) {
+            return installationService.get().
+                    map(Installation::getAdditionalInformation)
+                    .doOnSuccess(infos -> infos.put(Installation.COCKPIT_INSTALLATION_ID, reply.getInstallationId()))
+                    .doOnSuccess(infos -> infos.put(Installation.COCKPIT_INSTALLATION_STATUS, reply.getInstallationStatus()))
+                    .flatMap(installationService::setAdditionalInformation)
+                    .map(installation -> reply);
+        }
+
+        return Single.just(reply);
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.java
new file mode 100644
index 0000000000..2f5d3f4ae9
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.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.management.service.impl.commands;
+
+import io.gravitee.am.common.oidc.StandardClaims;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.service.InstallationService;
+import io.gravitee.am.service.model.NewUser;
+import io.gravitee.cockpit.api.command.Command;
+import io.gravitee.cockpit.api.command.CommandHandler;
+import io.gravitee.cockpit.api.command.CommandStatus;
+import io.gravitee.cockpit.api.command.hello.HelloCommand;
+import io.gravitee.cockpit.api.command.installation.InstallationCommand;
+import io.gravitee.cockpit.api.command.installation.InstallationPayload;
+import io.gravitee.cockpit.api.command.installation.InstallationReply;
+import io.gravitee.cockpit.api.command.user.UserCommand;
+import io.gravitee.cockpit.api.command.user.UserPayload;
+import io.gravitee.cockpit.api.command.user.UserReply;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class InstallationCommandHandler implements CommandHandler<InstallationCommand, InstallationReply> {
+
+    private final Logger logger = LoggerFactory.getLogger(InstallationCommandHandler.class);
+
+    private final InstallationService installationService;
+
+    public InstallationCommandHandler(InstallationService installationService) {
+
+        this.installationService = installationService;
+    }
+
+    @Override
+    public Command.Type handleType() {
+        return Command.Type.INSTALLATION_COMMAND;
+    }
+
+    @Override
+    public Single<InstallationReply> handle(InstallationCommand command) {
+
+        InstallationPayload installationPayload = command.getPayload();
+
+        return installationService.getOrInitialize().map(Installation::getAdditionalInformation)
+                .doOnSuccess(additionalInfos -> additionalInfos.put(Installation.COCKPIT_INSTALLATION_STATUS, installationPayload.getStatus()))
+                .flatMap(installationService::setAdditionalInformation)
+                .map(installation -> new InstallationReply(command.getId(), CommandStatus.SUCCEEDED))
+                .doOnSuccess(installation -> logger.info("Installation status is [{}].", installationPayload.getStatus()))
+                .doOnError(error -> logger.info("Error occurred when updating installation status.", error))
+                .onErrorReturn(throwable -> new InstallationReply(command.getId(), CommandStatus.ERROR));
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/OrganizationCommandHandler.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/OrganizationCommandHandler.java
index 785ddf608f..19ddfdd3fd 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/OrganizationCommandHandler.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/OrganizationCommandHandler.java
@@ -60,8 +60,8 @@ public class OrganizationCommandHandler implements CommandHandler<OrganizationCo
 
         return organizationService.createOrUpdate(organizationPayload.getId(), newOrganization, null)
                 .map(organization -> new OrganizationReply(command.getId(), CommandStatus.SUCCEEDED))
-                .doOnSuccess(reply -> logger.info("Organization [{}] created with id [{}].", organizationPayload.getName(), organizationPayload.getId()))
-                .doOnError(error -> logger.error("Error occurred when creating organization [{}] with id [{}].", organizationPayload.getName(), organizationPayload.getId(), error))
+                .doOnSuccess(reply -> logger.info("Organization [{}] handled with id [{}].", organizationPayload.getName(), organizationPayload.getId()))
+                .doOnError(error -> logger.error("Error occurred when handling organization [{}] with id [{}].", organizationPayload.getName(), organizationPayload.getId(), error))
                 .onErrorReturn(throwable -> new OrganizationReply(command.getId(), CommandStatus.ERROR));
     }
 }
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgrader.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgrader.java
index d27145abb5..8a679c6278 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgrader.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgrader.java
@@ -19,6 +19,7 @@ import io.gravitee.am.management.service.impl.upgrades.helpers.MembershipHelper;
 import io.gravitee.am.model.*;
 import io.gravitee.am.model.common.Page;
 import io.gravitee.am.model.permissions.DefaultRole;
+import io.gravitee.am.model.permissions.SystemRole;
 import io.gravitee.am.service.*;
 import io.gravitee.am.service.model.NewIdentityProvider;
 import io.gravitee.am.service.model.PatchOrganization;
@@ -100,7 +101,7 @@ public class DefaultOrganizationUpgrader implements Upgrader, Ordered {
                     do {
                         userPage = userService.findAll(ReferenceType.ORGANIZATION, Organization.DEFAULT, page, PAGE_SIZE).blockingGet();
                         // membership helper create membership only if
-                        userPage.getData().forEach(user -> membershipHelper.setRole(user, organizationOwnerRole));
+                        userPage.getData().forEach(user -> membershipHelper.setOrganizationRole(user, organizationOwnerRole));
                         page++;
                     } while (userPage.getData().size() == PAGE_SIZE);
 
@@ -145,6 +146,9 @@ public class DefaultOrganizationUpgrader implements Upgrader, Ordered {
                     }
                 }
             }
+
+            // The primary owner of the default organization must be considered as platform admin.
+            membershipHelper.setPlatformAdminRole();
         } catch (Exception e) {
             logger.error("An error occurred trying to initialize default organization", e);
             return false;
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/InstallationUpgrader.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/InstallationUpgrader.java
new file mode 100644
index 0000000000..92b35787af
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/InstallationUpgrader.java
@@ -0,0 +1,60 @@
+/**
+ * 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.impl.upgrades;
+
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.oauth2.Scope;
+import io.gravitee.am.service.*;
+import io.gravitee.am.service.model.NewScope;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class InstallationUpgrader implements Upgrader, Ordered {
+
+    private final Logger logger = LoggerFactory.getLogger(InstallationUpgrader.class);
+
+    private final InstallationService installationService;
+
+    public InstallationUpgrader(InstallationService installationService) {
+        this.installationService = installationService;
+    }
+
+    @Override
+    public boolean upgrade() {
+        final Installation installation = installationService.getOrInitialize().blockingGet();
+        logger.info("Current installation id is [{}]", installation.getId());
+        return true;
+    }
+
+    @Override
+    public int getOrder() {
+        return 0;
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelper.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelper.java
index b838d39f17..d2f877c96d 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelper.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelper.java
@@ -17,7 +17,6 @@ package io.gravitee.am.management.service.impl.upgrades.helpers;
 
 import io.gravitee.am.model.*;
 import io.gravitee.am.model.membership.MemberType;
-import io.gravitee.am.model.permissions.DefaultRole;
 import io.gravitee.am.model.permissions.SystemRole;
 import io.gravitee.am.repository.management.api.search.MembershipCriteria;
 import io.gravitee.am.service.MembershipService;
@@ -50,16 +49,35 @@ public class MembershipHelper {
 
         Role adminRole = roleService.findSystemRole(SystemRole.ORGANIZATION_PRIMARY_OWNER, ReferenceType.ORGANIZATION).blockingGet();
 
-        setRole(user, adminRole);
+        setOrganizationRole(user, adminRole);
     }
 
     /**
-     * Helper method to set specified role to the specified user.
+     * Helper method to set PLATFORM_ADMIN role to the primary owner of the default organization.
+     * In a standalone installation, someone needs to be defined as platform admin to be able to access some features outside of the organization scope.
+     * It seems that the primary owner of the default organization is the best choice we can make.
+     * Note: if the user already has a role, nothing is done.
+     */
+    public void setPlatformAdminRole() {
+
+        Role organizationPrimaryOwnerRole = roleService.findSystemRole(SystemRole.ORGANIZATION_PRIMARY_OWNER, ReferenceType.ORGANIZATION).blockingGet();
+
+        MembershipCriteria criteria = new MembershipCriteria();
+        criteria.setRoleId(organizationPrimaryOwnerRole.getId());
+        Membership member = membershipService.findByCriteria(ReferenceType.ORGANIZATION, Organization.DEFAULT, criteria).filter(membership -> membership.getMemberType() == MemberType.USER).blockingFirst(null);
+
+        if (member != null) {
+            membershipService.setPlatformAdmin(member.getMemberId()).blockingGet();
+        }
+    }
+
+    /**
+     * Helper method to set specified organization role to the specified user.
      * Note: if the user already has a role, nothing is done.
      *
      * @param user the user to define the role on.
      */
-    public void setRole(User user, Role role) {
+    public void setOrganizationRole(User user, Role role) {
 
         MembershipCriteria criteria = new MembershipCriteria();
         criteria.setUserId(user.getId());
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml
index ea53856a7d..eae3ff7248 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-container/pom.xml
@@ -39,13 +39,6 @@
     </build>
 
     <dependencies>
-        <!-- GraviteeSource dependencies -->
-        <dependency>
-            <groupId>com.graviteesource.license</groupId>
-            <artifactId>gravitee-license-node</artifactId>
-            <version>1.1.0</version>
-        </dependency>
-
         <!-- Gravitee.io -->
         <dependency>
             <groupId>io.gravitee.node</groupId>
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/src/main/resources/config/gravitee.yml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/src/main/resources/config/gravitee.yml
index fbc3b6ed1a..95b734286f 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/src/main/resources/config/gravitee.yml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/src/main/resources/config/gravitee.yml
@@ -258,6 +258,12 @@ gateway:
 #newsletter:
 #  enabled: true
 
+# Cockpit
+# Specify the URL to cockpit instance. Default is the Gravitee SAAS instance
+#cockpit:
+#  url: https://cockpit.gravitee.io
+
+
 # Referenced properties
 ds:
   mongodb:
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Installation.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Installation.java
new file mode 100644
index 0000000000..a3c2681015
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Installation.java
@@ -0,0 +1,96 @@
+/**
+ * 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.model;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class Installation {
+
+    public static final String COCKPIT_INSTALLATION_ID = "COCKPIT_INSTALLATION_ID";
+    public static final String COCKPIT_URL = "COCKPIT_URL";
+    public static final String COCKPIT_INSTALLATION_STATUS = "COCKPIT_INSTALLATION_STATUS";
+
+    /**
+     * Auto generated id.
+     * This id is generated at the first startup time.
+     */
+    private String id;
+
+    /**
+     * Additional information about this installation.
+     */
+    private Map<String, String> additionalInformation = new HashMap<>();
+
+    /**
+     * Creation date.
+     */
+    private Date createdAt;
+
+    /**
+     * Last update date.
+     */
+    private Date updatedAt;
+
+    public Installation() {
+    }
+
+    public Installation(Installation other) {
+
+        this.id = other.id;
+        this.additionalInformation = other.additionalInformation;
+        this.createdAt = other.createdAt;
+        this.updatedAt = other.updatedAt;
+    }
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Map<String, String> getAdditionalInformation() {
+        return additionalInformation;
+    }
+
+    public void setAdditionalInformation(Map<String, String> additionalInformation) {
+        this.additionalInformation = additionalInformation;
+    }
+
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
index e66ee4ac4e..e93490d3c6 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
@@ -75,7 +75,9 @@ public enum Permission {
     APPLICATION_CERTIFICATE(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION),
     APPLICATION_MEMBER(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION),
     APPLICATION_FACTOR(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION),
-    APPLICATION_RESOURCE(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION);
+    APPLICATION_RESOURCE(ReferenceType.ORGANIZATION, ReferenceType.ENVIRONMENT, ReferenceType.DOMAIN, ReferenceType.APPLICATION),
+
+    INSTALLATION(ReferenceType.PLATFORM);
 
     List<ReferenceType> relevantTypes;
 
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/plugin/CertificatePluginHandler.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/plugin/CertificatePluginHandler.java
index c01fad7c74..381dcb2305 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/plugin/CertificatePluginHandler.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-certificate/src/main/java/io/gravitee/am/plugins/certificate/plugin/CertificatePluginHandler.java
@@ -21,7 +21,6 @@ import io.gravitee.am.plugins.certificate.core.CertificatePluginManager;
 import io.gravitee.plugin.core.api.Plugin;
 import io.gravitee.plugin.core.api.PluginClassLoaderFactory;
 import io.gravitee.plugin.core.api.PluginHandler;
-import io.gravitee.plugin.core.api.PluginType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +34,8 @@ public class CertificatePluginHandler implements PluginHandler {
 
     private final Logger LOGGER = LoggerFactory.getLogger(CertificatePluginHandler.class);
 
+    private final static String PLUGIN_TYPE = "certificate";
+
     @Autowired
     private PluginClassLoaderFactory pluginClassLoaderFactory;
 
@@ -43,7 +44,7 @@ public class CertificatePluginHandler implements PluginHandler {
 
     @Override
     public boolean canHandle(Plugin plugin) {
-        return plugin.type() == PluginType.CERTIFICATE;
+        return PLUGIN_TYPE.equalsIgnoreCase(plugin.type());
     }
 
     @Override
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/src/main/java/io/gravitee/am/plugins/extensiongrant/plugin/ExtensionGrantPluginHandler.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/src/main/java/io/gravitee/am/plugins/extensiongrant/plugin/ExtensionGrantPluginHandler.java
index 94264310b4..f2e08527a7 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/src/main/java/io/gravitee/am/plugins/extensiongrant/plugin/ExtensionGrantPluginHandler.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-extensiongrant/src/main/java/io/gravitee/am/plugins/extensiongrant/plugin/ExtensionGrantPluginHandler.java
@@ -21,7 +21,6 @@ import io.gravitee.am.plugins.extensiongrant.core.ExtensionGrantPluginManager;
 import io.gravitee.plugin.core.api.Plugin;
 import io.gravitee.plugin.core.api.PluginClassLoaderFactory;
 import io.gravitee.plugin.core.api.PluginHandler;
-import io.gravitee.plugin.core.api.PluginType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +34,8 @@ public class ExtensionGrantPluginHandler implements PluginHandler {
 
     private final Logger LOGGER = LoggerFactory.getLogger(ExtensionGrantPluginHandler.class);
 
+    private final static String PLUGIN_TYPE = "extension_grant";
+
     @Autowired
     private PluginClassLoaderFactory pluginClassLoaderFactory;
 
@@ -43,7 +44,7 @@ public class ExtensionGrantPluginHandler implements PluginHandler {
 
     @Override
     public boolean canHandle(Plugin plugin) {
-        return plugin.type() == PluginType.EXTENSION_GRANT;
+        return PLUGIN_TYPE.equalsIgnoreCase(plugin.type());
     }
 
     @Override
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-factor/src/main/java/io/gravitee/am/plugins/factor/plugin/FactorPluginHandler.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-factor/src/main/java/io/gravitee/am/plugins/factor/plugin/FactorPluginHandler.java
index 0f127a576e..89df10c1c3 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-factor/src/main/java/io/gravitee/am/plugins/factor/plugin/FactorPluginHandler.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-factor/src/main/java/io/gravitee/am/plugins/factor/plugin/FactorPluginHandler.java
@@ -21,7 +21,6 @@ import io.gravitee.am.plugins.factor.core.FactorPluginManager;
 import io.gravitee.plugin.core.api.Plugin;
 import io.gravitee.plugin.core.api.PluginClassLoaderFactory;
 import io.gravitee.plugin.core.api.PluginHandler;
-import io.gravitee.plugin.core.api.PluginType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +34,8 @@ public class FactorPluginHandler implements PluginHandler {
 
     private final Logger LOGGER = LoggerFactory.getLogger(FactorPluginHandler.class);
 
+    private final static String PLUGIN_TYPE = "factor";
+
     @Autowired
     private PluginClassLoaderFactory pluginClassLoaderFactory;
 
@@ -43,7 +44,7 @@ public class FactorPluginHandler implements PluginHandler {
 
     @Override
     public boolean canHandle(Plugin plugin) {
-        return plugin.type() == PluginType.FACTOR;
+        return PLUGIN_TYPE.equalsIgnoreCase(plugin.type());
     }
 
     @Override
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/src/main/java/io/gravitee/am/plugins/idp/plugin/IdentityProviderPluginHandler.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/src/main/java/io/gravitee/am/plugins/idp/plugin/IdentityProviderPluginHandler.java
index 65b34c445f..05b963fc28 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/src/main/java/io/gravitee/am/plugins/idp/plugin/IdentityProviderPluginHandler.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-identityprovider/src/main/java/io/gravitee/am/plugins/idp/plugin/IdentityProviderPluginHandler.java
@@ -21,7 +21,6 @@ import io.gravitee.am.plugins.idp.core.IdentityProviderPluginManager;
 import io.gravitee.plugin.core.api.Plugin;
 import io.gravitee.plugin.core.api.PluginClassLoaderFactory;
 import io.gravitee.plugin.core.api.PluginHandler;
-import io.gravitee.plugin.core.api.PluginType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +34,8 @@ public class IdentityProviderPluginHandler implements PluginHandler {
 
     private final Logger LOGGER = LoggerFactory.getLogger(IdentityProviderPluginHandler.class);
 
+    private final static String PLUGIN_TYPE = "identity_provider";
+
     @Autowired
     private PluginClassLoaderFactory pluginClassLoaderFactory;
 
@@ -43,7 +44,7 @@ public class IdentityProviderPluginHandler implements PluginHandler {
 
     @Override
     public boolean canHandle(Plugin plugin) {
-        return plugin.type() == PluginType.IDENTITY_PROVIDER;
+        return PLUGIN_TYPE.equalsIgnoreCase(plugin.type());
     }
 
     @Override
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/src/main/java/io/gravitee/am/plugins/protocol/plugin/ProtocolPluginHandler.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/src/main/java/io/gravitee/am/plugins/protocol/plugin/ProtocolPluginHandler.java
index fea6e7a826..9d3eac903f 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/src/main/java/io/gravitee/am/plugins/protocol/plugin/ProtocolPluginHandler.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-protocol/src/main/java/io/gravitee/am/plugins/protocol/plugin/ProtocolPluginHandler.java
@@ -43,7 +43,7 @@ public class ProtocolPluginHandler implements PluginHandler {
 
     @Override
     public boolean canHandle(Plugin plugin) {
-        return PROTOCOL.equalsIgnoreCase(plugin.type().name());
+        return PROTOCOL.equalsIgnoreCase(plugin.type());
     }
 
     @Override
diff --git a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/src/main/java/io/gravitee/am/plugins/reporter/plugin/ReporterPluginHandler.java b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/src/main/java/io/gravitee/am/plugins/reporter/plugin/ReporterPluginHandler.java
index d6e14e7b64..eb42966ab6 100644
--- a/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/src/main/java/io/gravitee/am/plugins/reporter/plugin/ReporterPluginHandler.java
+++ b/gravitee-am-plugins-handlers/gravitee-am-plugins-handlers-reporter/src/main/java/io/gravitee/am/plugins/reporter/plugin/ReporterPluginHandler.java
@@ -21,7 +21,6 @@ import io.gravitee.am.reporter.api.Reporter;
 import io.gravitee.plugin.core.api.Plugin;
 import io.gravitee.plugin.core.api.PluginClassLoaderFactory;
 import io.gravitee.plugin.core.api.PluginHandler;
-import io.gravitee.plugin.core.api.PluginType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +34,8 @@ public class ReporterPluginHandler implements PluginHandler {
 
     private final Logger LOGGER = LoggerFactory.getLogger(ReporterPluginHandler.class);
 
+    private final static String PLUGIN_TYPE = "reporter";
+
     @Autowired
     private PluginClassLoaderFactory pluginClassLoaderFactory;
 
@@ -43,7 +44,7 @@ public class ReporterPluginHandler implements PluginHandler {
 
     @Override
     public boolean canHandle(Plugin plugin) {
-        return plugin.type() == PluginType.REPORTER;
+        return PLUGIN_TYPE.equalsIgnoreCase(plugin.type());
     }
 
     @Override
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/InstallationRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/InstallationRepository.java
new file mode 100644
index 0000000000..a2e0903db1
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/InstallationRepository.java
@@ -0,0 +1,31 @@
+/**
+ * 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.repository.management.api;
+
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.Organization;
+import io.gravitee.am.repository.common.CrudRepository;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface InstallationRepository extends CrudRepository<Installation, String> {
+
+    Maybe<Installation> find();
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoInstallationRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoInstallationRepository.java
new file mode 100644
index 0000000000..af28f54733
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoInstallationRepository.java
@@ -0,0 +1,110 @@
+/**
+ * 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.repository.mongodb.management;
+
+import com.mongodb.reactivestreams.client.MongoCollection;
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.Organization;
+import io.gravitee.am.repository.management.api.InstallationRepository;
+import io.gravitee.am.repository.management.api.InstallationRepository;
+import io.gravitee.am.repository.mongodb.management.internal.model.InstallationMongo;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.bson.Document;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+import static com.mongodb.client.model.Filters.eq;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class MongoInstallationRepository extends AbstractManagementMongoRepository implements InstallationRepository {
+
+    public static final String FIELD_ID = "_id";
+    private MongoCollection<InstallationMongo> collection;
+
+    @PostConstruct
+    public void init() {
+        collection = mongoOperations.getCollection("installation", InstallationMongo.class);
+        super.createIndex(collection, new Document(FIELD_ID, 1));
+    }
+
+    @Override
+    public Maybe<Installation> findById(String id) {
+
+        return Observable.fromPublisher(collection.find(eq(FIELD_ID, id)).first())
+                .firstElement()
+                .map(this::convert);
+    }
+
+    @Override
+    public Maybe<Installation> find() {
+
+        return Observable.fromPublisher(collection.find().first())
+                .firstElement()
+                .map(this::convert);
+    }
+
+    @Override
+    public Single<Installation> create(Installation installation) {
+
+        installation.setId(installation.getId() == null ? RandomString.generate() : installation.getId());
+
+        return Single.fromPublisher(collection.insertOne(convert(installation)))
+                .flatMap(success -> findById(installation.getId()).toSingle());
+    }
+
+    @Override
+    public Single<Installation> update(Installation installation) {
+
+        return Single.fromPublisher(collection.replaceOne(eq(FIELD_ID, installation.getId()), convert(installation)))
+                .flatMap(updateResult -> findById(installation.getId()).toSingle());
+    }
+
+    @Override
+    public Completable delete(String id) {
+        return Completable.fromPublisher(collection.deleteOne(eq(FIELD_ID, id)));
+    }
+    
+    private Installation convert(InstallationMongo installationMongo) {
+
+        Installation installation = new Installation();
+        installation.setId(installationMongo.getId());
+        installation.setAdditionalInformation(installationMongo.getAdditionalInformation());
+        installation.setCreatedAt(installationMongo.getCreatedAt());
+        installation.setUpdatedAt(installationMongo.getUpdatedAt());
+
+        return installation;
+    }
+
+    private InstallationMongo convert(Installation installation) {
+
+        InstallationMongo installationMongo = new InstallationMongo();
+        installationMongo.setId(installation.getId());
+        installationMongo.setAdditionalInformation(installation.getAdditionalInformation());
+        installationMongo.setCreatedAt(installation.getCreatedAt());
+        installationMongo.setUpdatedAt(installation.getUpdatedAt());
+
+        return installationMongo;
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/InstallationMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/InstallationMongo.java
new file mode 100644
index 0000000000..2cd762fe63
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/InstallationMongo.java
@@ -0,0 +1,68 @@
+/**
+ * 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.repository.mongodb.management.internal.model;
+
+import java.util.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class InstallationMongo {
+
+    private String id;
+
+    private Map<String, String> additionalInformation = new HashMap<>();
+
+    private Date createdAt;
+
+    private Date updatedAt;
+
+    public InstallationMongo() {
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Map<String, String> getAdditionalInformation() {
+        return additionalInformation;
+    }
+
+    public void setAdditionalInformation(Map<String, String> additionalInformation) {
+        this.additionalInformation = additionalInformation;
+    }
+
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/InstallationService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/InstallationService.java
new file mode 100644
index 0000000000..b74c0ebeb6
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/InstallationService.java
@@ -0,0 +1,52 @@
+/**
+ * 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;
+
+import io.gravitee.am.model.Installation;
+import io.reactivex.Single;
+
+import java.util.Map;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface InstallationService {
+
+    /**
+     * Get the current installation.
+     *
+     * @return the current installation or an {@link io.gravitee.am.service.exception.InstallationNotFoundException} exception.
+     */
+    Single<Installation> get();
+
+
+    /**
+     * Get or initialize the installation. A new installation will be created only if none exists.
+     *
+     * @return the created or already existing installation.
+     */
+    Single<Installation> getOrInitialize();
+
+    /**
+     * Set additional information of the current installation.
+     *
+     * @param additionalInformation the list of additional information to set on the existing installation.
+     *
+     * @return the updated installation
+     */
+    Single<Installation> setAdditionalInformation(Map<String, String> additionalInformation);
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/MembershipService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/MembershipService.java
index 875ffc54b0..60e8ecd2a5 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/MembershipService.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/MembershipService.java
@@ -42,6 +42,8 @@ public interface MembershipService {
 
     Single<Membership> addOrUpdate(String organizationId, Membership membership, User principal);
 
+    Single<Membership> setPlatformAdmin(String userId);
+
     Single<Map<String, Map<String, Object>>> getMetadata(List<Membership> memberships);
 
     Completable delete(String membershipId, User principal);
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/InstallationNotFoundException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/InstallationNotFoundException.java
new file mode 100644
index 0000000000..6f84643975
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/InstallationNotFoundException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.exception;
+
+/**
+ * @author Florent CHAMFROY (forent.chamfroy at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class InstallationNotFoundException extends AbstractNotFoundException {
+
+    public InstallationNotFoundException() {
+    }
+
+    @Override
+    public String getMessage() {
+        return "Installation can not be found.";
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java
new file mode 100644
index 0000000000..5b89ac7e5a
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java
@@ -0,0 +1,91 @@
+/**
+ * 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.impl;
+
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.repository.management.api.InstallationRepository;
+import io.gravitee.am.service.*;
+import io.gravitee.am.service.exception.InstallationNotFoundException;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class InstallationServiceImpl implements InstallationService {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(InstallationServiceImpl.class);
+
+    private final InstallationRepository installationRepository;
+
+    public InstallationServiceImpl(@Lazy InstallationRepository installationRepository) {
+        this.installationRepository = installationRepository;
+    }
+
+    @Override
+    public Single<Installation> get() {
+        return this.installationRepository.find()
+                .switchIfEmpty(Single.error(new InstallationNotFoundException()));
+    }
+
+    @Override
+    public Single<Installation> getOrInitialize() {
+        return this.installationRepository.find()
+                .switchIfEmpty(createInternal());
+    }
+
+    @Override
+    public Single<Installation> setAdditionalInformation(Map<String, String> additionalInformation) {
+        return get()
+                .flatMap(installation -> {
+                    Installation toUpdate = new Installation(installation);
+                    toUpdate.setAdditionalInformation(additionalInformation);
+
+                    return updateInternal(toUpdate);
+                });
+    }
+
+    private Single<Installation> createInternal() {
+
+        final Date now = Date.from(Instant.now());
+        final Installation installation = new Installation();
+        installation.setId(RandomString.generate());
+        installation.setCreatedAt(now);
+        installation.setUpdatedAt(now);
+        installation.setAdditionalInformation(new HashMap<>());
+
+        return installationRepository.create(installation);
+    }
+
+    private Single<Installation> updateInternal(Installation toUpdate) {
+
+        final Date now = Date.from(Instant.now());
+        toUpdate.setUpdatedAt(now);
+
+        return installationRepository.update(toUpdate);
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/MembershipServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/MembershipServiceImpl.java
index 74619964be..b6ded9545e 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/MembershipServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/MembershipServiceImpl.java
@@ -20,15 +20,13 @@ import io.gravitee.am.common.event.Action;
 import io.gravitee.am.common.event.Type;
 import io.gravitee.am.common.utils.RandomString;
 import io.gravitee.am.identityprovider.api.User;
-import io.gravitee.am.model.Group;
-import io.gravitee.am.model.Membership;
-import io.gravitee.am.model.ReferenceType;
-import io.gravitee.am.model.Role;
+import io.gravitee.am.model.*;
 import io.gravitee.am.model.common.event.Event;
 import io.gravitee.am.model.common.event.Payload;
 import io.gravitee.am.model.membership.Member;
 import io.gravitee.am.model.membership.MemberType;
 import io.gravitee.am.model.permissions.DefaultRole;
+import io.gravitee.am.model.permissions.SystemRole;
 import io.gravitee.am.repository.management.api.MembershipRepository;
 import io.gravitee.am.repository.management.api.search.MembershipCriteria;
 import io.gravitee.am.service.*;
@@ -157,6 +155,31 @@ public class MembershipServiceImpl implements MembershipService {
                 );
     }
 
+    @Override
+    public Single<Membership> setPlatformAdmin(String userId) {
+
+        MembershipCriteria criteria = new MembershipCriteria();
+        criteria.setUserId(userId);
+
+        return roleService.findSystemRole(SystemRole.PLATFORM_ADMIN, ReferenceType.PLATFORM)
+                .switchIfEmpty(Single.error(new RoleNotFoundException(SystemRole.PLATFORM_ADMIN.name())))
+                .flatMap(role -> findByCriteria(ReferenceType.PLATFORM, Platform.DEFAULT, criteria).firstElement()
+                        .switchIfEmpty(Single.defer(() -> {
+                            final Date now = new Date();
+                            Membership membership = new Membership();
+                            membership.setRoleId(role.getId());
+                            membership.setMemberType(MemberType.USER);
+                            membership.setMemberId(userId);
+                            membership.setReferenceType(ReferenceType.PLATFORM);
+                            membership.setReferenceId(Platform.DEFAULT);
+                            membership.setCreatedAt(now);
+                            membership.setUpdatedAt(now);
+
+                            return createInternal(membership, null);
+                        })));
+    }
+
+
     @Override
     public Single<Map<String, Map<String, Object>>> getMetadata(List<Membership> memberships) {
         if (memberships == null || memberships.isEmpty()) {
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java
index c39b04b138..7aa372e4c2 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/RoleServiceImpl.java
@@ -378,9 +378,9 @@ public class RoleServiceImpl implements RoleService {
         List<Role> roles = new ArrayList<>();
 
         // Create PRIMARY_OWNER roles and PLATFORM_ADMIN role.
+        Map<Permission, Set<Acl>> platformAdminPermissions = Permission.allPermissionAcls(ReferenceType.PLATFORM);
         Map<Permission, Set<Acl>> organizationPrimaryOwnerPermissions = Permission.allPermissionAcls(ReferenceType.ORGANIZATION);
         Map<Permission, Set<Acl>> environmentPrimaryOwnerPermissions = Permission.allPermissionAcls(ReferenceType.ENVIRONMENT);
-        Map<Permission, Set<Acl>> platformPrimaryOwnerPermissions = Permission.allPermissionAcls(ReferenceType.PLATFORM);
         Map<Permission, Set<Acl>> domainPrimaryOwnerPermissions = Permission.allPermissionAcls(ReferenceType.DOMAIN);
         Map<Permission, Set<Acl>> applicationPrimaryOwnerPermissions = Permission.allPermissionAcls(ReferenceType.APPLICATION);
 
@@ -397,7 +397,7 @@ public class RoleServiceImpl implements RoleService {
 
         applicationPrimaryOwnerPermissions.put(Permission.APPLICATION, Acl.of(READ, UPDATE, DELETE));
 
-        roles.add(buildSystemRole(SystemRole.PLATFORM_ADMIN.name(), ReferenceType.PLATFORM, platformPrimaryOwnerPermissions));
+        roles.add(buildSystemRole(SystemRole.PLATFORM_ADMIN.name(), ReferenceType.PLATFORM, platformAdminPermissions));
         roles.add(buildSystemRole(SystemRole.ORGANIZATION_PRIMARY_OWNER.name(), ReferenceType.ORGANIZATION, organizationPrimaryOwnerPermissions));
         roles.add(buildSystemRole(SystemRole.ENVIRONMENT_PRIMARY_OWNER.name(), ReferenceType.ENVIRONMENT, environmentPrimaryOwnerPermissions));
         roles.add(buildSystemRole(SystemRole.DOMAIN_PRIMARY_OWNER.name(), ReferenceType.DOMAIN, domainPrimaryOwnerPermissions));
diff --git a/gravitee-am-ui/src/app/app-routing.module.ts b/gravitee-am-ui/src/app/app-routing.module.ts
index 5cfc636c6c..070fb156ad 100644
--- a/gravitee-am-ui/src/app/app-routing.module.ts
+++ b/gravitee-am-ui/src/app/app-routing.module.ts
@@ -166,6 +166,8 @@ import {DomainSettingsEntrypointsComponent} from './domain/settings/entrypoints/
 import {DomainSettingsWebAuthnComponent} from './domain/settings/webauthn/webauthn.component';
 import {ApplicationLoginSettingsComponent} from "./domain/applications/application/advanced/login/login.component";
 import {CurrentEnvironmentResolver} from "./resolvers/current-environment.resolver";
+import {CockpitComponent} from "./settings/cockpit/cockpit.component";
+import {InstallationResolver} from "./resolvers/installation.resolver";
 
 const routes: Routes = [
   {
@@ -528,6 +530,23 @@ const routes: Routes = [
             }
           }
         ]
+      },
+      {
+        path: 'cockpit',
+        component: CockpitComponent,
+        canActivate: [AuthGuard],
+        resolve: {
+          installation: InstallationResolver
+        },
+        data: {
+          menu: {
+            label: 'Cockpit',
+            icon: 'explore'
+          },
+          perms: {
+            only: ['installation_read']
+          }
+        }
       }
     ]
   },
diff --git a/gravitee-am-ui/src/app/app.module.ts b/gravitee-am-ui/src/app/app.module.ts
index f45bad3898..f729b7a04f 100644
--- a/gravitee-am-ui/src/app/app.module.ts
+++ b/gravitee-am-ui/src/app/app.module.ts
@@ -287,6 +287,9 @@ import { ApplicationResourcePolicyResolver } from './resolvers/application-resou
 import { LoginSettingsComponent } from './domain/components/login/login-settings.component';
 import { ApplicationLoginSettingsComponent } from './domain/applications/application/advanced/login/login.component';
 import {CurrentEnvironmentResolver} from "./resolvers/current-environment.resolver";
+import {CockpitComponent} from "./settings/cockpit/cockpit.component";
+import {InstallationResolver} from "./resolvers/installation.resolver";
+import {InstallationService} from "./services/installation.service";
 
 @NgModule({
   declarations: [
@@ -297,6 +300,7 @@ import {CurrentEnvironmentResolver} from "./resolvers/current-environment.resolv
     LoginComponent,
     LoginCallbackComponent,
     DomainsComponent,
+    CockpitComponent,
     DomainComponent,
     DomainDashboardComponent,
     DomainSettingsComponent,
@@ -492,6 +496,7 @@ import {CurrentEnvironmentResolver} from "./resolvers/current-environment.resolv
     DashboardService,
     UserService,
     ExtensionGrantService,
+    InstallationService,
     AppConfig,
     DomainsResolver,
     DomainResolver,
@@ -552,6 +557,7 @@ import {CurrentEnvironmentResolver} from "./resolvers/current-environment.resolv
     ApplicationResourcesResolver,
     ApplicationResourceResolver,
     ApplicationResourcePolicyResolver,
+    InstallationResolver,
     {
       provide: HTTP_INTERCEPTORS,
       useClass: HttpRequestInterceptor,
diff --git a/gravitee-am-ui/src/app/components/sidenav/sidenav.component.scss b/gravitee-am-ui/src/app/components/sidenav/sidenav.component.scss
index 13f8b3055c..0cd0f6899c 100644
--- a/gravitee-am-ui/src/app/components/sidenav/sidenav.component.scss
+++ b/gravitee-am-ui/src/app/components/sidenav/sidenav.component.scss
@@ -6,7 +6,7 @@ $sidenavMinWidth: 60px;
 $sidenavItemLeftWidth: 25px;
 $sidenavReducedItemLeftWidth: 15px;
 $sidenavItemBorderSize: 4px;
-$sidenavFooterHeight: 100px;
+$sidenavFooterHeight: 150px;
 $sidenavHeaderHeight: 64px;
 
 @mixin min-max-width($width) {
diff --git a/gravitee-am-ui/src/app/guards/auth-guard.service.ts b/gravitee-am-ui/src/app/guards/auth-guard.service.ts
index 2d71c69744..450bfb3dd1 100644
--- a/gravitee-am-ui/src/app/guards/auth-guard.service.ts
+++ b/gravitee-am-ui/src/app/guards/auth-guard.service.ts
@@ -38,7 +38,7 @@ export class AuthGuard implements CanActivate {
     const requiredPerms = route.data.perms.only;
     const userResult: Observable<any> = !this.authService.isAuthenticated() ? this.authService.userInfo() : of(this.authService.user());
     let resourceResult: Observable<any> = of([]);
-    if (!requiredPerms[0].startsWith('organization') && requiredPerms[0] !== 'domain_create') {
+    if ((requiredPerms[0].startsWith('domain') || requiredPerms[0].startsWith('application')) && requiredPerms[0] !== 'domain_create') {
       // check if the authenticated user can navigate to the next route (domain settings or application settings)
       const domainId = route.parent.paramMap.get('domainId') ? route.parent.paramMap.get('domainId') : route.parent.parent.paramMap.get('domainId') ? route.parent.parent.paramMap.get('domainId') : route.parent.parent.parent.paramMap.get('domainId');
       const appId = route.parent.paramMap.get('appId') ? route.parent.paramMap.get('appId') : route.parent.parent.paramMap.get('appId');
diff --git a/gravitee-am-ui/src/app/resolvers/Installation.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/Installation.resolver.spec.ts
new file mode 100644
index 0000000000..310e811776
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/Installation.resolver.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { InstallationResolver } from './applications.resolver';
+
+describe('InstallationResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [InstallationResolver]
+    });
+  });
+
+  it('should ...', inject([InstallationResolver], (service: InstallationResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/installation.resolver.ts b/gravitee-am-ui/src/app/resolvers/installation.resolver.ts
new file mode 100644
index 0000000000..718a2286bf
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/installation.resolver.ts
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+import {Injectable} from '@angular/core';
+import {Resolve, ActivatedRouteSnapshot, RouterStateSnapshot} from "@angular/router";
+import {Observable} from "rxjs";
+import {ApplicationService} from "../services/application.service";
+import {InstallationService} from "../services/installation.service";
+
+@Injectable()
+export class InstallationResolver implements Resolve<any> {
+
+  constructor(private installationService: InstallationService) {
+  }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
+    return this.installationService.get();
+  }
+}
diff --git a/gravitee-am-ui/src/app/services/installation.service.spec.ts b/gravitee-am-ui/src/app/services/installation.service.spec.ts
new file mode 100644
index 0000000000..31fddeaf86
--- /dev/null
+++ b/gravitee-am-ui/src/app/services/installation.service.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+import {TestBed, inject} from '@angular/core/testing';
+
+import {InstallationService} from './installation.service';
+
+describe('InstallationService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [InstallationService]
+    });
+  });
+
+  it('should ...', inject([InstallationService], (service: InstallationService) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/services/installation.service.ts b/gravitee-am-ui/src/app/services/installation.service.ts
new file mode 100644
index 0000000000..c5c73998f4
--- /dev/null
+++ b/gravitee-am-ui/src/app/services/installation.service.ts
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+import {HttpClient} from '@angular/common/http';
+import {Injectable} from '@angular/core';
+import {AuthService} from './auth.service';
+import {AppConfig} from '../../config/app.config';
+import {Observable} from 'rxjs';
+import {map} from 'rxjs/operators';
+
+@Injectable()
+export class InstallationService {
+  private installationUrl = AppConfig.settings.baseURL + '/platform/installation';
+
+  constructor(private http: HttpClient) {
+  }
+
+  get(): Observable<any> {
+    return this.http.get<any>(this.installationUrl);
+  }
+}
diff --git a/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.html b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.html
new file mode 100644
index 0000000000..d4c2cb82fc
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.html
@@ -0,0 +1,40 @@
+<!--
+
+    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.
+
+-->
+<div class="gv-page-container">
+  <div>
+    <app-emptystate *ngIf="!installation!.additionalInformation!.COCKPIT_INSTALLATION_STATUS" message="Meet Cockpit..."
+                    subMessage="Create an account on <a href=&quot;http://localhost:4004&quot; target=&quot;_blank&quot;>Cockpit</a>, register your current installation and start creating new organizations and environments !"
+                    icon="explore">
+    </app-emptystate>
+
+    <app-emptystate *ngIf="installation!.additionalInformation!.COCKPIT_INSTALLATION_STATUS === 'PENDING'" message="Almost there !"
+                    subMessage="Your installation is connected but you still have to accept it on <a href=&quot;{{installation.additionalInformation.COCKPIT_URL}}&quot; target=&quot;_blank&quot;>Cockpit</a> !"
+                    icon="explore_off">
+    </app-emptystate>
+
+    <app-emptystate *ngIf="installation!.additionalInformation!.COCKPIT_INSTALLATION_STATUS === 'ACCEPTED'" message="Congratulation !"
+                    subMessage="Your installation is now connected to <a href=&quot;{{installation.additionalInformation.COCKPIT_URL}}&quot; target=&quot;_blank&quot;>Cockpit</a>, you can now explore all the possibilities offered by Cockpit !"
+                    icon="check_circle">
+    </app-emptystate>
+
+    <app-emptystate *ngIf="installation!.additionalInformation!.COCKPIT_INSTALLATION_STATUS === 'REJECTED'" message="No luck !"
+                    subMessage="Seems that your installation is connected to <a href=&quot;{{installation.additionalInformation.COCKPIT_URL}}&quot; target=&quot;_blank&quot;>Cockpit</a>, but has been rejected..."
+                    icon="warning">
+    </app-emptystate>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.scss b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.spec.ts b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.spec.ts
new file mode 100644
index 0000000000..cb6fb87395
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CockpitComponent } from './cockpit.component';
+
+describe('CockpitComponent', () => {
+  let component: CockpitComponent;
+  let fixture: ComponentFixture<CockpitComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ CockpitComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CockpitComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.ts b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.ts
new file mode 100644
index 0000000000..e40880c8e8
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/cockpit/cockpit.component.ts
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+import {Component, OnInit} from '@angular/core';
+import {AppConfig} from "../../../config/app.config";
+import {ActivatedRoute} from "@angular/router";
+import {EnvironmentService} from "../../services/environment.service";
+
+@Component({
+  selector: 'app-cockpit',
+  templateUrl: './cockpit.component.html',
+  styleUrls: ['./cockpit.component.scss']
+})
+export class CockpitComponent implements OnInit {
+  title = AppConfig.settings.portalTitle;
+  version = AppConfig.settings.version;
+  installation: any = { };
+
+  constructor(private route : ActivatedRoute) { }
+
+  ngOnInit() {
+    this.installation = this.route.snapshot.data.installation;
+  }
+}
diff --git a/pom.xml b/pom.xml
index 48e3278121..43bec6900b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,13 +64,13 @@
     <properties>
         <gravitee-parent.version>19</gravitee-parent.version>
         <gravitee-common.version>1.16.2</gravitee-common.version>
-        <gravitee-plugin.version>1.14.0-SNAPSHOT</gravitee-plugin.version>
-        <gravitee-node.version>1.7.0</gravitee-node.version>
+        <gravitee-plugin.version>1.16.0</gravitee-plugin.version>
+        <gravitee-node.version>1.10.0</gravitee-node.version>
         <gravitee-reporter.version>1.17.1</gravitee-reporter.version>
         <gravitee-gateway-api.version>1.20.0</gravitee-gateway-api.version>
         <gravitee-expression-language.version>1.4.0</gravitee-expression-language.version>
         <gravitee-platform-repository-api.version>1.0.0</gravitee-platform-repository-api.version>
-        <gravitee-cockpit-api.version>1.0.0-SNAPSHOT</gravitee-cockpit-api.version>
+        <gravitee-cockpit-api.version>1.1.0-SNAPSHOT</gravitee-cockpit-api.version>
         <spring-security.version>5.3.1.RELEASE</spring-security.version>
         <nimbus.version>8.17</nimbus.version>
         <tink.version>1.3.0</tink.version>


Test patch:
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/commands/HelloCommandProducerTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/commands/HelloCommandProducerTest.java
new file mode 100644
index 0000000000..6e9df30d3a
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/commands/HelloCommandProducerTest.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.impl.commands;
+
+import io.gravitee.am.model.Environment;
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.model.Organization;
+import io.gravitee.am.repository.exceptions.TechnicalException;
+import io.gravitee.am.service.InstallationService;
+import io.gravitee.cockpit.api.command.Command;
+import io.gravitee.cockpit.api.command.hello.HelloCommand;
+import io.gravitee.cockpit.api.command.hello.HelloPayload;
+import io.gravitee.node.api.Node;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class HelloCommandProducerTest {
+
+    private static final String HOSTNAME = "test.gravitee.io";
+    private static final String CUSTOM_VALUE = "customValue";
+    private static final String CUSTOM_KEY = "customKey";
+    private static final String INSTALLATION_ID = "installation#1";
+    @Mock
+    private InstallationService installationService;
+
+    @Mock
+    private Node node;
+
+    private HelloCommandProducer cut;
+
+
+    @Before
+    public void before() {
+        cut = new HelloCommandProducer(node, installationService);
+    }
+
+    @Test
+    public void produceType() {
+        assertEquals(Command.Type.HELLO_COMMAND, cut.produceType());
+    }
+
+    @Test
+    public void produce() {
+
+        final Installation installation = new Installation();
+        installation.setId(INSTALLATION_ID);
+        installation.getAdditionalInformation().put(CUSTOM_KEY, CUSTOM_VALUE);
+
+        when(node.hostname()).thenReturn(HOSTNAME);
+        when(installationService.getOrInitialize()).thenReturn(Single.just(installation));
+
+        final HelloCommand command = new HelloCommand();
+        final HelloPayload payload = new HelloPayload();
+        payload.setNode(new io.gravitee.cockpit.api.command.Node());
+        command.setPayload(payload);
+        final TestObserver<HelloCommand> obs = cut.prepare(command).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(helloCommand -> {
+            assertEquals(CUSTOM_VALUE, helloCommand.getPayload().getAdditionalInformation().get(CUSTOM_KEY));
+            assertEquals(HOSTNAME, helloCommand.getPayload().getNode().getHostname());
+            assertEquals(Organization.DEFAULT, helloCommand.getPayload().getDefaultOrganizationId());
+            assertEquals(Environment.DEFAULT, helloCommand.getPayload().getDefaultEnvironmentId());
+            assertEquals(INSTALLATION_ID, helloCommand.getPayload().getNode().getInstallationId());
+            assertEquals(HOSTNAME, helloCommand.getPayload().getNode().getHostname());
+
+            return true;
+        });
+    }
+
+    @Test
+    public void produceWithException() {
+
+        when(installationService.getOrInitialize()).thenReturn(Single.error(new TechnicalException()));
+        final TestObserver<HelloCommand> obs = cut.prepare(new HelloCommand()).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(TechnicalException.class);
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandlerTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandlerTest.java
new file mode 100644
index 0000000000..1ddf0369a6
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandlerTest.java
@@ -0,0 +1,111 @@
+/**
+ * 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.impl.commands;
+
+import io.gravitee.am.model.Installation;
+import io.gravitee.am.repository.exceptions.TechnicalException;
+import io.gravitee.am.service.InstallationService;
+import io.gravitee.cockpit.api.command.Command;
+import io.gravitee.cockpit.api.command.CommandStatus;
+import io.gravitee.cockpit.api.command.installation.InstallationCommand;
+import io.gravitee.cockpit.api.command.installation.InstallationPayload;
+import io.gravitee.cockpit.api.command.installation.InstallationReply;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.HashMap;
+
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class InstallationCommandHandlerTest extends TestCase {
+
+    private static final String CUSTOM_VALUE = "customValue";
+    private static final String CUSTOM_KEY = "customKey";
+    private static final String INSTALLATION_ID = "installation#1";
+
+    @Mock
+    private InstallationService installationService;
+
+    public InstallationCommandHandler cut;
+
+    @Before
+    public void before() {
+        cut = new InstallationCommandHandler(installationService);
+    }
+
+    @Test
+    public void handleType() {
+        assertEquals(Command.Type.INSTALLATION_COMMAND, cut.handleType());
+    }
+
+    @Test
+    public void handle() {
+        final Installation installation = new Installation();
+        installation.setId(INSTALLATION_ID);
+        installation.getAdditionalInformation().put(CUSTOM_KEY, CUSTOM_VALUE);
+
+        InstallationPayload installationPayload = new InstallationPayload();
+        InstallationCommand command = new InstallationCommand(installationPayload);
+        installationPayload.setId(INSTALLATION_ID);
+        installationPayload.setStatus("ACCEPTED");
+
+        when(installationService.getOrInitialize()).thenReturn(Single.just(installation));
+        when(installationService.setAdditionalInformation(anyMap())).thenAnswer(i -> Single.just(installation));
+
+        TestObserver<InstallationReply> obs = cut.handle(command).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(reply -> reply.getCommandId().equals(command.getId()) && reply.getCommandStatus().equals(CommandStatus.SUCCEEDED));
+
+        final HashMap<String, String> expectedAdditionalInfos = new HashMap<>();
+        expectedAdditionalInfos.put(CUSTOM_KEY, CUSTOM_VALUE);
+        expectedAdditionalInfos.put(Installation.COCKPIT_INSTALLATION_STATUS, "ACCEPTED");
+        verify(installationService, times(1)).setAdditionalInformation(expectedAdditionalInfos);
+    }
+
+    @Test
+    public void handleWithException() {
+
+        final Installation installation = new Installation();
+        installation.setId(INSTALLATION_ID);
+        installation.getAdditionalInformation().put(CUSTOM_KEY, CUSTOM_VALUE);
+
+        InstallationPayload installationPayload = new InstallationPayload();
+        InstallationCommand command = new InstallationCommand(installationPayload);
+        installationPayload.setId(INSTALLATION_ID);
+        installationPayload.setStatus("ACCEPTED");
+
+        when(installationService.getOrInitialize()).thenReturn(Single.just(installation));
+        when(installationService.setAdditionalInformation(anyMap())).thenReturn(Single.error(new TechnicalException()));
+
+        TestObserver<InstallationReply> obs = cut.handle(command).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(reply -> reply.getCommandId().equals(command.getId()) && reply.getCommandStatus().equals(CommandStatus.ERROR));
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgraderTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgraderTest.java
index 241d96a206..c4cdbcd540 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgraderTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/DefaultOrganizationUpgraderTest.java
@@ -98,6 +98,7 @@ public class DefaultOrganizationUpgraderTest {
         doNothing().when(membershipHelper).setOrganizationPrimaryOwnerRole(argThat(user -> user.getId().equals(adminUser.getId())));
 
         assertTrue(cut.upgrade());
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 
     @Test
@@ -116,6 +117,7 @@ public class DefaultOrganizationUpgraderTest {
 
         when(organizationService.createDefault()).thenReturn(Maybe.empty());
         assertTrue(cut.upgrade());
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 
     @Test
@@ -137,6 +139,7 @@ public class DefaultOrganizationUpgraderTest {
 
         when(organizationService.createDefault()).thenReturn(Maybe.empty());
         assertTrue(cut.upgrade());
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 
     @Test
@@ -161,6 +164,7 @@ public class DefaultOrganizationUpgraderTest {
 
         when(organizationService.createDefault()).thenReturn(Maybe.empty());
         assertTrue(cut.upgrade());
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 
     @Test
@@ -188,6 +192,7 @@ public class DefaultOrganizationUpgraderTest {
 
         when(organizationService.createDefault()).thenReturn(Maybe.empty());
         assertTrue(cut.upgrade());
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 
     @Test
@@ -216,6 +221,7 @@ public class DefaultOrganizationUpgraderTest {
         when(organizationService.createDefault()).thenReturn(Maybe.empty());
 
         assertTrue(cut.upgrade());
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 
     @Test
@@ -261,10 +267,11 @@ public class DefaultOrganizationUpgraderTest {
         when(userService.findAll(eq(ReferenceType.ORGANIZATION), eq(Organization.DEFAULT), eq(2), anyInt()))
                 .thenReturn(Single.just(new Page<>(Arrays.asList(user, user), 2, totalUsers)));
 
-        doNothing().when(membershipHelper).setRole(eq(user), eq(adminRole));
+        doNothing().when(membershipHelper).setOrganizationRole(eq(user), eq(adminRole));
 
         cut.upgrade();
 
-        verify(membershipHelper, times(totalUsers)).setRole(eq(user), eq(adminRole));
+        verify(membershipHelper, times(totalUsers)).setOrganizationRole(eq(user), eq(adminRole));
+        verify(membershipHelper, times(1)).setPlatformAdminRole();
     }
 }
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelperTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelperTest.java
index 08a54f40dd..ed82a9e042 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelperTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/upgrades/helpers/MembershipHelperTest.java
@@ -16,6 +16,7 @@
 package io.gravitee.am.management.service.impl.upgrades.helpers;
 
 import io.gravitee.am.model.*;
+import io.gravitee.am.model.membership.MemberType;
 import io.gravitee.am.model.permissions.DefaultRole;
 import io.gravitee.am.model.permissions.SystemRole;
 import io.gravitee.am.repository.management.api.search.MembershipCriteria;
@@ -83,4 +84,45 @@ public class MembershipHelperTest {
 
         cut.setOrganizationPrimaryOwnerRole(user);
     }
+
+    @Test
+    public void shouldSetPlatformAdminToOrganizationPrimaryOwner() {
+
+        final Role organizationPrimaryOwner = new Role();
+        organizationPrimaryOwner.setId("organization-primary-owner");
+
+        final Membership membership = new Membership();
+        final String userId = "user-id";
+
+        membership.setMemberId(userId);
+        membership.setMemberType(MemberType.USER);
+
+        when(roleService.findSystemRole(SystemRole.ORGANIZATION_PRIMARY_OWNER, ReferenceType.ORGANIZATION)).thenReturn(Maybe.just(organizationPrimaryOwner));
+        when(membershipService.findByCriteria(eq(ReferenceType.ORGANIZATION), eq(Organization.DEFAULT), any(MembershipCriteria.class))).thenReturn(Flowable.just(membership));
+        when(membershipService.setPlatformAdmin(userId)).thenReturn(Single.just(new Membership()));
+
+        cut.setPlatformAdminRole();
+
+        verify(membershipService).setPlatformAdmin(userId);
+    }
+
+    @Test
+    public void shouldNotSetPlatformAdminNoOrganizationPrimaryOwner() {
+
+        final Role organizationPrimaryOwner = new Role();
+        organizationPrimaryOwner.setId("organization-primary-owner");
+
+        final Membership membership = new Membership();
+        final String userId = "user-id";
+
+        membership.setMemberId(userId);
+        membership.setMemberType(MemberType.USER);
+
+        when(roleService.findSystemRole(SystemRole.ORGANIZATION_PRIMARY_OWNER, ReferenceType.ORGANIZATION)).thenReturn(Maybe.just(organizationPrimaryOwner));
+        when(membershipService.findByCriteria(eq(ReferenceType.ORGANIZATION), eq(Organization.DEFAULT), any(MembershipCriteria.class))).thenReturn(Flowable.empty());
+
+        cut.setPlatformAdminRole();
+
+        verify(membershipService, times(0)).setPlatformAdmin(anyString());
+    }
 }
\ No newline at end of file
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/MembershipServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/MembershipServiceTest.java
index d739095b3b..2afcc1b5e0 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/MembershipServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/MembershipServiceTest.java
@@ -36,6 +36,7 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 
@@ -546,4 +547,61 @@ public class MembershipServiceTest {
         verify(membershipRepository, times(0)).create(any());
         verifyZeroInteractions(auditService);
     }
+
+    @Test
+    public void shouldSetPlatformAdmin() {
+
+        final String userId = "userId";
+        final Role platformAdminRole = new Role();
+        platformAdminRole.setId("platform-admin");
+        when(roleService.findSystemRole(SystemRole.PLATFORM_ADMIN, ReferenceType.PLATFORM)).thenReturn(Maybe.just(platformAdminRole));
+        when(membershipRepository.findByCriteria(eq(ReferenceType.PLATFORM), eq(Platform.DEFAULT), argThat(criteria -> criteria != null && criteria.getUserId().get().equals(userId)))).thenReturn(Flowable.empty());
+        when(membershipRepository.create(any(Membership.class))).thenAnswer((i->Single.just(i.getArgument(0))));
+        when(eventService.create(any())).thenReturn(Single.just(new Event()));
+
+        final TestObserver<Membership> obs = membershipService.setPlatformAdmin(userId).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertComplete();
+        obs.assertValue(membership -> {
+            assertEquals(MemberType.USER, membership.getMemberType());
+            assertEquals(userId, membership.getMemberId());
+            assertEquals(ReferenceType.PLATFORM, membership.getReferenceType());
+            assertEquals(Platform.DEFAULT, membership.getReferenceId());
+            return true;
+        });
+    }
+
+    @Test
+    public void shouldNotSetPlatformAdmin_roleNotFound() {
+
+        final String userId = "userId";
+        when(roleService.findSystemRole(SystemRole.PLATFORM_ADMIN, ReferenceType.PLATFORM)).thenReturn(Maybe.empty());
+
+        final TestObserver<Membership> obs = membershipService.setPlatformAdmin(userId).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(RoleNotFoundException.class);
+    }
+
+    @Test
+    public void shouldNotSetPlatformAdmin_alreadySet() {
+
+        final String userId = "userId";
+        final Membership alreadyExisting = new Membership();
+        final Role platformAdminRole = new Role();
+        platformAdminRole.setId("platform-admin");
+        when(roleService.findSystemRole(SystemRole.PLATFORM_ADMIN, ReferenceType.PLATFORM)).thenReturn(Maybe.just(platformAdminRole));
+        when(membershipRepository.findByCriteria(eq(ReferenceType.PLATFORM), eq(Platform.DEFAULT), argThat(criteria -> criteria != null && criteria.getUserId().get().equals(userId)))).thenReturn(Flowable.just(alreadyExisting));
+
+        final TestObserver<Membership> obs = membershipService.setPlatformAdmin(userId).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertComplete();
+        obs.assertValue(alreadyExisting);
+
+        verify(membershipRepository, times(0)).create(any(Membership.class));
+        verifyZeroInteractions(eventService);
+        verifyZeroInteractions(auditService);
+    }
 }