Restrict registration info for plugins (solves #572)

Only available for owners & admins.
Added token scopes for DemoUser.

Change-Id: I74e6e2a3c3dc477dbd0402155048299ed6a78a48
diff --git a/.gitignore b/.gitignore
index f79fa55..dbb295d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,4 +59,5 @@
 /data/
 /old-jars/
 /data-backup
-/notes
\ No newline at end of file
+/notes
+/docker
\ No newline at end of file
diff --git a/Changes b/Changes
index 691a5d7..a7d713a 100644
--- a/Changes
+++ b/Changes
@@ -6,6 +6,7 @@
 - Updated tests using the old match info web-services (#757)
 - Added deprecation warning for the old matchInfo service (#757)
 - Fixed empty named VC path in configuration (solves #754)
+- Restrict registration info for plugins (solves #572)  
 
 # version 0.73.1
 
diff --git a/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java b/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
index d50d79b..6f45900 100644
--- a/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
+++ b/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
@@ -40,6 +40,7 @@
     @JsonProperty("registered_by")
     private String registeredBy;
     @JsonProperty("refresh_token_expiry")
+    @JsonInclude(Include.NON_DEFAULT)
     private int refreshTokenExpiry; // in seconds
 
     @JsonProperty("permitted")
@@ -47,28 +48,41 @@
     private JsonNode source;
 
     public OAuth2ClientInfoDto (OAuth2Client client) throws KustvaktException {
+        this(client,true);
+    }
+    
+    public OAuth2ClientInfoDto (OAuth2Client client, boolean showAllInfo) throws KustvaktException {
         this.setClientId(client.getId());
         this.setClientName(client.getName());
         this.setDescription(client.getDescription());
         this.setClientType(client.getType());
         this.setUrl(client.getUrl());
         this.setClientType(client.getType());
-        this.setRedirect_uri(client.getRedirectURI());
-        this.setSuper(client.isSuper());
         this.setPermitted(client.isPermitted());
-        this.setRegisteredBy(client.getRegisteredBy());
-
+        
         String source = client.getSource();
-        if (source != null && !source.isEmpty()) {
-            this.source = JsonUtils.readTree(source);
+        
+        if (showAllInfo) {
+            this.setSuper(client.isSuper());
+            this.setRedirect_uri(client.getRedirectURI());
+            this.setRegisteredBy(client.getRegisteredBy());
+            ZonedDateTime registrationDate = client.getRegistrationDate();
+            if (registrationDate != null) {
+                this.setRegistrationDate(registrationDate.toString());
+            }
+            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+                this.setRefreshTokenExpiry(client.getRefreshTokenExpiry());
+            }
+            
+            if (source != null && !source.isEmpty()) {
+                this.source = JsonUtils.readTree(source);
+            } 
         }
-        if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
-            this.setRefreshTokenExpiry(client.getRefreshTokenExpiry());
-        }
-        ZonedDateTime registrationDate = client.getRegistrationDate();
-        if (registrationDate != null) {
-            this.setRegistrationDate(registrationDate.toString());
-        }
+        else { //plugins
+            if (source != null && !source.isEmpty()) {
+                this.source = JsonUtils.readTree(source);
+            }
+        } 
     }
 
     public boolean isSuper () {
diff --git a/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java b/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
index 5f29815..16d6879 100644
--- a/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
+++ b/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
@@ -49,6 +49,7 @@
     private String description;
     private String url;
 
+    // Source determines if a client is a plugin
     private String source;
     @Column(name = "is_permitted")
     private boolean isPermitted;
diff --git a/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java b/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index 3781458..d6feae2 100644
--- a/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
+++ b/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -341,17 +341,27 @@
         return clientDao.retrieveClientById(clientId);
     }
 
-    public OAuth2ClientInfoDto retrieveClientInfo (String clientId)
+    public OAuth2ClientInfoDto retrieveClientInfo (String clientId, String username)
             throws KustvaktException {
         OAuth2Client client = clientDao.retrieveClientById(clientId);
-        //        if (adminDao.isAdmin(username)
-        //                || username.equals(client.getRegisteredBy())) {
-        return new OAuth2ClientInfoDto(client);
-        //        }
-        //        else {
-        //            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
-        //                    "Unauthorized operation for user: " + username, username);
-        //        }
+        
+        // all client info is only available to the owner/admin
+        if (adminDao.isAdmin(username)
+                || username.equals(client.getRegisteredBy())) {
+            return new OAuth2ClientInfoDto(client);
+        }
+        // plugin info is available for all users inclusive guest
+        else if (isPlugin(client)) {
+                return new OAuth2ClientInfoDto(client, false);
+        } 
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + username, username);
+        }
+    }
+    
+    public boolean isPlugin (OAuth2Client client) {
+        return (client.getSource() != null && !client.getSource().isEmpty());
     }
 
     public OAuth2Client retrieveClient (String clientId)
@@ -376,7 +386,7 @@
         }
 
         Collections.sort(uniqueClients);
-        return createClientDtos(uniqueClients);
+        return createClientDtos(uniqueClients,username);
     }
 
     public List<OAuth2ClientInfoDto> listUserRegisteredClients (String username)
