diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue index 6bc6368123010a1fa486de03fe63bf4f711141a6..d47d343a36e39a5902762758bd0fb00993a35f6e 100644 --- a/gms-ui/src/App.vue +++ b/gms-ui/src/App.vue @@ -61,7 +61,7 @@ export default { } #loading { - position: absolute; + position: fixed; top: 0; bottom: 0; right: 0; diff --git a/gms/pom.xml b/gms/pom.xml index 71f2aa10dba68bf0275420b1794335329c27ef68..ae112f7a3fe550c0638370828026a8a37666dc0a 100644 --- a/gms/pom.xml +++ b/gms/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>2.1.6.RELEASE</version> + <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>it.inaf.ia2</groupId> @@ -23,15 +23,15 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>${project.parent.version}</version> </dependency> - <dependency> - <groupId>org.springframework.security</groupId> - <artifactId>spring-security-oauth2-client</artifactId> - </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> @@ -52,11 +52,6 @@ <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.springframework.security</groupId> - <artifactId>spring-security-test</artifactId> - <scope>test</scope> - </dependency> <!-- Embedded PostgreSQL: --> <dependency> <groupId>com.opentable.components</groupId> diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java index 71c9da2c349c61dec4ec7d359bf91a55538a154c..3a10843e77aab01104d92cde091f01feb50cf9f0 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomAuthenticationData.java @@ -5,17 +5,16 @@ import java.util.Map; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; public class CustomAuthenticationData extends UsernamePasswordAuthenticationToken { private final Map<String, Object> attributes; private final OAuth2AccessToken accessToken; - private final OAuth2RefreshToken refreshToken; + private final String refreshToken; public CustomAuthenticationData(String username, Map<String, Object> attributes, Collection<? extends GrantedAuthority> authorities, - OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) { + OAuth2AccessToken accessToken, String refreshToken) { super(username, "N/A", authorities); this.attributes = attributes; this.accessToken = accessToken; @@ -30,7 +29,7 @@ public class CustomAuthenticationData extends UsernamePasswordAuthenticationToke return accessToken; } - public OAuth2RefreshToken getRefreshToken() { + public String getRefreshToken() { return refreshToken; } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java index 7d0bc239c842bc738fe100c86cc38e5aec3fab34..fd53449e8445fe9d556ae087eee9e9fca496ff08 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/CustomIdTokenConverter.java @@ -6,7 +6,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore; @@ -25,8 +24,9 @@ public class CustomIdTokenConverter extends DefaultUserAuthenticationConverter { OAuth2AccessToken token = jwkTokenStore.readAccessToken(idToken); + String refreshToken = (String) map.get("refresh_token"); + Map<String, Object> claims = token.getAdditionalInformation(); - OAuth2RefreshToken refreshToken = token.getRefreshToken(); String principal = (String) claims.get("sub"); diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java index bc41752728384707944747b74b2b2991e5bee185..51c50e64f519c4c52ae3c3e8a1589ec231f8064a 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java @@ -16,12 +16,14 @@ public class SessionData { private String userId; private String accessToken; + private String refreshToken; @PostConstruct public void init() { CustomAuthenticationData authn = (CustomAuthenticationData) ((OAuth2Authentication) request.getUserPrincipal()).getUserAuthentication(); userId = (String) authn.getPrincipal(); accessToken = (String) authn.getAccessToken().getValue(); + refreshToken = authn.getRefreshToken(); } public String getUserId() { @@ -31,4 +33,16 @@ public class SessionData { public String getAccessToken() { return accessToken; } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java index 6481b8d00e37057b43431342d7973eee85d0f691..d73ab74db97d31dba85a2b83de18bac31844d707 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java +++ b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java @@ -5,7 +5,9 @@ import it.inaf.ia2.gms.model.RapUser; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; @@ -13,7 +15,11 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; @Component @@ -22,11 +28,30 @@ public class RapClient { @Value("${rap.ws-url}") private String rapBaseUrl; - @Autowired - private SessionData sessionData; + @Value("${security.oauth2.client.access-token-uri}") + private String accessTokenUri; + + @Value("${security.oauth2.client.client-id}") + private String clientId; + + @Value("${security.oauth2.client.client-secret}") + private String clientSecret; + + @Value("${security.oauth2.client.scope}") + private String scope; + + private final SessionData sessionData; + + private final RestTemplate rapRestTemplate; + + private final RestTemplate refreshTokenRestTemplate; @Autowired - private RestTemplate rapRestTemplate; + public RapClient(SessionData sessionData, RestTemplate rapRestTemplate) { + this.sessionData = sessionData; + this.rapRestTemplate = rapRestTemplate; + this.refreshTokenRestTemplate = new RestTemplate(); + } public List<RapUser> getUsers(Set<String> identifiers) { @@ -35,8 +60,11 @@ public class RapClient { } String url = rapBaseUrl + "/user?identifiers=" + String.join(",", identifiers); - return rapRestTemplate.exchange(url, HttpMethod.GET, getEntity(), new ParameterizedTypeReference<List<RapUser>>() { - }).getBody(); + + return httpCall(entity -> { + return rapRestTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<List<RapUser>>() { + }).getBody(); + }); } public List<RapUser> searchUsers(String searchText) { @@ -46,12 +74,24 @@ public class RapClient { } String url = rapBaseUrl + "/user?search=" + searchText; - return rapRestTemplate.exchange(url, HttpMethod.GET, getEntity(), new ParameterizedTypeReference<List<RapUser>>() { - }).getBody(); + + return httpCall(entity -> { + return rapRestTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<List<RapUser>>() { + }).getBody(); + }); + } + + private <R> R httpCall(Function<HttpEntity<?>, R> function) { + return httpCall(function, null); } - private HttpEntity<?> getEntity() { - return getEntity(null); + private <R, T> R httpCall(Function<HttpEntity<?>, R> function, T body) { + try { + return function.apply(getEntity(body)); + } catch (HttpClientErrorException.Unauthorized ex) { + refreshToken(); + return function.apply(getEntity(body)); + } } private <T> HttpEntity<T> getEntity(T body) { @@ -63,4 +103,27 @@ public class RapClient { return new HttpEntity<>(body, headers); } + + private void refreshToken() { + + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setBasicAuth(clientId, clientSecret); + + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); + map.add("grant_type", "refresh_token"); + map.add("refresh_token", sessionData.getRefreshToken()); + map.add("scope", scope.replace(",", " ")); + + HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); + + ResponseEntity<Map> response = refreshTokenRestTemplate.postForEntity(accessTokenUri, request, Map.class); + + Map<String, String> values = response.getBody(); + + sessionData.setAccessToken(values.get("access_token")); + sessionData.setRefreshToken(values.get("refresh_token")); + } }