Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ public void getGatewayResourceProfile(
}
}

@Override
public void getGatewayResourceProfileWithAccess(
GetGatewayResourceProfileRequest request, StreamObserver<GatewayResourceProfileWithAccess> observer) {
try {
RequestContext ctx = GrpcRequestContext.current();
GatewayResourceProfileWithAccess result =
gatewayResourceProfileService.getGatewayResourceProfileWithAccess(ctx, request.getGatewayId());
observer.onNext(result);
observer.onCompleted();
} catch (Exception e) {
observer.onError(GrpcStatusMapper.toStatusException(e));
}
}

@Override
public void updateGatewayResourceProfile(
UpdateGatewayResourceProfileRequest request, StreamObserver<Empty> observer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ public void getGroupResourceProfile(
}
}

@Override
public void getGroupResourceProfileWithAccess(
GetGroupResourceProfileRequest request, StreamObserver<GroupResourceProfileWithAccess> observer) {
try {
RequestContext ctx = GrpcRequestContext.current();
GroupResourceProfileWithAccess result = groupResourceProfileService.getGroupResourceProfileWithAccess(
ctx, request.getGroupResourceProfileId());
observer.onNext(result);
observer.onCompleted();
} catch (Exception e) {
observer.onError(GrpcStatusMapper.toStatusException(e));
}
}

@Override
public void updateGroupResourceProfile(UpdateGroupResourceProfileRequest request, StreamObserver<Empty> observer) {
try {
Expand All @@ -87,6 +101,20 @@ public void updateGroupResourceProfile(UpdateGroupResourceProfileRequest request
}
}

@Override
public void updateGroupResourceProfileReconciled(
UpdateGroupResourceProfileRequest request, StreamObserver<GroupResourceProfileWithAccess> observer) {
try {
RequestContext ctx = GrpcRequestContext.current();
GroupResourceProfileWithAccess result = groupResourceProfileService.updateGroupResourceProfileReconciled(
ctx, request.getGroupResourceProfile());
observer.onNext(result);
observer.onCompleted();
} catch (Exception e) {
observer.onError(GrpcStatusMapper.toStatusException(e));
}
}

@Override
public void removeGroupResourceProfile(RemoveGroupResourceProfileRequest request, StreamObserver<Empty> observer) {
try {
Expand Down Expand Up @@ -115,6 +143,22 @@ public void getGroupResourceList(
}
}

@Override
public void getGroupResourceListWithAccess(
GetGroupResourceListRequest request, StreamObserver<GetGroupResourceListWithAccessResponse> observer) {
try {
RequestContext ctx = GrpcRequestContext.current();
List<GroupResourceProfileWithAccess> profiles =
groupResourceProfileService.getGroupResourceListWithAccess(ctx, ctx.getGatewayId());
observer.onNext(GetGroupResourceListWithAccessResponse.newBuilder()
.addAllProfiles(profiles)
.build());
observer.onCompleted();
} catch (Exception e) {
observer.onError(GrpcStatusMapper.toStatusException(e));
}
}

// --- Group Compute Preferences ---

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@

import java.util.ArrayList;
import java.util.List;
import org.apache.airavata.api.gatewayprofile.GatewayResourceProfileWithAccess;
import org.apache.airavata.config.RequestContext;
import org.apache.airavata.exception.ServiceException;
import org.apache.airavata.exception.ServiceNotFoundException;
import org.apache.airavata.interfaces.ConfigParam;
import org.apache.airavata.interfaces.ResourceProfileRegistry;
import org.apache.airavata.interfaces.SSHAccountProvisionerFactory;
Expand All @@ -33,6 +35,7 @@
import org.apache.airavata.model.appcatalog.gatewayprofile.proto.ComputeResourcePreference;
import org.apache.airavata.model.appcatalog.gatewayprofile.proto.GatewayResourceProfile;
import org.apache.airavata.model.appcatalog.gatewayprofile.proto.StoragePreference;
import org.apache.airavata.model.commons.proto.AccessFlags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -69,6 +72,32 @@ public GatewayResourceProfile getGatewayResourceProfile(RequestContext ctx, Stri
}
}

/**
* {@link #getGatewayResourceProfile} plus the caller's server-computed access flags (additive).
* Reuses {@code getGatewayResourceProfile} for READ enforcement so a caller can never
* self-authorize. A gateway resource profile is a gateway-level entity with no owner and no
* sharing entity, so {@code is_owner} is always false and {@code user_has_write_access} reflects
* the gateway-admin (admin-rw) role of the caller.
*/
public GatewayResourceProfileWithAccess getGatewayResourceProfileWithAccess(RequestContext ctx, String gatewayId)
throws ServiceException {
GatewayResourceProfile profile = getGatewayResourceProfile(ctx, gatewayId);
if (profile == null) {
throw new ServiceNotFoundException("Gateway resource profile " + gatewayId + " does not exist");
}
try {
return GatewayResourceProfileWithAccess.newBuilder()
.setGatewayResourceProfile(profile)
.setAccess(AccessFlags.newBuilder()
.setIsOwner(false)
.setUserHasWriteAccess(ctx.isGatewayAdmin())
.build())
.build();
} catch (Exception e) {
throw new ServiceException("Error while computing gateway resource profile access: " + e.getMessage(), e);
}
}

public boolean updateGatewayResourceProfile(
RequestContext ctx, String gatewayId, GatewayResourceProfile gatewayResourceProfile)
throws ServiceException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.airavata.api.groupprofile.GroupResourceProfileWithAccess;
import org.apache.airavata.config.RequestContext;
import org.apache.airavata.exception.ServiceAuthorizationException;
import org.apache.airavata.exception.ServiceException;
Expand All @@ -35,6 +36,7 @@
import org.apache.airavata.model.appcatalog.groupresourceprofile.proto.ComputeResourcePolicy;
import org.apache.airavata.model.appcatalog.groupresourceprofile.proto.GroupComputeResourcePreference;
import org.apache.airavata.model.appcatalog.groupresourceprofile.proto.GroupResourceProfile;
import org.apache.airavata.model.commons.proto.AccessFlags;
import org.apache.airavata.model.group.proto.ResourcePermissionType;
import org.apache.airavata.model.group.proto.ResourceType;
import org.apache.airavata.sharing.registry.models.proto.EntitySearchField;
Expand Down Expand Up @@ -124,6 +126,58 @@ public void updateGroupResourceProfile(RequestContext ctx, GroupResourceProfile
}
}