@@ -384,15 +394,15 @@
         List<OAuth2Client> userClients = clientDao
                 .retrieveUserRegisteredClients(username);
         Collections.sort(userClients);
-        return createClientDtos(userClients);
+        return createClientDtos(userClients,username);
     }
 
-    public List<OAuth2ClientInfoDto> listPlugins (boolean isPermitted)
+    public List<OAuth2ClientInfoDto> listPlugins (boolean isPermitted, String username)
             throws KustvaktException {
 
         List<OAuth2Client> plugins = clientDao.retrievePlugins(isPermitted);
         Collections.sort(plugins);
-        return createClientDtos(plugins);
+        return createClientDtos(plugins,username);
     }
 
     public List<InstalledPluginDto> listInstalledPlugins (String superClientId,
@@ -452,12 +462,18 @@
     }
 
     private List<OAuth2ClientInfoDto> createClientDtos (
-            List<OAuth2Client> userClients) throws KustvaktException {
+            List<OAuth2Client> userClients, String username) throws KustvaktException {
         List<OAuth2ClientInfoDto> dtoList = new ArrayList<>(userClients.size());
+        boolean showAllInfo;
         for (OAuth2Client uc : userClients) {
-            if (uc.isSuper())
+            showAllInfo = false;
+            if (uc.isSuper()) {
                 continue;
-            OAuth2ClientInfoDto dto = new OAuth2ClientInfoDto(uc);
+            }
+            if (uc.getRegisteredBy().equals(username)) {
+                showAllInfo = true;
+            }
+            OAuth2ClientInfoDto dto = new OAuth2ClientInfoDto(uc, showAllInfo);
             dtoList.add(dto);
         }
         return dtoList;
diff --git a/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index b530e19..ee2d72f 100644
--- a/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -16,6 +16,8 @@
 import de.ids_mannheim.korap.web.filter.APIVersionFilter;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
 import de.ids_mannheim.korap.web.filter.BlockingFilter;
+import de.ids_mannheim.korap.web.filter.DemoFilter;
+import de.ids_mannheim.korap.web.filter.DemoUserFilter;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 import de.ids_mannheim.korap.web.utils.ResourceFilters;
 import jakarta.ws.rs.Consumes;
@@ -158,17 +160,34 @@
         }
     }
 
+    /**
+     * Returns information about the given client id. Only super
+     * clients are allowed to use this service.
+     * 
+     * @param securityContext
+     * @param clientId
+     *            plugin or OAuth2 client id
+     * @param superClientId
+     * @param superClientSecret
+     * @return OAuth2 client/plugin information
+     */
     @POST
     @Path("{client_id}")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    @ResourceFilters({ APIVersionFilter.class })
