From 85beb10d4b9589e5224b3ece9b2d64c0d91c730a Mon Sep 17 00:00:00 2001 From: Daniel Kelemen Date: Fri, 30 Aug 2024 16:04:56 +0200 Subject: [PATCH 1/3] feat(run): add oauth2 identity provider related to https://github.com/camunda/camunda-bpm-platform/issues/4453 --- ...SpringSecurityOAuth2AutoConfiguration.java | 30 +- .../security/oauth2/OAuth2Properties.java | 81 ++++ .../impl/OAuth2GrantedAuthoritiesMapper.java | 83 ++++ .../oauth2/impl/OAuth2IdentityProvider.java | 395 ++++++++++++++++++ .../impl/OAuth2IdentityProviderFactory.java | 33 ++ .../impl/OAuth2IdentityProviderPlugin.java | 29 ++ 6 files changed, 648 insertions(+), 3 deletions(-) create mode 100644 spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/OAuth2Properties.java create mode 100644 spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2GrantedAuthoritiesMapper.java create mode 100644 spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProvider.java create mode 100644 spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java create mode 100644 spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java index 8656160168f..143ca39e8b8 100644 --- a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java @@ -23,6 +23,8 @@ import org.camunda.bpm.spring.boot.starter.CamundaBpmAutoConfiguration; import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties; import org.camunda.bpm.spring.boot.starter.property.WebappProperty; +import org.camunda.bpm.spring.boot.starter.security.oauth2.impl.OAuth2GrantedAuthoritiesMapper; +import org.camunda.bpm.spring.boot.starter.security.oauth2.impl.OAuth2IdentityProviderPlugin; import org.camunda.bpm.spring.boot.starter.security.oauth2.impl.OAuth2AuthenticationProvider; import org.camunda.bpm.webapp.impl.security.auth.ContainerBasedAuthenticationFilter; import org.slf4j.Logger; @@ -30,8 +32,10 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -39,6 +43,7 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.web.SecurityFilterChain; import java.util.Map; @@ -47,13 +52,17 @@ @AutoConfigureAfter({ CamundaBpmAutoConfiguration.class, SpringProcessEngineServicesConfiguration.class }) @ConditionalOnBean(CamundaBpmProperties.class) @Conditional(ClientsConfiguredCondition.class) +@EnableConfigurationProperties(OAuth2Properties.class) public class CamundaSpringSecurityOAuth2AutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(CamundaSpringSecurityOAuth2AutoConfiguration.class); public static final int CAMUNDA_OAUTH2_ORDER = Ordered.HIGHEST_PRECEDENCE + 100; + private final OAuth2Properties oAuth2Properties; private final String webappPath; - public CamundaSpringSecurityOAuth2AutoConfiguration(CamundaBpmProperties properties) { + public CamundaSpringSecurityOAuth2AutoConfiguration(CamundaBpmProperties properties, + OAuth2Properties oAuth2Properties) { + this.oAuth2Properties = oAuth2Properties; WebappProperty webapp = properties.getWebapp(); this.webappPath = webapp.getApplicationPath(); } @@ -71,16 +80,31 @@ public FilterRegistrationBean webappAuthenticationFilter() { filterRegistration.setDispatcherTypes(DispatcherType.REQUEST); return filterRegistration; } - + + @Bean + @ConditionalOnProperty(name = "identity-provider.enabled", prefix = OAuth2Properties.PREFIX) + public OAuth2IdentityProviderPlugin identityProviderPlugin() { + logger.debug("Registering OAuth2IdentityProviderPlugin"); + return new OAuth2IdentityProviderPlugin(); + } + + @Bean + @ConditionalOnProperty(name = "identity-provider.group-name-attribute", prefix = OAuth2Properties.PREFIX) + protected GrantedAuthoritiesMapper grantedAuthoritiesMapper() { + logger.debug("Registering OAuth2GrantedAuthoritiesMapper"); + return new OAuth2GrantedAuthoritiesMapper(oAuth2Properties); + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { logger.info("Enabling Camunda Spring Security oauth2 integration"); - + http.authorizeHttpRequests(c -> c .requestMatchers(webappPath + "/app/**").authenticated() .requestMatchers(webappPath + "/api/**").authenticated() .anyRequest().permitAll() ) + .anonymous(AbstractHttpConfigurer::disable) .oauth2Login(Customizer.withDefaults()) .oidcLogout(Customizer.withDefaults()) .oauth2Client(Customizer.withDefaults()) diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/OAuth2Properties.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/OAuth2Properties.java new file mode 100644 index 00000000000..d9396842ae9 --- /dev/null +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/OAuth2Properties.java @@ -0,0 +1,81 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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 org.camunda.bpm.spring.boot.starter.security.oauth2; + +import org.camunda.bpm.spring.boot.starter.security.oauth2.impl.OAuth2IdentityProvider; +import org.camunda.bpm.spring.boot.starter.property.CamundaBpmProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(OAuth2Properties.PREFIX) +public class OAuth2Properties { + + public static final String PREFIX = CamundaBpmProperties.PREFIX + ".oauth2"; + + /** + * OAuth2 identity provider properties. + */ + private OAuth2IdentityProviderProperties identityProvider; + + public static class OAuth2IdentityProviderProperties { + /** + * Enable {@link OAuth2IdentityProvider}. + */ + private boolean enabled = false; + + /** + * Name of the attribute (claim) that holds the groups. + */ + private String groupNameAttribute; + + /** + * Group name attribute delimiter. Only used if the {@link #groupNameAttribute} is a {@link String}. + */ + private String groupNameDelimiter = ","; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getGroupNameAttribute() { + return groupNameAttribute; + } + + public void setGroupNameAttribute(String groupNameAttribute) { + this.groupNameAttribute = groupNameAttribute; + } + + public String getGroupNameDelimiter() { + return groupNameDelimiter; + } + + public void setGroupNameDelimiter(String groupNameDelimiter) { + this.groupNameDelimiter = groupNameDelimiter; + } + } + + public OAuth2IdentityProviderProperties getIdentityProvider() { + return identityProvider; + } + + public void setIdentityProvider(OAuth2IdentityProviderProperties identityProvider) { + this.identityProvider = identityProvider; + } +} \ No newline at end of file diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2GrantedAuthoritiesMapper.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2GrantedAuthoritiesMapper.java new file mode 100644 index 00000000000..e6a388019e6 --- /dev/null +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2GrantedAuthoritiesMapper.java @@ -0,0 +1,83 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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 org.camunda.bpm.spring.boot.starter.security.oauth2.impl; + +import org.camunda.bpm.spring.boot.starter.security.oauth2.OAuth2Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +public class OAuth2GrantedAuthoritiesMapper implements GrantedAuthoritiesMapper { + + private static final Logger logger = LoggerFactory.getLogger(OAuth2GrantedAuthoritiesMapper.class); + private final OAuth2Properties oAuth2Properties; + + public OAuth2GrantedAuthoritiesMapper(OAuth2Properties oAuth2Properties) { + this.oAuth2Properties = oAuth2Properties; + } + + @Override + public Collection mapAuthorities(Collection authorities) { + var identityProviderProperties = oAuth2Properties.getIdentityProvider(); + var groupNameAttribute = identityProviderProperties.getGroupNameAttribute(); + Set mappedAuthorities = new HashSet<>(); + + authorities.forEach(authority -> { + if (authority instanceof OAuth2UserAuthority) { + var oauth2UserAuthority = (OAuth2UserAuthority) authority; + Object groupAttribute = oauth2UserAuthority.getAttributes().get(groupNameAttribute); + + if (groupAttribute == null) { + logger.debug("Attribute {} is not available", groupNameAttribute); + return; + } + + if (groupAttribute instanceof Collection) { + //noinspection unchecked + Collection groupsAttribute = (Collection) groupAttribute; + var grantedAuthorities = groupsAttribute.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + mappedAuthorities.addAll(grantedAuthorities); + } else if (groupAttribute instanceof String) { + String groupNameDelimiter = identityProviderProperties.getGroupNameDelimiter(); + String groupsAttribute = (String) groupAttribute; + + var grantedAuthorities = Arrays.stream(groupsAttribute.split(groupNameDelimiter)) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + mappedAuthorities.addAll(grantedAuthorities); + } else { + logger.error("Could not map granted authorities, unsupported group attribute type: {}", groupAttribute.getClass()); + } + } + }); + + logger.debug("Authorities mapped from {} to {}", authorities, mappedAuthorities); + return mappedAuthorities; + } + +} \ No newline at end of file diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProvider.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProvider.java new file mode 100644 index 00000000000..ef8ec9740b8 --- /dev/null +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProvider.java @@ -0,0 +1,395 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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 org.camunda.bpm.spring.boot.starter.security.oauth2.impl; + +import org.camunda.bpm.engine.identity.Group; +import org.camunda.bpm.engine.identity.GroupQuery; +import org.camunda.bpm.engine.identity.NativeUserQuery; +import org.camunda.bpm.engine.identity.Tenant; +import org.camunda.bpm.engine.identity.TenantQuery; +import org.camunda.bpm.engine.identity.User; +import org.camunda.bpm.engine.identity.UserQuery; +import org.camunda.bpm.engine.impl.GroupQueryImpl; +import org.camunda.bpm.engine.impl.Page; +import org.camunda.bpm.engine.impl.TenantQueryImpl; +import org.camunda.bpm.engine.impl.UserQueryImpl; +import org.camunda.bpm.engine.impl.identity.IdentityOperationResult; +import org.camunda.bpm.engine.impl.identity.IdentityProviderException; +import org.camunda.bpm.engine.impl.identity.db.DbIdentityServiceProvider; +import org.camunda.bpm.engine.impl.interceptor.CommandContext; +import org.camunda.bpm.engine.impl.persistence.entity.GroupEntity; +import org.camunda.bpm.engine.impl.persistence.entity.TenantEntity; +import org.camunda.bpm.engine.impl.persistence.entity.UserEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * OAuth2 identity provider with fallback for {@link DbIdentityServiceProvider} + * if the Spring security context doesn't contain an authenticated user. + *

