Implemented initial super client registration for user authentication.

Change-Id: I60a7396bef8c2f9b2c2e8bf1cb6e0d5018c79408
diff --git a/core/Changes b/core/Changes
index 1926b51..43d6a0b 100644
--- a/core/Changes
+++ b/core/Changes
@@ -3,6 +3,7 @@
  - Added OAuth2 scopes: INSTALL_USER_CLIENT, UNINSTALL_USER_CLIENT
  - Added status codes
  - Implemented searching option using a network endpoint
+ - Updated JsonUtils
 
 # version 0.67.1
 
diff --git a/core/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java b/core/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java
index b018688..792a6bb 100644
--- a/core/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java
+++ b/core/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java
@@ -2,6 +2,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -46,8 +47,7 @@
                     "Failed deserializing json object: " + json, json, e);
         }
     }
-
-
+    
     public static ObjectNode createObjectNode () {
         return mapper.createObjectNode();
     }
@@ -71,6 +71,10 @@
         return mapper.readValue(json, cl);
     }
 
+    public static <T> T read (InputStream is, Class<T> cl) throws IOException {
+        return mapper.readValue(is, cl);
+    }
+
 
     public static <T> T readFile (String path, Class<T> clazz)
             throws IOException {
@@ -78,7 +82,7 @@
     }
 
 
-    public static void writeFile (String path, String content)
+    public static void writeFile (String path, Object content)
             throws IOException {
         mapper.writeValue(new File(path), content);
     }
diff --git a/full/Changes b/full/Changes
index 049a39f..c2adc2a 100644
--- a/full/Changes
+++ b/full/Changes
@@ -19,7 +19,7 @@
    service paths under /plugins.
 2022-06-03 
  - Implemented searching option using a network endpoint
-
+ - Implemented initial super client registration for user authentication.
  
 # version 0.67.1
 
diff --git a/full/src/main/java/de/ids_mannheim/de/init/Initializator.java b/full/src/main/java/de/ids_mannheim/de/init/Initializator.java
index 2062e20..bea303e 100644
--- a/full/src/main/java/de/ids_mannheim/de/init/Initializator.java
+++ b/full/src/main/java/de/ids_mannheim/de/init/Initializator.java
@@ -6,10 +6,12 @@
 import org.springframework.beans.factory.annotation.Autowired;
 
 import de.ids_mannheim.korap.annotation.FreeResourceParser;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.config.NamedVCLoader;
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.dao.AccessScopeDao;
+import de.ids_mannheim.korap.oauth2.service.OAuth2InitClientService;
 import de.ids_mannheim.korap.util.QueryException;
 
 /**
@@ -19,7 +21,7 @@
  * @author margaretha
  *
  */
-public class Initializator{
+public class Initializator {
 
     @Autowired
     private AccessScopeDao accessScopeDao;
@@ -27,20 +29,33 @@
     private NamedVCLoader vcLoader;
     @Autowired
     private FreeResourceParser resourceParser;
-    
+    @Autowired
+    private FullConfiguration config;
+    @Autowired
+    private OAuth2InitClientService clientService;
+
     public Initializator () {}
 
     public void init () throws IOException, QueryException, KustvaktException {
         setInitialAccessScope();
         resourceParser.run();
+
+        if (config.createInitialSuperClient()) {
+            clientService.createInitialSuperClient(
+                    OAuth2InitClientService.OUTPUT_FILENAME);
+        }
+
         Thread t = new Thread(vcLoader);
         t.start();
     }
 
     public void initTest () throws IOException, KustvaktException {
         setInitialAccessScope();
+        if (config.createInitialSuperClient()) {
+            clientService.createInitialTestSuperClient();
+        }
     }
-    
+
     public void initResourceTest () throws IOException, KustvaktException {
         setInitialAccessScope();
         resourceParser.run();
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index 32f98f8..77108d8 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -92,6 +92,8 @@
     private String rsaKeyId;
 
     private String namedVCPath;
+    
+    private boolean createInitialSuperClient;
 
     public FullConfiguration (Properties properties) throws Exception {
         super(properties);
@@ -127,7 +129,6 @@
         config.setMaxBytesLocalDisk(properties.getProperty("cache.max.bytes.local.disk", "2G"));
         jlog.info("max local heap:"+config.getMaxBytesLocalHeapAsString());
         jlog.info("max local disk:"+config.getMaxBytesLocalDiskAsString());
-
     }
 
     private void setSecurityConfiguration (Properties properties) {
@@ -247,6 +248,8 @@
                         "oauth2.password.authentication", "TEST")));
         setNativeClientHost(properties.getProperty("oauth2.native.client.host",
                 "korap.ids-mannheim.de"));
+        setCreateInitialSuperClient(Boolean.valueOf(
+                properties.getProperty("oauth2.initial.super.client", "false")));
 
         setMaxAuthenticationAttempts(Integer
                 .parseInt(properties.getProperty("oauth2.max.attempts", "1")));
@@ -674,4 +677,14 @@
     public void setRefreshTokenLongExpiry (int refreshTokenLongExpiry) {
         this.refreshTokenLongExpiry = refreshTokenLongExpiry;
     }
+
+    public boolean createInitialSuperClient () {
+        return createInitialSuperClient;
+    }
+    
+    public void setCreateInitialSuperClient (boolean initialSuperClient) {
+        this.createInitialSuperClient = initialSuperClient;
+    }
+    
+    
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2InitClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2InitClientService.java
new file mode 100644
index 0000000..e35b0e4
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2InitClientService.java
@@ -0,0 +1,110 @@
+package de.ids_mannheim.korap.oauth2.service;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientDto;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+@Service
+public class OAuth2InitClientService {
+
+    private static Logger log =
+            LogManager.getLogger(OAuth2InitClientService.class);
+    public static String OAUTH2_CLIENT_JSON_INPUT_FILE =
+            "initial_super_client.json";
+    public static String OUTPUT_FOLDER = "client";
+    public static String OUTPUT_FILENAME = "super_client_info";
+    public static String TEST_OUTPUT_FILENAME = "test_super_client_info";
+
+    @Autowired
+    private OAuth2ClientService clientService;
+    @Autowired
+    private OAuth2ClientDao clientDao;
+    @Autowired
+    private EncryptionIface encryption;
+
+    public void createInitialSuperClient (String outputFilename)
+            throws IOException, KustvaktException {
+
+        File dir = new File(OUTPUT_FOLDER);
+        if (!dir.exists()) {
+            dir.mkdir();
+        }
+
+        String path = OUTPUT_FOLDER + "/" + outputFilename;
+        File f = new File(path);
+
+        if (!f.exists()) {
+            OAuth2ClientJson json = readOAuth2ClientJsonFile();
+            OAuth2ClientDto clientDto =
+                    clientService.registerClient(json, "system");
+            String clientId = clientDto.getClient_id();
+            OAuth2Client client = clientService.retrieveClient(clientId);
+            client.setSuper(true);
+            clientDao.updateClient(client);
+            JsonUtils.writeFile(path, clientDto);
+            
+            log.info(
+                    "Initial super client has been successfully registered. See "+path);
+        }
+        else {
+            JsonNode node = JsonUtils.readFile(path, JsonNode.class);
+            String existingClientId = node.at("/client_id").asText();
+            String clientSecret = node.at("/client_secret").asText();
+            String secretHashcode = encryption.secureHash(clientSecret);
+            
+            try {
+                clientService.retrieveClient(existingClientId);
+                log.info(
+                        "Super client info file exists. Initial super client "
+                        + "registration is cancelled.");
+            }
+            catch (Exception e) {
+                log.info("Super client info file exists but the client "
+                        + "doesn't exist in the database.");
+                OAuth2ClientJson json = readOAuth2ClientJsonFile();
+                OAuth2ClientDto clientDto =
+                        clientService.registerClient(json, "system");
+                String clientId = clientDto.getClient_id();
+                OAuth2Client client = clientService.retrieveClient(clientId);
+                client.setSuper(true);
+                client.setId(existingClientId);
+                client.setSecret(secretHashcode);
+                clientDao.updateClient(client);
+            }
+        }
+    }
+
+    private OAuth2ClientJson readOAuth2ClientJsonFile () throws IOException, KustvaktException {
+        File f = new File(OAUTH2_CLIENT_JSON_INPUT_FILE);
+        if (f.exists()) {
+            return JsonUtils.readFile(OAUTH2_CLIENT_JSON_INPUT_FILE,
+                    OAuth2ClientJson.class);
+        }
+        else {
+            InputStream is = getClass().getClassLoader()
+                    .getResourceAsStream("json/"+OAUTH2_CLIENT_JSON_INPUT_FILE);
+            return JsonUtils.read(is, OAuth2ClientJson.class);
+        }
+
+    }
+
+    public void createInitialTestSuperClient ()
+            throws IOException, KustvaktException {
+        createInitialSuperClient(TEST_OUTPUT_FILENAME);
+    }
+}
diff --git a/full/src/main/resources/json/initial_super_client.json b/full/src/main/resources/json/initial_super_client.json
new file mode 100644
index 0000000..a8c997b
--- /dev/null
+++ b/full/src/main/resources/json/initial_super_client.json
@@ -0,0 +1,5 @@
+{
+  "name":"initial super client",
+  "type": "CONFIDENTIAL",
+  "description":"initial super client for user authentication"
+}
\ No newline at end of file
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/InitialSuperClientTest.java b/full/src/test/java/de/ids_mannheim/korap/web/InitialSuperClientTest.java
new file mode 100644
index 0000000..46e2526
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/InitialSuperClientTest.java
@@ -0,0 +1,76 @@
+package de.ids_mannheim.korap.web;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientResponse;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.service.OAuth2InitClientService;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.controller.OAuth2TestBase;
+
+public class InitialSuperClientTest extends OAuth2TestBase {
+
+    @Autowired
+    private FullConfiguration config;
+    @Autowired
+    private OAuth2ClientDao clientDao;
+    
+    private String path = OAuth2InitClientService.OUTPUT_FOLDER + "/"
+            + OAuth2InitClientService.TEST_OUTPUT_FILENAME;
+
+    @Test
+    public void testCreatingInitialSuperClient ()
+            throws IOException, KustvaktException {
+        assertTrue(config.createInitialSuperClient());
+       
+        File f = new File(path);
+        assertTrue(f.exists());
+
+        JsonNode node = JsonUtils.readFile(path, JsonNode.class);
+        String superClientId = node.at("/client_id").asText();
+        String superClientSecret = node.at("/client_secret").asText();
+
+        OAuth2Client superClient = clientDao.retrieveClientById(superClientId);
+        assertTrue(superClient.isSuper());
+
+        testLogin(superClientId, superClientSecret);
+        
+        removeSuperClientFile();
+    }
+
+    private void testLogin (String superClientId, String superClientSecret)
+            throws KustvaktException {
+        ClientResponse response = requestTokenWithPassword(superClientId,
+                superClientSecret, "username", "password");
+
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        assertTrue(!node.at("/access_token").isMissingNode());
+        assertTrue(!node.at("/refresh_token").isMissingNode());
+        assertTrue(!node.at("/expires_in").isMissingNode());
+        assertEquals("all", node.at("/scope").asText());
+        assertEquals("Bearer", node.at("/token_type").asText());
+    }
+    
+    private void removeSuperClientFile () {
+        File f = new File(path);
+        if (f.exists()) {
+            f.delete();
+        }
+    }
+}
diff --git a/full/src/test/resources/kustvakt-test.conf b/full/src/test/resources/kustvakt-test.conf
index fb17890..e72b1fd 100644
--- a/full/src/test/resources/kustvakt-test.conf
+++ b/full/src/test/resources/kustvakt-test.conf
@@ -66,6 +66,8 @@
 oauth2.default.scopes = search match_info
 oauth2.client.credentials.scopes = client_info
 
+oauth2.initial.super.client=true
+
 ## OpenId
 ### multiple values are separated by space
 openid.grant.types = authorization_code