This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch feat/airavata-service-layer
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit cde0907f9828e5cc1c8716391efc979cc15c0b3a
Author: yasithdev <[email protected]>
AuthorDate: Thu Mar 26 10:14:34 2026 -0500

    feat: add CredentialService
    
    Extract credential business logic from AiravataServerHandler into
    CredentialService, using RequestContext instead of AuthzToken claims
    and direct sharingHandler.userHasAccess() for permission checks.
---
 .../service/credential/CredentialService.java      | 209 +++++++++++++++++++++
 .../service/credential/CredentialServiceTest.java  | 151 +++++++++++++++
 2 files changed, 360 insertions(+)

diff --git 
a/airavata-api/src/main/java/org/apache/airavata/service/credential/CredentialService.java
 
b/airavata-api/src/main/java/org/apache/airavata/service/credential/CredentialService.java
new file mode 100644
index 0000000000..2c2420253b
--- /dev/null
+++ 
b/airavata-api/src/main/java/org/apache/airavata/service/credential/CredentialService.java
@@ -0,0 +1,209 @@
+package org.apache.airavata.service.credential;
+
+import org.apache.airavata.common.utils.ServerSettings;
+import 
org.apache.airavata.credential.store.server.CredentialStoreServerHandler;
+import org.apache.airavata.model.credential.store.CredentialSummary;
+import org.apache.airavata.model.credential.store.PasswordCredential;
+import org.apache.airavata.model.credential.store.SSHCredential;
+import org.apache.airavata.model.credential.store.SummaryType;
+import org.apache.airavata.model.group.ResourcePermissionType;
+import org.apache.airavata.model.group.ResourceType;
+import org.apache.airavata.service.context.RequestContext;
+import org.apache.airavata.service.exception.ServiceAuthorizationException;
+import org.apache.airavata.service.exception.ServiceException;
+import org.apache.airavata.sharing.registry.models.Entity;
+import org.apache.airavata.sharing.registry.models.EntitySearchField;
+import org.apache.airavata.sharing.registry.models.SearchCondition;
+import org.apache.airavata.sharing.registry.models.SearchCriteria;
+import 
org.apache.airavata.sharing.registry.server.SharingRegistryServerHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CredentialService {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(CredentialService.class);
+
+    private final CredentialStoreServerHandler credentialHandler;
+    private final SharingRegistryServerHandler sharingHandler;
+
+    public CredentialService(CredentialStoreServerHandler credentialHandler, 
SharingRegistryServerHandler sharingHandler) {
+        this.credentialHandler = credentialHandler;
+        this.sharingHandler = sharingHandler;
+    }
+
+    public String generateAndRegisterSSHKeys(RequestContext ctx, String 
description) throws ServiceException {
+        String gatewayId = ctx.getGatewayId();
+        String userName = ctx.getUserId();
+        try {
+            SSHCredential sshCredential = new SSHCredential();
+            sshCredential.setUsername(userName);
+            sshCredential.setGatewayId(gatewayId);
+            sshCredential.setDescription(description);
+            String key = credentialHandler.addSSHCredential(sshCredential);
+            try {
+                Entity entity = new Entity();
+                entity.setEntityId(key);
+                entity.setDomainId(gatewayId);
+                entity.setEntityTypeId(gatewayId + ":" + 
ResourceType.CREDENTIAL_TOKEN);
+                entity.setOwnerId(userName + "@" + gatewayId);
+                entity.setName(key);
+                entity.setDescription(description);
+                sharingHandler.createEntity(entity);
+            } catch (Exception ex) {
+                logger.error(ex.getMessage(), ex);
+                logger.error("Rolling back ssh key creation for user " + 
userName + " and description [" + description + "]");
+                credentialHandler.deleteSSHCredential(key, gatewayId);
+                throw new ServiceException("Failed to create sharing registry 
record");
+            }
+            logger.debug("Airavata generated SSH keys for gateway : " + 
gatewayId + " and for user : " + userName);
+            return key;
+        } catch (ServiceException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ServiceException("Error occurred while registering SSH 
Credential. More info : " + e.getMessage(), e);
+        }
+    }
+
+    public String registerPwdCredential(RequestContext ctx, String 
loginUserName, String password, String description)
+            throws ServiceException {
+        String gatewayId = ctx.getGatewayId();
+        String userName = ctx.getUserId();
+        try {
+            PasswordCredential pwdCredential = new PasswordCredential();
+            pwdCredential.setPortalUserName(userName);
+            pwdCredential.setLoginUserName(loginUserName);
+            pwdCredential.setPassword(password);
+            pwdCredential.setDescription(description);
+            pwdCredential.setGatewayId(gatewayId);
+            String key = 
credentialHandler.addPasswordCredential(pwdCredential);
+            try {
+                Entity entity = new Entity();
+                entity.setEntityId(key);
+                entity.setDomainId(gatewayId);
+                entity.setEntityTypeId(gatewayId + ":" + 
ResourceType.CREDENTIAL_TOKEN);
+                entity.setOwnerId(userName + "@" + gatewayId);
+                entity.setName(key);
+                entity.setDescription(description);
+                sharingHandler.createEntity(entity);
+            } catch (Exception ex) {
+                logger.error(ex.getMessage(), ex);
+                logger.error("Rolling back password registration for user " + 
userName + " and description [" + description + "]");
+                credentialHandler.deletePWDCredential(key, gatewayId);
+                throw new ServiceException("Failed to create sharing registry 
record");
+            }
+            logger.debug("Airavata generated PWD credential for gateway : " + 
gatewayId + " and for user : " + loginUserName);
+            return key;
+        } catch (ServiceException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ServiceException("Error occurred while registering PWD 
Credential. More info : " + e.getMessage(), e);
+        }
+    }
+
+    public CredentialSummary getCredentialSummary(RequestContext ctx, String 
tokenId) throws ServiceException {
+        String gatewayId = ctx.getGatewayId();
+        String userName = ctx.getUserId();
+        try {
+            if (!userHasAccess(gatewayId, userName, tokenId, 
ResourcePermissionType.READ)) {
+                logger.info("User " + userName + " not allowed to access 
credential store token " + tokenId);
+                throw new ServiceAuthorizationException("User does not have 
permission to access this resource");
+            }
+            CredentialSummary credentialSummary = 
credentialHandler.getCredentialSummary(tokenId, gatewayId);
+            logger.debug("Airavata retrieved the credential summary for token 
" + tokenId + " GatewayId: " + gatewayId);
+            return credentialSummary;
+        } catch (ServiceException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ServiceException("Error retrieving credential summary 
for token " + tokenId + ". GatewayId: " + gatewayId + " More info : " + 
e.getMessage(), e);
+        }
+    }
+
+    public List<CredentialSummary> getAllCredentialSummaries(RequestContext 
ctx, SummaryType type) throws ServiceException {
+        String gatewayId = ctx.getGatewayId();
+        String userName = ctx.getUserId();
+        try {
+            List<SearchCriteria> filters = new ArrayList<>();
+            SearchCriteria searchCriteria = new SearchCriteria();
+            searchCriteria.setSearchField(EntitySearchField.ENTITY_TYPE_ID);
+            searchCriteria.setSearchCondition(SearchCondition.EQUAL);
+            searchCriteria.setValue(gatewayId + ":" + 
ResourceType.CREDENTIAL_TOKEN.name());
+            filters.add(searchCriteria);
+            List<String> accessibleTokenIds =
+                    sharingHandler.searchEntities(gatewayId, userName + "@" + 
gatewayId, filters, 0, -1).stream()
+                            .map(p -> p.getEntityId())
+                            .collect(Collectors.toList());
+            List<CredentialSummary> credentialSummaries =
+                    credentialHandler.getAllCredentialSummaries(type, 
accessibleTokenIds, gatewayId);
+            logger.debug("Airavata successfully retrieved credential summaries 
of type " + type + " GatewayId: " + gatewayId);
+            return credentialSummaries;
+        } catch (Exception e) {
+            throw new ServiceException("Error retrieving credential summaries 
of type " + type + ". GatewayId: " + gatewayId + " More info : " + 
e.getMessage(), e);
+        }
+    }
+
+    public boolean deleteSSHPubKey(RequestContext ctx, String 
airavataCredStoreToken) throws ServiceException {
+        String gatewayId = ctx.getGatewayId();
+        String userName = ctx.getUserId();
+        try {
+            if (!userHasAccess(gatewayId, userName, airavataCredStoreToken, 
ResourcePermissionType.WRITE)) {
+                logger.info("User " + userName + " not allowed to delete (no 
WRITE permission) credential store token " + airavataCredStoreToken);
+                throw new ServiceAuthorizationException("User does not have 
permission to delete this resource.");
+            }
+            logger.debug("Airavata deleted SSH pub key for gateway Id : " + 
gatewayId + " and with token id : " + airavataCredStoreToken);
+            return 
credentialHandler.deleteSSHCredential(airavataCredStoreToken, gatewayId);
+        } catch (ServiceException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ServiceException("Error occurred while deleting SSH 
credential. More info : " + e.getMessage(), e);
+        }
+    }
+
+    public boolean deletePWDCredential(RequestContext ctx, String 
airavataCredStoreToken) throws ServiceException {
+        String gatewayId = ctx.getGatewayId();
+        String userName = ctx.getUserId();
+        try {
+            if (!userHasAccess(gatewayId, userName, airavataCredStoreToken, 
ResourcePermissionType.WRITE)) {
+                logger.info("User " + userName + " not allowed to delete (no 
WRITE permission) credential store token " + airavataCredStoreToken);
+                throw new ServiceAuthorizationException("User does not have 
permission to delete this resource.");
+            }
+            logger.debug("Airavata deleted PWD credential for gateway Id : " + 
gatewayId + " and with token id : " + airavataCredStoreToken);
+            return 
credentialHandler.deletePWDCredential(airavataCredStoreToken, gatewayId);
+        } catch (ServiceException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ServiceException("Error occurred while deleting PWD 
credential. More info : " + e.getMessage(), e);
+        }
+    }
+
+    private boolean userHasAccess(String gatewayId, String userName, String 
entityId, ResourcePermissionType permissionType) {
+        String userId = userName + "@" + gatewayId;
+        try {
+            boolean hasOwnerAccess = sharingHandler.userHasAccess(
+                    gatewayId, userId, entityId, gatewayId + ":" + 
ResourcePermissionType.OWNER);
+            if (permissionType.equals(ResourcePermissionType.WRITE)) {
+                return hasOwnerAccess
+                        || sharingHandler.userHasAccess(gatewayId, userId, 
entityId, gatewayId + ":" + ResourcePermissionType.WRITE);
+            } else if (permissionType.equals(ResourcePermissionType.READ)) {
+                return hasOwnerAccess
+                        || sharingHandler.userHasAccess(gatewayId, userId, 
entityId, gatewayId + ":" + ResourcePermissionType.READ);
+            } else if (permissionType.equals(ResourcePermissionType.OWNER)) {
+                return hasOwnerAccess;
+            }
+            return false;
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to check if user has access", 
e);
+        }
+    }
+
+    private boolean isSharingEnabled() {
+        try {
+            return ServerSettings.isEnableSharing();
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}
diff --git 
a/airavata-api/src/test/java/org/apache/airavata/service/credential/CredentialServiceTest.java
 
b/airavata-api/src/test/java/org/apache/airavata/service/credential/CredentialServiceTest.java
new file mode 100644
index 0000000000..25424bdd92
--- /dev/null
+++ 
b/airavata-api/src/test/java/org/apache/airavata/service/credential/CredentialServiceTest.java
@@ -0,0 +1,151 @@
+package org.apache.airavata.service.credential;
+
+import 
org.apache.airavata.credential.store.server.CredentialStoreServerHandler;
+import org.apache.airavata.model.credential.store.CredentialSummary;
+import org.apache.airavata.model.credential.store.SummaryType;
+import org.apache.airavata.model.group.ResourcePermissionType;
+import org.apache.airavata.service.context.RequestContext;
+import org.apache.airavata.service.exception.ServiceAuthorizationException;
+import org.apache.airavata.sharing.registry.models.Entity;
+import 
org.apache.airavata.sharing.registry.server.SharingRegistryServerHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class CredentialServiceTest {
+
+    @Mock CredentialStoreServerHandler credentialHandler;
+    @Mock SharingRegistryServerHandler sharingHandler;
+
+    CredentialService credentialService;
+    RequestContext ctx;
+
+    @BeforeEach
+    void setUp() {
+        credentialService = new CredentialService(credentialHandler, 
sharingHandler);
+        ctx = new RequestContext("testUser", "testGateway", "token123",
+                Map.of("userName", "testUser", "gatewayId", "testGateway"));
+    }
+
+    @Test
+    void generateAndRegisterSSHKeys_returnsToken() throws Exception {
+        
when(credentialHandler.addSSHCredential(any())).thenReturn("ssh-token-1");
+
+        String result = credentialService.generateAndRegisterSSHKeys(ctx, "my 
ssh key");
+
+        assertEquals("ssh-token-1", result);
+        verify(credentialHandler).addSSHCredential(any());
+        verify(sharingHandler).createEntity(any());
+    }
+
+    @Test
+    void generateAndRegisterSSHKeys_rollsBackOnSharingFailure() throws 
Exception {
+        
when(credentialHandler.addSSHCredential(any())).thenReturn("ssh-token-1");
+        doThrow(new RuntimeException("sharing 
error")).when(sharingHandler).createEntity(any());
+
+        assertThrows(Exception.class, () -> 
credentialService.generateAndRegisterSSHKeys(ctx, "my ssh key"));
+        verify(credentialHandler).deleteSSHCredential("ssh-token-1", 
"testGateway");
+    }
+
+    @Test
+    void registerPwdCredential_returnsToken() throws Exception {
+        
when(credentialHandler.addPasswordCredential(any())).thenReturn("pwd-token-1");
+
+        String result = credentialService.registerPwdCredential(ctx, 
"loginUser", "secret", "my pwd");
+
+        assertEquals("pwd-token-1", result);
+        verify(credentialHandler).addPasswordCredential(any());
+        verify(sharingHandler).createEntity(any());
+    }
+
+    @Test
+    void registerPwdCredential_rollsBackOnSharingFailure() throws Exception {
+        
when(credentialHandler.addPasswordCredential(any())).thenReturn("pwd-token-1");
+        doThrow(new RuntimeException("sharing 
error")).when(sharingHandler).createEntity(any());
+
+        assertThrows(Exception.class, () -> 
credentialService.registerPwdCredential(ctx, "loginUser", "secret", "my pwd"));
+        verify(credentialHandler).deletePWDCredential("pwd-token-1", 
"testGateway");
+    }
+
+    @Test
+    void getCredentialSummary_delegatesToCredentialHandler() throws Exception {
+        when(sharingHandler.userHasAccess(eq("testGateway"), 
eq("testUser@testGateway"), eq("tok-1"),
+                eq("testGateway:" + 
ResourcePermissionType.OWNER))).thenReturn(true);
+        CredentialSummary summary = new CredentialSummary();
+        summary.setToken("tok-1");
+        when(credentialHandler.getCredentialSummary("tok-1", 
"testGateway")).thenReturn(summary);
+
+        CredentialSummary result = credentialService.getCredentialSummary(ctx, 
"tok-1");
+
+        assertNotNull(result);
+        assertEquals("tok-1", result.getToken());
+        verify(credentialHandler).getCredentialSummary("tok-1", "testGateway");
+    }
+
+    @Test
+    void getCredentialSummary_throwsAuthorizationExceptionWhenNoAccess() 
throws Exception {
+        when(sharingHandler.userHasAccess(any(), any(), any(), 
any())).thenReturn(false);
+
+        assertThrows(ServiceAuthorizationException.class, () -> 
credentialService.getCredentialSummary(ctx, "tok-1"));
+        verify(credentialHandler, never()).getCredentialSummary(any(), any());
+    }
+
+    @Test
+    void deleteSSHPubKey_delegatesToCredentialHandler() throws Exception {
+        when(sharingHandler.userHasAccess(eq("testGateway"), 
eq("testUser@testGateway"), eq("tok-1"),
+                eq("testGateway:" + 
ResourcePermissionType.OWNER))).thenReturn(true);
+        when(credentialHandler.deleteSSHCredential("tok-1", 
"testGateway")).thenReturn(true);
+
+        boolean result = credentialService.deleteSSHPubKey(ctx, "tok-1");
+
+        assertTrue(result);
+        verify(credentialHandler).deleteSSHCredential("tok-1", "testGateway");
+    }
+
+    @Test
+    void deleteSSHPubKey_throwsAuthorizationExceptionWhenNoAccess() throws 
Exception {
+        when(sharingHandler.userHasAccess(any(), any(), any(), 
any())).thenReturn(false);
+
+        assertThrows(ServiceAuthorizationException.class, () -> 
credentialService.deleteSSHPubKey(ctx, "tok-1"));
+        verify(credentialHandler, never()).deleteSSHCredential(any(), any());
+    }
+
+    @Test
+    void deletePWDCredential_delegatesToCredentialHandler() throws Exception {
+        when(sharingHandler.userHasAccess(eq("testGateway"), 
eq("testUser@testGateway"), eq("tok-1"),
+                eq("testGateway:" + 
ResourcePermissionType.OWNER))).thenReturn(true);
+        when(credentialHandler.deletePWDCredential("tok-1", 
"testGateway")).thenReturn(true);
+
+        boolean result = credentialService.deletePWDCredential(ctx, "tok-1");
+
+        assertTrue(result);
+        verify(credentialHandler).deletePWDCredential("tok-1", "testGateway");
+    }
+
+    @Test
+    void getAllCredentialSummaries_delegatesToCredentialHandler() throws 
Exception {
+        Entity entity = new Entity();
+        entity.setEntityId("tok-1");
+        when(sharingHandler.searchEntities(eq("testGateway"), 
eq("testUser@testGateway"), any(), eq(0), eq(-1)))
+                .thenReturn(List.of(entity));
+        CredentialSummary summary = new CredentialSummary();
+        summary.setToken("tok-1");
+        when(credentialHandler.getAllCredentialSummaries(eq(SummaryType.SSH), 
any(), eq("testGateway")))
+                .thenReturn(List.of(summary));
+
+        List<CredentialSummary> result = 
credentialService.getAllCredentialSummaries(ctx, SummaryType.SSH);
+
+        assertEquals(1, result.size());
+        
verify(credentialHandler).getAllCredentialSummaries(eq(SummaryType.SSH), any(), 
eq("testGateway"));
+    }
+}

Reply via email to