From 2b7084920c5e49cd35b37adf1b3c619e10db30d6 Mon Sep 17 00:00:00 2001
From: jerrypan44 <jerrypan44@gmail.com>
Date: Fri, 22 Nov 2019 15:35:02 +0000
Subject: [PATCH] WIP committed working features roles and keycloak integration

---
 docker/config/application.tmpl                |   5 +-
 pom.xml                                       |   6 -
 .../configuration/KeycloakConfiguration.java  |  14 -
 .../configuration/SecurityConfiguration.java  | 417 +++++++++---------
 .../eu/hbp/mip/controllers/SecurityApi.java   |   2 +
 5 files changed, 213 insertions(+), 231 deletions(-)
 delete mode 100644 src/main/java/eu/hbp/mip/configuration/KeycloakConfiguration.java

diff --git a/docker/config/application.tmpl b/docker/config/application.tmpl
index e3a624a13..c05c497c1 100644
--- a/docker/config/application.tmpl
+++ b/docker/config/application.tmpl
@@ -30,11 +30,11 @@ hbp:
     clientSecret: {{ .Env.CLIENT_SECRET }}
     accessTokenUri: {{ default .Env.TOKEN_URI "https://services.humanbrainproject.eu/oidc/token" }}
     userAuthorizationUri: {{ default .Env.AUTH_URI "https://services.humanbrainproject.eu/oidc/authorize" }}
-    tokenName: oauth_token
+    tokenName: access_token
     authenticationScheme: query
     clientAuthenticationScheme: form
     useCurrentUri: false
-    preEstablishedRedirectUri: {{ default .Env.FRONTEND_LOGIN_URL "http://frontend/services/login/hbp" }}
+    preEstablishedRedirectUri: {{ default .Env.FRONTEND_LOGIN_URL "http://localhost:8080/services/login/hbp" }}
   resource:
     userInfoUri: {{ default .Env.USER_INFO_URI "https://services.humanbrainproject.eu/oidc/userinfo" }}
     revokeTokenUri: {{ default .Env.REVOKE_TOKEN_URI "https://services.humanbrainproject.eu/oidc/slo" }}
@@ -52,6 +52,7 @@ logging:
     root: {{ default .Env.LOG_LEVEL "INFO" }}
     org:
       springframework:
+        security: DEBUG
         web: {{ default .Env.LOGGING_LEVEL_WEB "WARN" }}
         web.servlet.handler.BeanNameUrlHandlerMapping: WARN
       hibernate: {{ default .Env.LOGGING_LEVEL_HIBERNATE "WARN" }}
diff --git a/pom.xml b/pom.xml
index d206d2ad2..f0fbbe9dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -255,12 +255,6 @@
             <artifactId>java-jwt</artifactId>
             <version>3.8.3</version>
         </dependency>
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-spring-boot-starter</artifactId>
-        </dependency>
-
-
     </dependencies>
 
     <build>
diff --git a/src/main/java/eu/hbp/mip/configuration/KeycloakConfiguration.java b/src/main/java/eu/hbp/mip/configuration/KeycloakConfiguration.java
deleted file mode 100644
index 45c598924..000000000
--- a/src/main/java/eu/hbp/mip/configuration/KeycloakConfiguration.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package eu.hbp.mip.configuration;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import org.keycloak.KeycloakSecurityContext;
-
-public class KeycloakConfiguration {
-
-    @Autowired
-    private HttpServletRequest request;
-    public KeycloakSecurityContext getKeycloakSecurityContext() {
-        return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/hbp/mip/configuration/SecurityConfiguration.java b/src/main/java/eu/hbp/mip/configuration/SecurityConfiguration.java
index ba6a380de..b8259bca6 100644
--- a/src/main/java/eu/hbp/mip/configuration/SecurityConfiguration.java
+++ b/src/main/java/eu/hbp/mip/configuration/SecurityConfiguration.java
@@ -8,6 +8,7 @@ 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.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
 import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
 import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
 import org.springframework.boot.context.embedded.FilterRegistrationBean;
@@ -34,6 +35,10 @@ import org.springframework.security.web.csrf.CsrfTokenRepository;
 import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.util.WebUtils;
+import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -42,223 +47,217 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 // See https://spring.io/guides/tutorials/spring-boot-oauth2/ for reference about configuring OAuth2 login
 // also http://cscarioni.blogspot.ch/2013/04/pro-spring-security-and-oauth-2.html
 
-/**
- * Configuration for security.
- */
-@KeycloakConfiguration
-public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
-    @Autowired
-    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
-        auth.authenticationProvider(keycloakAuthenticationProvider());
-    }
+@Configuration
+@EnableOAuth2Client
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
-    /**
-     * Defines the session authentication strategy.
-     */
-    @Bean
-    @Override
-    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
-        return new NullAuthenticatedSessionStrategy();
-    }
+   private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfiguration.class);
 
