From fa4bf6f13da0d2940362c8a5ff8f7a9280698f42 Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Wed, 11 Sep 2019 15:18:08 +0200
Subject: [PATCH] Added refresh token support

---
 gms-ui/src/App.vue                            |  2 +-
 gms/pom.xml                                   | 15 ++--
 .../gms/authn/CustomAuthenticationData.java   |  7 +-
 .../ia2/gms/authn/CustomIdTokenConverter.java |  4 +-
 .../it/inaf/ia2/gms/authn/SessionData.java    | 14 ++++
 .../java/it/inaf/ia2/gms/rap/RapClient.java   | 81 ++++++++++++++++---
 6 files changed, 97 insertions(+), 26 deletions(-)

diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue
index 6bc6368..d47d343 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 71f2aa1..ae112f7 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 71c9da2..3a10843 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 7d0bc23..fd53449 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 bc41752..51c50e6 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 6481b8d..d73ab74 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"));
+    }
 }
-- 
GitLab