You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by di...@apache.org on 2026/08/03 12:36:08 UTC

(airavata) branch refactor-single-module updated: Framework implementation to manage users

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

DImuthuUpe pushed a commit to branch refactor-single-module
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/refactor-single-module by this push:
     new 59a6ba2073 Framework implementation to manage users
59a6ba2073 is described below

commit 59a6ba207323774d0e61c49facc01071fd1bccd4
Author: DImuthuUpe <di...@gmail.com>
AuthorDate: Mon Aug 3 08:36:02 2026 -0400

    Framework implementation to manage users
---
 .../airavata/api/ApplicationModuleController.java  |  18 ++--
 .../org/apache/airavata/api/UserController.java    |  46 +++++++++
 .../org/apache/airavata/common/IAMDBConstants.java |  83 ----------------
 .../apache/airavata/dto/iam/UserResponseDto.java   |  25 -----
 .../{dto/iam => iam/dto}/UserRegistrationDto.java  |   2 +-
 .../apache/airavata/iam/dto/UserResponseDto.java   |  52 ++++++++++
 .../org/apache/airavata/iam/mapper/UserMapper.java |   4 +-
 .../apache/airavata/iam/service/UserService.java   | 110 +++++++++++++++++++++
 .../server/auth/RootAccountTokenProvider.java      |  13 ++-
 src/main/resources/application.properties          |  66 +------------
 10 files changed, 235 insertions(+), 184 deletions(-)

diff --git a/src/main/java/org/apache/airavata/api/ApplicationModuleController.java b/src/main/java/org/apache/airavata/api/ApplicationModuleController.java
index 697b193502..516a8f6cd8 100644
--- a/src/main/java/org/apache/airavata/api/ApplicationModuleController.java
+++ b/src/main/java/org/apache/airavata/api/ApplicationModuleController.java
@@ -4,15 +4,17 @@ import java.util.List;
 
 import org.apache.airavata.application.service.ApplicationModuleService;
 import org.apache.airavata.models.appcatalog.appdeployment.ApplicationModule;
-import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.service.annotation.DeleteExchange;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.DeleteMapping;
 