-    @Override
-    protected void configure(HttpSecurity http) throws Exception
-    {
-        super.configure(http);
-        http
-                .authorizeRequests()
-                .antMatchers("/user*").authenticated()
-                .antMatchers("/public1*").hasAuthority("public13")
-                .antMatchers("/public2*").hasAuthority("public2")
-                .antMatchers("/public3*").hasAuthority("public3")
-                .antMatchers("/admin*").authenticated()
-                .anyRequest().permitAll()
-                .and()
-                .logout()
-                .addLogoutHandler(keycloakLogoutHandler())//.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
-                .logoutUrl("/logout").permitAll()
-                .logoutSuccessUrl("/");
-//        .anyRequest().permitAll().and().logout().addLogoutHandler(new KeycloakLogoutHandler(new RestTemplate())).logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
-        ;
-    }
+   @Autowired
+   private OAuth2ClientContext oauth2ClientContext;
 
-}
+   /**
+    * Enable HBP collab authentication (1) or disable it (0). Default is 1
+    */
+   @Value("#{'${hbp.authentication.enabled:1}'}")
+   private boolean authentication;
+
+   /**
+    * Absolute URL to redirect to when login is required
+    */
+   @Value("#{'${frontend.loginUrl:/login/hbp}'}")
+   private String loginUrl;
+
+   /**
+    * Absolute URL to redirect to after successful login
+    */
+   @Value("#{'${frontend.redirectAfterLoginUrl:http://frontend/home}'}")
+   private String frontendRedirectAfterLogin;
+
+   /**
+    * Absolute URL to redirect to after logout has occurred
+    */
+   @Value("#{'${frontend.redirectAfterLogoutUrl:/login/hbp}'}")
+   private String redirectAfterLogoutUrl;
+
+   /**
+    * URL to revoke auth token
+    */
+   @Value("#{'${hbp.resource.revokeTokenUri:https://services.humanbrainproject.eu/oidc/revoke}'}")
+   private String revokeTokenURI;
 
-//@Configuration
-//@EnableOAuth2Client
-//public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
-//
-//    private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfiguration.class);
-//
 //    @Autowired