/**
* Reconcile-then-update: removes the child compute preferences / resource policies / batch-queue
* policies that are no longer present in the incoming profile, applies the update, and returns the
* refreshed profile with the caller's access flags. Composes the existing single-purpose service
* methods — the reconcile orchestration the SDK helper did client-side now lives server-side.
*/
public GroupResourceProfileWithAccess updateGroupResourceProfileReconciled(
RequestContext ctx, GroupResourceProfile groupResourceProfile) throws ServiceException {
String profileId = groupResourceProfile.getGroupResourceProfileId();
try {
GroupResourceProfile original = getGroupResourceProfile(ctx, profileId);

Set<String> newPrefIds = new HashSet<>();
for (GroupComputeResourcePreference cp : groupResourceProfile.getComputePreferencesList()) {
newPrefIds.add(cp.getComputeResourceId());
}
for (GroupComputeResourcePreference cp : original.getComputePreferencesList()) {
if (!newPrefIds.contains(cp.getComputeResourceId())) {
removeGroupComputePrefs(ctx, cp.getComputeResourceId(), cp.getGroupResourceProfileId());
}
}

Set<String> newPolicyIds = new HashSet<>();
for (ComputeResourcePolicy p : groupResourceProfile.getComputeResourcePoliciesList()) {
newPolicyIds.add(p.getResourcePolicyId());
}
for (ComputeResourcePolicy p : original.getComputeResourcePoliciesList()) {
if (!p.getResourcePolicyId().isEmpty() && !newPolicyIds.contains(p.getResourcePolicyId())) {
removeGroupComputeResourcePolicy(ctx, p.getResourcePolicyId());
}
}

Set<String> newBqIds = new HashSet<>();
for (BatchQueueResourcePolicy p : groupResourceProfile.getBatchQueueResourcePoliciesList()) {
newBqIds.add(p.getResourcePolicyId());
}
for (BatchQueueResourcePolicy p : original.getBatchQueueResourcePoliciesList()) {
if (!p.getResourcePolicyId().isEmpty() && !newBqIds.contains(p.getResourcePolicyId())) {
removeGroupBatchQueueResourcePolicy(ctx, p.getResourcePolicyId());
}
}

updateGroupResourceProfile(ctx, groupResourceProfile);
return getGroupResourceProfileWithAccess(ctx, profileId);
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
throw new ServiceException(
"Error reconciling group resource profile " + profileId + ": " + e.getMessage(), e);
}
}

