diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/controller/LoginController.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/controller/LoginController.java index 973736a2eb9..421675ff7b5 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/controller/LoginController.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/controller/LoginController.java @@ -43,6 +43,7 @@ import java.util.stream.Collectors; import static io.gravitee.am.management.handlers.management.api.authentication.provider.generator.RedirectCookieGenerator.DEFAULT_REDIRECT_COOKIE_NAME; +import static java.util.Collections.emptyList; /** * @author David BRASSELY (david.brassely at graviteesource.com) @@ -85,7 +86,9 @@ public ModelAndView login(HttpServletRequest request, @RequestParam(value=ORGANI // fetch domain social identity providers List socialProviders = null; try { - socialProviders = organizationService.findById(organizationId).map(Organization::getIdentities).blockingGet() + socialProviders = organizationService.findById(organizationId) + .map(org -> Optional.ofNullable(org.getIdentities()).orElse(emptyList())) + .blockingGet() .stream() .map(identity -> identityProviderManager.getIdentityProvider(identity)) .filter(Objects::nonNull) diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/IdentityProviderManager.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/IdentityProviderManager.java index 55b5b4e23ae..767cd728ace 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/IdentityProviderManager.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/IdentityProviderManager.java @@ -18,6 +18,8 @@ import io.gravitee.am.identityprovider.api.AuthenticationProvider; import io.gravitee.am.model.IdentityProvider; +import java.util.List; + /** * @author David BRASSELY (david.brassely at graviteesource.com) * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) @@ -28,4 +30,7 @@ public interface IdentityProviderManager { AuthenticationProvider get(String id); IdentityProvider getIdentityProvider(String id); + + List getAuthenticationProviderFor(String organizationId); + } diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/impl/IdentityProviderManagerImpl.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/impl/IdentityProviderManagerImpl.java index 2a0856929b0..c734748fe57 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/impl/IdentityProviderManagerImpl.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/manager/idp/impl/IdentityProviderManagerImpl.java @@ -18,11 +18,13 @@ import io.gravitee.am.common.event.IdentityProviderEvent; import io.gravitee.am.identityprovider.api.AuthenticationProvider; import io.gravitee.am.management.handlers.management.api.authentication.manager.idp.IdentityProviderManager; +import io.gravitee.am.management.service.InMemoryIdentityProviderListener; import io.gravitee.am.model.IdentityProvider; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.common.event.Payload; import io.gravitee.am.plugins.idp.core.IdentityProviderPluginManager; import io.gravitee.am.service.IdentityProviderService; +import io.gravitee.am.service.RoleService; import io.gravitee.common.event.Event; import io.gravitee.common.event.EventListener; import io.gravitee.common.event.EventManager; @@ -30,10 +32,14 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import java.util.Collections; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; /** * @author David BRASSELY (david.brassely at graviteesource.com) @@ -41,7 +47,7 @@ * @author GraviteeSource Team */ @Component("managementIdentityProviderManager") -public class IdentityProviderManagerImpl implements IdentityProviderManager, InitializingBean, EventListener { +public class IdentityProviderManagerImpl implements IdentityProviderManager, InitializingBean, EventListener, InMemoryIdentityProviderListener { private final Logger logger = LoggerFactory.getLogger(IdentityProviderManagerImpl.class); @@ -54,6 +60,15 @@ public class IdentityProviderManagerImpl implements IdentityProviderManager, Ini @Autowired private EventManager eventManager; + @Autowired + private RoleService roleService; + + @Autowired + private Environment environment; + + @Autowired + private io.gravitee.am.management.service.IdentityProviderManager commonIdentityProviderManager; + private ConcurrentMap providers = new ConcurrentHashMap<>(); private ConcurrentMap identities = new ConcurrentHashMap<>(); @@ -78,8 +93,28 @@ public void afterPropertiesSet() throws Exception { } catch (Exception e) { logger.error("Unable to initialize identity providers", e); } + + this.commonIdentityProviderManager.setListener(this); } + @Override + public void registerAuthenticationProvider(IdentityProvider provider) { + updateAuthenticationProvider(provider); + } + + public List getAuthenticationProviderFor(String organizationId) { + if (this.identities == null) { + return Collections.emptyList(); + } + return this.identities.values() + .stream() + .filter(idp -> organizationId.equals(idp.getReferenceId()) && ReferenceType.ORGANIZATION.equals(idp.getReferenceType())) + .map(IdentityProvider::getId) + .collect(Collectors.toList()); + } + + + @Override public void onEvent(Event event) { if (event.content().getReferenceType() == ReferenceType.ORGANIZATION && event.content().getReferenceId() != null) { diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/provider/security/ManagementAuthenticationProvider.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/provider/security/ManagementAuthenticationProvider.java index 8992c129402..7e2a59d96d0 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/provider/security/ManagementAuthenticationProvider.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/authentication/provider/security/ManagementAuthenticationProvider.java @@ -33,10 +33,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.util.StringUtils; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) @@ -68,11 +65,14 @@ public Authentication authenticate(Authentication authentication) throws Authent } details.putIfAbsent(Claims.organization, Organization.DEFAULT); - String organizationId = details.get(Claims.organization); + + List identities = identityProviderManager.getAuthenticationProviderFor(organizationId); Organization organization = organizationService.findById(organizationId).blockingGet(); + if (organization.getIdentities() != null) { + identities.addAll(organization.getIdentities()); + } - List identities = organization.getIdentities(); Iterator iter = identities.iterator(); io.gravitee.am.identityprovider.api.User user = null; AuthenticationException lastException = null; diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java index e15fdfea066..975021aeef7 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/IdentityProviderManager.java @@ -35,4 +35,9 @@ public interface IdentityProviderManager extends Service create(String domain); boolean userProviderExists(String identityProviderId); + + void setListener(InMemoryIdentityProviderListener listener); + + void loadIdentityProviders(); + } diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/InMemoryIdentityProviderListener.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/InMemoryIdentityProviderListener.java new file mode 100644 index 00000000000..51dcd146eb7 --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/InMemoryIdentityProviderListener.java @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.management.service; + +import io.gravitee.am.model.IdentityProvider; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +public interface InMemoryIdentityProviderListener { + + void registerAuthenticationProvider(IdentityProvider provider); + +} diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java index 07e2a05e70b..2de906c6b7d 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/IdentityProviderManagerImpl.java @@ -19,11 +19,14 @@ import io.gravitee.am.common.event.IdentityProviderEvent; import io.gravitee.am.identityprovider.api.UserProvider; import io.gravitee.am.management.service.IdentityProviderManager; +import io.gravitee.am.management.service.InMemoryIdentityProviderListener; +import io.gravitee.am.management.service.impl.utils.InlineOrganizationProviderConfiguration; import io.gravitee.am.model.IdentityProvider; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.common.event.Payload; import io.gravitee.am.plugins.idp.core.IdentityProviderPluginManager; import io.gravitee.am.service.IdentityProviderService; +import io.gravitee.am.service.RoleService; import io.gravitee.am.service.model.NewIdentityProvider; import io.gravitee.common.event.Event; import io.gravitee.common.event.EventListener; @@ -35,13 +38,18 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static io.gravitee.am.management.service.impl.utils.InlineOrganizationProviderConfiguration.MEMORY_TYPE; + /** * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) * @author GraviteeSource Team @@ -103,6 +111,18 @@ public class IdentityProviderManagerImpl extends AbstractService event) { } } + @Override + public void loadIdentityProviders() { + if (this.listener != null) { + loadProvidersFromConfig().forEach(listener::registerAuthenticationProvider); + } + } + + private List loadProvidersFromConfig(){ + List providers = new ArrayList<>(); + boolean found = true; + int idx = 0; + + while (found) { + String type = environment.getProperty("security.providers[" + idx + "].type"); + found = (type != null); + if (found) { + switch (type) { + case MEMORY_TYPE: + InlineOrganizationProviderConfiguration providerConfig = new InlineOrganizationProviderConfiguration(roleService, environment, idx); + if (providerConfig.isEnabled()) { + providers.add(providerConfig.buildIdentityProvider()); + } + break; + default: + logger.warn("Unsupported provider with type '{}'", type); + } + } + idx++; + } + + return providers; + } + @Override public Maybe getUserProvider(String userProvider) { if (userProvider == null) { @@ -241,4 +295,5 @@ private void loadUserProvider(IdentityProvider identityProvider) { userProviders.remove(identityProvider.getId()); } } + } 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 595308e1e7b..bbf4a7990b6 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 @@ -15,22 +15,24 @@ */ package io.gravitee.am.management.service.impl.upgrades; +import io.gravitee.am.management.service.IdentityProviderManager; 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) @@ -57,18 +59,29 @@ public class DefaultOrganizationUpgrader implements Upgrader, Ordered { private final DomainService domainService; + private final Environment environment; + + private final IdentityProviderManager identityProviderManager; + + private final boolean useDefaultAdmin; + public DefaultOrganizationUpgrader(OrganizationService organizationService, IdentityProviderService identityProviderService, UserService userService, MembershipHelper membershipHelper, RoleService roleService, - DomainService domainService) { + DomainService domainService, + Environment environment, + IdentityProviderManager identityProviderManager) { this.organizationService = organizationService; this.identityProviderService = identityProviderService; this.userService = userService; this.membershipHelper = membershipHelper; this.roleService = roleService; this.domainService = domainService; + this.environment = environment; + this.identityProviderManager = identityProviderManager; + this.useDefaultAdmin = environment.getProperty("security.defaultAdmin", boolean.class, true); } @Override @@ -107,7 +120,7 @@ public boolean upgrade() { // then delete the domain domainService.delete(ADMIN_DOMAIN).blockingGet(); - } else { + } else if (useDefaultAdmin) { // Need to create an inline provider and an admin user for this newly created default organization. IdentityProvider inlineProvider = createInlineProvider(); User adminUser = createAdminUser(inlineProvider); @@ -115,32 +128,39 @@ public boolean upgrade() { } } + if (identityProviderManager != null) { + // call the idpManager here to ensure that roles have been created + identityProviderManager.loadIdentityProviders(); + } + // Get organization with fresh data. organization = organizationService.findById(Organization.DEFAULT).blockingGet(); logger.info("Check if default organization is up to date"); - // Need to check that inline idp and default admin user has 'admin' role. - final List identities = organization.getIdentities(); - - IdentityProvider inlineIdp = identityProviderService.findAll(ReferenceType.ORGANIZATION, Organization.DEFAULT) - .filter(identityProvider -> identityProvider.getType().equals("inline-am-idp") - && !identityProvider.isExternal() - && identities.contains(identityProvider.getId())) - .firstElement().blockingGet(); - - // If inline idp doesn't exist or is not enabled, it is probably an administrator choice. So do not go further. - if (inlineIdp != null) { - // If inline idp doesn't have "admin" user in its configuration, it is probably an administrator choice. So do not go further. - if (inlineIdp.getConfiguration().contains(",\"username\":\"" + ADMIN_USERNAME + "\",") && inlineIdp.getRoleMapper().isEmpty()) { - - // Check the user admin exists. - User adminUser = userService.findByUsernameAndSource(ReferenceType.ORGANIZATION, Organization.DEFAULT, ADMIN_USERNAME, inlineIdp.getId()).blockingGet(); - - if (adminUser == null) { - // Create the admin user with organization primary owner role on the default organization. - adminUser = createAdminUser(inlineIdp); - membershipHelper.setOrganizationPrimaryOwnerRole(adminUser); + if (useDefaultAdmin) { + // Need to check that inline idp and default admin user has 'admin' role. + final List identities = Optional.ofNullable(organization.getIdentities()).orElse(Collections.emptyList()); + + IdentityProvider inlineIdp = identityProviderService.findAll(ReferenceType.ORGANIZATION, Organization.DEFAULT) + .filter(identityProvider -> identityProvider.getType().equals("inline-am-idp") + && !identityProvider.isExternal() + && identities.contains(identityProvider.getId())) + .firstElement().blockingGet(); + + // If inline idp doesn't exist or is not enabled, it is probably an administrator choice. So do not go further. + if (inlineIdp != null) { + // If inline idp doesn't have "admin" user in its configuration, it is probably an administrator choice. So do not go further. + if (inlineIdp.getConfiguration().contains(",\"username\":\"" + ADMIN_USERNAME + "\",") && inlineIdp.getRoleMapper().isEmpty()) { + + // Check the user admin exists. + User adminUser = userService.findByUsernameAndSource(ReferenceType.ORGANIZATION, Organization.DEFAULT, ADMIN_USERNAME, inlineIdp.getId()).blockingGet(); + + if (adminUser == null) { + // Create the admin user with organization primary owner role on the default organization. + adminUser = createAdminUser(inlineIdp); + membershipHelper.setOrganizationPrimaryOwnerRole(adminUser); + } } } } 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 d2f877c96dc..7b50e05ad52 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,12 +17,18 @@ 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; import io.gravitee.am.service.RoleService; +import io.reactivex.Flowable; +import io.reactivex.Maybe; import org.springframework.stereotype.Component; +import java.util.Arrays; +import java.util.List; + /** * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com) * @author GraviteeSource Team diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/utils/InlineOrganizationProviderConfiguration.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/utils/InlineOrganizationProviderConfiguration.java new file mode 100644 index 00000000000..e50c83c9a60 --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/utils/InlineOrganizationProviderConfiguration.java @@ -0,0 +1,216 @@ +/** + * 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.utils; + +import io.gravitee.am.model.*; +import io.gravitee.am.model.permissions.SystemRole; +import io.gravitee.am.service.RoleService; +import io.reactivex.Flowable; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +import java.util.*; + +import static io.gravitee.am.model.permissions.DefaultRole.ORGANIZATION_OWNER; +import static io.gravitee.am.model.permissions.DefaultRole.ORGANIZATION_USER; +import static io.gravitee.am.model.permissions.SystemRole.ORGANIZATION_PRIMARY_OWNER; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +public class InlineOrganizationProviderConfiguration extends OrganizationProviderConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(InlineOrganizationProviderConfiguration.class); + + public static final String MEMORY_TYPE = "memory"; + + private static final List authorizedRoles = Arrays.asList(ORGANIZATION_OWNER.name(), ORGANIZATION_USER.name(), ORGANIZATION_PRIMARY_OWNER.name()); + + private final String passwordEncoder; + + private final Map users = new LinkedHashMap<>(); + + private RoleService roleService; + + public InlineOrganizationProviderConfiguration(RoleService roleService, Environment env, int index) { + super(MEMORY_TYPE, env, index); + this.roleService = roleService; + final String propertyBase = this.getPropertyBase(index); + this.passwordEncoder = env.getProperty(propertyBase+"password-encoding-algo", String.class, "BCrypt"); + + boolean found = true; + int idx = 0; + + while (found) { + final String userPropertyBase = propertyBase + "users[" + idx + "]."; + final String username = env.getProperty(userPropertyBase + "username"); + found = (username != null); + if (found) { + UserDefinition user = new UserDefinition(); + user.setUsername(username); + user.setFirstname(env.getProperty(userPropertyBase+"firstname")); + user.setLastname(env.getProperty(userPropertyBase+"lastname")); + user.setEmail(env.getProperty(userPropertyBase+"email")); + user.setPassword(env.getProperty(userPropertyBase+"password")); + user.setRole(env.getProperty(userPropertyBase+"role")); + if (StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getRole())) { + LOGGER.warn("User definition ignored for '{}': missing role or password", username); + } else if (!authorizedRoles.contains(user.getRole())) { + LOGGER.warn("User definition ignored for '{}': invalid role. (expected: \"ORGANIZATION_OWNER\", \"ORGANIZATION_USER\", \"ORGANIZATION_PRIMARY_OWNER\")", username); + } else { + this.users.put(username, user); + } + } + idx++; + } + } + + @Override + public IdentityProvider buildIdentityProvider() { + IdentityProvider provider = new IdentityProvider(); + provider.setId("memory"); + + provider.setConfiguration(generateConfiguration()); + + provider.setExternal(false); + provider.setType("inline-am-idp");// use the inline provider implementation as InMemory provider + provider.setName(getName()); + provider.setReferenceId(Organization.DEFAULT); + provider.setReferenceType(ReferenceType.ORGANIZATION); + provider.setRoleMapper(generateRoleMapper()); + return provider; + } + + private String generateConfiguration() { + JsonObject json = new JsonObject(); + if ("BCrypt".equals(passwordEncoder)) { + json.put("passwordEncoder", passwordEncoder); + } + JsonArray arrayOfUsers = new JsonArray(); + json.put("users", arrayOfUsers); + users.forEach((username, def) -> { + JsonObject user = new JsonObject() + .put("firstname", def.firstname) + .put("lastname", def.lastname) + .put("username", def.username) + .put("email", def.email) + .put("password", def.password); + arrayOfUsers.add(user); + }); + return json.encode(); + } + + private Map generateRoleMapper() { + Map result = new HashMap<>(); + + final List roleNames = Arrays.asList( + SystemRole.ORGANIZATION_PRIMARY_OWNER.name(), + ORGANIZATION_OWNER.name(), + ORGANIZATION_USER.name()); + + final Map organizationRoles = Flowable.merge( + roleService.findRolesByName(ReferenceType.PLATFORM, Platform.DEFAULT, ReferenceType.ORGANIZATION, roleNames), + roleService.findRolesByName(ReferenceType.ORGANIZATION, Organization.DEFAULT, ReferenceType.ORGANIZATION, roleNames)) + .collect(HashMap::new, (acc, role) -> { + acc.put(role.getName(), role); + }).blockingGet(); + + users.forEach((username, def) -> { + Role role = organizationRoles.get(def.getRole()); + if (role != null) { + String[] rules = result.get(role.getId()); + if (rules == null) { + rules = new String[1]; + } else { + rules = Arrays.copyOf(rules, rules.length + 1); + } + rules[rules.length - 1] = "username=" + username; + result.put(role.getId(), rules); + } + }); + + return result; + } + + public String getPasswordEncoder() { + return passwordEncoder; + } + + public Map getUsers() { + return users; + } + + public final static class UserDefinition { + private String username; + private String email; + private String firstname; + private String lastname; + private String password; + private String role; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + } +} diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/utils/OrganizationProviderConfiguration.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/utils/OrganizationProviderConfiguration.java new file mode 100644 index 00000000000..e5ae64c0728 --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/utils/OrganizationProviderConfiguration.java @@ -0,0 +1,53 @@ +/** + * 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.utils; + +import io.gravitee.am.model.IdentityProvider; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +public abstract class OrganizationProviderConfiguration { + private final String type; + + private final String name; + + private final boolean enabled; + + public OrganizationProviderConfiguration(String type, Environment env, int index) { + this.type = type; + final String propertyBase = getPropertyBase(index); + this.name = env.getProperty(propertyBase + "name", StringUtils.capitalize(this.type) + " users"); + this.enabled = env.getProperty(propertyBase + "enabled", boolean.class, false); + } + + protected String getPropertyBase(int index) { + return "security.providers[" + index + "]."; + } + + public abstract IdentityProvider buildIdentityProvider(); + + public String getName() { + return name; + } + + public boolean isEnabled() { + return enabled; + } +} diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/IdentityProviderManagerTest.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/IdentityProviderManagerTest.java new file mode 100644 index 00000000000..23683e02282 --- /dev/null +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/test/java/io/gravitee/am/management/service/impl/IdentityProviderManagerTest.java @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gravitee.am.management.service.impl; + +import io.gravitee.am.management.service.InMemoryIdentityProviderListener; +import io.gravitee.am.model.Organization; +import io.gravitee.am.model.ReferenceType; +import io.gravitee.am.model.Role; +import io.gravitee.am.service.RoleService; +import io.reactivex.Flowable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * @author Eric LELEU (eric.leleu at graviteesource.com) + * @author GraviteeSource Team + */ +@RunWith(MockitoJUnitRunner.class) +public class IdentityProviderManagerTest { + private static final String ADMIN_USERNAME = "admin"; + + @Spy + private Environment environment = new StandardEnvironment(); + + @Mock + private RoleService roleService; + + @Mock + private InMemoryIdentityProviderListener listener; + + @InjectMocks + private IdentityProviderManagerImpl cut = new IdentityProviderManagerImpl(); + + @Before + public void init() { + cut.setListener(listener); + } + + @Test + public void shouldRegisterProvider() { + defineDefaultSecurityConfig(true); + Role role = new Role(); + role.setId("roleid"); + role.setName("ORGANIZATION_PRIMARY_OWNER"); + + when(roleService.findRolesByName(any(), any(), any(), any())).thenReturn(Flowable.just(role)); + + cut.loadIdentityProviders(); + + verify(listener).registerAuthenticationProvider(argThat(idp -> { + return ReferenceType.ORGANIZATION.equals(idp.getReferenceType()) && + Organization.DEFAULT.equals(idp.getReferenceId()) && + idp.getRoleMapper() != null && + idp.getRoleMapper().containsKey("roleid"); + })); + } + + @Test + public void shouldNotRegisterProvider_Disabled() { + defineDefaultSecurityConfig(false); + Role role = new Role(); + role.setId("roleid"); + role.setName("ORGANIZATION_PRIMARY_OWNER"); + + cut.loadIdentityProviders(); + + verify(listener, never()).registerAuthenticationProvider(any()); + } + + private void defineDefaultSecurityConfig(boolean enabled) { + reset(environment); + doReturn("memory").when(environment).getProperty("security.providers[0].type"); + doReturn("none").when(environment).getProperty(eq("security.providers[0].password-encoding-algo"), any(), any()); + doReturn(ADMIN_USERNAME).when(environment).getProperty("security.providers[0].users[0].username"); + doReturn("adminadmin").when(environment).getProperty("security.providers[0].users[0].password"); + doReturn("ORGANIZATION_PRIMARY_OWNER").when(environment).getProperty("security.providers[0].users[0].role"); + doReturn(enabled).when(environment).getProperty("security.providers[0].enabled", boolean.class, false); + } +} 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 24dd68c6d45..788dad59a70 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 @@ -32,6 +32,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.env.Environment; import java.util.Arrays; import java.util.Collections; @@ -70,12 +71,15 @@ public class DefaultOrganizationUpgraderTest { @Mock private DomainService domainService; + @Mock + private Environment environment; + private DefaultOrganizationUpgrader cut; @Before public void before() { - - cut = new DefaultOrganizationUpgrader(organizationService, identityProviderService, userService, membershipHelper, roleService, domainService); + when(environment.getProperty("security.defaultAdmin", boolean.class, true)).thenReturn(true); + cut = new DefaultOrganizationUpgrader(organizationService, identityProviderService, userService, membershipHelper, roleService, domainService, environment, null); } @Test 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 10fbbfcaf39..65e22fba811 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 @@ -176,6 +176,35 @@ jwt: #cookie-domain: .gravitee.io # cookie domain (default "") #cookie-secure: true # cookie secure flag (default false) +# Security section is used to defined organization users available on AM bootstrap +security: + # If true create on AM bootstrap an inline identity provider with an admin user (login: admin) + # this is the legacy mode + defaultAdmin: true + ## authentication providers + ## currently, only "in memory" provider is supported + providers: + - type: memory + enabled: false + ## Name of IdentityProvider + ## If missing the type will be used to create a generic name (ex: Memory users) + #name: + ## password encoding/hashing algorithm. One of: + ## - BCrypt : passwords are hashed with bcrypt (supports only $2a$ algorithm) + ## - none : passwords are not hashed/encrypted + #default value is BCrypt + password-encoding-algo: BCrypt + users: + - user: + username: admin + #email: + firstname: Administrator + lastname: Administrator + ## Passwords are encoded using BCrypt + ## Password value: adminadmin + password: $2a$10$NG5WLbspq8V1yJDzUKfUK.oum94qL/Ne3B5fQCgekw/Y4aOEaoFZq + role: ORGANIZATION_OWNER + # SMTP configuration used to send mails email: enabled: false @@ -318,4 +347,3 @@ ds: dbname: gravitee-am host: localhost port: 27017 - diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/RoleRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/RoleRepository.java index f38414250d1..0a5e9348349 100644 --- a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/RoleRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/RoleRepository.java @@ -43,4 +43,6 @@ public interface RoleRepository extends CrudRepository { Maybe findById(ReferenceType referenceType, String referenceId, String role); Maybe findByNameAndAssignableType(ReferenceType referenceType, String referenceId, String name, ReferenceType assignableType); + + Flowable findByNamesAndAssignableType(ReferenceType referenceType, String referenceId, List name, ReferenceType assignableType); } diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcRoleRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcRoleRepository.java index 5d41060d467..73dd2056590 100644 --- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcRoleRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcRoleRepository.java @@ -148,6 +148,14 @@ public Maybe findByNameAndAssignableType(ReferenceType referenceType, Stri .flatMap(role -> completeWithScopes(Maybe.just(role), role.getId())); } + @Override + public Flowable findByNamesAndAssignableType(ReferenceType referenceType, String referenceId, List names, ReferenceType assignableType) { + LOGGER.debug("findByNamesAndAssignableType({},{},{},{})", referenceType, referenceId, names, assignableType); + return roleRepository.findByNamesAndAssignableType(referenceType.name(), referenceId, names, assignableType.name()) + .map(this::toEntity) + .flatMapMaybe(role -> completeWithScopes(Maybe.just(role), role.getId())); + } + @Override public Maybe findById(String id) { LOGGER.debug("findById({})", id); diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/role/SpringRoleRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/role/SpringRoleRepository.java index 4f82cdb4b63..95078b0f788 100644 --- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/role/SpringRoleRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/role/SpringRoleRepository.java @@ -47,4 +47,7 @@ public interface SpringRoleRepository extends RxJava2CrudRepository findByNameAndAssignableType(@Param("refType") String refType, @Param("refId") String refId, @Param("name")String name, @Param("assignable")String assignableType); + + @Query("select * from roles r where r.reference_type = :refType and r.reference_id = :refId and r.name in (:names) and r.assignable_type = :assignable") + Flowable findByNamesAndAssignableType(@Param("refType") String refType, @Param("refId") String refId, @Param("names")List names, @Param("assignable")String assignableType); } diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java index a49bc61e834..6675f5e7fa4 100644 --- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java +++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoRoleRepository.java @@ -129,6 +129,11 @@ public Maybe findByNameAndAssignableType(ReferenceType referenceType, Stri return Observable.fromPublisher(rolesCollection.find(and(eq(FIELD_REFERENCE_TYPE, referenceType.name()), eq(FIELD_REFERENCE_ID, referenceId), eq(FIELD_NAME, name), eq(FIELD_ASSIGNABLE_TYPE, assignableType.name()))).first()).firstElement().map(this::convert); } + @Override + public Flowable findByNamesAndAssignableType(ReferenceType referenceType, String referenceId, List names, ReferenceType assignableType) { + return Flowable.fromPublisher(rolesCollection.find(and(eq(FIELD_REFERENCE_TYPE, referenceType.name()), eq(FIELD_REFERENCE_ID, referenceId), in(FIELD_NAME, names), eq(FIELD_ASSIGNABLE_TYPE, assignableType.name())))).map(this::convert); + } + private Role convert(RoleMongo roleMongo) { if (roleMongo == null) { return null; diff --git a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/RoleRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/RoleRepositoryTest.java index 3628b29c9fc..336f2abc9e3 100644 --- a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/RoleRepositoryTest.java +++ b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/RoleRepositoryTest.java @@ -16,6 +16,7 @@ package io.gravitee.am.repository.management.api; import io.gravitee.am.model.Acl; +import io.gravitee.am.model.Platform; import io.gravitee.am.model.ReferenceType; import io.gravitee.am.model.Role; import io.gravitee.am.model.permissions.Permission; @@ -28,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.*; +import java.util.stream.Collectors; import static org.junit.Assert.assertTrue; @@ -59,6 +61,50 @@ public void testFindByDomain() throws TechnicalException { testObserver.assertValue(roles -> roles.size() == 1); } + @Test + public void testFindByNamesAndAssignable() throws TechnicalException { + // create role + Role role = new Role(); + final String NAME_1 = "testName"; + role.setName(NAME_1); + role.setReferenceType(ReferenceType.PLATFORM); + role.setReferenceId(Platform.DEFAULT); + role.setAssignableType(ReferenceType.ORGANIZATION); + roleRepository.create(role).blockingGet(); + + Role role2 = new Role(); + final String NAME_2 = "testName2"; + role2.setName(NAME_2); + role2.setReferenceType(ReferenceType.PLATFORM); + role2.setReferenceId(Platform.DEFAULT); + role2.setAssignableType(ReferenceType.ORGANIZATION); + roleRepository.create(role2).blockingGet(); + + Role role3 = new Role(); + final String NAME_3 = "testName3"; + role3.setName(NAME_3); + role3.setReferenceType(ReferenceType.PLATFORM); + role3.setReferenceId(Platform.DEFAULT); + role3.setAssignableType(ReferenceType.ORGANIZATION); + roleRepository.create(role3).blockingGet(); + + Role role4 = new Role(); + final String NAME_4 = "testName4"; + role4.setName(NAME_4); + role4.setReferenceType(ReferenceType.PLATFORM); + role4.setReferenceId(Platform.DEFAULT); + role4.setAssignableType(ReferenceType.ENVIRONMENT); + roleRepository.create(role4).blockingGet(); + + // fetch roles 1 & 2 + TestObserver> testObserver = roleRepository.findByNamesAndAssignableType(ReferenceType.PLATFORM, Platform.DEFAULT, Arrays.asList(NAME_1, NAME_2), ReferenceType.ORGANIZATION).toList().test(); + testObserver.awaitTerminalEvent(); + + testObserver.assertComplete(); + testObserver.assertNoErrors(); + testObserver.assertValue(roles -> roles.size() == 2 && roles.stream().map(Role::getName).collect(Collectors.toList()).containsAll(Arrays.asList(NAME_1, NAME_2))); + } + @Test public void testFindById() throws TechnicalException { // create role diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/RoleService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/RoleService.java index cde9cb86d67..f30cbae0309 100644 --- a/gravitee-am-service/src/main/java/io/gravitee/am/service/RoleService.java +++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/RoleService.java @@ -51,6 +51,8 @@ public interface RoleService { Maybe findSystemRole(SystemRole systemRole, ReferenceType assignableType); + Flowable findRolesByName(ReferenceType referenceType, String referenceId, ReferenceType assignableType, List roleNames); + Maybe findDefaultRole(String organizationId, DefaultRole defaultRole, ReferenceType assignableType); Single> findByIdIn(List ids); 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 2469ce181a5..bddee289466 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 @@ -48,6 +48,7 @@ import org.springframework.stereotype.Component; import java.util.*; +import java.util.stream.Collectors; import static io.gravitee.am.model.Acl.*; @@ -135,6 +136,17 @@ public Maybe findSystemRole(SystemRole systemRole, ReferenceType assignabl }); } + @Override + public Flowable findRolesByName(ReferenceType referenceType, String referenceId, ReferenceType assignableType, List roleNames) { + return roleRepository.findByNamesAndAssignableType(referenceType, referenceId, roleNames, assignableType) + .onErrorResumeNext(ex -> { + String joinedRoles = roleNames.stream().collect(Collectors.joining(", ")); + LOGGER.error("An error occurs while trying to find roles : {}", joinedRoles, ex); + return Flowable.error(new TechnicalManagementException( + String.format("An error occurs while trying to find roles : %s", joinedRoles), ex)); + }); + } + @Override public Maybe findDefaultRole(String organizationId, DefaultRole defaultRole, ReferenceType assignableType) { LOGGER.debug("Find default role {} of organization {} for the type {}", defaultRole.name(), organizationId, assignableType);