commit 63d9be79c913753b31fc3972ccdd3afd908a90b4 Author: k.triantafyllou Date: Thu May 18 11:44:50 2023 +0300 Move all components from login-service to login-core diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0c48967 --- /dev/null +++ b/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + eu.dnetlib + uoa-spring-boot-parent + 1.0.0 + + uoa-login-core + 2.0.0-SNAPSHOT + jar + uoa-login-core + + scm:git:gitea@code-repo.d4science.org:MaDgIK/uoa-login-core.git + HEAD + + + UTF-8 + UTF-8 + ${maven.build.timestamp} + E MMM dd HH:mm:ss z yyyy + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.session + spring-session-data-redis + + + biz.paluch.redis + lettuce + 4.3.3.Final + + + + org.mitre + openid-connect-client + 1.3.0 + + + org.bouncycastle + bcprov-jdk15on + + + + + + uoa-login-core + + diff --git a/src/main/java/eu/dnetlib/authentication/configuration/APIProperties.java b/src/main/java/eu/dnetlib/authentication/configuration/APIProperties.java new file mode 100644 index 0000000..71d249f --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/configuration/APIProperties.java @@ -0,0 +1,38 @@ +package eu.dnetlib.authentication.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("api") +public class APIProperties { + + private String title; + private String description; + private String version; + + public APIProperties() { + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/src/main/java/eu/dnetlib/authentication/configuration/AuthenticationConfiguration.java b/src/main/java/eu/dnetlib/authentication/configuration/AuthenticationConfiguration.java new file mode 100644 index 0000000..b34713b --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/configuration/AuthenticationConfiguration.java @@ -0,0 +1,14 @@ +package eu.dnetlib.authentication.configuration; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({Properties.class, APIProperties.class}) +@ComponentScan(basePackages = {"eu.dnetlib.authentication"}) +public class AuthenticationConfiguration { + + public AuthenticationConfiguration() { + } +} diff --git a/src/main/java/eu/dnetlib/authentication/configuration/OIDC.java b/src/main/java/eu/dnetlib/authentication/configuration/OIDC.java new file mode 100644 index 0000000..e589951 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/configuration/OIDC.java @@ -0,0 +1,68 @@ +package eu.dnetlib.authentication.configuration; + +public class OIDC { + + private String issuer; + private String home; + private String redirect; + 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 getRedirect() { + return redirect; + } + + public void setRedirect(String redirect) { + this.redirect = redirect; + } + + 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/authentication/configuration/Properties.java b/src/main/java/eu/dnetlib/authentication/configuration/Properties.java new file mode 100644 index 0000000..c6b05fa --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/configuration/Properties.java @@ -0,0 +1,83 @@ +package eu.dnetlib.authentication.configuration; + +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 accessToken; + private String redirect; + private String authoritiesMapper; + private Boolean keycloak; + + 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 getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRedirect() { + return redirect; + } + + public void setRedirect(String redirect) { + this.redirect = redirect; + } + + public String getAuthoritiesMapper() { + return authoritiesMapper; + } + + public void setAuthoritiesMapper(String authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } + + public Boolean getKeycloak() { + return keycloak; + } + + public void setKeycloak(Boolean keycloak) { + this.keycloak = keycloak; + } +} diff --git a/src/main/java/eu/dnetlib/authentication/configuration/Redis.java b/src/main/java/eu/dnetlib/authentication/configuration/Redis.java new file mode 100644 index 0000000..6e0d670 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/configuration/Redis.java @@ -0,0 +1,44 @@ +package eu.dnetlib.authentication.configuration; + +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/authentication/controllers/HealthController.java b/src/main/java/eu/dnetlib/authentication/controllers/HealthController.java new file mode 100644 index 0000000..98c1ef1 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/controllers/HealthController.java @@ -0,0 +1,51 @@ +package eu.dnetlib.authentication.controllers; + +import eu.dnetlib.authentication.configuration.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +public class HealthController { + private final Logger log = LogManager.getLogger(this.getClass()); + private final Properties properties; + + @Autowired + public HealthController(Properties properties) { + this.properties = properties; + } + + @RequestMapping(value = {"", "/health_check"}, method = RequestMethod.GET) + public String hello() { + log.debug("Hello from Login service!"); + return "Hello from Login service!"; + } + + @PreAuthorize("hasAnyAuthority('PORTAL_ADMINISTRATOR')") + @RequestMapping(value = "/health_check/advanced", method = RequestMethod.GET) + public Map checkEverything() { + Map response = new HashMap<>(); + response.put("authentication.domain", properties.getDomain()); + response.put("authentication.keycloak", properties.getKeycloak().toString()); + response.put("authentication.redis.host", properties.getRedis().getHost()); + response.put("authentication.oidc.issuer", properties.getOidc().getIssuer()); + response.put("authentication.oidc.logout", properties.getOidc().getLogout()); + response.put("authentication.oidc.home", properties.getOidc().getHome()); + response.put("authentication.oidc.redirect", properties.getOidc().getRedirect()); + response.put("authentication.oidc.scope", properties.getOidc().getScope()); + response.put("authentication.oidc.id", properties.getOidc().getId()); + response.put("authentication.oidc.secret", properties.getOidc().getSecret()); + response.put("authentication.session", properties.getSession()); + response.put("authentication.accessToken", properties.getAccessToken()); + response.put("authentication.redirect", properties.getRedirect()); + response.put("authentication.authorities-mapper", properties.getAuthoritiesMapper()); + return response; + } +} diff --git a/src/main/java/eu/dnetlib/authentication/controllers/UserController.java b/src/main/java/eu/dnetlib/authentication/controllers/UserController.java new file mode 100644 index 0000000..1f5f128 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/controllers/UserController.java @@ -0,0 +1,45 @@ +package eu.dnetlib.authentication.controllers; + +import eu.dnetlib.authentication.entities.User; +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.services.UserInfoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +@RestController +public class UserController { + + private final UserInfoService userInfoService; + private final Properties properties; + + @Autowired + public UserController(UserInfoService userInfoService, Properties properties) { + this.userInfoService = userInfoService; + this.properties = properties; + } + + @RequestMapping(value = "/userInfo", method = RequestMethod.GET) + public ResponseEntity getUserInfo() { + return ResponseEntity.ok(userInfoService.getUserInfo()); + } + + @RequestMapping(value = "/redirect",method = RequestMethod.GET) + public void redirect(HttpServletRequest request, HttpServletResponse response) 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(redirect); + } +} diff --git a/src/main/java/eu/dnetlib/authentication/entities/User.java b/src/main/java/eu/dnetlib/authentication/entities/User.java new file mode 100644 index 0000000..f6d0bbc --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/entities/User.java @@ -0,0 +1,74 @@ +package eu.dnetlib.authentication.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/authentication/exception/ResourceNotFoundException.java b/src/main/java/eu/dnetlib/authentication/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..2ef1bec --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/exception/ResourceNotFoundException.java @@ -0,0 +1,20 @@ +package eu.dnetlib.authentication.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/authentication/security/CorsConfig.java b/src/main/java/eu/dnetlib/authentication/security/CorsConfig.java new file mode 100644 index 0000000..39beacc --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/CorsConfig.java @@ -0,0 +1,16 @@ +package eu.dnetlib.authentication.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/authentication/security/RedisConfig.java b/src/main/java/eu/dnetlib/authentication/security/RedisConfig.java new file mode 100644 index 0000000..9062b9b --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/RedisConfig.java @@ -0,0 +1,43 @@ +package eu.dnetlib.authentication.security; + +import eu.dnetlib.authentication.configuration.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.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 = LogManager.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/authentication/security/WebSecurityConfig.java b/src/main/java/eu/dnetlib/authentication/security/WebSecurityConfig.java new file mode 100644 index 0000000..b2ab819 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/WebSecurityConfig.java @@ -0,0 +1,82 @@ +package eu.dnetlib.authentication.security; + +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.security.oidc.OpenAIREAuthenticationFilter; +import eu.dnetlib.authentication.security.oidc.OpenAIREAuthenticationSuccessHandler; +import eu.dnetlib.authentication.security.oidc.OpenAIRELogoutHandler; +import eu.dnetlib.authentication.security.oidc.OpenAIRELogoutSuccessHandler; +import eu.dnetlib.authentication.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/authentication/security/initiliazers/Configurations.java b/src/main/java/eu/dnetlib/authentication/security/initiliazers/Configurations.java new file mode 100644 index 0000000..12962bd --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/initiliazers/Configurations.java @@ -0,0 +1,77 @@ +package eu.dnetlib.authentication.security.initiliazers; + +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.security.oidc.OpenAIREAuthoritiesMapper; +import eu.dnetlib.authentication.security.oidc.OpenAIREUserInfoFetcher; +import eu.dnetlib.authentication.utils.PropertyReader; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.client.OIDCAuthenticationProvider; +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.Arrays; +import java.util.HashSet; + +@Configuration +public class Configurations { + + private final Properties properties; + private final PropertyReader scopeReader; + private final OpenAIREAuthoritiesMapper authoritiesMapper; + private final OpenAIREUserInfoFetcher userInfoFetcher; + + @Autowired + public Configurations(Properties properties, OpenAIREAuthoritiesMapper authoritiesMapper, OpenAIREUserInfoFetcher userInfoFetcher, PropertyReader scopeReader) { + this.properties = properties; + this.authoritiesMapper = authoritiesMapper; + this.userInfoFetcher = userInfoFetcher; + this.scopeReader = scopeReader; + } + + @Bean + public OIDCAuthenticationProvider provider() { + OIDCAuthenticationProvider provider = new OIDCAuthenticationProvider(); + if(properties.getKeycloak()) { + provider.setUserInfoFetcher(this.userInfoFetcher); + } + if(this.properties.getAuthoritiesMapper() != null && this.scopeReader.getScopes().contains(this.properties.getAuthoritiesMapper())) { + provider.setAuthoritiesMapper(this.authoritiesMapper); + } + return provider; + } + + @Bean + public ServerConfiguration serverConfiguration() { + String issuer = properties.getOidc().getIssuer(); + ServerConfiguration serverConfiguration = new ServerConfiguration(); + serverConfiguration.setIssuer(issuer); + Boolean keycloak = properties.getKeycloak(); + if(keycloak) { + serverConfiguration.setAuthorizationEndpointUri(issuer + "/protocol/openid-connect/auth"); + serverConfiguration.setTokenEndpointUri(issuer + "/protocol/openid-connect/token"); + serverConfiguration.setUserInfoUri(issuer + "/protocol/openid-connect/userinfo"); + serverConfiguration.setJwksUri(issuer + "/protocol/openid-connect/certs"); + } else { + 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(new HashSet<>(Arrays.asList(properties.getOidc().getHome(), properties.getOidc().getRedirect()))); + return client; + } +} diff --git a/src/main/java/eu/dnetlib/authentication/security/initiliazers/Primitives.java b/src/main/java/eu/dnetlib/authentication/security/initiliazers/Primitives.java new file mode 100644 index 0000000..72554ec --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/initiliazers/Primitives.java @@ -0,0 +1,56 @@ +package eu.dnetlib.authentication.security.initiliazers; + +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.utils.EntryPoint; +import eu.dnetlib.authentication.utils.PropertyReader; +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; + +@Configuration +public class Primitives { + + private final Properties properties; + + @Autowired + public Primitives(Properties properties) { + this.properties = properties; + + } + + @Bean + public PropertyReader scopeReader() { + return new PropertyReader(this.properties.getOidc().getScope()); + } + + @Bean + public DefaultWebSecurityExpressionHandler handler() { + return new DefaultWebSecurityExpressionHandler(); + } + + @Bean + public PlainAuthRequestUrlBuilder builder() { + return new PlainAuthRequestUrlBuilder(); + } + + @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/authentication/security/initiliazers/Services.java b/src/main/java/eu/dnetlib/authentication/security/initiliazers/Services.java new file mode 100644 index 0000000..c18d6c2 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/initiliazers/Services.java @@ -0,0 +1,47 @@ +package eu.dnetlib.authentication.security.initiliazers; + +import eu.dnetlib.authentication.configuration.Properties; +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/authentication/security/oidc/OpenAIREAuthenticationFilter.java b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREAuthenticationFilter.java new file mode 100644 index 0000000..33552c8 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREAuthenticationFilter.java @@ -0,0 +1,28 @@ +package eu.dnetlib.authentication.security.oidc; + +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.utils.Redirect; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.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 = LogManager.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/authentication/security/oidc/OpenAIREAuthenticationSuccessHandler.java b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREAuthenticationSuccessHandler.java new file mode 100644 index 0000000..95af87c --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREAuthenticationSuccessHandler.java @@ -0,0 +1,66 @@ +package eu.dnetlib.authentication.security.oidc; + +import com.google.gson.JsonParser; +import eu.dnetlib.authentication.configuration.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.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 = LogManager.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(properties.getAccessToken(), 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/authentication/security/oidc/OpenAIREAuthoritiesMapper.java b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREAuthoritiesMapper.java new file mode 100644 index 0000000..b64254f --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREAuthoritiesMapper.java @@ -0,0 +1,30 @@ +package eu.dnetlib.authentication.security.oidc; + +import com.google.gson.JsonArray; +import com.nimbusds.jwt.JWT; +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.utils.AuthoritiesMapper; +import org.mitre.openid.connect.client.OIDCAuthoritiesMapper; +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Component +public class OpenAIREAuthoritiesMapper implements OIDCAuthoritiesMapper { + + private final Properties properties; + + @Autowired + OpenAIREAuthoritiesMapper(Properties properties) { + this.properties = properties; + } + + @Override + public Collection mapAuthorities(JWT jwtToken, UserInfo userInfo) { + JsonArray entitlements = userInfo.getSource().getAsJsonArray(properties.getAuthoritiesMapper()); + return AuthoritiesMapper.map(entitlements); + } +} diff --git a/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIRELogoutHandler.java b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIRELogoutHandler.java new file mode 100644 index 0000000..f59c087 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIRELogoutHandler.java @@ -0,0 +1,27 @@ +package eu.dnetlib.authentication.security.oidc; + +import eu.dnetlib.authentication.configuration.Properties; +import eu.dnetlib.authentication.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 final 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/authentication/security/oidc/OpenAIRELogoutSuccessHandler.java b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIRELogoutSuccessHandler.java new file mode 100644 index 0000000..bcf27de --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIRELogoutSuccessHandler.java @@ -0,0 +1,54 @@ +package eu.dnetlib.authentication.security.oidc; + +import eu.dnetlib.authentication.configuration.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.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +@Configuration +public class OpenAIRELogoutSuccessHandler implements LogoutSuccessHandler { + + private final Properties properties; + + @Autowired + public OpenAIRELogoutSuccessHandler(Properties properties) { + this.properties = properties; + } + + private String encodeValue(String value) throws UnsupportedEncodingException { + return URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + if(properties.getOidc().getRedirect() == null) { + 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() + encodeValue(redirect)); + } else { + StringBuilder sb = new StringBuilder(properties.getOidc().getIssuer()); + if(properties.getKeycloak()) { + sb.append("/protocol/openid-connect/logout"); + sb.append("?client_id=").append(properties.getOidc().getId()); + sb.append("&post_logout_redirect_uri=").append(encodeValue(properties.getOidc().getRedirect())); + } else { + sb.append("/saml/logout"); + } + response.sendRedirect(sb.toString()); + } + } +} diff --git a/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREUserInfoFetcher.java b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREUserInfoFetcher.java new file mode 100644 index 0000000..0a8866a --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/security/oidc/OpenAIREUserInfoFetcher.java @@ -0,0 +1,28 @@ +package eu.dnetlib.authentication.security.oidc; + +import org.mitre.openid.connect.client.UserInfoFetcher; +import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken; +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +@Component +public class OpenAIREUserInfoFetcher extends UserInfoFetcher { + + public OpenAIREUserInfoFetcher() {} + + @Override + public UserInfo loadUserInfo(PendingOIDCAuthenticationToken token) { + UserInfo userInfo = super.loadUserInfo(token); + userInfo.setGivenName(encoder(userInfo.getGivenName())); + userInfo.setFamilyName(encoder(userInfo.getFamilyName())); + userInfo.setName(encoder(userInfo.getName())); + return userInfo; + } + + private String encoder(String value) { + String decodedString = new String(value.getBytes(StandardCharsets.UTF_8)); + return new String(decodedString.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/eu/dnetlib/authentication/services/UserInfoService.java b/src/main/java/eu/dnetlib/authentication/services/UserInfoService.java new file mode 100644 index 0000000..b6a3708 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/services/UserInfoService.java @@ -0,0 +1,20 @@ +package eu.dnetlib.authentication.services; + +import eu.dnetlib.authentication.entities.User; +import eu.dnetlib.authentication.exception.ResourceNotFoundException; +import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +@Service +public class UserInfoService { + + public User getUserInfo() throws ResourceNotFoundException { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if(authentication instanceof OIDCAuthenticationToken) { + return new User((OIDCAuthenticationToken) authentication); + } + throw new ResourceNotFoundException("No Session has been found"); + } +} diff --git a/src/main/java/eu/dnetlib/authentication/utils/AuthoritiesMapper.java b/src/main/java/eu/dnetlib/authentication/utils/AuthoritiesMapper.java new file mode 100644 index 0000000..3046f94 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/utils/AuthoritiesMapper.java @@ -0,0 +1,42 @@ +package eu.dnetlib.authentication.utils; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.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 = LogManager.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/authentication/utils/EntryPoint.java b/src/main/java/eu/dnetlib/authentication/utils/EntryPoint.java new file mode 100644 index 0000000..f5c2501 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/utils/EntryPoint.java @@ -0,0 +1,19 @@ +package eu.dnetlib.authentication.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/authentication/utils/PropertyReader.java b/src/main/java/eu/dnetlib/authentication/utils/PropertyReader.java new file mode 100644 index 0000000..8ec0681 --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/utils/PropertyReader.java @@ -0,0 +1,25 @@ +package eu.dnetlib.authentication.utils; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class PropertyReader { + + Set scopes; + + public PropertyReader(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/dnetlib/authentication/utils/Redirect.java b/src/main/java/eu/dnetlib/authentication/utils/Redirect.java new file mode 100644 index 0000000..3315abd --- /dev/null +++ b/src/main/java/eu/dnetlib/authentication/utils/Redirect.java @@ -0,0 +1,40 @@ +package eu.dnetlib.authentication.utils; + + +import eu.dnetlib.authentication.configuration.Properties; +import org.apache.http.client.utils.URIBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.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 = LogManager.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/resources/authentication.properties b/src/main/resources/authentication.properties new file mode 100644 index 0000000..1fa597a --- /dev/null +++ b/src/main/resources/authentication.properties @@ -0,0 +1,16 @@ +authentication.domain=di.uoa.gr + +authentication.keycloak=false +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:19080/login-service/openid_connect_login +authentication.oidc.scope=openid,profile,email,eduperson_entitlement +authentication.oidc.id=id +authentication.oidc.secret=secret +#authentication.oidc.redirect=http://mpagasas.di.uoa.gr:19080/login-service/redirect + +authentication.session=openAIRESession +authentication.accessToken=AccessToken +authentication.redirect=http://mpagasas.di.uoa.gr:4600/reload + +#authentication.authorities-mapper=eduperson_entitlement