-@Controller
+@RestController
+@RequestMapping("/api/v1/appmodule")
 public class ApplicationModuleController {
 
     private final ApplicationModuleService applicationModuleService;
@@ -21,32 +23,32 @@ public class ApplicationModuleController {
         this.applicationModuleService = applicationModuleService;
     }
 
-    @GetMapping("/application-module/{moduleId}")
+    @GetMapping("/{moduleId}")
     @ResponseBody
     public ApplicationModule getApplicationModule(@PathVariable String moduleId) throws Exception {
         return applicationModuleService.getApplicationModule(null, moduleId);
     }
 
-    @GetMapping("/application-module")
+    @GetMapping("")
     @ResponseBody
     public List<ApplicationModule> getAllApplicationModules() throws Exception {
         return applicationModuleService.getAllApplicationModules();
     }
 
-    @PostMapping("/application-module")
+    @PostMapping("")
     @ResponseBody
     public ApplicationModule createApplicationModule(@RequestBody ApplicationModule module) throws Exception {
         return applicationModuleService.createApplicationModule(null, module);
     }
 
-    @PostMapping("/application-module/{moduleId}")
+    @PostMapping("/{moduleId}")
     @ResponseBody
     public ApplicationModule updateApplicationModule(
             @PathVariable String moduleId, @RequestBody ApplicationModule module) throws Exception {
         return applicationModuleService.updateApplicationModule(null, moduleId, module);
     }
 
-    @DeleteExchange("/application-module/{moduleId}")
+    @DeleteMapping("/{moduleId}")
     @ResponseBody
     public void deleteApplicationModule(@PathVariable String moduleId) throws Exception {
         applicationModuleService.deleteApplicationModule(null, moduleId);
diff --git a/src/main/java/org/apache/airavata/api/UserController.java b/src/main/java/org/apache/airavata/api/UserController.java
new file mode 100644
index 0000000000..3da55f92e2
--- /dev/null
+++ b/src/main/java/org/apache/airavata/api/UserController.java
@@ -0,0 +1,46 @@
+package org.apache.airavata.api;
+
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.apache.airavata.iam.dto.UserResponseDto;
+
+import java.util.List;
+
+import org.apache.airavata.iam.dto.UserRegistrationDto;
+import org.apache.airavata.iam.service.UserService;
+
+@RestController
+@RequestMapping("/api/v1/users")
+public class UserController {
+
+    private final UserService userService;
+
+    public UserController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @PostMapping
+    public UserResponseDto createNewUser(@RequestBody UserRegistrationDto userRegistrationDto) {
+        return userService.registerUser(userRegistrationDto);
+    }
+
+    @GetMapping("/{userId}")
+    public UserResponseDto getUserById(@PathVariable String userId) {
+        return userService.getUserById(userId);
+    }
+
+    @GetMapping
+    public List<UserResponseDto> getAllUsers() {
+        return userService.getAllUsers();
+    }
+
+    @PostMapping("/{userId}")
+    public UserResponseDto updateUser(@PathVariable String userId,
+            @RequestBody UserRegistrationDto userRegistrationDto) {
+        return userService.updateUser(userId, userRegistrationDto);
+    }
+}
diff --git a/src/main/java/org/apache/airavata/common/IAMDBConstants.java b/src/main/java/org/apache/airavata/common/IAMDBConstants.java
deleted file mode 100644
index 709fb7d0b5..0000000000
--- a/src/main/java/org/apache/airavata/common/IAMDBConstants.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.apache.airavata.common;
-
-public class IAMDBConstants {
-
-    public static int SELECT_MAX_ROWS = 1000;
-
-    public static class DomainTable {
-        public static final String DOMAIN_ID = "domainId";
-        public static final String NAME = "name";
-        public static final String DESCRIPTION = "description";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-
-    public static class UserTable {
-        public static final String USER_ID = "userId";
-        public static final String DOMAIN_ID = "domainId";
-        public static final String USER_NAME = "userName";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-
-    public static class UserGroupTable {
-        public static final String GROUP_ID = "groupId";
-        public static final String DOMAIN_ID = "domainId";
-        public static final String NAME = "name";
-        public static final String DESCRIPTION = "description";
-        public static final String OWNER_ID = "ownerId";
-        public static final String GROUP_TYPE = "groupType";
-        public static final String GROUP_CARDINALITY = "groupCardinality";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-
-    public static class GroupMembershipTable {
-        public static final String PARENT_ID = "parentId";
-        public static final String CHILD_ID = "childId";
-        public static final String CHILD_TYPE = "childType";
-        public static final String DOMAIN_ID = "domainId";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-
-    public static class EntityTypeTable {
-        public static final String ENTITY_TYPE_ID = "entityTypeId";
-        public static final String DOMAIN_ID = "domainId";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-
-    public static class PermissionTypeTable {
-        public static final String ENTITY_TYPE_ID = "permissionTypeId";
-        public static final String DOMAIN_ID = "domainId";
-        public static final String NAME = "name";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-
-    public static class EntityTable {
-        public static final String ENTITY_ID = "entityId";
-        public static final String PARENT_ENTITY_ID = "parentEntityId";
-        public static final String ENTITY_TYPE_ID = "entityTypeId";
-        public static final String NAME = "name";
-        public static final String DESCRIPTION = "description";
-        public static final String FULL_TEXT = "fullText";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-        public static final String DOMAIN_ID = "domainId";
-        public static final String ORIGINAL_ENTITY_CREATION_TIME = "originalEntityCreationTime";
-        public static final String SHARED = "shared";
-    }
-
-    public static class SharingTable {
-        public static final String DOMAIN_ID = "domainId";
-        public static final String PERMISSION_TYPE_ID = "permissionTypeId";
-        public static final String ENTITY_ID = "entityId";
-        public static final String GROUP_ID = "groupId";
-        public static final String INHERITED_PARENT_ID = "inheritedParentId";
-        public static final String SHARING_TYPE = "sharingType";
-        public static final String CREATED_TIME = "createdTime";
-        public static final String UPDATED_TIME = "updatedTime";
-    }
-}
diff --git a/src/main/java/org/apache/airavata/dto/iam/UserResponseDto.java b/src/main/java/org/apache/airavata/dto/iam/UserResponseDto.java
deleted file mode 100644
index 169a099e4e..0000000000
--- a/src/main/java/org/apache/airavata/dto/iam/UserResponseDto.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.apache.airavata.dto.iam;
-
-import org.apache.airavata.iam.model.enums.UserStatus;
-
-public class UserResponseDto {
-
-    private String userId;
-    private UserStatus status;
-
-    public String getUserId() {
-        return userId;
-    }
-
-    public void setUserId(String userId) {
-        this.userId = userId;
-    }
-
-    public UserStatus getStatus() {
-        return status;
-    }
-
-    public void setStatus(UserStatus status) {
-        this.status = status;
-    }
-}
diff --git a/src/main/java/org/apache/airavata/dto/iam/UserRegistrationDto.java b/src/main/java/org/apache/airavata/iam/dto/UserRegistrationDto.java
similarity index 97%
rename from src/main/java/org/apache/airavata/dto/iam/UserRegistrationDto.java
rename to src/main/java/org/apache/airavata/iam/dto/UserRegistrationDto.java
index 96971595bf..6ff4bd24be 100644
--- a/src/main/java/org/apache/airavata/dto/iam/UserRegistrationDto.java
+++ b/src/main/java/org/apache/airavata/iam/dto/UserRegistrationDto.java
@@ -1,4 +1,4 @@
-package org.apache.airavata.dto.iam;
+package org.apache.airavata.iam.dto;
 
 import org.apache.airavata.iam.model.enums.AuthMethod;
 
diff --git a/src/main/java/org/apache/airavata/iam/dto/UserResponseDto.java b/src/main/java/org/apache/airavata/iam/dto/UserResponseDto.java
new file mode 100644
index 0000000000..e62cf2bc90
--- /dev/null
+++ b/src/main/java/org/apache/airavata/iam/dto/UserResponseDto.java
@@ -0,0 +1,52 @@
+package org.apache.airavata.iam.dto;
+
+import org.apache.airavata.iam.model.enums.UserStatus;
+
+public class UserResponseDto {
+
+    private String userId;
+    private UserStatus status;
+    private String firstName;
+    private String lastName;
+    private long createdAt;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public UserStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(UserStatus status) {
+        this.status = status;
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public long getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(long createdAt) {
+        this.createdAt = createdAt;
+    }
+}
diff --git a/src/main/java/org/apache/airavata/iam/mapper/UserMapper.java b/src/main/java/org/apache/airavata/iam/mapper/UserMapper.java
index 821da1b162..d7e01a3e42 100644
--- a/src/main/java/org/apache/airavata/iam/mapper/UserMapper.java
+++ b/src/main/java/org/apache/airavata/iam/mapper/UserMapper.java
@@ -1,7 +1,7 @@
 package org.apache.airavata.iam.mapper;
 
-import org.apache.airavata.dto.iam.UserRegistrationDto;
-import org.apache.airavata.dto.iam.UserResponseDto;
+import org.apache.airavata.iam.dto.UserRegistrationDto;
+import org.apache.airavata.iam.dto.UserResponseDto;
 import org.apache.airavata.iam.model.UserEntity;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
diff --git a/src/main/java/org/apache/airavata/iam/service/UserService.java b/src/main/java/org/apache/airavata/iam/service/UserService.java
new file mode 100644
index 0000000000..4c1156f72e
--- /dev/null
+++ b/src/main/java/org/apache/airavata/iam/service/UserService.java
@@ -0,0 +1,110 @@
+package org.apache.airavata.iam.service;
+
+import java.util.List;
+
+import org.apache.airavata.iam.dto.UserRegistrationDto;
+import org.apache.airavata.iam.dto.UserResponseDto;
+import org.apache.airavata.iam.mapper.UserMapper;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.apache.airavata.iam.model.UserEntity;
+import org.apache.airavata.iam.model.enums.UserStatus;
+import org.apache.airavata.iam.repository.UserRepository;
+
+@Service
+public class UserService {
+
+    private UserMapper userMapper;
+    private UserRepository userRepository;
+
+    public UserService(UserMapper userMapper, UserRepository userRepository) {
+        this.userMapper = userMapper;
+        this.userRepository = userRepository;
+    }
+
+    public UserResponseDto registerUser(UserRegistrationDto userRegistrationDto) {
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        boolean hasSuperAdminRole = authentication.getAuthorities().stream()
+                .anyMatch(auth -> auth.getAuthority().equals("SUPER_ADMIN"));
+
+        if (!hasSuperAdminRole) {
+            throw new AccessDeniedException("Only Super Admin can register users");
+        }
+
+        UserEntity userEntity = userMapper.toEntity(userRegistrationDto);
+        userEntity.setStatus(UserStatus.ACTIVE); // Default status
+        userEntity.setCreatedAt(System.currentTimeMillis()); // Set creation timestamp
+        userRepository.save(userEntity);
+
+        UserResponseDto responseDto = userMapper.toResponseDto(userEntity);
+        return responseDto;
+    }
+
+    public UserResponseDto getUserById(String userId) {
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        boolean hasSuperAdminRole = authentication.getAuthorities().stream()
+                .anyMatch(auth -> auth.getAuthority().equals("SUPER_ADMIN") || auth.getAuthority().equals("ADMIN"));
+
+        boolean isSelf = authentication.getName().equals(userId);
+
+        if (!hasSuperAdminRole && !isSelf) {
+            throw new AccessDeniedException("Access denied: You can only access your own user information");
+        }
+
+        UserEntity userEntity = userRepository.findById(userId)
+                .orElseThrow(() -> new RuntimeException("User not found with ID: " + userId));
+        return userMapper.toResponseDto(userEntity);
+    }
+
+    public java.util.List<UserResponseDto> getAllUsers() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        boolean hasSuperAdminRole = authentication.getAuthorities().stream()
+                .anyMatch(auth -> auth.getAuthority().equals("SUPER_ADMIN")
+                        || auth.getAuthority().equals("ADMIN"));
+
+        if (!hasSuperAdminRole) {
+            throw new AccessDeniedException("Only Super Admin can access all users");
+        }
+
+        List<UserEntity> userEntities = new java.util.ArrayList<>();
+        userRepository.findAll().forEach(userEntities::add);
+        return userEntities.stream()
+                .map(userMapper::toResponseDto)
+                .collect(java.util.stream.Collectors.toList());
+    }
+
+    public UserResponseDto updateUser(String userId, UserRegistrationDto userRegistrationDto) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        boolean hasSuperAdminRole = authentication.getAuthorities().stream()
+                .anyMatch(auth -> auth.getAuthority().equals("SUPER_ADMIN"));
+
+        boolean isSelf = authentication.getName().equals(userId);
+
+        if (!hasSuperAdminRole && !isSelf) {
+            throw new AccessDeniedException("Access denied: You can only update your own user information");
+        }
+
+        UserEntity existingUserEntity = userRepository.findById(userId)
+                .orElseThrow(() -> new RuntimeException("User not found with ID: " + userId));
+
+        // Update fields from the DTO
+        existingUserEntity.setEmail(userRegistrationDto.getEmail());
+        existingUserEntity.setFirstName(userRegistrationDto.getFirstName());
+        existingUserEntity.setLastName(userRegistrationDto.getLastName());
+
+        // Save the updated entity
+        userRepository.save(existingUserEntity);
+
+        return userMapper.toResponseDto(existingUserEntity);
+    }
+
+}
diff --git a/src/main/java/org/apache/airavata/server/auth/RootAccountTokenProvider.java b/src/main/java/org/apache/airavata/server/auth/RootAccountTokenProvider.java
index 0f44fc40f6..d06cf7e736 100644
--- a/src/main/java/org/apache/airavata/server/auth/RootAccountTokenProvider.java
+++ b/src/main/java/org/apache/airavata/server/auth/RootAccountTokenProvider.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -46,11 +47,19 @@ public class RootAccountTokenProvider {
 
     private final String rootAccountToken;
 
-    public RootAccountTokenProvider() {
-        this.rootAccountToken = generateRandomToken();
+
+    public RootAccountTokenProvider(
+            @Value("${airavata.security.root-account.token:}") String configuredToken) {
+        this.rootAccountToken = initializeToken(configuredToken);
         printToken();
     }
 
+    private String initializeToken(String configuredToken) {
+        return configuredToken != null && !configuredToken.isEmpty()
+                ? configuredToken
+                : generateRandomToken();
+    }
+
     private String generateRandomToken() {
         return UUID.randomUUID().toString();
     }
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 053a82705d..edd4ccf311 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -57,10 +57,6 @@ spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
 # --- Spring ---
 spring.main.web-application-type=servlet
 
-# --- Actuator ---
-management.endpoints.web.exposure.include=health,info
-management.endpoint.health.show-details=always
-
 # --- CORS ---
 airavata.cors.allowed-origins=*
 
@@ -77,62 +73,6 @@ airavata.security.cilogon.client-secret=${CILOGON_CLIENT_SECRET:}
 # Set to false to disable root account authentication
 # Use this for initial setup and testing only; in production disable root account and use a proper IAM system (e.g., CILogon) for authentication.
 airavata.security.root-account.enabled=${AIRAVATA_ROOT_ACCOUNT_ENABLED:true}
-iam.server.url=http://localhost:18080
-iam.server.super.admin.username=admin
-iam.server.super.admin.password=admin
-security.manager.class=org.apache.airavata.iam.KeyCloakSecurityManager
-authz.cache.enabled=true
-authz.cache.manager.class=org.apache.airavata.iam.authzcache.DefaultAuthzCacheManager
-TLS.enabled=false
-TLS.client.timeout=10000
-tls.cert.path=conf/keystores/server.crt
-tls.key.path=conf/keystores/server.key
-credential.store.keystore.url=conf/keystores/airavata.sym.p12
-credential.store.keystore.password=airavata
-credential.store.keystore.alias=airavata
-
-# --- Gateway ---
-default.registry.gateway=default
-default.registry.oauth.client.id=pga
-default.registry.oauth.client.secret=m36BXQIxX3j3VILadeHMK5IvbOeRlCCc
-default.registry.user=default-admin
-default.registry.password=ade4#21242ftfd
-super.tenant.gatewayId=default
-enable.sharing=true
-enable.validation=true
-
-# --- Orchestration ---
-host.scheduler=org.apache.airavata.orchestration.scheduling.DefaultHostScheduler
-job.validators=org.apache.airavata.orchestration.validation.BatchQueueValidator,org.apache.airavata.orchestration.validation.ExperimentStatusValidator
-
-# --- DB-transactional Process Executor ---
-orchestration.executor.threads=4
-orchestration.executor.poll.interval.ms=3000
-# The lease MUST exceed the longest task's max runtime (currently MonitoringTask MAX_WAIT_MS = 30 min)
-# or the sweeper reclaims still-running tasks, causing duplicate concurrent runs and false failures.
-orchestration.executor.lease.timeout.ms=2700000
-orchestration.executor.max.attempts=3
-
-# --- Job Notifications ---
-job.notification.enable=true
-job.notification.emailids=
-
-# --- Storage ---
-local.data.location=/tmp
-max.archive.size=21474836480
-enable.streaming.transfer=false
-
-# --- Agent Tunnel ---
-airavata.agent.tunnel.server-host=localhost
-airavata.agent.tunnel.server-port=17000
-airavata.agent.tunnel.server-token=airavata
-airavata.agent.tunnel.server-api-url=http://localhost:8000
-
-# --- Research Hub ---
-airavata.research.hub-url=http://localhost:20000
-airavata.research.admin-api-key=JUPYTER_ADMIN_API_KEY
-airavata.research.limit=10
-
-# --- Misc ---
-in.memory.cache.size=1000
-StrictHostKeyChecking=no
+# Optional: Provide a fixed token instead of generating a random one
+# If not set, a random UUID will be generated on startup
+airavata.security.root-account.token=${AIRAVATA_ROOT_ACCOUNT_TOKEN:}