public GroupResourceProfile getGroupResourceProfile(RequestContext ctx, String groupResourceProfileId)
throws ServiceException {
String userId = ctx.getUserId();
Expand Down Expand Up @@ -154,6 +208,90 @@ public GroupResourceProfile getGroupResourceProfile(RequestContext ctx, String g
}
}

/**
* {@link #getGroupResourceProfile} plus the caller's server-computed access flags (additive).
* Reuses {@code getGroupResourceProfile} for READ enforcement so a caller can never
* self-authorize. {@code GroupResourceProfile} carries no owner field, so ownership is derived
* from the sharing OWNER grant established at creation.
*
* <p>{@code userHasWriteAccess} is a COMPOSITE that mirrors what
* {@link #updateGroupResourceProfile} actually enforces: the caller must have sharing WRITE
* (or OWNER) on the profile AND READ on every credential token the profile references — the
* {@code default_credential_store_token} and each compute preference's
* {@code resource_specific_credential_store_token}. {@link #updateGroupResourceProfile}
* re-validates those token READs ({@link #validateGroupResourceProfileCredentials}), so a
* profile that looks editable but whose update would be rejected is reported as not writable.
*/
public GroupResourceProfileWithAccess getGroupResourceProfileWithAccess(
RequestContext ctx, String groupResourceProfileId) throws ServiceException {
GroupResourceProfile groupResourceProfile = getGroupResourceProfile(ctx, groupResourceProfileId);
if (groupResourceProfile == null) {
throw new ServiceAuthorizationException("User does not have permission to access this resource");
}
try {
return computeProfileAccess(ctx, groupResourceProfile);
} catch (Exception e) {
throw new ServiceException("Error while computing group resource profile access: " + e.getMessage(), e);
}
}

/**
* Computes the caller's access flags for an already-loaded {@link GroupResourceProfile} and unions
* them onto it, without re-fetching the profile or re-enforcing READ (the caller must have already
* passed a READ gate before reaching this point). This is the per-profile core shared by
* {@link #getGroupResourceProfileWithAccess} and {@link #getGroupResourceListWithAccess}, so the list
* variant reuses the exact same token-composite write logic per row without N extra fetches.
*
* <p>{@code userHasWriteAccess} is a COMPOSITE that mirrors what {@link #updateGroupResourceProfile}
* actually enforces: the caller must have sharing WRITE (or OWNER) on the profile AND READ on every
* credential token the profile references — the {@code default_credential_store_token} and each
* compute preference's {@code resource_specific_credential_store_token}.
*/
private GroupResourceProfileWithAccess computeProfileAccess(
RequestContext ctx, GroupResourceProfile groupResourceProfile) {
String userId = ctx.getUserId();
String gatewayId = ctx.getGatewayId();
String groupResourceProfileId = groupResourceProfile.getGroupResourceProfileId();
boolean isOwner = false;
boolean userHasWriteAccess = false;
if (SharingHelper.isSharingEnabled()) {
isOwner = SharingHelper.userHasAccess(
sharingHandler, gatewayId, userId, groupResourceProfileId, ResourcePermissionType.OWNER);
userHasWriteAccess = isOwner
|| SharingHelper.userHasAccess(
sharingHandler, gatewayId, userId, groupResourceProfileId, ResourcePermissionType.WRITE);
if (userHasWriteAccess) {
// Token READ uses the OWNER-inclusive helper, matching the check
// validateGroupResourceProfileCredentials enforces on update, so the write flag
// accurately predicts whether an update would be permitted.
String defaultToken = groupResourceProfile.getDefaultCredentialStoreToken();
if (!defaultToken.isEmpty()
&& !SharingHelper.userHasAccess(
sharingHandler, gatewayId, userId, defaultToken, ResourcePermissionType.READ)) {
userHasWriteAccess = false;
}
if (userHasWriteAccess) {
for (GroupComputeResourcePreference pref : groupResourceProfile.getComputePreferencesList()) {
String token = pref.getResourceSpecificCredentialStoreToken();
if (!token.isEmpty()
&& !SharingHelper.userHasAccess(
sharingHandler, gatewayId, userId, token, ResourcePermissionType.READ)) {
userHasWriteAccess = false;
break;
}
}
}
}
}
return GroupResourceProfileWithAccess.newBuilder()
.setGroupResourceProfile(groupResourceProfile)
.setAccess(AccessFlags.newBuilder()
.setIsOwner(isOwner)
.setUserHasWriteAccess(userHasWriteAccess)
.build())
.build();
}

public boolean removeGroupResourceProfile(RequestContext ctx, String groupResourceProfileId)
throws ServiceException {
String userId = ctx.getUserId();
Expand Down Expand Up @@ -213,6 +351,32 @@ public List<GroupResourceProfile> getGroupResourceList(RequestContext ctx, Strin
}
}

/**
* {@link #getGroupResourceList} plus the caller's server-computed access flags per profile (additive).
* Reuses {@code getGroupResourceList} for READ enforcement (outside the try, so it can never be
* self-authorized) and maps each already-loaded profile through {@link #computeProfileAccess}, so
* the per-row flags use the exact same token-composite write logic as
* {@link #getGroupResourceProfileWithAccess} without re-fetching any profile.
*/
public List<GroupResourceProfileWithAccess> getGroupResourceListWithAccess(RequestContext ctx, String gatewayId)
throws ServiceException {
List<GroupResourceProfile> profiles = getGroupResourceList(ctx, gatewayId);
try {
List<GroupResourceProfileWithAccess> result = new ArrayList<>(profiles.size());
for (GroupResourceProfile profile : profiles) {
result.add(computeProfileAccess(ctx, profile));
}
logger.debug(
"Computed access flags for {} group resource profiles in gateway {}", result.size(), gatewayId);
return result;
} catch (Exception e) {
throw new ServiceException(
"Error while computing group resource profile list access for gateway " + gatewayId + ": "
+ e.getMessage(),
e);
}
}

public boolean removeGroupComputePrefs(RequestContext ctx, String computeResourceId, String groupResourceProfileId)
throws ServiceException {
String userId = ctx.getUserId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@ public void moveFile(String sourcePath, String destinationPath) throws AgentExce
}
}

@Override
public void copyFile(String sourcePath, String destinationPath) throws AgentException {
// SFTP has no server-side copy primitive, so stage the source through a local temp
// file and re-upload to the destination (same get/put mechanism used by download/upload).
// Unlike moveFile, the source is left in place.
try (SFTPClient sftp = openSftp()) {
java.io.File tempFile = java.io.File.createTempFile("airavata-copy-", ".tmp");
try {
sftp.get(sourcePath, tempFile.getAbsolutePath());
sftp.put(tempFile.getAbsolutePath(), destinationPath);
} finally {
tempFile.delete();
}
} catch (Exception e) {
throw new AgentException("Failed to copy file: " + sourcePath + " -> " + destinationPath, e);
}
}

@Override
public void createSymlink(String targetPath, String linkPath) throws AgentException {
try (SFTPClient sftp = openSftp()) {
Expand Down
Loading
Loading