+ * Since the fallback {@link DbIdentityServiceProvider} is a writeable provider + * this class is also writeable but with OAuth2 authentication it works effectively as a read-only provider. + */ +public class OAuth2IdentityProvider extends DbIdentityServiceProvider { + + private static final Logger logger = LoggerFactory.getLogger(OAuth2IdentityProvider.class); + + protected static void unsupportedOperationForOAuth2() { + throw new IdentityProviderException("This operation is not supported for OAuth2 identity provider."); + } + + protected static void unsupportedFilterForOAuth2() { + throw new IdentityProviderException("This filter is not supported for OAuth2 identity provider."); + } + + /** + * @param searchLike the like value to search for + * @param value the actual user attribute value + * @return true if either values are {@code null} or if {@code value} contains {@code searchLike} (case-insensitive) + */ + protected static boolean nullOrContainsIgnoreCase(String searchLike, String value) { + return searchLike == null || value == null || value.toLowerCase() + .contains(searchLike.replaceAll("%", "").toLowerCase()); + } + + /** + * @return true if user is authenticated in Spring security context + */ + protected boolean springSecurityAuthentication() { + var authentication = SecurityContextHolder.getContext().getAuthentication(); + boolean springSecurityAuthenticated = authentication != null && authentication.isAuthenticated(); + logger.debug("Using {}", springSecurityAuthenticated ? "OAuth2IdentityProvider" : "DbIdentityServiceProvider"); + return springSecurityAuthenticated; + } + + protected static UserEntity transformUser() { + var authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return null; + } + + Object principal = authentication.getPrincipal(); + String userId = authentication.getName(); + UserEntity user = new UserEntity(); + user.setId(userId); + if (principal instanceof OidcUser) { + var oidcUser = (OidcUser) principal; + user.setFirstName(oidcUser.getGivenName()); + user.setLastName(oidcUser.getFamilyName()); + user.setEmail(oidcUser.getEmail()); + } + return user; + } + + protected static List transformGroups() { + return SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().map(a -> { + var group = new GroupEntity(); + group.setId(a.getAuthority()); + group.setName(a.getAuthority()); + return group; + }).collect(Collectors.toList()); + } + + public static class OAuth2UserQuery extends UserQueryImpl { + @Override + public long executeCount(CommandContext commandContext) { + return 1; + } + + @Override + public List executeList(CommandContext commandContext, Page page) { + if (this.tenantId != null) { + unsupportedFilterForOAuth2(); + } + + return Stream.of(transformUser()) + .filter(Objects::nonNull) + .filter(u -> this.id == null || this.id.equals(u.getId())) + .filter(u -> this.ids == null || Arrays.stream(this.ids).anyMatch(id -> u.getId().equals(id))) + .filter(u -> this.firstName == null || this.firstName.equals(u.getFirstName())) + .filter(u -> nullOrContainsIgnoreCase(this.firstNameLike, u.getFirstName())) + .filter(u -> this.lastName == null || this.lastName.equals(u.getLastName())) + .filter(u -> nullOrContainsIgnoreCase(this.lastNameLike, u.getLastName())) + .filter(u -> this.email == null || this.email.equals(u.getEmail())) + .filter(u -> nullOrContainsIgnoreCase(this.emailLike, u.getEmail())) + .filter(u -> this.groupId == null || transformGroups().stream().anyMatch(g -> g.getId().equals(this.groupId))) + .collect(Collectors.toList()); + } + } + + @Override + public UserEntity findUserById(String userId) { + if (springSecurityAuthentication()) { + var user = transformUser(); + return user != null && Objects.equals(userId, user.getId()) ? user : null; + } else { + return super.findUserById(userId); + } + } + + @Override + public UserQuery createUserQuery() { + return springSecurityAuthentication() ? new OAuth2UserQuery() : super.createUserQuery(); + } + + @Override + public UserQueryImpl createUserQuery(CommandContext commandContext) { + return springSecurityAuthentication() ? new OAuth2UserQuery() : super.createUserQuery(commandContext); + } + + @Override + public NativeUserQuery createNativeUserQuery() { + if (springSecurityAuthentication()) { + unsupportedFilterForOAuth2(); + return null; + } else { + return super.createNativeUserQuery(); + } + } + + @Override + public boolean checkPassword(String userId, String password) { + return !springSecurityAuthentication() && super.checkPassword(userId, password); + } + + public static class OAuth2GroupQuery extends GroupQueryImpl { + + @Override + public long executeCount(CommandContext commandContext) { + return executeList(commandContext, null).size(); + } + + @Override + public List executeList(CommandContext commandContext, Page page) { + if (this.type != null || this.tenantId != null) { + unsupportedFilterForOAuth2(); + } + + return transformGroups().stream() + .filter(g -> this.id == null || this.id.equals(g.getId())) + .filter(g -> this.ids == null || Arrays.stream(this.ids).anyMatch(id -> g.getId().equals(id))) + .filter(g -> this.name == null || this.name.equals(g.getName())) + .filter(g -> nullOrContainsIgnoreCase(this.nameLike, g.getName())) + .filter(g -> { + var user = transformUser(); + return this.userId == null || user == null || this.userId.equals(user.getId()); + }) + .collect(Collectors.toList()); + } + } + + @Override + public GroupEntity findGroupById(String groupId) { + if (springSecurityAuthentication()) { + var groups = transformGroups(); + return (GroupEntity) groups.stream().filter(g -> g.getId().equals(groupId)).findFirst().orElse(null); + } else { + return super.findGroupById(groupId); + } + } + + @Override + public GroupQuery createGroupQuery() { + return springSecurityAuthentication() ? new OAuth2GroupQuery() : super.createGroupQuery(); + } + + @Override + public GroupQuery createGroupQuery(CommandContext commandContext) { + return springSecurityAuthentication() ? new OAuth2GroupQuery() : super.createGroupQuery(commandContext); + } + + public static class OAuth2TenantQuery extends TenantQueryImpl { + @Override + public long executeCount(CommandContext commandContext) { + return 0; + } + + @Override + public List executeList(CommandContext commandContext, Page page) { + return Collections.emptyList(); + } + } + + @Override + public TenantEntity findTenantById(String tenantId) { + return springSecurityAuthentication() ? null : super.findTenantById(tenantId); + } + + @Override + public TenantQuery createTenantQuery() { + return springSecurityAuthentication() ? new OAuth2TenantQuery() : super.createTenantQuery(); + } + + @Override + public TenantQuery createTenantQuery(CommandContext commandContext) { + return springSecurityAuthentication() ? new OAuth2TenantQuery() : super.createTenantQuery(); + } + + @Override + public void flush() { + if (!springSecurityAuthentication()) { + super.flush(); + } // else nothing to do + } + + @Override + public void close() { + if (!springSecurityAuthentication()) { + super.flush(); + } // else nothing to do + } + + // WriteableIdentityProvider methods + + @Override + public UserEntity createNewUser(String userId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.createNewUser(userId); + } + + @Override + public IdentityOperationResult saveUser(User user) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.saveUser(user); + } + + @Override + public IdentityOperationResult deleteUser(String userId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.deleteUser(userId); + } + + @Override + public IdentityOperationResult unlockUser(String userId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.unlockUser(userId); + } + + @Override + public GroupEntity createNewGroup(String groupId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.createNewGroup(groupId); + } + + @Override + public IdentityOperationResult saveGroup(Group group) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.saveGroup(group); + } + + @Override + public IdentityOperationResult deleteGroup(String groupId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.deleteGroup(groupId); + } + + @Override + public Tenant createNewTenant(String tenantId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.createNewTenant(tenantId); + } + + @Override + public IdentityOperationResult saveTenant(Tenant tenant) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.saveTenant(tenant); + } + + @Override + public IdentityOperationResult deleteTenant(String tenantId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.deleteTenant(tenantId); + } + + @Override + public IdentityOperationResult createMembership(String userId, String groupId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.createMembership(userId, groupId); + } + + @Override + public IdentityOperationResult deleteMembership(String userId, String groupId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.deleteMembership(userId, groupId); + } + + @Override + public IdentityOperationResult createTenantUserMembership(String tenantId, String userId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.createTenantUserMembership(tenantId, userId); + } + + @Override + public IdentityOperationResult createTenantGroupMembership(String tenantId, String groupId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.createTenantGroupMembership(tenantId, groupId); + } + + @Override + public IdentityOperationResult deleteTenantUserMembership(String tenantId, String userId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.deleteTenantUserMembership(tenantId, userId); + } + + @Override + public IdentityOperationResult deleteTenantGroupMembership(String tenantId, String groupId) { + if (springSecurityAuthentication()) { + unsupportedOperationForOAuth2(); + } + return super.deleteTenantGroupMembership(tenantId, groupId); + } + +} \ No newline at end of file diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java new file mode 100644 index 00000000000..c61f235f764 --- /dev/null +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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 org.camunda.bpm.spring.boot.starter.security.oauth2.impl; + +import org.camunda.bpm.engine.impl.identity.ReadOnlyIdentityProvider; +import org.camunda.bpm.engine.impl.interceptor.Session; +import org.camunda.bpm.engine.impl.interceptor.SessionFactory; + +public class OAuth2IdentityProviderFactory implements SessionFactory { + @Override + public Class getSessionType() { + return ReadOnlyIdentityProvider.class; + } + + @Override + public Session openSession() { + return new OAuth2IdentityProvider(); + } +} diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java new file mode 100644 index 00000000000..983f7fc4a2d --- /dev/null +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java @@ -0,0 +1,29 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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 org.camunda.bpm.spring.boot.starter.security.oauth2.impl; + +import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.camunda.bpm.engine.spring.SpringProcessEnginePlugin; + +public class OAuth2IdentityProviderPlugin extends SpringProcessEnginePlugin { + + @Override + public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) { + super.preInit(processEngineConfiguration); + processEngineConfiguration.setIdentityProviderSessionFactory(new OAuth2IdentityProviderFactory()); + } +} From 210933622c3579b7f2f5686e406d227e81514b17 Mon Sep 17 00:00:00 2001 From: Daniel Kelemen Date: Tue, 3 Sep 2024 17:01:53 +0200 Subject: [PATCH 2/3] change IdP to writeable --- .../security/oauth2/impl/OAuth2IdentityProviderFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java index c61f235f764..7f16b7e3f01 100644 --- a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderFactory.java @@ -16,14 +16,14 @@ */ package org.camunda.bpm.spring.boot.starter.security.oauth2.impl; -import org.camunda.bpm.engine.impl.identity.ReadOnlyIdentityProvider; +import org.camunda.bpm.engine.impl.identity.WritableIdentityProvider; import org.camunda.bpm.engine.impl.interceptor.Session; import org.camunda.bpm.engine.impl.interceptor.SessionFactory; public class OAuth2IdentityProviderFactory implements SessionFactory { @Override public Class getSessionType() { - return ReadOnlyIdentityProvider.class; + return WritableIdentityProvider.class; } @Override From 28710dbf4947facc1e34b343302fa0c7c12fcff4 Mon Sep 17 00:00:00 2001 From: Tassilo Weidner <3015690+tasso94@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:44:11 +0200 Subject: [PATCH 3/3] wip --- .../oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java | 1 + .../security/oauth2/impl/OAuth2IdentityProviderPlugin.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java index 143ca39e8b8..7f8fd40ec77 100644 --- a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/CamundaSpringSecurityOAuth2AutoConfiguration.java @@ -108,6 +108,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .oauth2Login(Customizer.withDefaults()) .oidcLogout(Customizer.withDefaults()) .oauth2Client(Customizer.withDefaults()) + .cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable); return http.build(); diff --git a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java index 983f7fc4a2d..8d70926a7d6 100644 --- a/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java +++ b/spring-boot-starter/starter-security/src/main/java/org/camunda/bpm/spring/boot/starter/security/oauth2/impl/OAuth2IdentityProviderPlugin.java @@ -17,9 +17,9 @@ package org.camunda.bpm.spring.boot.starter.security.oauth2.impl; import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; -import org.camunda.bpm.engine.spring.SpringProcessEnginePlugin; +import org.camunda.bpm.spring.boot.starter.util.SpringBootProcessEnginePlugin; -public class OAuth2IdentityProviderPlugin extends SpringProcessEnginePlugin { +public class OAuth2IdentityProviderPlugin extends SpringBootProcessEnginePlugin { @Override public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {