Added columns to the oauth2_client table and updated client registration

Change-Id: Ic16b41859fdda464fc2315ae40b1ddded9c45dca
diff --git a/full/Changes b/full/Changes
index f10e02c..5117c88 100644
--- a/full/Changes
+++ b/full/Changes
@@ -4,6 +4,10 @@
  - Updated query and user-group name pattern.
  2022-04-08
  - Added redirect_uri to client info API.
+2022-04-11
+ - Added registration_date, refresh_token_expiry, source and is_permitted
+   to the oauth2_client database table, and updated the OAuth2 client 
+   registration mechanism.
 
 # version 0.65.2
 
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 969d4b6..32f98f8 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
@@ -79,6 +79,7 @@
 
     private int accessTokenLongExpiry;
     private int accessTokenExpiry;
+    private int refreshTokenLongExpiry;
     private int refreshTokenExpiry;
     private int authorizationCodeExpiry;
 
@@ -270,6 +271,8 @@
         
         setAccessTokenLongExpiry(TimeUtils.convertTimeToSeconds(
                 properties.getProperty("oauth2.access.token.long.expiry", "365D")));
+        setRefreshTokenLongExpiry(TimeUtils.convertTimeToSeconds(
+                properties.getProperty("oauth2.refresh.token.long.expiry", "365D")));
     }
 
     private void setMailConfiguration (Properties properties) {
@@ -663,4 +666,12 @@
     public void setAccessTokenLongExpiry (int accessTokenLongExpiry) {
         this.accessTokenLongExpiry = accessTokenLongExpiry;
     }
+
+    public int getRefreshTokenLongExpiry () {
+        return refreshTokenLongExpiry;
+    }
+
+    public void setRefreshTokenLongExpiry (int refreshTokenLongExpiry) {
+        this.refreshTokenLongExpiry = refreshTokenLongExpiry;
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
index dff99d6..989deb2 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
@@ -15,10 +15,14 @@
 import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
+import com.fasterxml.jackson.databind.JsonNode;
+
 import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
@@ -42,10 +46,13 @@
 
     @PersistenceContext
     private EntityManager entityManager;
+    @Autowired
+    private FullConfiguration config;
 
     public void registerClient (String id, String secretHashcode, String name,
             OAuth2ClientType type, String url, String redirectURI,
-            String registeredBy, String description) throws KustvaktException {
+            String registeredBy, String description, int refreshTokenExpiry,
+            JsonNode source) throws KustvaktException {
         ParameterChecker.checkStringValue(id, "client_id");
         ParameterChecker.checkStringValue(name, "client_name");
         ParameterChecker.checkObjectValue(type, "client_type");
@@ -63,7 +70,17 @@
         client.setUrl(url);
         client.setRedirectURI(redirectURI);
         client.setRegisteredBy(registeredBy);
+        client.setRegistrationDate(ZonedDateTime.now());
         client.setDescription(description);
+        if (source !=null && !source.isNull()) {
+            client.setSource(source.toString());
+        }
+        else {
+            client.setPermitted(true);
+        }
+        if (refreshTokenExpiry <= 0) {
+           refreshTokenExpiry = config.getRefreshTokenLongExpiry();
+        }
         entityManager.persist(client);
     }
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
index 3f22288..04dc175 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
@@ -1,11 +1,14 @@
 package de.ids_mannheim.korap.oauth2.dto;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
+import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.utils.JsonUtils;
 
 /** Describes information about an OAuth2 client.
  * 
@@ -24,9 +27,15 @@
     private String redirect_uri;
     @JsonProperty("registered_by")
     private String registeredBy;
+    @JsonProperty("registration_date")
+    private String registrationDate;
     private OAuth2ClientType type;
+    
+    @JsonProperty("permitted")
+    private boolean isPermitted;
+    private JsonNode source;
 
-    public OAuth2ClientInfoDto (OAuth2Client client) {
+    public OAuth2ClientInfoDto (OAuth2Client client) throws KustvaktException {
         this.id = client.getId();
         this.name = client.getName();
         this.description = client.getDescription();
@@ -34,7 +43,12 @@
         this.url = client.getUrl();
         this.registeredBy = client.getRegisteredBy();
         this.redirect_uri = client.getRedirectURI();
-
+        this.registrationDate = client.getRegistrationDate().toString();
+        this.isPermitted = client.isPermitted();
+        String source = client.getSource();
+        if (source != null) {
+            this.source = JsonUtils.readTree(source);
+        }
         if (client.isSuper()) {
             this.isSuper = "true";
         }
@@ -103,5 +117,26 @@
     public void setRedirect_uri (String redirect_uri) {
         this.redirect_uri = redirect_uri;
     }
+    
+    public String getRegistrationDate () {
+        return registrationDate;
+    }
+    public void setRegistrationDate (String registrationDate) {
+        this.registrationDate = registrationDate;
+    }
+    
+    public JsonNode getSource () {
+        return source;
+    }
+    public void setSource (JsonNode source) {
+        this.source = source;
+    }
+    
+    public boolean isPermitted () {
+        return isPermitted;
+    }
+    public void setPermitted (boolean isPermitted) {
+        this.isPermitted = isPermitted;
+    }
 
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
index fb5cbbb..b7d1031 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.oauth2.entity;
 
+import java.time.ZonedDateTime;
 import java.util.List;
 
 import javax.persistence.Column;
@@ -33,12 +34,20 @@
     private boolean isSuper;
     @Column(name = "redirect_uri")
     private String redirectURI;
-    @Column(name = "registered_by")
+    @Column(name = "registered_by", updatable = false)
     private String registeredBy;
+    @Column(name = "registration_date", updatable = false)
+    private ZonedDateTime registrationDate;
+    
+    @Column(name = "refresh_token_expiry")
+    private int refresTokenExpiry;
     private String description;
-
     private String url;
 
+    private String source;
+    @Column(name = "is_permitted")
+    private boolean isPermitted;
+    
     @OneToMany(fetch = FetchType.LAZY, mappedBy = "client")
     private List<RefreshToken> refreshTokens;
     
@@ -113,6 +122,13 @@
     public void setRegisteredBy (String registeredBy) {
         this.registeredBy = registeredBy;
     }
+    
+    public ZonedDateTime getRegistrationDate () {
+        return registrationDate;
+    }
+    public void setRegistrationDate (ZonedDateTime registrationDate) {
+        this.registrationDate = registrationDate;
+    }
 
     public String getDescription () {
         return description;
@@ -130,4 +146,19 @@
         this.url = url;
     }
 
+    public String getSource () {
+        return source;
+    }
+
+    public void setSource (String source) {
+        this.source = source;
+    }
+
+    public boolean isPermitted () {
+        return isPermitted;
+    }
+    
+    public void setPermitted (boolean isPermitted) {
+        this.isPermitted = isPermitted;
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index 505140c..d714111 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -13,6 +13,8 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import com.fasterxml.jackson.databind.JsonNode;
+
 import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.dao.AdminDao;
 import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
@@ -128,10 +130,13 @@
 
         String id = codeGenerator.createRandomCode();
         id = codeGenerator.filterRandomCode(id);
+        
         try {
             clientDao.registerClient(id, secretHashcode, clientJson.getName(),
                     clientJson.getType(), url, redirectURI, registeredBy,
-                    clientJson.getDescription());
+                    clientJson.getDescription(),
+                    clientJson.getRefreshTokenExpiry(),
+                    clientJson.getSource());
         }
         catch (KustvaktException e) {
             throw new KustvaktException(e.getStatusCode(),
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
index b11ac63..7180eda 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.web.input;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
 
 import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
 
@@ -11,6 +12,8 @@
  * To accommodate desktop applications such as R, url and redirectURI
  * are not compulsory.
  * 
+ * Source is json description of a plugin.
+ * 
  * @author margaretha
  *
  */
@@ -27,7 +30,11 @@
     // the user to after they have authorized a client.
     @JsonProperty("redirect_uri")
     private String redirectURI;
+    // Default 365 days
+    private int refreshTokenExpiry; // in seconds
     
+    // plugins
+    private JsonNode source;
 
     public String getName () {
         return name;
@@ -68,4 +75,20 @@
     public void setDescription (String description) {
         this.description = description;
     }
+
+    public int getRefreshTokenExpiry () {
+        return refreshTokenExpiry;
+    }
+
+    public void setRefreshTokenExpiry (int refreshTokenExpiry) {
+        this.refreshTokenExpiry = refreshTokenExpiry;
+    }
+
+    public JsonNode getSource () {
+        return source;
+    }
+
+    public void setSource (JsonNode source2) {
+        this.source = source2;
+    }
 }
diff --git a/full/src/main/resources/db/sqlite/V1.11__plugin.sql b/full/src/main/resources/db/sqlite/V1.11__plugin.sql
new file mode 100644
index 0000000..94b90ca
--- /dev/null
+++ b/full/src/main/resources/db/sqlite/V1.11__plugin.sql
@@ -0,0 +1,22 @@
+ALTER TABLE oauth2_client 
+	ADD COLUMN registration_date TIMESTAMP NOT NULL;
+
+-- default 365 days in seconds
+ALTER TABLE oauth2_client 
+	ADD COLUMN refresh_token_expiry INTEGER DEFAULT 31536000;
+	
+ALTER TABLE oauth2_client 
+	ADD COLUMN source BLOB DEFAULT NULL;
+
+ALTER TABLE oauth2_client 
+	ADD COLUMN is_permitted BOOLEAN DEFAULT FALSE;
+
+--CREATE TABLE IF NOT EXISTS user_installed_client (
+--	id INTEGER PRIMARY KEY AUTOINCREMENT,
+--	installed_by VARCHAR(100) NOT NULL,
+--	installed_date TIMESTAMP NOT NULL,
+--	client_id VARCHAR(100) NOT NULL,
+--	FOREIGN KEY (client_id)
+--	   REFERENCES oauth2_client(id)
+--	   ON DELETE CASCADE
+--);
\ No newline at end of file
diff --git a/full/src/main/resources/db/sqlite/V1.9__query_alteration.sql b/full/src/main/resources/db/sqlite/V1.9__query_alteration.sql
index 0d3bf8c..02cf486 100644
--- a/full/src/main/resources/db/sqlite/V1.9__query_alteration.sql
+++ b/full/src/main/resources/db/sqlite/V1.9__query_alteration.sql
@@ -23,7 +23,6 @@
 	ON query(name,created_by);
 
 
-
 ALTER TABLE virtual_corpus_access 
 RENAME COLUMN virtual_corpus_id TO query_id;
 
@@ -31,8 +30,6 @@
 RENAME TO query_access;
 
 
-
-
 DROP TABLE IF EXISTS query_reference;
 
 DROP INDEX IF EXISTS query_reference_owner_index;
diff --git a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
index 858d238..043426d 100644
--- a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
+++ b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
@@ -2,50 +2,55 @@
 
 -- plain secret value is "secret"
 INSERT INTO oauth2_client(id,name,secret,type,super,
-  redirect_uri,registered_by, description, url) 
+  redirect_uri,registered_by, description, url, registration_date, 
+  is_permitted) 
 VALUES ("fCBbQkAyYzI4NzUxMg","super confidential client",
   "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
   "CONFIDENTIAL", 1, 
   "https://korap.ids-mannheim.de/confidential/redirect", "system",
   "This is a test super confidential client.", 
-  "http://korap.ids-mannheim.de/confidential");
+  "http://korap.ids-mannheim.de/confidential", CURRENT_TIMESTAMP, 1);
 
   
 -- plain secret value is "secret"
 INSERT INTO oauth2_client(id,name,secret,type,super,
-  redirect_uri,registered_by, description,url) 
+  redirect_uri,registered_by, description,url,registration_date, 
+  is_permitted) 
 VALUES ("9aHsGW6QflV13ixNpez","non super confidential client",
   "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
   "CONFIDENTIAL", 0,
   "https://third.party.com/confidential/redirect", "system",
   "This is a test nonsuper confidential client.",
-  "http://third.party.com/confidential");
+  "http://third.party.com/confidential", CURRENT_TIMESTAMP,1);
 
 INSERT INTO oauth2_client(id,name,secret,type,super,
-  redirect_uri,registered_by, description,url) 
+  redirect_uri,registered_by, description,url, registration_date, 
+  is_permitted) 
 VALUES ("52atrL0ajex_3_5imd9Mgw","confidential client 2",
   "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
   "CONFIDENTIAL", 0,
   "https://example.client.de/redirect", "system",
   "This is a test nonsuper confidential client.",
-  "http://example.client.de");
+  "http://example.client.de", CURRENT_TIMESTAMP, 1);
 
 INSERT INTO oauth2_client(id,name,secret,type,super,
-  redirect_uri, registered_by, description, url) 
+  redirect_uri, registered_by, description, url, registration_date, 
+  is_permitted)
 VALUES ("8bIDtZnH6NvRkW2Fq","third party client",null,
   "PUBLIC", 0,
   "https://third.party.client.com/redirect","system",
   "This is a test public client.",
-  "http://third.party.client.com");
+  "http://third.party.client.com", CURRENT_TIMESTAMP,1);
 
   
 INSERT INTO oauth2_client(id,name,secret,type,super,
-  redirect_uri, registered_by, description,url) 
+  redirect_uri, registered_by, description, url, registration_date, 
+  is_permitted) 
 VALUES ("nW5qM63Rb2a7KdT9L","test public client",null,
   "PUBLIC", 0, 
   "https://korap.ids-mannheim.de/public/redirect","system", 
   "This is a test public client.",
-  "http://korap.ids-mannheim.de/public");
+  "http://korap.ids-mannheim.de/public", CURRENT_TIMESTAMP, 1);
   
 
 INSERT INTO oauth2_access_token(token,user_id,created_date, 
diff --git a/full/src/main/resources/kustvakt.conf b/full/src/main/resources/kustvakt.conf
index daf0e9a..5d822ae 100644
--- a/full/src/main/resources/kustvakt.conf
+++ b/full/src/main/resources/kustvakt.conf
@@ -5,10 +5,10 @@
 krill.index.commit.log = log/krill.commit.log
 krill.index.commit.auto = 500
 krill.index.relations.max = 100
-## Directory path of virtual corpora to cache
+# Directory path of virtual corpora to cache
 #krill.namedVC = vc
 
-## LDAP
+# LDAP
 ldap.config = file-path-to-ldap-config
 
 # Kustvakt
@@ -17,20 +17,20 @@
 # multiple versions separated by space
 # supported.api.version = v1.0
 
-## server
+# server
 server.port=8089
 server.host=localhost
 
-## mail settings
+# mail settings
 mail.enabled = false
 mail.receiver = test@localhost
 mail.sender = noreply@ids-mannheim.de
 mail.address.retrieval = test
 
-## mail.templates
+# mail.templates
 template.group.invitation = notification.vm
 
-## default foundries for specific layers
+# default foundries for specific layers
 default.foundry.partOfSpeech = tt
 default.foundry.lemma = tt
 default.foundry.orthography = opennlp
@@ -39,32 +39,33 @@
 default.foundry.morphology = marmot
 default.foundry.surface = base
 
-## delete configuration (default hard)
+# delete configuration (default hard)
 # delete.auto.group = hard
 delete.group = soft
 delete.group.member = soft
 
-## availability regex
-## only support |
+# availability regex (only support | )
 availability.regex.free = CC-BY.*
 availability.regex.public = ACA.*|QAO.NC
 availability.regex.all = QAO.*
 
-## options referring to the security module!
+# options referring to the security module!
 
-## OAuth 
-### (see de.ids_mannheim.korap.constant.AuthenticationMethod for possible 
-### oauth.password.authentication values)
+# OAuth 
+# (see de.ids_mannheim.korap.constant.AuthenticationMethod for possible 
+# oauth.password.authentication values)
 oauth.password.authentication = TEST
-### used to determine native client, currently not used
-oauth2.native.client.host = korap.ids-mannheim.de
+# used to determine native client, currently not used
+# oauth2.native.client.host = korap.ids-mannheim.de
 oauth2.max.attempts = 1
 # expiry in seconds (S), minutes (M), hours (H), days (D)
 oauth2.access.token.expiry = 1D
-oauth2.access.token.long.expiry = 365D
 oauth2.refresh.token.expiry = 90D
+# default 365D
+# oauth2.access.token.long.expiry = 365D
+# oauth2.refresh.token.long.expiry = 365D
 oauth2.authorization.code.expiry = 10M
-# -- scopes separated by space
+# scopes separated by space
 oauth2.default.scopes = search match_info 
 oauth2.client.credentials.scopes = client_info
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index a9f1095..ec73796 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -118,13 +118,43 @@
         String clientSecret = node.at("/client_secret").asText();
         assertNotNull(clientId);
         assertNotNull(clientSecret);
-
         assertFalse(clientId.contains("a"));
         
         testConfidentialClientInfo(clientId, username);
         testResetConfidentialClientSecret(clientId, clientSecret);
         deregisterConfidentialClient(username, clientId);
     }
+    
+    @Test
+    public void testRegisterPlugin () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        JsonNode source = JsonUtils.readTree("{ \"plugin\" : \"source\"}");
+                
+        OAuth2ClientJson json = new OAuth2ClientJson();
+        json.setName("Plugin");
+        json.setType(OAuth2ClientType.CONFIDENTIAL);
+        json.setDescription("This is a plugin test client.");
+        json.setSource(source);
+        
+        ClientResponse response = registerClient(username, json);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        String clientId = node.at("/client_id").asText();
+        String clientSecret = node.at("/client_secret").asText();
+        assertNotNull(clientId);
+        assertNotNull(clientSecret);
+        
+        JsonNode clientInfo = retrieveClientInfo(clientId, username);
+        assertEquals(clientId, clientInfo.at("/id").asText());
+        assertEquals("Plugin", clientInfo.at("/name").asText());
+        assertEquals(OAuth2ClientType.CONFIDENTIAL.name(),
+                clientInfo.at("/type").asText());
+        assertEquals(username, clientInfo.at("/registered_by").asText());
+        assertNotNull(clientInfo.at("/registration_date"));
+        
+        assertFalse(clientInfo.at("/permitted").asBoolean());
+        assertNotNull(clientInfo.at("/source"));
+    }
 
     @Test
     public void testRegisterClientNameTooShort ()
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index 9eb287d..1574505 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -2,6 +2,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.net.URI;
 
@@ -260,6 +261,10 @@
         assertEquals(clientURL, clientInfo.at("/url").asText());
         assertEquals(clientRedirectUri, clientInfo.at("/redirect_uri").asText());
         assertNotNull(clientInfo.at("/description"));
+        assertNotNull(clientInfo.at("/registration_date"));
+        assertTrue(clientInfo.at("/permitted").asBoolean());
+        assertTrue(clientInfo.at("/source").isMissingNode());
+        
     }
     
     protected void deregisterConfidentialClient (String username, String clientId)