+    @ResourceFilters({ APIVersionFilter.class, AuthenticationFilter.class,
+            DemoUserFilter.class })
     public OAuth2ClientInfoDto retrieveClientInfo (
+            @Context SecurityContext securityContext,
             @PathParam("client_id") String clientId,
             @FormParam("super_client_id") String superClientId,
             @FormParam("super_client_secret") String superClientSecret) {
+        TokenContext context = (TokenContext) securityContext
+                .getUserPrincipal();
         try {
             clientService.verifySuperClient(superClientId, superClientSecret);
-            return clientService.retrieveClientInfo(clientId);
+            scopeService.verifyScope(context, OAuth2Scope.CLIENT_INFO);
+            return clientService.retrieveClientInfo(clientId,
+                    context.getUsername());
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
diff --git a/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java b/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java
index 0afda45..2d41dc6 100644
--- a/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java
+++ b/src/main/java/de/ids_mannheim/korap/web/controller/PluginController.java
@@ -56,7 +56,7 @@
                     OAuth2Scope.LIST_USER_CLIENT);
 
             clientService.verifySuperClient(superClientId, superClientSecret);
-            return clientService.listPlugins(permittedOnly);
+            return clientService.listPlugins(permittedOnly, tokenContext.getUsername());
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
diff --git a/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java b/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
index 300c3da..2bf4b69 100644
--- a/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
+++ b/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
@@ -1,12 +1,16 @@
 package de.ids_mannheim.korap.web.filter;
 
 import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.glassfish.jersey.server.ContainerRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.constant.TokenType;
 import de.ids_mannheim.korap.security.context.KustvaktContext;
 import de.ids_mannheim.korap.security.context.TokenContext;
@@ -39,7 +43,7 @@
         String authentication = request
                 .getHeaderString(ContainerRequest.AUTHORIZATION);
 
-        // means that this is the public service
+        // means that this is a public service
         if (authentication == null || authentication.isEmpty()) {
             Principal pr = null;
             SecurityContext securityContext = request.getSecurityContext();
@@ -58,9 +62,16 @@
         c.setUsername(demo.getUsername());
         c.setHostAddress(host);
         c.setUserAgent(agent);
-        c.setExpirationTime(
-                TimeUtils.plusSeconds(config.getShortTokenTTL()).getMillis());
+//        c.setExpirationTime(
+//                TimeUtils.plusSeconds(config.getShortTokenTTL()).getMillis());
         c.setTokenType(TokenType.BASIC);
+        
+        Set<String> scopeSet = new HashSet<>();
+        scopeSet.add(OAuth2Scope.SEARCH.toString());
+        scopeSet.add(OAuth2Scope.CLIENT_INFO.toString());
+        scopeSet.add(OAuth2Scope.MATCH_INFO.toString());
+        String scopes = String.join(" ", scopeSet);
+        c.addContextParameter(Attributes.SCOPE, scopes);
         return c;
     }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index 5f24cd6..6c8652d 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -8,13 +8,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.http.entity.ContentType;
-import org.glassfish.jersey.server.ContainerRequest;
 import org.junit.jupiter.api.Test;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -63,15 +59,15 @@
 
     @Test
     public void testRetrieveClientInfo () throws KustvaktException {
-        // public client
+        // public client plugin
         JsonNode clientInfo = retrieveClientInfo(publicClientId, "system");
         assertEquals(publicClientId, clientInfo.at("/client_id").asText());
         assertEquals(clientInfo.at("/client_name").asText(),
                 "public client plugin with redirect uri");
         assertNotNull(clientInfo.at("/client_description"));
         assertNotNull(clientInfo.at("/client_url"));
-        assertEquals(clientInfo.at("/client_type").asText(), "PUBLIC");
-        assertEquals(clientInfo.at("/registered_by").asText(), "system");
+        assertEquals("PUBLIC", clientInfo.at("/client_type").asText());
+        assertEquals("system", clientInfo.at("/registered_by").asText());
         // confidential client
         clientInfo = retrieveClientInfo(confidentialClientId, "system");
         assertEquals(confidentialClientId,
@@ -81,15 +77,15 @@
         assertNotNull(clientInfo.at("/client_url"));
         assertNotNull(clientInfo.at("/redirect_uri"));
         assertEquals(false, clientInfo.at("/super").asBoolean());
-        assertEquals(clientInfo.at("/client_type").asText(), "CONFIDENTIAL");
+        assertEquals("CONFIDENTIAL", clientInfo.at("/client_type").asText());
         // super client
         clientInfo = retrieveClientInfo(superClientId, "system");
         assertEquals(superClientId, clientInfo.at("/client_id").asText());
-        assertEquals(clientInfo.at("/client_name").asText(),
-                "super confidential client");
+        assertEquals("super confidential client",
+                clientInfo.at("/client_name").asText());
         assertNotNull(clientInfo.at("/client_url"));
         assertNotNull(clientInfo.at("/redirect_uri"));
-        assertEquals(clientInfo.at("/client_type").asText(), "CONFIDENTIAL");
+        assertEquals("CONFIDENTIAL", clientInfo.at("/client_type").asText());
         assertTrue(clientInfo.at("/super").asBoolean());
     }
 
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
index 90597e2..d72787f 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
@@ -91,17 +91,12 @@
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
         assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
         assertFalse(node.at("/error_description").isMissingNode());
-        // assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        // String clientId = node.at("/client_id").asText();
-        // assertTrue(node.at("/client_secret").isMissingNode());
-        // 
-        // deregisterClient(username, clientId);
     }
 
     private void testRetrievePluginInfo (String clientId,
             int refreshTokenExpiry)
             throws ProcessingException, KustvaktException {
-        JsonNode clientInfo = retrieveClientInfo(clientId, username);
+        JsonNode clientInfo = retrieveClientInfo(clientId, "other-user");
         assertEquals(clientId, clientInfo.at("/client_id").asText());
         assertEquals(clientInfo.at("/client_name").asText(), "Plugin");
         assertEquals(OAuth2ClientType.CONFIDENTIAL.name(),
@@ -109,10 +104,10 @@
         assertNotNull(clientInfo.at("/client_description").asText());
         assertNotNull(clientInfo.at("/source").asText());
         assertFalse(clientInfo.at("/permitted").asBoolean());
-        assertEquals(username, clientInfo.at("/registered_by").asText());
-        assertNotNull(clientInfo.at("/registration_date"));
-        assertEquals(refreshTokenExpiry,
-                clientInfo.at("/refresh_token_expiry").asInt());
+        assertTrue(clientInfo.at("/registered_by").isMissingNode());
+        assertTrue(clientInfo.at("/registration_date").isMissingNode());
+        assertTrue(clientInfo.at("/refresh_token_expiry").isMissingNode());
+        
     }
 
     private void testListUserRegisteredPlugins (String username,
@@ -264,10 +259,10 @@
         assertFalse(node.at("/0/client_description").isMissingNode());
         assertFalse(node.at("/0/client_type").isMissingNode());
         assertFalse(node.at("/0/permitted").isMissingNode());
-        assertFalse(node.at("/0/registration_date").isMissingNode());
+        assertTrue(node.at("/0/registration_date").isMissingNode());
         assertFalse(node.at("/0/source").isMissingNode());
-        assertFalse(node.at("/0/refresh_token_expiry").isMissingNode());
-        // assertTrue(node.at("/1/refresh_token_expiry").isMissingNode());
+        assertTrue(node.at("/0/refresh_token_expiry").isMissingNode());
+        assertTrue(node.at("/1/client_redirect_uri").isMissingNode());
     }
 
     private JsonNode listPlugins (boolean permitted_only)
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index b27fd0f..bd57686 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -349,8 +349,8 @@
 
         Response response = target().path(API_VERSION).path("oauth2")
                 .path("client").path(clientId).request()
-                //                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                //                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .post(Entity.form(form));