From 248272b36062b097039d7de016b98c143302db62 Mon Sep 17 00:00:00 2001 From: Marek Kasztelnik Date: Thu, 6 Jul 2017 16:05:03 +0200 Subject: [PATCH 1/2] Secure slam REST API using Bearer token --- .../ltos/security/AuthenticationService.java | 129 ++++++++++++++++++ .../ltos/security/ClientApplication.java | 7 +- .../OpenIDConnectAuthenticationFilter.java | 117 +--------------- .../security/RestAuthenticationFilter.java | 57 ++++++++ .../ltos/security/SecurityConfig.java | 44 ++++-- 5 files changed, 226 insertions(+), 128 deletions(-) create mode 100644 src/main/java/pl/cyfronet/ltos/security/AuthenticationService.java create mode 100644 src/main/java/pl/cyfronet/ltos/security/RestAuthenticationFilter.java diff --git a/src/main/java/pl/cyfronet/ltos/security/AuthenticationService.java b/src/main/java/pl/cyfronet/ltos/security/AuthenticationService.java new file mode 100644 index 0000000..e49b086 --- /dev/null +++ b/src/main/java/pl/cyfronet/ltos/security/AuthenticationService.java @@ -0,0 +1,129 @@ +package pl.cyfronet.ltos.security; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.net.ssl.HttpsURLConnection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.stereotype.Service; + +import com.agreemount.bean.identity.Identity; +import com.agreemount.bean.identity.provider.IdentityProvider; + +import pl.cyfronet.ltos.bean.Role; +import pl.cyfronet.ltos.bean.User; +import pl.cyfronet.ltos.repository.UserRepository; +import pl.cyfronet.ltos.security.AuthenticationProviderDev.UserOperations; + +@Service +public class AuthenticationService { + private static final Logger log = LoggerFactory.getLogger(AuthenticationService.class); + + @Value("${unity.server.base}") + private String authorizeUrl; + + @Value("${unity.server.userInfoAction}") + private String userInfoAction; + + @Value("${hostname.verification}") + private Boolean hostnameVerification; + + @Autowired + private OAuth2RestOperations restTemplate; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserOperations userOperations; + + @Autowired + IdentityProvider identityProvider; + + //development variable, users whose email matches this will have provider role assigned + @Value("${provider.email:null}") + private String providerEmail; + + public void engineLogin(User user) { + Identity identity = new Identity(); + identity.setLogin(user.getEmail()); + identity.setRoles(user.getRoles().stream().map(entry -> entry.getName()).collect(Collectors.toList())); + + + identityProvider.setIdentity(identity); + } + + public PortalUser getPortalUser() { + return getPortalUser(getUserInfo()); + } + + private PortalUser getPortalUser(UserInfo userInfo) { + User user = getUser(userInfo); + userInfo.setId(user.getId()); + + PortalUser.PortalUserBuilder builder = PortalUser.builder(); + builder.isAuthenticated(true); + builder.user(user); + builder.authorities(getRoles(user)); + builder.principal(userInfo); + + return builder.build(); + } + + private UserInfo getUserInfo() { + if (hostnameVerification.equals(false)) { + HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> { + log.warn("Hostname verification is disabled!!!"); + return true; + }); + } + + return restTemplate.getForObject(authorizeUrl + userInfoAction, UserInfo.class); + } + + private User getUser(UserInfo userInfo) { + User user = userRepository.findByEmail(userInfo.getEmail()); + if (user == null) { + user = User.builder().name(userInfo.getName()).email(userInfo.getEmail()) + .organisationName(userInfo.getOrganisation_name()) + .roles(Arrays.asList(userOperations.loadOrCreateRoleByName("manager"))).build(); + + userRepository.save(user); + } + + Role providerRole = userOperations.loadOrCreateRoleByName("provider"); + if (user.getEmail().equals(providerEmail) && !user.hasRole("provider")) { + if (!user.getRoles().contains(providerRole)) { + user.getRoles().add(providerRole); + userRepository.save(user); + } + } else { // remove if not in settings + ArrayList elementsToRemove = new ArrayList<>(); + for (Role role : user.getRoles()) { + if (role.getName().equals(providerRole.getName())) { + elementsToRemove.add(role); + } + } + if (elementsToRemove.size() > 0) { + user.getRoles().removeAll(elementsToRemove); + userRepository.save(user); + } + } + + return user; + } + + private List getRoles(User user) { + return user.getRoles().stream() + .map(r -> new SimpleGrantedAuthority("ROLE_" + r.getName().toUpperCase())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/pl/cyfronet/ltos/security/ClientApplication.java b/src/main/java/pl/cyfronet/ltos/security/ClientApplication.java index 1a6049d..cf0755d 100644 --- a/src/main/java/pl/cyfronet/ltos/security/ClientApplication.java +++ b/src/main/java/pl/cyfronet/ltos/security/ClientApplication.java @@ -1,5 +1,9 @@ package pl.cyfronet.ltos.security; +import java.util.List; + +import javax.annotation.Resource; + import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,9 +16,6 @@ import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; -import javax.annotation.Resource; -import java.util.List; - /** * Created by km on 04.08.16. */ diff --git a/src/main/java/pl/cyfronet/ltos/security/OpenIDConnectAuthenticationFilter.java b/src/main/java/pl/cyfronet/ltos/security/OpenIDConnectAuthenticationFilter.java index 4658015..d05be84 100644 --- a/src/main/java/pl/cyfronet/ltos/security/OpenIDConnectAuthenticationFilter.java +++ b/src/main/java/pl/cyfronet/ltos/security/OpenIDConnectAuthenticationFilter.java @@ -1,12 +1,7 @@ package pl.cyfronet.ltos.security; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import javax.net.ssl.HttpsURLConnection; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -15,8 +10,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; import org.springframework.security.web.DefaultRedirectStrategy; @@ -24,47 +17,21 @@ import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import com.agreemount.bean.identity.Identity; import com.agreemount.bean.identity.provider.IdentityProvider; -import com.google.common.base.Preconditions; - -import pl.cyfronet.ltos.bean.Role; -import pl.cyfronet.ltos.bean.User; -import pl.cyfronet.ltos.repository.UserRepository; -import pl.cyfronet.ltos.security.AuthenticationProviderDev.UserOperations; /** * Created by km on 04.08.16. */ public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - @Value("${unity.server.base}") - private String authorizeUrl; - @Value("${unity.unauthorizedAction}") private String unauthorizedAction; - @Value("${hostname.verification}") - private Boolean hostnameVerification; - - @Value("${unity.server.userInfoAction}") - private String userInfoAction; - - @Autowired - private OAuth2RestOperations restTemplate; - @Autowired IdentityProvider identityProvider; @Autowired - private UserRepository userRepository; - - @Autowired - private UserOperations userOperations; - - //development variable, users whose email matches this will have provider role assigned - @Value("${provider.email:null}") - private String providerEmail; + private AuthenticationService authenticationService; protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) { super(defaultFilterProcessesUrl); @@ -82,89 +49,17 @@ protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { - logger.error(request.toString() + " COKOLWIEK " + response.toString()); - if (hostnameVerification.equals(false)) { - HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> { - logger.warn("Hostname verification is disabled!!!"); - return true; - }); - } - - PortalUser.PortalUserBuilder builder = PortalUser.builder(); try { - //TODO: poprawne odparsowanie i debugowanie - UserInfo userInfo = restTemplate.getForObject(authorizeUrl + userInfoAction, UserInfo.class); - builder.isAuthenticated(true); - User user = userRepository.findByEmail(userInfo.getEmail()); - if (user == null) { - user = User.builder().name(userInfo.getName()).email(userInfo.getEmail()) - .organisationName(userInfo.getOrganisation_name()) - .roles(Arrays.asList(userOperations.loadOrCreateRoleByName("manager"))) - .build(); - - userRepository.save(user); - } - Role providerRole = userOperations.loadOrCreateRoleByName("provider"); - if (user.getEmail().equals(providerEmail) && !user.hasRole("provider")){ - if (!user.getRoles().contains(providerRole)) { - user.getRoles().add(providerRole); - userRepository.save(user); - } - } - else { //remove if not in settings - ArrayList elementsToRemove = new ArrayList<>(); - for (Role role : user.getRoles()){ - if (role.getName().equals(providerRole.getName())){ - elementsToRemove.add(role); - } - } - if(elementsToRemove.size() > 0) { - user.getRoles().removeAll(elementsToRemove); - userRepository.save(user); - } - } - - builder.user(user); - userInfo.setId(user.getId()); - List authorities = new ArrayList<>(); - - for(Role role : user.getRoles()) { - authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName().toUpperCase())); - } - - builder.authorities(authorities); - - Identity identity = getIdentity(user); - Preconditions.checkNotNull(identity, "Identity [%s] was not found", user.getEmail()); - identityProvider.setIdentity(identity); - - builder.principal(userInfo); - logger.error("userinfo: "+userInfo.toString()); + PortalUser portalUser = authenticationService.getPortalUser(); + authenticationService.engineLogin(portalUser.getUserBean()); + + return portalUser; } catch (UserDeniedAuthorizationException | InvalidRequestException ex) { RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); redirectStrategy.sendRedirect(request, response, unauthorizedAction); } - return builder.build(); - } - - public Identity getIdentity(User user) { - Identity identity = new Identity(); - identity.setLogin(user.getEmail()); - List roles = user.getRoles().stream().map(entry -> entry.getName()).collect(Collectors.toList()); - identity.setRoles(roles); -// List teams = user.getTeamMemberships(); -// List teamMembers = new LinkedList(); -// if (teams != null) { -// for (TeamMembership team : teams) { -// for (TeamRole role : team.getTeamRoles()) { -// TeamMember teamMember = new TeamMember(role.getName(), team.getTeam().getName()); -// teamMembers.add(teamMember); -// } -// } -// } -// identity.setTeamMembers(teamMembers); - return identity; + return null; } } diff --git a/src/main/java/pl/cyfronet/ltos/security/RestAuthenticationFilter.java b/src/main/java/pl/cyfronet/ltos/security/RestAuthenticationFilter.java new file mode 100644 index 0000000..5551b8a --- /dev/null +++ b/src/main/java/pl/cyfronet/ltos/security/RestAuthenticationFilter.java @@ -0,0 +1,57 @@ +package pl.cyfronet.ltos.security; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor; +import org.springframework.web.filter.OncePerRequestFilter; + +public class RestAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger log = LoggerFactory.getLogger(RestAuthenticationFilter.class); + + @Autowired + private OAuth2ClientContext context; + + @Autowired + private AuthenticationService authenticationService; + + private BearerTokenExtractor extractor = new BearerTokenExtractor(); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + tryToAuthorize(request); + filterChain.doFilter(request, response); + } + + private void tryToAuthorize(HttpServletRequest request) { + try { + Authentication extract = extractor.extract(request); + if (extract != null) { + String jwt = extract.getPrincipal().toString(); + context.setAccessToken(new DefaultOAuth2AccessToken(jwt)); + + PortalUser portalUser = authenticationService.getPortalUser(); + + authenticationService.engineLogin(portalUser.getUserBean()); + SecurityContextHolder.getContext().setAuthentication(portalUser); + } + } catch(Exception e) { + log.info("Unable to authenticate to rest API - wrong token", e); + } + } +} + diff --git a/src/main/java/pl/cyfronet/ltos/security/SecurityConfig.java b/src/main/java/pl/cyfronet/ltos/security/SecurityConfig.java index 1bcea4f..be39c67 100644 --- a/src/main/java/pl/cyfronet/ltos/security/SecurityConfig.java +++ b/src/main/java/pl/cyfronet/ltos/security/SecurityConfig.java @@ -9,7 +9,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; @@ -22,46 +24,60 @@ @Profile("production") @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity +@EnableOAuth2Client public class SecurityConfig { - + @Configuration - @Order(1) + @Order(1) public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { - protected void configure(HttpSecurity http) throws Exception { - http - .antMatcher("/rest/slam/**") - .authorizeRequests().anyRequest().permitAll(); + @Bean + public OAuth2ClientContextFilter oAuth2ClientContextFilter() { + return new OAuth2ClientContextFilter(); + } + + @Bean + public RestAuthenticationFilter restAuthenticationFilter() { + return new RestAuthenticationFilter(); + } + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .antMatcher("/rest/slam/**") + .authorizeRequests().anyRequest().authenticated() + .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and().csrf().disable() + .addFilterAfter(restAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class); } } - + @Configuration public static class UiWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { - + @Value("${unity.entryPointUnityUrl}") protected String entryPointUnityUrl; - + @Value("${unity.entryPointAuthUrl}") protected String entryPointAuthUrl; - + @Value("${unity.unauthorizedAction}") protected String unauthorizedAction; - + @Bean public AuthenticationEntryPoint authenticationEntryPoint() { return new LoginUrlAuthenticationEntryPoint(entryPointAuthUrl); } - + @Bean public OpenIDConnectAuthenticationFilter openIdConnectAuthenticationFilter() { return new OpenIDConnectAuthenticationFilter(entryPointUnityUrl); } - + @Bean public OAuth2ClientContextFilter oAuth2ClientContextFilter() { return new OAuth2ClientContextFilter(); } - + @Override protected void configure(HttpSecurity http) throws Exception { http From c2085cc8b6583be46f815be7a0fe26acbcc5e404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szostak?= Date: Mon, 24 Jul 2017 15:53:10 +0200 Subject: [PATCH 2/2] Fixed messages yml file so it does not break the system --- src/main/resources/rules/messages.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) mode change 100755 => 100644 src/main/resources/rules/messages.yml diff --git a/src/main/resources/rules/messages.yml b/src/main/resources/rules/messages.yml old mode 100755 new mode 100644 index 2ab4b41..8d41137 --- a/src/main/resources/rules/messages.yml +++ b/src/main/resources/rules/messages.yml @@ -1,15 +1,14 @@ messages: -# - !com.agreemount.slaneg.message.Message -# id: sampleMsg #unique identifier -# type: success #type of message, string -# body: thisIsDraft -# constraint: -# !com.agreemount.slaneg.constraint.action.definition.Operator -# type: AND -# children: -# - !com.agreemount.slaneg.constraint.action.definition.StateEqualsDef -# negation: true #always visible -# state: asfsd -# value: drfasdfsdafaft + - !com.agreemount.slaneg.message.Message + id: sampleMsg #unique identifier + type: success #type of message, string + body: thisIsDraft + constraint: + !com.agreemount.slaneg.constraint.action.definition.Operator + type: AND + children: + - !com.agreemount.slaneg.constraint.action.definition.StateEqualsDef + state: asfsd + value: sample_value