-//    private OAuth2ClientContext oauth2ClientContext;
-//
-//    /**
-//     * Enable HBP collab authentication (1) or disable it (0). Default is 1
-//     */
-//    @Value("#{'${hbp.authentication.enabled:1}'}")
-//    private boolean authentication;
-//
-//    /**
-//     * Absolute URL to redirect to when login is required
-//     */
-//    @Value("#{'${frontend.loginUrl:/login/hbp}'}")
-//    private String loginUrl;
-//
-//    /**
-//     * Absolute URL to redirect to after successful login
-//     */
-//    @Value("#{'${frontend.redirectAfterLoginUrl:http://frontend/home}'}")
-//    private String frontendRedirectAfterLogin;
-//
-//    /**
-//     * Absolute URL to redirect to after logout has occurred
-//     */
-//    @Value("#{'${frontend.redirectAfterLogoutUrl:/login/hbp}'}")
-//    private String redirectAfterLogoutUrl;
-//
-//    /**
-//     * URL to revoke auth token
-//     */
-//    @Value("#{'${hbp.resource.revokeTokenUri:https://services.humanbrainproject.eu/oidc/revoke}'}")
-//    private String revokeTokenURI;
-//
-////    @Autowired
-////    private HttpServletRequest request;
-//
-//    @Override
-//    protected void configure(HttpSecurity http) throws Exception {
-//        // @formatter:off
-//        http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
-//
-//        if (authentication) {
+//    private HttpServletRequest request;
+
+   @Override
+   protected void configure(HttpSecurity http) throws Exception {
+       // @formatter:off
+       http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
+
+       if (authentication) {
+           http.antMatcher("/**")
+                   .authorizeRequests()
+                   .antMatchers(
+                           "/", "/login/**", "/health/**", "/info/**", "/metrics/**", "/trace/**", "/frontend/**", "/webjars/**", "/v2/api-docs", "/swagger-ui.html", "/swagger-resources/**"
+                   )
+				   .permitAll()
+                   .antMatchers("/galaxy*","/galaxy/*").hasRole("Data Manager")
+				   //.anyRequest().authenticated()
+				   .anyRequest().hasRole("Researcher")
+                   .and().exceptionHandling().authenticationEntryPoint(new CustomLoginUrlAuthenticationEntryPoint(loginUrl))
+                   .and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessUrl(redirectAfterLogoutUrl)
+                   .and().logout().permitAll()
+                   .and().csrf().ignoringAntMatchers("/logout").csrfTokenRepository(csrfTokenRepository())
+                   .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
+                   .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
+       }
+       else {
+           //keycloak
+           //KeycloakConfiguration.getKeycloakSecurityContext();
 //            http.antMatcher("/**")
 //                    .authorizeRequests()
-//                    .antMatchers(
-//                            "/", "/login/**", "/health/**", "/info/**", "/metrics/**", "/trace/**", "/frontend/**", "/webjars/**", "/v2/api-docs", "/swagger-ui.html", "/swagger-resources/**"
-//                    ).permitAll()
-//                    .anyRequest().authenticated()
-//                    .and().exceptionHandling().authenticationEntryPoint(new CustomLoginUrlAuthenticationEntryPoint(loginUrl))
-//                    .and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessUrl(redirectAfterLogoutUrl)
-//                    .and().logout().permitAll()
-//                    .and().csrf().ignoringAntMatchers("/logout").csrfTokenRepository(csrfTokenRepository())
-//                    .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
-//                    .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
-//        }
-//        else {
-//            //keycloak
-//            //KeycloakConfiguration.getKeycloakSecurityContext();
-////            http.antMatcher("/**")
-////                    .authorizeRequests()
-////                    .antMatchers("/**").permitAll().and().csrf().disable();
-//        }
-//    }
-//
-//    private Filter ssoFilter() {
-//        OAuth2ClientAuthenticationProcessingFilter hbpFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/hbp");
-//        OAuth2RestTemplate hbpTemplate = new OAuth2RestTemplate(hbp(), oauth2ClientContext);
-//        hbpFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler(frontendRedirectAfterLogin));
-//        hbpFilter.setRestTemplate(hbpTemplate);
-//        hbpFilter.setTokenServices(new UserInfoTokenServices(hbpResource().getUserInfoUri(), hbp().getClientId()));
-//        return hbpFilter;
-//    }
-//
-//    @Bean
-//    public FilterRegistrationBean oauth2ClientFilterRegistration(
-//            OAuth2ClientContextFilter filter) {
-//        FilterRegistrationBean registration = new FilterRegistrationBean();
-//        registration.setFilter(filter);
-//        registration.setOrder(-100);
-//        return registration;
-//    }
-//
-//    @Bean(name="hbp")
-//    @ConfigurationProperties("hbp.client")
-//    public OAuth2ProtectedResourceDetails hbp() {
-//        return new AuthorizationCodeResourceDetails();
-//    }
-//
-//    @Bean(name="hbpResource")
-//    @ConfigurationProperties("hbp.resource")
-//    public ResourceServerProperties hbpResource() {
-//        return new ResourceServerProperties();
-//    }
-//
-//    public boolean isAuthentication() {
-//        return authentication;
-//    }
-//
-//    public String getFrontendRedirectAfterLogin() {
-//        return frontendRedirectAfterLogin;
-//    }
-//
-//    private Filter csrfHeaderFilter() {
-//        return new OncePerRequestFilter() {
-//            @Override
-//            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
-//                                            FilterChain filterChain) throws ServletException, IOException {
-//                CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
-//                if (csrf != null) {
-//                    Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
-//                    String token = csrf.getToken();
-//                    if (cookie == null || token != null && !token.equals(cookie.getValue())) {
-//                        cookie = new Cookie("XSRF-TOKEN", token);
-//                        cookie.setPath("/");
-//                        response.addCookie(cookie);
-//                    }
-//                }
-//                filterChain.doFilter(request, response);
-//            }
-//        };
-//    }
-//
-//    private CsrfTokenRepository csrfTokenRepository() {
-//        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
-//        repository.setHeaderName("X-XSRF-TOKEN");
-//        return repository;
-//    }
-//
-//    private class CustomLogoutHandler implements LogoutHandler {
-//        @Override
-//        public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
-//
-//            // Hackish way of accessing to this information...
-//            final UserInfo userInfo = (UserInfo) httpServletRequest.getSession().getAttribute("userInfo");
-//            if (userInfo != null) {
-//                userInfo.setFakeAuth(false);
-//            }
-//
-//            if (oauth2ClientContext == null || oauth2ClientContext.getAccessToken() == null)
-//            {
-//                return;
-//            }
-//
-//            String idToken = oauth2ClientContext.getAccessToken().getAdditionalInformation().get("id_token").toString();
-//
-//            StringBuilder query = new StringBuilder();
-//            query.append("{");
-//            query.append("\"token\":");
-//            query.append("\"").append(idToken).append("\"");
-//            query.append("}");
-//
-//            try {
-//                int responseCode = HTTPUtil.sendPost(revokeTokenURI, query.toString(), new StringBuilder());
-//                if (responseCode != 200)
-//                {
-//                    LOGGER.warn("Cannot send request to OIDC server for revocation ! ");
-//                }
-//                else{
-//                    LOGGER.info("Should be logged out");
-//                }
-//            } catch (IOException e) {
-//                LOGGER.warn("Cannot notify logout to OIDC server !");
-//                LOGGER.trace("Cannot notify logout", e);
-//            }
-//
-//        }
-//    }
-//}
+//                    .antMatchers("/**").permitAll().and().csrf().disable();
+       }
+   }
+
+   private Filter ssoFilter() {
+       OAuth2ClientAuthenticationProcessingFilter hbpFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/hbp");
+       OAuth2RestTemplate hbpTemplate = new OAuth2RestTemplate(hbp(), oauth2ClientContext);
+       hbpFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler(frontendRedirectAfterLogin));
+       hbpFilter.setRestTemplate(hbpTemplate);
+       hbpFilter.setTokenServices(new UserInfoTokenServices(hbpResource().getUserInfoUri(), hbp().getClientId()));
+       return hbpFilter;
+   }
+
+   @Bean
+   public FilterRegistrationBean oauth2ClientFilterRegistration(
+           OAuth2ClientContextFilter filter) {
+       FilterRegistrationBean registration = new FilterRegistrationBean();
+       registration.setFilter(filter);
+       registration.setOrder(-100);
+       return registration;
+   }
+
+   @Bean(name="hbp")
+   @ConfigurationProperties("hbp.client")
+   public BaseOAuth2ProtectedResourceDetails hbp() {
+       return new AuthorizationCodeResourceDetails();
+   }
+
+   @Bean(name="hbpResource")
+   @ConfigurationProperties("hbp.resource")
+   public ResourceServerProperties hbpResource() {
+       return new ResourceServerProperties();
+   }
+
+   public boolean isAuthentication() {
+       return authentication;
+   }
+
+   public String getFrontendRedirectAfterLogin() {
+       return frontendRedirectAfterLogin;
+   }
+
+   private Filter csrfHeaderFilter() {
+       return new OncePerRequestFilter() {
+           @Override
+           protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+                                           FilterChain filterChain) throws ServletException, IOException {
+               CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
+               if (csrf != null) {
+                   Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
+                   String token = csrf.getToken();
+                   if (cookie == null || token != null && !token.equals(cookie.getValue())) {
+                       cookie = new Cookie("XSRF-TOKEN", token);
+                       cookie.setPath("/");
+                       response.addCookie(cookie);
+                   }
+               }
+               filterChain.doFilter(request, response);
+           }
+       };
+   }
+
+   private CsrfTokenRepository csrfTokenRepository() {
+       HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
+       repository.setHeaderName("X-XSRF-TOKEN");
+       return repository;
+   }
+
+   private class CustomLogoutHandler implements LogoutHandler {
+       @Override
+       public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
+
+           // Hackish way of accessing to this information...
+           final UserInfo userInfo = (UserInfo) httpServletRequest.getSession().getAttribute("userInfo");
+           if (userInfo != null) {
+               userInfo.setFakeAuth(false);
+           }
+
+           if (oauth2ClientContext == null || oauth2ClientContext.getAccessToken() == null)
+           {
+               return;
+           }
+
+           String idToken = oauth2ClientContext.getAccessToken().getAdditionalInformation().get("id_token").toString();
+
+           StringBuilder query = new StringBuilder();
+           query.append("{");
+           query.append("\"token\":");
+           query.append("\"").append(idToken).append("\"");
+           query.append("}");
+
+           try {
+               int responseCode = HTTPUtil.sendPost(revokeTokenURI, query.toString(), new StringBuilder());
+               if (responseCode != 200)
+               {
+                   LOGGER.warn("Cannot send request to OIDC server for revocation ! ");
+               }
+               else{
+                   LOGGER.info("Should be logged out");
+               }
+           } catch (IOException e) {
+               LOGGER.warn("Cannot notify logout to OIDC server !");
+               LOGGER.trace("Cannot notify logout", e);
+           }
+
+       }
+   }
+   
+    @Bean
+    public AuthoritiesExtractor keycloakAuthoritiesExtractor() {
+        return new KeycloakAuthoritiesExtractor();
+    }
+
+
+    public class KeycloakAuthoritiesExtractor
+            implements AuthoritiesExtractor {
+
+        @Override
+        public List<GrantedAuthority> extractAuthorities
+                (Map<String, Object> map) {
+            return AuthorityUtils
+                    .commaSeparatedStringToAuthorityList(asAuthorities(map));
+        }
+
+        private String asAuthorities(Map<String, Object> map) {
+            List<String> authorities = new ArrayList<>();
+//            authorities.add("BAELDUNG_USER");
+            List<LinkedHashMap<String, String>> authz;
+            authz = (List<LinkedHashMap<String, String>>) map.get("authorities");
+            for (LinkedHashMap<String, String> entry : authz) {
+                authorities.add(entry.get("authority"));
+            }
+            return String.join(",", authorities);
+        }
+    }
+   
+}
diff --git a/src/main/java/eu/hbp/mip/controllers/SecurityApi.java b/src/main/java/eu/hbp/mip/controllers/SecurityApi.java
index cfddbd334..08c6f0410 100644
--- a/src/main/java/eu/hbp/mip/controllers/SecurityApi.java
+++ b/src/main/java/eu/hbp/mip/controllers/SecurityApi.java
@@ -17,6 +17,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.security.access.prepost.PreAuthorize;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
@@ -102,6 +103,7 @@ public class SecurityApi {
      */
 
     @RequestMapping(path = "/galaxy", method = RequestMethod.GET, produces = "application/json")
+	@PreAuthorize("hasRole('Data Manager')")
     @ResponseStatus(value = HttpStatus.OK)
     public ResponseEntity getGalaxyConfiguration(){
         String stringEncoded = Base64.getEncoder().encodeToString((galaxyUsername + ":" + galaxyPassword).getBytes());
-- 
GitLab