Skip to content

Commit

Permalink
feat(run): add oauth2 identity provider (#4570)
Browse files Browse the repository at this point in the history
related to camunda/camunda-bpm-platform#4453

Backported commit a485ee9828 from the camunda-bpm-platform repository.
Original author: Daniel Kelemen <[email protected]>
  • Loading branch information
hauptmedia authored and javahippie committed Nov 8, 2024
1 parent 45aa0a7 commit 002a043
Show file tree
Hide file tree
Showing 6 changed files with 649 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -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.operaton.bpm.spring.boot.starter.security.oauth2;

import org.operaton.bpm.spring.boot.starter.security.oauth2.impl.OAuth2IdentityProvider;
import org.operaton.bpm.spring.boot.starter.property.OperatonBpmProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(OAuth2Properties.PREFIX)
public class OAuth2Properties {

public static final String PREFIX = OperatonBpmProperties.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,27 @@
import org.operaton.bpm.spring.boot.starter.OperatonBpmAutoConfiguration;
import org.operaton.bpm.spring.boot.starter.property.OperatonBpmProperties;
import org.operaton.bpm.spring.boot.starter.property.WebappProperty;
import org.operaton.bpm.spring.boot.starter.security.oauth2.impl.OAuth2GrantedAuthoritiesMapper;
import org.operaton.bpm.spring.boot.starter.security.oauth2.impl.OAuth2IdentityProviderPlugin;
import org.operaton.bpm.spring.boot.starter.security.oauth2.impl.OAuth2AuthenticationProvider;
import org.operaton.bpm.webapp.impl.security.auth.ContainerBasedAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import org.springframework.core.Ordered;
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;
Expand All @@ -47,13 +52,17 @@
@AutoConfigureAfter({ OperatonBpmAutoConfiguration.class, SpringProcessEngineServicesConfiguration.class })
@ConditionalOnBean(OperatonBpmProperties.class)
@Conditional(ClientsConfiguredCondition.class)
@EnableConfigurationProperties(OAuth2Properties.class)
public class OperatonSpringSecurityOAuth2AutoConfiguration {

private static final Logger logger = LoggerFactory.getLogger(OperatonSpringSecurityOAuth2AutoConfiguration.class);
public static final int CAMUNDA_OAUTH2_ORDER = Ordered.HIGHEST_PRECEDENCE + 100;
private final OAuth2Properties oAuth2Properties;
private final String webappPath;

public OperatonSpringSecurityOAuth2AutoConfiguration(OperatonBpmProperties properties) {
public OperatonSpringSecurityOAuth2AutoConfiguration(OperatonBpmProperties properties,
OAuth2Properties oAuth2Properties) {
this.oAuth2Properties = oAuth2Properties;
WebappProperty webapp = properties.getWebapp();
this.webappPath = webapp.getApplicationPath();
}
Expand All @@ -71,19 +80,35 @@ 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 Operaton 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())
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable);

return http.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.operaton.bpm.spring.boot.starter.security.oauth2.impl;

import org.operaton.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<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
var identityProviderProperties = oAuth2Properties.getIdentityProvider();
var groupNameAttribute = identityProviderProperties.getGroupNameAttribute();
Set<GrantedAuthority> 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<String> groupsAttribute = (Collection<String>) 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;
}

}
Loading

0 comments on commit 002a043

Please sign in to comment.