package com.sequenceiq.cloudbreak.conf; import static com.sequenceiq.cloudbreak.api.CoreApi.API_ROOT_CONTEXT; import java.io.IOException; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jasypt.encryption.pbe.PBEStringCleanablePasswordEncryptor; import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.web.filter.OncePerRequestFilter; import com.sequenceiq.cloudbreak.domain.CbUser; import com.sequenceiq.cloudbreak.service.user.UserDetailsService; import com.sequenceiq.cloudbreak.service.user.UserFilterField; @Configuration public class SecurityConfig { @EnableGlobalMethodSecurity(prePostEnabled = true) protected static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Inject private OwnerBasedPermissionEvaluator ownerBasedPermissionEvaluator; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { OAuth2MethodSecurityExpressionHandler expressionHandler = new OAuth2MethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(ownerBasedPermissionEvaluator); return expressionHandler; } } @Configuration @EnableResourceServer protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { private static final String[] BLUEPRINT_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/blueprints/**"}; private static final String[] TEMPLATE_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/templates/**"}; private static final String[] CREDENTIAL_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/credentials/**"}; private static final String[] RECIPE_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/recipes/**"}; private static final String[] NETWORK_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/networks/**"}; private static final String[] SECURITYGROUP_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/securitygroups/**"}; private static final String[] STACK_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/stacks/**"}; private static final String[] STACK_TEMPLATE_URL_PATTERNS = new String[]{API_ROOT_CONTEXT + "/clustertemplates/**"}; private static final String ACCOUNT_PREFERENCES = API_ROOT_CONTEXT + "/accountpreferences/**"; @Value("${cb.client.id}") private String clientId; @Value("${cb.client.secret}") private String clientSecret; @Inject @Named("identityServerUrl") private String identityServerUrl; @Inject private UserDetailsService userDetailsService; @Bean RemoteTokenServices remoteTokenServices() { RemoteTokenServices rts = new RemoteTokenServices(); rts.setClientId(clientId); rts.setClientSecret(clientSecret); rts.setCheckTokenEndpointUrl(identityServerUrl + "/check_token"); return rts; } @Bean PBEStringCleanablePasswordEncryptor encryptor() { StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setPassword(clientSecret); return encryptor; } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("cloudbreak"); resources.tokenServices(remoteTokenServices()); } @Override public void configure(HttpSecurity http) throws Exception { http.addFilterAfter(new ScimAccountGroupReaderFilter(userDetailsService), AbstractPreAuthenticatedProcessingFilter.class) .authorizeRequests() .antMatchers(HttpMethod.GET, BLUEPRINT_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.blueprints.read') or #oauth2.hasScope('cloudbreak.blueprints')") .antMatchers(HttpMethod.GET, STACK_TEMPLATE_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.stacks.read') or #oauth2.hasScope('cloudbreak.stacks')") .antMatchers(HttpMethod.GET, TEMPLATE_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.templates.read') or #oauth2.hasScope('cloudbreak.templates')") .antMatchers(HttpMethod.GET, CREDENTIAL_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.credentials.read') or #oauth2.hasScope('cloudbreak.credentials')") .antMatchers(HttpMethod.GET, RECIPE_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.recipes.read') or #oauth2.hasScope('cloudbreak.recipes')") .antMatchers(HttpMethod.GET, NETWORK_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.networks.read') or #oauth2.hasScope('cloudbreak.networks')") .antMatchers(HttpMethod.GET, SECURITYGROUP_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.securitygroups.read') or #oauth2.hasScope('cloudbreak.securitygroups')") .antMatchers(HttpMethod.GET, STACK_URL_PATTERNS) .access("#oauth2.hasScope('cloudbreak.stacks.read') or #oauth2.hasScope('cloudbreak.stacks')" + " or #oauth2.hasScope('cloudbreak.autoscale')") .antMatchers(HttpMethod.GET, ACCOUNT_PREFERENCES) .permitAll() .antMatchers(API_ROOT_CONTEXT + "/users/**").access("#oauth2.hasScope('openid')") .antMatchers(BLUEPRINT_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.blueprints')") .antMatchers(TEMPLATE_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.templates')") .antMatchers(CREDENTIAL_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.credentials')") .antMatchers(RECIPE_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.recipes')") .antMatchers(NETWORK_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.networks')") .antMatchers(SECURITYGROUP_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.securitygroups')") .antMatchers(STACK_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.stacks') or #oauth2.hasScope('cloudbreak.autoscale')") .antMatchers(STACK_TEMPLATE_URL_PATTERNS).access("#oauth2.hasScope('cloudbreak.stacks')") .antMatchers(ACCOUNT_PREFERENCES) .access("#oauth2.hasScope('cloudbreak.templates') and #oauth2.hasScope('cloudbreak.stacks')") .antMatchers(API_ROOT_CONTEXT + "/stacks/ambari", API_ROOT_CONTEXT + "/stacks/*/certificate", API_ROOT_CONTEXT + "/stacks/all") .access("#oauth2.hasScope('cloudbreak.autoscale')") .antMatchers(API_ROOT_CONTEXT + "/events").access("#oauth2.hasScope('cloudbreak.events')") .antMatchers(API_ROOT_CONTEXT + "/usages/account/**").access("#oauth2.hasScope('cloudbreak.usages.account')") .antMatchers(API_ROOT_CONTEXT + "/usages/user/**").access("#oauth2.hasScope('cloudbreak.usages.user')") .antMatchers(API_ROOT_CONTEXT + "/usages/flex/**").access("#oauth2.hasScope('cloudbreak.flex')") .antMatchers(API_ROOT_CONTEXT + "/usages/**").access("#oauth2.hasScope('cloudbreak.usages.global')") .antMatchers(API_ROOT_CONTEXT + "/subscriptions").access("#oauth2.hasScope('cloudbreak.subscribe')") .antMatchers(API_ROOT_CONTEXT + "/constraints/**") .access("#oauth2.hasScope('cloudbreak.stacks') or #oauth2.hasScope('cloudbreak.autoscale')") .antMatchers(API_ROOT_CONTEXT + "/topologies/**") .access("#oauth2.hasScope('cloudbreak.stacks') or #oauth2.hasScope('cloudbreak.autoscale')") .antMatchers(API_ROOT_CONTEXT + "/settings/**") .access("#oauth2.hasScope('cloudbreak.stacks') or #oauth2.hasScope('cloudbreak.recipes')") .antMatchers(API_ROOT_CONTEXT + "/sssd/**") .access("#oauth2.hasScope('cloudbreak.stacks') or #oauth2.hasScope('cloudbreak.recipes')") .antMatchers(API_ROOT_CONTEXT + "/ldap/**").access("#oauth2.hasScope('cloudbreak.stacks')") .antMatchers(API_ROOT_CONTEXT + "/util/**").access("#oauth2.hasScope('cloudbreak.stacks')") .antMatchers(API_ROOT_CONTEXT + "/rdsconfigs/**").access("#oauth2.hasScope('cloudbreak.stacks')") .antMatchers(API_ROOT_CONTEXT + "/smartsensesubscriptions/**").access("#oauth2.hasScope('cloudbreak.stacks')") .antMatchers(API_ROOT_CONTEXT + "/flexsubscriptions/**").access("#oauth2.hasScope('cloudbreak.stacks')") .antMatchers(API_ROOT_CONTEXT + "/swagger.json").permitAll() .antMatchers(API_ROOT_CONTEXT + "/api-docs/**").permitAll() .antMatchers(API_ROOT_CONTEXT + "/connectors/**").permitAll() .antMatchers(API_ROOT_CONTEXT + "/**").denyAll(); http.csrf().disable(); http.headers().contentTypeOptions(); } } private static class ScimAccountGroupReaderFilter extends OncePerRequestFilter { private UserDetailsService userDetailsService; ScimAccountGroupReaderFilter(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @SuppressWarnings("unchecked") @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { OAuth2Authentication oauth = (OAuth2Authentication) authentication; if (oauth.getUserAuthentication() != null) { String username = (String) authentication.getPrincipal(); CbUser user = userDetailsService.getDetails(username, UserFilterField.USERNAME); request.setAttribute("user", user); } } filterChain.doFilter(request, response); } } }