diff --git a/pom.xml b/pom.xml index 83ff017..a10c2fc 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,6 @@ dnet45-parent 1.0.0 - eu.dnetlinb login-service 0.0.1-SNAPSHOT war @@ -33,6 +32,11 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-configuration-processor + true + org.springframework.boot spring-boot-starter-tomcat @@ -69,24 +73,6 @@ - - - com.google.code.gson - gson - 2.6.2 - - - - io.springfox - springfox-swagger2 - 2.7.0 - - - - io.springfox - springfox-swagger-ui - 2.7.0 - diff --git a/src/main/java/eu/dnetlib/loginservice/LoginServiceApplication.java b/src/main/java/eu/dnetlib/loginservice/LoginServiceApplication.java new file mode 100644 index 0000000..a0dde7b --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/LoginServiceApplication.java @@ -0,0 +1,22 @@ +package eu.dnetlib.loginservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.annotation.PropertySources; +import eu.dnetlib.loginservice.properties.Properties; + +@SpringBootApplication(scanBasePackages = {"eu.dnetlib.loginservice"}) +@PropertySources({ + @PropertySource("classpath:application.properties"), + @PropertySource(value = "classpath:dnet-override.properties", ignoreResourceNotFound = true) +}) +@EnableConfigurationProperties({Properties.class}) +public class LoginServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(LoginServiceApplication.class, args); + } + +} diff --git a/src/main/java/eu/dnetlinb/loginservice/ServletInitializer.java b/src/main/java/eu/dnetlib/loginservice/ServletInitializer.java similarity index 92% rename from src/main/java/eu/dnetlinb/loginservice/ServletInitializer.java rename to src/main/java/eu/dnetlib/loginservice/ServletInitializer.java index 09a249f..faf52ff 100644 --- a/src/main/java/eu/dnetlinb/loginservice/ServletInitializer.java +++ b/src/main/java/eu/dnetlib/loginservice/ServletInitializer.java @@ -1,4 +1,4 @@ -package eu.dnetlinb.loginservice; +package eu.dnetlib.loginservice; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.support.SpringBootServletInitializer; diff --git a/src/main/java/eu/dnetlib/loginservice/controllers/HealthController.java b/src/main/java/eu/dnetlib/loginservice/controllers/HealthController.java new file mode 100644 index 0000000..fb7d4d0 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/controllers/HealthController.java @@ -0,0 +1,17 @@ +package eu.dnetlib.loginservice.controllers; + +import org.apache.log4j.Logger; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HealthController { + private final Logger log = Logger.getLogger(this.getClass()); + + @RequestMapping(value = {"", "/health_check"}, method = RequestMethod.GET) + public String hello() { + log.debug("Hello from Login service!"); + return "Hello from Login service!"; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/controllers/UserController.java b/src/main/java/eu/dnetlib/loginservice/controllers/UserController.java new file mode 100644 index 0000000..3ada99c --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/controllers/UserController.java @@ -0,0 +1,22 @@ +package eu.dnetlib.loginservice.controllers; + +import eu.dnetlib.loginservice.entities.User; +import eu.dnetlib.loginservice.exception.ResourceNotFoundException; +import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class UserController { + + @RequestMapping(value = "/userInfo", method = RequestMethod.GET) + public ResponseEntity getUserInfo(Authentication authentication) { + if(authentication instanceof OIDCAuthenticationToken) { + return ResponseEntity.ok(new User((OIDCAuthenticationToken) authentication)); + } + throw new ResourceNotFoundException("No Session has been found"); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/entities/User.java b/src/main/java/eu/dnetlib/loginservice/entities/User.java new file mode 100644 index 0000000..ca459d1 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/entities/User.java @@ -0,0 +1,74 @@ +package eu.dnetlib.loginservice.entities; + +import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Set; +import java.util.stream.Collectors; + +public class User { + + private String sub; + private String name; + private String given_name; + private String family_name; + private String email; + private Set roles; + + public User(OIDCAuthenticationToken token) { + this.sub = token.getUserInfo().getSub(); + this.name = token.getUserInfo().getName(); + this.given_name = token.getUserInfo().getGivenName(); + this.family_name = token.getUserInfo().getFamilyName(); + this.email = token.getUserInfo().getEmail(); + this.roles = token.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); + } + + public String getSub() { + return sub; + } + + public void setSub(String sub) { + this.sub = sub; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGiven_name() { + return given_name; + } + + public void setGiven_name(String given_name) { + this.given_name = given_name; + } + + public String getFamily_name() { + return family_name; + } + + public void setFamily_name(String family_name) { + this.family_name = family_name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/exception/ResourceNotFoundException.java b/src/main/java/eu/dnetlib/loginservice/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..dfff6f8 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/exception/ResourceNotFoundException.java @@ -0,0 +1,20 @@ +package eu.dnetlib.loginservice.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_FOUND) // 404 +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException(String message) { + super(message); + } + + public ResourceNotFoundException(String message, Throwable err) { + super(message, err); + } + + public HttpStatus getStatus() { + return HttpStatus.NOT_FOUND; + } +} + diff --git a/src/main/java/eu/dnetlib/loginservice/properties/OIDC.java b/src/main/java/eu/dnetlib/loginservice/properties/OIDC.java new file mode 100644 index 0000000..114a036 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/properties/OIDC.java @@ -0,0 +1,59 @@ +package eu.dnetlib.loginservice.properties; + +public class OIDC { + + private String issuer; + private String home; + private String id; + private String secret; + private String scope = ""; + private String logout; + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getHome() { + return home; + } + + public void setHome(String home) { + this.home = home; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getLogout() { + return logout; + } + + public void setLogout(String logout) { + this.logout = logout; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/properties/Properties.java b/src/main/java/eu/dnetlib/loginservice/properties/Properties.java new file mode 100644 index 0000000..8632ed3 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/properties/Properties.java @@ -0,0 +1,56 @@ +package eu.dnetlib.loginservice.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("authentication") +public class Properties { + + private Redis redis = new Redis(); + private OIDC oidc = new OIDC(); + private String domain; + private String session; + private String redirect; + + public Properties() { + } + + public Redis getRedis() { + return redis; + } + + public void setRedis(Redis redis) { + this.redis = redis; + } + + public OIDC getOidc() { + return oidc; + } + + public void setOidc(OIDC oidc) { + this.oidc = oidc; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getSession() { + return session; + } + + public void setSession(String session) { + this.session = session; + } + + public String getRedirect() { + return redirect; + } + + public void setRedirect(String redirect) { + this.redirect = redirect; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/properties/Redis.java b/src/main/java/eu/dnetlib/loginservice/properties/Redis.java new file mode 100644 index 0000000..5ff24a4 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/properties/Redis.java @@ -0,0 +1,44 @@ +package eu.dnetlib.loginservice.properties; + +public class Redis { + + private String host = "localhost"; + private String port = "6379"; + private String password; + + public Redis() { + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "Redis{" + + "host='" + host + '\'' + + ", port='" + port + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/CorsConfig.java b/src/main/java/eu/dnetlib/loginservice/security/CorsConfig.java new file mode 100644 index 0000000..230e4d1 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/CorsConfig.java @@ -0,0 +1,16 @@ +package eu.dnetlib.loginservice.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +public class CorsConfig extends WebMvcConfigurerAdapter { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS") + .allowCredentials(true); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/RedisConfig.java b/src/main/java/eu/dnetlib/loginservice/security/RedisConfig.java new file mode 100644 index 0000000..20fe0a5 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/RedisConfig.java @@ -0,0 +1,42 @@ +package eu.dnetlib.loginservice.security; + +import eu.dnetlib.loginservice.properties.Properties; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.web.http.CookieSerializer; +import org.springframework.session.web.http.DefaultCookieSerializer; + +@EnableRedisHttpSession +@Configuration +public class RedisConfig { + + private final Properties properties; + + private static final Logger logger = Logger.getLogger(RedisConfig.class); + + @Autowired + public RedisConfig(Properties properties) { + this.properties = properties; + } + + @Bean + public LettuceConnectionFactory connectionFactory() { + logger.info(String.format("Redis connection listens to %s:%s ", properties.getRedis().getHost(), properties.getRedis().getPort())); + LettuceConnectionFactory factory = new LettuceConnectionFactory(properties.getRedis().getHost(), Integer.parseInt(properties.getRedis().getPort())); + if (properties.getRedis().getPassword() != null) factory.setPassword(properties.getRedis().getPassword()); + return factory; + } + + @Bean + public CookieSerializer cookieSerializer() { + DefaultCookieSerializer serializer = new DefaultCookieSerializer(); + serializer.setCookieName(properties.getSession()); + serializer.setCookiePath("/"); + serializer.setDomainName(properties.getDomain()); + return serializer; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/WebSecurityConfig.java b/src/main/java/eu/dnetlib/loginservice/security/WebSecurityConfig.java new file mode 100644 index 0000000..1277c0c --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/WebSecurityConfig.java @@ -0,0 +1,82 @@ +package eu.dnetlib.loginservice.security; + +import eu.dnetlib.loginservice.properties.Properties; +import eu.dnetlib.loginservice.security.oidc.OpenAIREAuthenticationFilter; +import eu.dnetlib.loginservice.security.oidc.OpenAIREAuthenticationSuccessHandler; +import eu.dnetlib.loginservice.security.oidc.OpenAIRELogoutHandler; +import eu.dnetlib.loginservice.security.oidc.OpenAIRELogoutSuccessHandler; +import eu.dnetlib.loginservice.utils.EntryPoint; +import org.mitre.openid.connect.client.OIDCAuthenticationProvider; +import org.mitre.openid.connect.client.service.ClientConfigurationService; +import org.mitre.openid.connect.client.service.IssuerService; +import org.mitre.openid.connect.client.service.ServerConfigurationService; +import org.mitre.openid.connect.client.service.impl.PlainAuthRequestUrlBuilder; +import org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.web.authentication.www.BasicAuthenticationFilter; + +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true) +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final Properties properties; + private final EntryPoint entryPoint; + private final OIDCAuthenticationProvider provider; + private final IssuerService issuerService; + private final ServerConfigurationService serverConfigurationService; + private final ClientConfigurationService clientConfigurationService; + private final StaticAuthRequestOptionsService optionsService; + private final PlainAuthRequestUrlBuilder builder; + private final OpenAIREAuthenticationSuccessHandler authenticationSuccessHandler; + private final OpenAIRELogoutHandler logoutHandler; + private final OpenAIRELogoutSuccessHandler logoutSuccessHandler; + + @Autowired + public WebSecurityConfig(Properties properties, EntryPoint entryPoint, OIDCAuthenticationProvider provider, + IssuerService issuerService, ServerConfigurationService serverConfigurationService, + ClientConfigurationService clientConfigurationService, StaticAuthRequestOptionsService optionsService, + PlainAuthRequestUrlBuilder builder, OpenAIREAuthenticationSuccessHandler authenticationSuccessHandler, + OpenAIRELogoutHandler logoutHandler, OpenAIRELogoutSuccessHandler logoutSuccessHandler) { + super(); + this.properties = properties; + this.entryPoint = entryPoint; + this.provider = provider; + this.issuerService = issuerService; + this.serverConfigurationService = serverConfigurationService; + this.clientConfigurationService = clientConfigurationService; + this.optionsService = optionsService; + this.builder = builder; + this.authenticationSuccessHandler = authenticationSuccessHandler; + this.logoutHandler = logoutHandler; + this.logoutSuccessHandler = logoutSuccessHandler; + } + + public OpenAIREAuthenticationFilter initFilter() throws Exception { + OpenAIREAuthenticationFilter filter = new OpenAIREAuthenticationFilter(properties); + filter.setAuthenticationManager(authenticationManagerBean()); + filter.afterPropertiesSet(); + filter.setIssuerService(issuerService); + filter.setServerConfigurationService(serverConfigurationService); + filter.setClientConfigurationService(clientConfigurationService); + filter.setAuthRequestOptionsService(optionsService); + filter.setAuthRequestUrlBuilder(builder); + filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); + return filter; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + http.authenticationProvider(provider); + http.addFilterBefore(initFilter(), BasicAuthenticationFilter.class); + http.httpBasic().authenticationEntryPoint(entryPoint); + http.logout().logoutUrl("/openid_logout").addLogoutHandler(logoutHandler) + .logoutSuccessHandler(logoutSuccessHandler).invalidateHttpSession(false); + http.authorizeRequests().anyRequest().permitAll(); + } + +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Configurations.java b/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Configurations.java new file mode 100644 index 0000000..cf11f81 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Configurations.java @@ -0,0 +1,49 @@ +package eu.dnetlib.loginservice.security.initiliazers; + +import eu.dnetlib.loginservice.properties.Properties; +import eu.dnetlib.loginservice.utils.ScopeReader; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.config.ServerConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; + +@Configuration +public class Configurations { + + private final Properties properties; + private final ScopeReader scopeReader; + + @Autowired + public Configurations(Properties properties, ScopeReader scopeReader) { + this.properties = properties; + this.scopeReader = scopeReader; + } + + @Bean + public ServerConfiguration serverConfiguration() { + String issuer = properties.getOidc().getIssuer(); + ServerConfiguration serverConfiguration = new ServerConfiguration(); + serverConfiguration.setIssuer(issuer); + serverConfiguration.setAuthorizationEndpointUri(issuer + "authorize"); + serverConfiguration.setTokenEndpointUri(issuer + "token"); + serverConfiguration.setUserInfoUri(issuer + "userinfo"); + serverConfiguration.setJwksUri(issuer + "jwk"); + serverConfiguration.setRevocationEndpointUri(issuer + "revoke"); + return serverConfiguration; + } + + @Bean + public RegisteredClient registeredClient() { + RegisteredClient client = new RegisteredClient(); + client.setClientId(properties.getOidc().getId()); + client.setClientSecret(properties.getOidc().getSecret()); + client.setScope(scopeReader.getScopes()); + client.setTokenEndpointAuthMethod(ClientDetailsEntity.AuthMethod.SECRET_BASIC); + client.setRedirectUris(Collections.singleton(properties.getOidc().getHome())); + return client; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Primitives.java b/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Primitives.java new file mode 100644 index 0000000..b1f82bf --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Primitives.java @@ -0,0 +1,68 @@ +package eu.dnetlib.loginservice.security.initiliazers; + +import eu.dnetlib.loginservice.properties.Properties; +import eu.dnetlib.loginservice.security.oidc.OpenAIREAuthoritiesMapper; +import eu.dnetlib.loginservice.utils.EntryPoint; +import eu.dnetlib.loginservice.utils.ScopeReader; +import org.mitre.openid.connect.client.OIDCAuthenticationFilter; +import org.mitre.openid.connect.client.OIDCAuthenticationProvider; +import org.mitre.openid.connect.client.service.impl.PlainAuthRequestUrlBuilder; +import org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService; +import org.mitre.openid.connect.client.service.impl.StaticSingleIssuerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; + +@Configuration +public class Primitives { + + private final Properties properties; + private final OpenAIREAuthoritiesMapper authoritiesMapper;; + + @Autowired + public Primitives(Properties properties, OpenAIREAuthoritiesMapper authoritiesMapper) { + this.properties = properties; + this.authoritiesMapper = authoritiesMapper; + } + + @Bean + public ScopeReader scopeReader() { + return new ScopeReader(this.properties.getOidc().getScope()); + } + + @Bean + public DefaultWebSecurityExpressionHandler handler() { + return new DefaultWebSecurityExpressionHandler(); + } + + @Bean + public PlainAuthRequestUrlBuilder builder() { + return new PlainAuthRequestUrlBuilder(); + } + + @Bean + public OIDCAuthenticationProvider provider() { + OIDCAuthenticationProvider provider = new OIDCAuthenticationProvider(); + provider.setAuthoritiesMapper(authoritiesMapper); + return provider; + } + + @Bean + public StaticSingleIssuerService issuerService() { + StaticSingleIssuerService issuerService = new StaticSingleIssuerService(); + issuerService.setIssuer(properties.getOidc().getIssuer()); + return issuerService; + } + + @Bean + public EntryPoint entryPoint() { + return new EntryPoint(); + } + + @Bean + public StaticAuthRequestOptionsService optionsService() { + return new StaticAuthRequestOptionsService(); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Services.java b/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Services.java new file mode 100644 index 0000000..ab48d75 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/initiliazers/Services.java @@ -0,0 +1,48 @@ +package eu.dnetlib.loginservice.security.initiliazers; + +import eu.dnetlib.loginservice.properties.Properties; +import eu.dnetlib.loginservice.utils.ScopeReader; +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService; +import org.mitre.openid.connect.client.service.impl.StaticServerConfigurationService; +import org.mitre.openid.connect.config.ServerConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class Services { + + private final Properties properties; + private final ServerConfiguration serverConfiguration; + private final RegisteredClient clientConfiguration; + + @Autowired + public Services(Properties properties, ServerConfiguration serverConfiguration, RegisteredClient clientConfiguration) { + this.properties = properties; + this.serverConfiguration = serverConfiguration; + this.clientConfiguration = clientConfiguration; + } + + + @Bean + public StaticServerConfigurationService serverConfigurationService() { + StaticServerConfigurationService configurationService = new StaticServerConfigurationService(); + Map servers = new HashMap<>(); + servers.put(properties.getOidc().getIssuer(), serverConfiguration); + configurationService.setServers(servers); + return configurationService; + } + + @Bean + public StaticClientConfigurationService clientConfigurationService() { + StaticClientConfigurationService configurationService = new StaticClientConfigurationService(); + Map clients = new HashMap<>(); + clients.put(properties.getOidc().getIssuer(), clientConfiguration); + configurationService.setClients(clients); + return configurationService; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthenticationFilter.java b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthenticationFilter.java new file mode 100644 index 0000000..5fd35fa --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthenticationFilter.java @@ -0,0 +1,27 @@ +package eu.dnetlib.loginservice.security.oidc; + +import eu.dnetlib.loginservice.properties.Properties; +import eu.dnetlib.loginservice.utils.Redirect; +import org.apache.log4j.Logger; +import org.mitre.openid.connect.client.OIDCAuthenticationFilter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class OpenAIREAuthenticationFilter extends OIDCAuthenticationFilter { + + private final static Logger logger = Logger.getLogger(OpenAIREAuthenticationSuccessHandler.class); + private final Properties properties; + + public OpenAIREAuthenticationFilter(Properties properties) { + super(); + this.properties = properties; + } + + @Override + protected void handleAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { + Redirect.setRedirect(request, properties); + super.handleAuthorizationRequest(request, response); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthenticationSuccessHandler.java b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthenticationSuccessHandler.java new file mode 100644 index 0000000..7b50015 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthenticationSuccessHandler.java @@ -0,0 +1,65 @@ +package eu.dnetlib.loginservice.security.oidc; + +import com.google.gson.JsonParser; +import eu.dnetlib.loginservice.properties.Properties; +import org.apache.log4j.Logger; +import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.session.FindByIndexNameSessionRepository; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Base64; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Configuration +public class OpenAIREAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private static final Logger logger = Logger.getLogger(OpenAIREAuthenticationSuccessHandler.class); + private final Properties properties; + + @Autowired + public OpenAIREAuthenticationSuccessHandler(Properties properties) { + this.properties = properties; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + OIDCAuthenticationToken token = (OIDCAuthenticationToken) authentication; + HttpSession session = request.getSession(); + String redirect = (String) session.getAttribute("redirect"); + session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, token.getUserInfo().getSub()); + try { + Cookie accessToken = new Cookie("AccessToken", token.getAccessTokenValue()); + String regex = "^([A-Za-z0-9-_=]+)\\.([A-Za-z0-9-_=]+)\\.?([A-Za-z0-9-_.+=]*)$"; + Matcher matcher = Pattern.compile(regex).matcher(token.getAccessTokenValue()); + if (matcher.find()) { + long exp = new JsonParser().parse(new String(Base64.getDecoder().decode(matcher.group(2)))).getAsJsonObject().get("exp").getAsLong(); + accessToken.setMaxAge((int) (exp - (new Date().getTime() / 1000))); + } else { + accessToken.setMaxAge(3600); + } + accessToken.setPath("/"); + accessToken.setDomain(properties.getDomain()); + response.addCookie(accessToken); + if(redirect != null) { + response.sendRedirect(redirect); + session.removeAttribute("redirect"); + } else { + response.sendRedirect(properties.getRedirect()); + } + } catch (IOException e) { + logger.error("IOException in redirection ", e); + throw new IOException(e); + } + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthoritiesMapper.java b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthoritiesMapper.java new file mode 100644 index 0000000..29d7cf9 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIREAuthoritiesMapper.java @@ -0,0 +1,21 @@ +package eu.dnetlib.loginservice.security.oidc; + +import com.google.gson.JsonArray; +import com.nimbusds.jwt.JWT; +import eu.dnetlib.loginservice.utils.AuthoritiesMapper; +import org.mitre.openid.connect.client.OIDCAuthoritiesMapper; +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Component +public class OpenAIREAuthoritiesMapper implements OIDCAuthoritiesMapper { + + @Override + public Collection mapAuthorities(JWT jwtToken, UserInfo userInfo) { + JsonArray entitlements = userInfo.getSource().getAsJsonArray("edu_person_entitlements"); + return AuthoritiesMapper.map(entitlements); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIRELogoutHandler.java b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIRELogoutHandler.java new file mode 100644 index 0000000..9285554 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIRELogoutHandler.java @@ -0,0 +1,27 @@ +package eu.dnetlib.loginservice.security.oidc; + +import eu.dnetlib.loginservice.properties.Properties; +import eu.dnetlib.loginservice.utils.Redirect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Service +public class OpenAIRELogoutHandler implements LogoutHandler { + + private Properties properties; + + @Autowired + public OpenAIRELogoutHandler(Properties properties) { + this.properties = properties; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + Redirect.setRedirect(request, properties); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIRELogoutSuccessHandler.java b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIRELogoutSuccessHandler.java new file mode 100644 index 0000000..f42afa7 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/security/oidc/OpenAIRELogoutSuccessHandler.java @@ -0,0 +1,37 @@ +package eu.dnetlib.loginservice.security.oidc; + +import eu.dnetlib.loginservice.properties.Properties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Collectors; + +@Configuration +public class OpenAIRELogoutSuccessHandler implements LogoutSuccessHandler { + + private final Properties properties; + + @Autowired + public OpenAIRELogoutSuccessHandler(Properties properties) { + this.properties = properties; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + HttpSession session = request.getSession(); + String redirect = (String) session.getAttribute("redirect"); + session.removeAttribute("redirect"); + if(redirect == null) { + redirect = properties.getRedirect(); + } + session.invalidate(); + response.sendRedirect(properties.getOidc().getLogout() + redirect); + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/utils/AuthoritiesMapper.java b/src/main/java/eu/dnetlib/loginservice/utils/AuthoritiesMapper.java new file mode 100644 index 0000000..da9321e --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/utils/AuthoritiesMapper.java @@ -0,0 +1,41 @@ +package eu.dnetlib.loginservice.utils; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import org.apache.log4j.Logger; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.Collection; +import java.util.HashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AuthoritiesMapper { + + private static final Logger logger = Logger.getLogger(AuthoritiesMapper.class); + + public static Collection map(JsonArray entitlements) { + HashSet authorities = new HashSet<>(); + String regex = "urn:geant:openaire[.]eu:group:([^:]*):?(.*)?:role=member#aai[.]openaire[.]eu"; + for(JsonElement obj: entitlements) { + Matcher matcher = Pattern.compile(regex).matcher(obj.getAsString()); + if (matcher.find()) { + StringBuilder sb = new StringBuilder(); + if(matcher.group(1) != null && matcher.group(1).length() > 0) { + sb.append(matcher.group(1).replace("+-+", "_").replaceAll("[+.]", "_").toUpperCase()); + } + if(matcher.group(2).length() > 0) { + sb.append("_"); + if(matcher.group(2).equals("admins")) { + sb.append("MANAGER"); + } else { + sb.append(matcher.group(2).toUpperCase()); + } + } + authorities.add(new SimpleGrantedAuthority(sb.toString())); + } + } + return authorities; + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/utils/EntryPoint.java b/src/main/java/eu/dnetlib/loginservice/utils/EntryPoint.java new file mode 100644 index 0000000..cbd5538 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/utils/EntryPoint.java @@ -0,0 +1,19 @@ +package eu.dnetlib.loginservice.utils; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class EntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); + } + +} + diff --git a/src/main/java/eu/dnetlib/loginservice/utils/Redirect.java b/src/main/java/eu/dnetlib/loginservice/utils/Redirect.java new file mode 100644 index 0000000..909fd81 --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/utils/Redirect.java @@ -0,0 +1,39 @@ +package eu.dnetlib.loginservice.utils; + + +import eu.dnetlib.loginservice.properties.Properties; +import org.apache.http.client.utils.URIBuilder; +import org.apache.log4j.Logger; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.net.URISyntaxException; +import java.util.Enumeration; + +public class Redirect { + + private final static Logger logger = Logger.getLogger(Redirect.class); + + private static String getDomain(String url) throws URISyntaxException { + URIBuilder uriBuilder = new URIBuilder(url); + return uriBuilder.getHost(); + } + + public static void setRedirect(HttpServletRequest request, Properties properties) { + HttpSession session = request.getSession(); + Enumeration params = request.getParameterNames(); + while (params.hasMoreElements()) { + String param = params.nextElement(); + if(param.equalsIgnoreCase("redirect")) { + String redirect = request.getParameter(param); + try { + if(getDomain(redirect).endsWith(properties.getDomain())) { + session.setAttribute("redirect", redirect); + } + } catch (URISyntaxException e) { + logger.error(e.getMessage()); + } + } + } + } +} diff --git a/src/main/java/eu/dnetlib/loginservice/utils/ScopeReader.java b/src/main/java/eu/dnetlib/loginservice/utils/ScopeReader.java new file mode 100644 index 0000000..096361e --- /dev/null +++ b/src/main/java/eu/dnetlib/loginservice/utils/ScopeReader.java @@ -0,0 +1,25 @@ +package eu.dnetlib.loginservice.utils; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class ScopeReader { + + Set scopes; + + public ScopeReader(String property) { + if (!property.trim().isEmpty()){ + scopes = new HashSet<>(); + Collections.addAll(scopes, property.split(",")); + } + } + + public Set getScopes() { + return scopes; + } + + public void setScopes(Set scopes) { + this.scopes = scopes; + } +} diff --git a/src/main/java/eu/dnetlinb/loginservice/LoginServiceApplication.java b/src/main/java/eu/dnetlinb/loginservice/LoginServiceApplication.java deleted file mode 100644 index 413447f..0000000 --- a/src/main/java/eu/dnetlinb/loginservice/LoginServiceApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package eu.dnetlinb.loginservice; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class LoginServiceApplication { - - public static void main(String[] args) { - SpringApplication.run(LoginServiceApplication.class, args); - } - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..220bf44 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,9 @@ - +authentication.domain=di.uoa.gr +authentication.oidc.issuer=https://aai.openaire.eu/oidc/ +authentication.oidc.logout=https://aai.openaire.eu/proxy/saml2/idp/SingleLogoutService.php?ReturnTo= +authentication.oidc.home=http://mpagasas.di.uoa.gr:8090/openid_connect_login +authentication.oidc.scope=openid,profile,email,eduperson_entitlement +authentication.oidc.id=id +authentication.oidc.secret=secret +authentication.session=openAIRESession +authentication.redirect=http://mpagasas.di.uoa.gr:4600/reload \ No newline at end of file diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..d9dc948 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,20 @@ +log4j.rootLogger = DEBUG, R + +log4j.logger.eu.dnetlib = DEBUG +log4j.logger.org.springframework = DEBUG, S + +log4j.additivity.org.springframework = false + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=/var/log/dnet/login-service/login-service.log +log4j.appender.R.MaxFileSize=10MB +log4j.appender.R.MaxBackupIndex=10 +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern= %d %p %t [%c] - %m%n + +log4j.appender.S=org.apache.log4j.RollingFileAppender +log4j.appender.S.File=/var/log/dnet/login-service/login-service-spring.log +log4j.appender.S.MaxFileSize=10MB +log4j.appender.S.MaxBackupIndex=10 +log4j.appender.S.layout=org.apache.log4j.PatternLayout +log4j.appender.S.layout.ConversionPattern= %d %p %t [%c] - %m%n \ No newline at end of file diff --git a/src/test/java/eu/dnetlinb/loginservice/LoginServiceApplicationTests.java b/src/test/java/eu/dnetlib/loginservice/LoginServiceApplicationTests.java similarity index 84% rename from src/test/java/eu/dnetlinb/loginservice/LoginServiceApplicationTests.java rename to src/test/java/eu/dnetlib/loginservice/LoginServiceApplicationTests.java index 9df54fc..f9ece22 100644 --- a/src/test/java/eu/dnetlinb/loginservice/LoginServiceApplicationTests.java +++ b/src/test/java/eu/dnetlib/loginservice/LoginServiceApplicationTests.java @@ -1,4 +1,4 @@ -package eu.dnetlinb.loginservice; +package eu.dnetlib.loginservice; import org.junit.Test; import org.springframework.boot.test.context.SpringBootTest;