This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/master by this push:
new e76920dc24 Public private resources (#535)
e76920dc24 is described below
commit e76920dc246003ec0efe0e6cba87b43f0f75e487
Author: Ganning Xu <[email protected]>
AuthorDate: Sat Jul 19 21:08:05 2025 -0700
Public private resources (#535)
* backend support for public/private resources
* spotless
* fix bug with local threads maintaining state across requests
---
.../research/service/config/AuthzTokenFilter.java | 65 +++++++++++-----------
.../service/controller/ResourceController.java | 3 +-
.../research/service/handlers/ResourceHandler.java | 53 +++++++++++++++---
.../research/service/model/UserContext.java | 9 +++
.../service/model/repo/ResourceRepository.java | 52 ++++++++++++++++-
5 files changed, 136 insertions(+), 46 deletions(-)
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
index 5b459f8405..fa55625547 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
@@ -69,47 +69,46 @@ public class AuthzTokenFilter extends OncePerRequestFilter {
|| path.startsWith("/v3/api-docs")
|| path.startsWith("/swagger-ui")
|| path.startsWith("/swagger-resources")
- || path.startsWith("/webjars/")
- || path.startsWith("/api/v1/rf/resources/public");
+ || path.startsWith("/webjars/");
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
+ try {
+ String authorizationHeader = request.getHeader("Authorization");
+ String xClaimsHeader = request.getHeader("X-Claims");
+
+ if (request.getMethod().equals("OPTIONS")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ if (authorizationHeader != null &&
authorizationHeader.startsWith("Bearer ") && xClaimsHeader != null) {
+ try {
+ String accessToken = authorizationHeader.substring(7); //
Remove "Bearer " prefix
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map<String, String> claimsMap =
objectMapper.readValue(xClaimsHeader, new TypeReference<>() {});
+
+ AuthzToken authzToken = new AuthzToken();
+ authzToken.setAccessToken(accessToken);
+ authzToken.setClaimsMap(claimsMap);
+ UserContext.setAuthzToken(authzToken);
+
+ UserProfile userProfile = airavataService.getUserProfile(
+ authzToken, getClaim(authzToken, USERNAME_CLAIM),
getClaim(authzToken, GATEWAY_CLAIM));
+ UserContext.setUser(userProfile);
+ } catch (Exception e) {
+ LOGGER.error("Invalid authorization data", e);
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Invalid authorization data");
+ return;
+ }
+ }
- String authorizationHeader = request.getHeader("Authorization");
- String xClaimsHeader = request.getHeader("X-Claims");
-
- if (request.getMethod().equals("OPTIONS")) {
filterChain.doFilter(request, response);
- return;
- }
-
- if (authorizationHeader == null ||
!authorizationHeader.startsWith("Bearer ") || xClaimsHeader == null) {
- LOGGER.error("Missing or invalid Authorization header");
- return;
+ } finally {
+ UserContext.clear();
}
-
- try {
- String accessToken = authorizationHeader.substring(7); // Remove
"Bearer " prefix
- ObjectMapper objectMapper = new ObjectMapper();
- Map<String, String> claimsMap =
objectMapper.readValue(xClaimsHeader, new TypeReference<>() {});
-
- AuthzToken authzToken = new AuthzToken();
- authzToken.setAccessToken(accessToken);
- authzToken.setClaimsMap(claimsMap);
- UserContext.setAuthzToken(authzToken);
-
- UserProfile userProfile = airavataService.getUserProfile(
- authzToken, getClaim(authzToken, USERNAME_CLAIM),
getClaim(authzToken, GATEWAY_CLAIM));
- UserContext.setUser(userProfile);
- } catch (Exception e) {
- LOGGER.error("Invalid authorization data", e);
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid
authorization data");
- return;
- }
-
- filterChain.doFilter(request, response);
}
private static String getClaim(AuthzToken authzToken, String claimId) {
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
index d20de6bb65..af849205fd 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
@@ -134,13 +134,14 @@ public class ResourceController {
typeList.add(DatasetResource.class);
}
}
+
Page<Resource> response = resourceHandler.getAllResources(pageNumber,
pageSize, typeList, tags, nameSearch);
return ResponseEntity.ok(response);
}
@Operation(summary = "Get resource by name")
- @GetMapping("/public/search")
+ @GetMapping("/search")
public ResponseEntity<List<Resource>> searchResource(
@RequestParam(value = "type") ResourceTypeEnum type,
@RequestParam(value = "name", required = false) String name) {
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
index 0e84a2b36f..d4bff347f4 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
@@ -30,6 +30,7 @@ import org.apache.airavata.research.service.AiravataService;
import org.apache.airavata.research.service.dto.CreateResourceRequest;
import org.apache.airavata.research.service.dto.ModifyResourceRequest;
import org.apache.airavata.research.service.dto.ResourceResponse;
+import org.apache.airavata.research.service.enums.PrivacyEnum;
import org.apache.airavata.research.service.enums.ResourceTypeEnum;
import org.apache.airavata.research.service.enums.StateEnum;
import org.apache.airavata.research.service.enums.StatusEnum;
@@ -224,14 +225,23 @@ public class ResourceHandler {
}
public Resource getResourceById(String id) {
- // Your logic to fetch the resource by ID
Optional<Resource> opResource =
resourceRepository.findByIdAndState(id, StateEnum.ACTIVE);
if (opResource.isEmpty()) {
throw new EntityNotFoundException("Resource not found: " + id);
}
- return opResource.get();
+ Resource resource = opResource.get();
+ boolean isAuthenticated = UserContext.isAuthenticated();
+
+ if (resource.getPrivacy().equals(PrivacyEnum.PUBLIC)) {
+ return resource;
+ } else if (isAuthenticated
+ &&
resource.getAuthors().contains(UserContext.userId().toLowerCase())) {
+ return resource;
+ } else {
+ throw new EntityNotFoundException("Resource not found: " + id);
+ }
}
public boolean deleteResourceById(String id) {
@@ -259,13 +269,11 @@ public class ResourceHandler {
public Page<Resource> getAllResources(
int pageNumber, int pageSize, List<Class<? extends Resource>>
typeList, String[] tag, String nameSearch) {
- Pageable pageable = PageRequest.of(pageNumber, pageSize);
- if (tag == null || tag.length == 0) {
- return resourceRepository.findAllByTypes(typeList, nameSearch,
pageable);
+ boolean isAuthenticated = UserContext.isAuthenticated();
+ if (isAuthenticated) {
+ return getAllResourcesUserSignedIn(pageNumber, pageSize, typeList,
tag, nameSearch, UserContext.userId());
}
-
- return resourceRepository.findAllByTypesAndAllTags(
- typeList, tag, tag.length, nameSearch.toLowerCase(), pageable);
+ return getAllPublicResources(pageNumber, pageSize, typeList, tag,
nameSearch);
}
public List<Tag> getAllTags() {
@@ -277,7 +285,34 @@ public class ResourceHandler {
}
public List<Resource> getAllResourcesByTypeAndName(Class<? extends
Resource> type, String name) {
+ return resourceRepository.findByTypeAndNameContainingIgnoreCase(type,
name.toLowerCase(), UserContext.userId());
+ }
+
+ private Page<Resource> getAllPublicResources(
+ int pageNumber, int pageSize, List<Class<? extends Resource>>
typeList, String[] tag, String nameSearch) {
+ Pageable pageable = PageRequest.of(pageNumber, pageSize);
+ if (tag == null || tag.length == 0) {
+ return resourceRepository.findAllByTypes(typeList, nameSearch,
pageable);
+ }
+
+ return resourceRepository.findAllByTypesAndAllTags(
+ typeList, tag, tag.length, nameSearch.toLowerCase(), pageable);
+ }
+
+ private Page<Resource> getAllResourcesUserSignedIn(
+ int pageNumber,
+ int pageSize,
+ List<Class<? extends Resource>> typeList,
+ String[] tag,
+ String nameSearch,
+ String userId) {
+ Pageable pageable = PageRequest.of(pageNumber, pageSize);
+
+ if (tag == null || tag.length == 0) {
+ return resourceRepository.findAllByTypesForUser(typeList,
nameSearch.toLowerCase(), userId, pageable);
+ }
- return resourceRepository.findByTypeAndNameContainingIgnoreCase(type,
name.toLowerCase());
+ return resourceRepository.findAllByTypesAndAllTagsForUser(
+ typeList, tag, (long) tag.length, nameSearch.toLowerCase(),
userId, pageable);
}
}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/UserContext.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/UserContext.java
index abb71aa862..b6c1b52887 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/UserContext.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/UserContext.java
@@ -50,4 +50,13 @@ public class UserContext {
public static String gatewayId() {
return CURRENT_USER.get().getGatewayId();
}
+
+ public static boolean isAuthenticated() {
+ return AUTHZ_TOKEN.get() != null;
+ }
+
+ public static void clear() {
+ AUTHZ_TOKEN.remove();
+ CURRENT_USER.remove();
+ }
}
diff --git
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
index e53058ab10..98e7b27a2f 100644
---
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
+++
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
@@ -36,8 +36,11 @@ public interface ResourceRepository extends
JpaRepository<Resource, String> {
@Query(
"""
SELECT r
- FROM #{#entityName} r
- WHERE TYPE(r) IN :types AND r.name LIKE CONCAT('%',
:nameSearch, '%') AND r.state = 'ACTIVE'
+ FROM Resource r
+ WHERE TYPE(r) IN :types
+ AND r.name LIKE CONCAT('%', :nameSearch, '%')
+ AND r.state = 'ACTIVE'
+ AND r.privacy = 'PUBLIC'
ORDER BY r.name
""")
Page<Resource> findAllByTypes(
@@ -45,6 +48,23 @@ public interface ResourceRepository extends
JpaRepository<Resource, String> {
@Param("nameSearch") String nameSearch,
Pageable pageable);
+ @Query(
+ """
+ SELECT DISTINCT r
+ FROM Resource r
+ JOIN r.authors a
+ WHERE r.class IN :typeList
+ AND LOWER(r.name) LIKE LOWER(CONCAT('%', :nameSearch, '%'))
+ AND r.state = 'ACTIVE'
+ AND (r.privacy = 'PUBLIC' or a = :userId)
+ ORDER BY r.name
+ """)
+ Page<Resource> findAllByTypesForUser(
+ @Param("typeList") List<Class<? extends Resource>> typeList,
+ @Param("nameSearch") String nameSearch,
+ @Param("userId") String userId,
+ Pageable pageable);
+
@Query(
"""
SELECT r
@@ -54,6 +74,7 @@ public interface ResourceRepository extends
JpaRepository<Resource, String> {
AND t.value IN :tags
AND LOWER(r.name) LIKE LOWER(CONCAT('%', :nameSearch,
'%'))
AND r.state = 'ACTIVE'
+ AND r.privacy = 'PUBLIC'
GROUP BY r
HAVING COUNT(DISTINCT t.value) = :tagCount
ORDER BY r.name
@@ -65,15 +86,40 @@ public interface ResourceRepository extends
JpaRepository<Resource, String> {
@Param("nameSearch") String nameSearch,
Pageable pageable);
+ @Query(
+ """
+ SELECT r
+ FROM Resource r
+ JOIN r.tags t
+ JOIN r.authors a
+ WHERE r.class IN :typeList
+ AND t.value IN :tags
+ AND LOWER(r.name) LIKE LOWER(CONCAT('%', :nameSearch, '%'))
+ AND r.state = 'ACTIVE'
+ AND (r.privacy = 'PUBLIC' OR a = :userId)
+ GROUP BY r
+ HAVING COUNT(DISTINCT t.value) = :tagCount
+ ORDER BY r.name
+ """)
+ Page<Resource> findAllByTypesAndAllTagsForUser(
+ @Param("typeList") List<Class<? extends Resource>> typeList,
+ @Param("tags") String[] tags,
+ @Param("tagCount") Long tagCount,
+ @Param("nameSearch") String nameSearch,
+ @Param("userId") String userId,
+ Pageable pageable);
+
@Query(
"""
SELECT r
FROM Resource r
+ JOIN r.authors a
WHERE TYPE(r) = :type AND r.state = 'ACTIVE'
AND LOWER(r.name) LIKE LOWER(CONCAT('%', :name, '%'))
+ AND (r.privacy = "PUBLIC" OR a = :userId)
""")
List<Resource> findByTypeAndNameContainingIgnoreCase(
- @Param("type") Class<? extends Resource> type, @Param("name")
String name);
+ @Param("type") Class<? extends Resource> type, @Param("name")
String name, @Param("userId") String userId);
Optional<Resource> findByIdAndState(String id, StateEnum state);
}