diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/Application.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/Application.java index c102c5f55..2b73637cc 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/Application.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/Application.java @@ -167,4 +167,8 @@ public Object getLiveContainerSize() { return liveContainerSize.get(); } + public Map getPublishedApplicationDSL() { + return publishedApplicationDSL; + } + } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/ApplicationRequestType.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/ApplicationRequestType.java new file mode 100644 index 000000000..ca5e63ea1 --- /dev/null +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/model/ApplicationRequestType.java @@ -0,0 +1,7 @@ +package org.lowcoder.domain.application.model; + +public enum ApplicationRequestType { + PUBLIC_TO_ALL, + PUBLIC_TO_MARKETPLACE, + AGENCY_PROFILE, +} diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java index c6b2951fd..de6b069ef 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java @@ -2,7 +2,6 @@ import java.util.Collection; -import java.util.List; import javax.annotation.Nonnull; @@ -18,6 +17,7 @@ @Repository public interface ApplicationRepository extends ReactiveMongoRepository, CustomApplicationRepository { + // publishedApplicationDSL : 0 -> excludes publishedApplicationDSL from the return @Query(fields = "{ publishedApplicationDSL : 0 , editingApplicationDSL : 0 }") Flux findByOrganizationId(String organizationId); @@ -32,19 +32,30 @@ public interface ApplicationRepository extends ReactiveMongoRepository findByDatasourceId(String datasourceId); - Flux findByIdIn(List ids); + Flux findByIdIn(Collection ids); + /** + * Filter public applications from list of supplied IDs + */ + Flux findByPublicToAllIsTrueAndIdIn(Collection ids); - // Falk: Why to combine? Marketplace-List and Agency-List are different Endpoints + /** + * Filter marketplace applications from list of supplied IDs + */ + Flux findByPublicToAllIsTrueAndPublicToMarketplaceIsTrueAndIdIn(Collection ids); - @Query(value = "{$and:[{'publicToAll':true},{'$or':[{'publicToMarketplace':?0},{'agencyProfile':?1}]}, {'_id': { $in: ?2}}]}", fields = "{_id : 1}") - Flux findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn - (Boolean publicToMarketplace, Boolean agencyProfile, Collection ids); - - Flux findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(Boolean publicToMarketplace, Boolean agencyProfile, Collection ids); + /** + * Filter agency applications from list of supplied IDs + */ + Flux findByPublicToAllIsTrueAndAgencyProfileIsTrueAndIdIn(Collection ids); + /** + * Find all marketplace applications + */ Flux findByPublicToAllIsTrueAndPublicToMarketplaceIsTrue(); - + + /** + * Find all agency applications + */ Flux findByPublicToAllIsTrueAndAgencyProfileIsTrue(); - } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java index 34b24f1e2..a5f83cdaf 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.lowcoder.domain.application.model.Application; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.application.model.ApplicationStatus; import org.lowcoder.domain.application.repository.ApplicationRepository; import org.lowcoder.domain.permission.model.ResourceRole; @@ -157,8 +158,6 @@ public Mono setApplicationPublicToMarketplace(String applicationId, Boo return findById(applicationId) - // Falk: question - do we need Map applicationDsl = application.getEditingApplicationDSL(); and .editingApplicationDSL(applicationDsl) - or is .publicToMarketplace(publicToMarketplace).build(); enough? - .map(application -> { Map applicationDsl = application.getEditingApplicationDSL(); @@ -204,27 +203,84 @@ public Mono setApplicationAsAgencyProfile(String applicationId, boolean return mongoUpsertHelper.updateById(application, applicationId); } + + @NonEmptyMono + @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") + public Mono> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection applicationIds, boolean isAnonymous, Boolean isPrivateMarketplace) { + + switch(requestType) + { + case PUBLIC_TO_ALL: + if (isAnonymous) + { + return getPublicApplicationIds(applicationIds); + } + else + { + return getPrivateApplicationIds(applicationIds); + } + case PUBLIC_TO_MARKETPLACE: + return getPublicMarketplaceApplicationIds(applicationIds, isAnonymous, isPrivateMarketplace); + case AGENCY_PROFILE: + return getPublicAgencyApplicationIds(applicationIds); + default: + return Mono.empty(); + } + } + + + /** + * Find all public applications - doesn't matter if user is anonymous, because these apps are public + */ @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") - public Mono> getPublicApplicationIds(Collection applicationIds, Boolean isAnonymous, Boolean isPrivateMarketplace) { + public Mono> getPublicApplicationIds(Collection applicationIds) { - if(isAnonymous) { - if(isPrivateMarketplace) { - return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(false, false, applicationIds) + return repository.findByPublicToAllIsTrueAndIdIn(applicationIds) .map(HasIdAndAuditing::getId) .collect(Collectors.toSet()); - } else { - return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(true, false, applicationIds) - .map(HasIdAndAuditing::getId) - .collect(Collectors.toSet()); - } - } else { - return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn(true, true, applicationIds) + } + + + /** + * Find all private applications for viewing. + */ + @NonEmptyMono + @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") + public Mono> getPrivateApplicationIds(Collection applicationIds) { + // TODO: in 2.4.0 we need to check whether the app was published or not + return repository.findByIdIn(applicationIds) .map(HasIdAndAuditing::getId) .collect(Collectors.toSet()); - } + } + + + /** + * Find all marketplace applications - filter based on whether user is anonymous and whether it's a private marketplace + */ + @NonEmptyMono + @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") + public Mono> getPublicMarketplaceApplicationIds(Collection applicationIds, boolean isAnonymous, boolean isPrivateMarketplace) { + + if ((isAnonymous && !isPrivateMarketplace) || !isAnonymous) + { + return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsTrueAndIdIn(applicationIds) + .map(HasIdAndAuditing::getId) + .collect(Collectors.toSet()); + } + return Mono.empty(); + } + /** + * Find all agency applications + */ + @NonEmptyMono + @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") + public Mono> getPublicAgencyApplicationIds(Collection applicationIds) { + return repository.findByPublicToAllIsTrueAndAgencyProfileIsTrueAndIdIn(applicationIds) + .map(HasIdAndAuditing::getId) + .collect(Collectors.toSet()); } public Flux findAll() { diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java index 5d6448d13..c97c2b236 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java @@ -10,11 +10,13 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.lowcoder.domain.application.model.Application; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.application.service.ApplicationService; import org.lowcoder.domain.permission.model.ResourceAction; import org.lowcoder.domain.permission.model.ResourcePermission; @@ -46,7 +48,7 @@ protected Mono>> getAnonymousUserPermission } Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.TRUE, config.getMarketplace().isPrivateMode()), + return Mono.zip(applicationService.getPublicApplicationIds(applicationIds), templateSolution.getTemplateApplicationIds(applicationIds)) .map(tuple -> { Set publicAppIds = tuple.getT1(); @@ -61,7 +63,7 @@ protected Mono>> getAnonymousUserPermission (Collection resourceIds, ResourceAction resourceAction) { Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.FALSE, config.getMarketplace().isPrivateMode()), + return Mono.zip(applicationService.getPrivateApplicationIds(applicationIds), templateSolution.getTemplateApplicationIds(applicationIds)) .map(tuple -> { Set publicAppIds = tuple.getT1(); @@ -70,7 +72,41 @@ protected Mono>> getAnonymousUserPermission }); } - private List getAnonymousUserPermission(String applicationId) { + + @Override + protected Mono>> getAnonymousUserApplicationPermissions( + Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) + { + if (!ANONYMOUS_USER_ROLE.canDo(resourceAction)) { + return Mono.just(emptyMap()); + } + + Set applicationIds = newHashSet(resourceIds); + return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, Boolean.TRUE, config.getMarketplace().isPrivateMode()) + .defaultIfEmpty(new HashSet<>()), + templateSolution.getTemplateApplicationIds(applicationIds) + .defaultIfEmpty(new HashSet<>()) + ).map(tuple -> { + Set publicAppIds = tuple.getT1(); + Set templateAppIds = tuple.getT2(); + return collectMap(union(publicAppIds, templateAppIds), identity(), this::getAnonymousUserPermission); + }); + } + + @Override + protected Mono>> getNonAnonymousUserApplicationPublicResourcePermissions( + Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) { + Set applicationIds = newHashSet(resourceIds); + return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, Boolean.FALSE, config.getMarketplace().isPrivateMode()), + templateSolution.getTemplateApplicationIds(applicationIds)) + .map(tuple -> { + Set publicAppIds = tuple.getT1(); + Set templateAppIds = tuple.getT2(); + return collectMap(union(publicAppIds, templateAppIds), identity(), this::getAnonymousUserPermission); + }); + } + + private List getAnonymousUserPermission(String applicationId) { return Collections.singletonList(ResourcePermission.builder() .resourceId(applicationId) .resourceType(ResourceType.APPLICATION) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java index 130ee8033..75e034218 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.datasource.model.Datasource; import org.lowcoder.domain.datasource.service.DatasourceService; import org.lowcoder.domain.permission.model.ResourceAction; @@ -44,6 +45,18 @@ protected Mono>> getNonAnonymousUserPublicR } @Override + protected Mono>> getAnonymousUserApplicationPermissions( + Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) { + return Mono.just(Collections.emptyMap()); + } + + @Override + protected Mono>> getNonAnonymousUserApplicationPublicResourcePermissions( + Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) { + return Mono.just(Collections.emptyMap()); + } + + @Override protected Mono getOrgId(String resourceId) { return datasourceService.getById(resourceId) .map(Datasource::getOrganizationId); diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java index 09efd31f2..8b0587480 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java @@ -18,6 +18,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.group.service.GroupMemberService; import org.lowcoder.domain.organization.service.OrgMemberService; import org.lowcoder.domain.permission.model.ResourceAction; @@ -153,6 +154,13 @@ protected abstract Mono>> getAnonymousUserP protected abstract Mono>> getNonAnonymousUserPublicResourcePermissions (Collection resourceIds, ResourceAction resourceAction); + protected abstract Mono>> getAnonymousUserApplicationPermissions(Collection resourceIds, + ResourceAction resourceAction, ApplicationRequestType requestType); + + protected abstract Mono>> getNonAnonymousUserApplicationPublicResourcePermissions + (Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType); + + private Mono>> getAllMatchingPermissions0(String userId, String orgId, ResourceType resourceType, Collection resourceIds, ResourceAction resourceAction) { @@ -212,4 +220,63 @@ private Mono> getUserGroupIds(String orgId, String userId) { } protected abstract Mono getOrgId(String resourceId); + + public Mono checkUserPermissionStatusOnApplication(String userId, String resourceId, + ResourceAction resourceAction, ApplicationRequestType requestType) + { + ResourceType resourceType = resourceAction.getResourceType(); + + Mono publicResourcePermissionMono = getAnonymousUserApplicationPermissions(singletonList(resourceId), resourceAction, requestType) + .map(it -> it.getOrDefault(resourceId, emptyList())) + .map(it -> { + if (!it.isEmpty()) { + return UserPermissionOnResourceStatus.success(it.get(0)); + } + return isAnonymousUser(userId) ? UserPermissionOnResourceStatus.anonymousUser() : UserPermissionOnResourceStatus.notInOrg(); + }); + + if (isAnonymousUser(userId)) { + return publicResourcePermissionMono; + } + + Mono nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserApplicationPublicResourcePermissions(singletonList(resourceId), resourceAction, requestType) + .map(it -> it.getOrDefault(resourceId, emptyList())) + .map(it -> { + if (!it.isEmpty()) { + return UserPermissionOnResourceStatus.success(it.get(0)); + } + return isAnonymousUser(userId) ? UserPermissionOnResourceStatus.anonymousUser() : UserPermissionOnResourceStatus.notInOrg(); + }); + + + Mono orgUserPermissionMono = getOrgId(resourceId) + .flatMap(orgId -> orgMemberService.getOrgMember(orgId, userId)) + .flatMap(orgMember -> { + if (orgMember.isAdmin()) { + return Mono.just(UserPermissionOnResourceStatus.success(buildAdminPermission(resourceType, resourceId, userId))); + } + return getAllMatchingPermissions0(userId, orgMember.getOrgId(), resourceType, Collections.singleton(resourceId), resourceAction) + .map(it -> it.getOrDefault(resourceId, emptyList())) + .map(permissions -> permissions.isEmpty() ? UserPermissionOnResourceStatus.notEnoughPermission() + : UserPermissionOnResourceStatus.success(getMaxPermission(permissions))); + }) + .defaultIfEmpty(UserPermissionOnResourceStatus.notInOrg()); + + return Mono.zip(publicResourcePermissionMono, nonAnonymousPublicResourcePermissionMono, orgUserPermissionMono) + .map(tuple -> { + UserPermissionOnResourceStatus publicResourcePermission = tuple.getT1(); + UserPermissionOnResourceStatus nonAnonymousPublicResourcePermission = tuple.getT2(); + UserPermissionOnResourceStatus orgUserPermission = tuple.getT3(); + if (orgUserPermission.hasPermission()) { + return orgUserPermission; + } + if(nonAnonymousPublicResourcePermission.hasPermission()) { + return nonAnonymousPublicResourcePermission; + } + if (publicResourcePermission.hasPermission()) { + return publicResourcePermission; + } + return orgUserPermission; + }); + } } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionService.java index 9cdba0e30..8dd06e9d4 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionService.java @@ -19,6 +19,7 @@ import javax.validation.constraints.NotNull; import org.apache.commons.collections4.CollectionUtils; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.permission.model.ResourceAction; import org.lowcoder.domain.permission.model.ResourceHolder; import org.lowcoder.domain.permission.model.ResourcePermission; @@ -221,6 +222,14 @@ public Mono checkAndReturnMaxPermission(String userId, Strin return resourcePermissionHandler.checkUserPermissionStatusOnResource(userId, resourceId, resourceAction); } + public Mono checkUserPermissionStatusOnApplication + (String userId, String resourceId, ResourceAction resourceAction, ApplicationRequestType requestType) { + ResourceType resourceType = resourceAction.getResourceType(); + var resourcePermissionHandler = getResourcePermissionHandler(resourceType); + return resourcePermissionHandler.checkUserPermissionStatusOnApplication(userId, resourceId, resourceAction, requestType); +} + + public Mono removeUserApplicationPermission(String appId, String userId) { return repository.removePermissionBy(ResourceType.APPLICATION, appId, ResourceHolder.USER, userId); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java index 6ced87d03..ea81c199c 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java @@ -39,6 +39,7 @@ import org.lowcoder.api.permission.view.PermissionItemView; import org.lowcoder.api.usermanagement.OrgDevChecker; import org.lowcoder.domain.application.model.Application; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.application.model.ApplicationStatus; import org.lowcoder.domain.application.model.ApplicationType; import org.lowcoder.domain.application.service.ApplicationService; @@ -73,10 +74,12 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +@RequiredArgsConstructor @Service @Slf4j public class ApplicationApiService { @@ -85,54 +88,25 @@ public class ApplicationApiService { private static final String JS_DATASOURCE_TYPE = "js"; private static final String VIEW_DATASOURCE_TYPE = "view"; - @Autowired - private ApplicationService applicationService; - - @Autowired - private ResourcePermissionService resourcePermissionService; - - @Autowired - private SessionUserService sessionUserService; - - @Autowired - private OrgMemberService orgMemberService; - - @Autowired - private GroupService groupService; - - @Autowired - private OrganizationService organizationService; - - @Autowired - private UserService userService; - - @Autowired - private AbstractBizThresholdChecker bizThresholdChecker; - - @Autowired - private TemplateSolution templateSolution; - - @Autowired - private SuggestAppAdminSolution suggestAppAdminSolution; - - @Autowired - private OrgDevChecker orgDevChecker; - @Autowired - private FolderApiService folderApiService; - @Autowired - private UserHomeApiService userHomeApiService; - @Autowired - private UserApplicationInteractionService userApplicationInteractionService; - @Autowired - private DatasourceMetaInfoService datasourceMetaInfoService; - @Autowired - private CompoundApplicationDslFilter compoundApplicationDslFilter; - @Autowired - private TemplateService templateService; - @Autowired - private PermissionHelper permissionHelper; - @Autowired - private DatasourceService datasourceService; + private final ApplicationService applicationService; + private final ResourcePermissionService resourcePermissionService; + private final SessionUserService sessionUserService; + private final OrgMemberService orgMemberService; + private final OrganizationService organizationService; + + private final AbstractBizThresholdChecker bizThresholdChecker; + private final OrgDevChecker orgDevChecker; + private final TemplateSolution templateSolution; + private final SuggestAppAdminSolution suggestAppAdminSolution; + + private final FolderApiService folderApiService; + private final UserHomeApiService userHomeApiService; + private final UserApplicationInteractionService userApplicationInteractionService; + private final DatasourceMetaInfoService datasourceMetaInfoService; + private final CompoundApplicationDslFilter compoundApplicationDslFilter; + private final TemplateService templateService; + private final PermissionHelper permissionHelper; + private final DatasourceService datasourceService; public Mono create(CreateApplicationRequest createApplicationRequest) { @@ -141,7 +115,8 @@ public Mono create(CreateApplicationRequest createApplicationRe createApplicationRequest.applicationType(), NORMAL, createApplicationRequest.publishedApplicationDSL(), - false, false, false, createApplicationRequest.editingApplicationDSL()); + createApplicationRequest.editingApplicationDSL(), + false, false, false); if (StringUtils.isBlank(application.getOrganizationId())) { return deferredError(INVALID_PARAMETER, "ORG_ID_EMPTY"); @@ -248,22 +223,22 @@ private Mono checkApplicationStatus(Application application, ApplicationSt return Mono.error(new BizException(BizError.UNSUPPORTED_OPERATION, "BAD_REQUEST")); } - private Mono checkApplicationViewRequest(Application application, ApplicationEndpoints.ApplicationRequestType expected) { - // TODO: The check is correct ( logically ) but we need to provide some time for the users to adapt. Will bring it back in the next release + private Mono checkApplicationViewRequest(Application application, ApplicationRequestType expected) { - // Falk: switched && application.isPublicToAll() on again - seems here is the bug. - if (expected == ApplicationEndpoints.ApplicationRequestType.PUBLIC_TO_ALL && application.isPublicToAll()) { + // TODO: check application.isPublicToAll() from v2.4.0 + if (expected == ApplicationRequestType.PUBLIC_TO_ALL) { return Mono.empty(); } // Falk: here is to check the ENV Variable LOWCODER_MARKETPLACE_PRIVATE_MODE // isPublicToMarketplace & isPublicToAll must be both true - if (expected == ApplicationEndpoints.ApplicationRequestType.PUBLIC_TO_MARKETPLACE && application.isPublicToMarketplace() && application.isPublicToAll()) { - return Mono.empty(); - } + if (expected == ApplicationRequestType.PUBLIC_TO_MARKETPLACE && application.isPublicToMarketplace() && application.isPublicToAll()) { + return Mono.empty(); + } + // // Falk: application.agencyProfile() & isPublicToAll must be both true - if (expected == ApplicationEndpoints.ApplicationRequestType.AGENCY_PROFILE && application.agencyProfile() && application.isPublicToAll()) { + if (expected == ApplicationRequestType.AGENCY_PROFILE && application.agencyProfile() && application.isPublicToAll()) { return Mono.empty(); } return Mono.error(new BizException(BizError.UNSUPPORTED_OPERATION, "BAD_REQUEST")); @@ -301,8 +276,8 @@ public Mono getEditingApplication(String applicationId) { }); } - public Mono getPublishedApplication(String applicationId, ApplicationEndpoints.ApplicationRequestType requestType) { - return checkPermissionWithReadableErrorMsg(applicationId, READ_APPLICATIONS) + public Mono getPublishedApplication(String applicationId, ApplicationRequestType requestType) { + return checkApplicationPermissionWithReadableErrorMsg(applicationId, READ_APPLICATIONS, requestType) .zipWhen(permission -> applicationService.findById(applicationId) .delayUntil(application -> checkApplicationStatus(application, NORMAL)) .delayUntil(application -> checkApplicationViewRequest(application, requestType))) @@ -493,6 +468,32 @@ public Mono checkPermissionWithReadableErrorMsg(String appli }); } + @Nonnull + public Mono checkApplicationPermissionWithReadableErrorMsg(String applicationId, ResourceAction action, ApplicationRequestType requestType) { + return sessionUserService.getVisitorId() + .flatMap(visitorId -> resourcePermissionService.checkUserPermissionStatusOnApplication(visitorId, applicationId, action, requestType)) + .flatMap(permissionStatus -> { + if (!permissionStatus.hasPermission()) { + if (permissionStatus.failByAnonymousUser()) { + return ofError(USER_NOT_SIGNED_IN, "USER_NOT_SIGNED_IN"); + } + + if (permissionStatus.failByNotInOrg()) { + return ofError(NO_PERMISSION_TO_REQUEST_APP, "INSUFFICIENT_PERMISSION"); + } + + return suggestAppAdminSolution.getSuggestAppAdminNames(applicationId) + .flatMap(names -> { + String messageKey = action == EDIT_APPLICATIONS ? "NO_PERMISSION_TO_EDIT" : "NO_PERMISSION_TO_VIEW"; + return ofError(NO_PERMISSION_TO_REQUEST_APP, messageKey, names); + }); + } + return Mono.just(permissionStatus.getPermission()); + }); + } + + + private ApplicationInfoView buildView(Application application, String role) { return buildView(application, role, null); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java index 86be1e576..d12297b33 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java @@ -6,7 +6,6 @@ import static org.lowcoder.infra.event.EventType.APPLICATION_RECYCLED; import static org.lowcoder.infra.event.EventType.APPLICATION_RESTORE; import static org.lowcoder.infra.event.EventType.APPLICATION_UPDATE; -import static org.lowcoder.infra.event.EventType.VIEW; import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; @@ -16,15 +15,18 @@ import org.lowcoder.api.application.view.ApplicationPermissionView; import org.lowcoder.api.application.view.ApplicationView; import org.lowcoder.api.application.view.MarketplaceApplicationInfoView; +// should we not have a AgencyApplicationInfoView import org.lowcoder.api.framework.view.ResponseView; import org.lowcoder.api.home.SessionUserService; import org.lowcoder.api.home.UserHomeApiService; import org.lowcoder.api.home.UserHomepageView; import org.lowcoder.api.util.BusinessEventPublisher; import org.lowcoder.domain.application.model.Application; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.application.model.ApplicationStatus; import org.lowcoder.domain.application.model.ApplicationType; import org.lowcoder.domain.permission.model.ResourceRole; +import org.lowcoder.infra.event.EventType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -91,11 +93,12 @@ public Mono> getEditingApplication(@PathVariable S .map(ResponseView::success); } + // will call the check in ApplicationApiService and ApplicationService @Override public Mono> getPublishedApplication(@PathVariable String applicationId) { return applicationApiService.getPublishedApplication(applicationId, ApplicationRequestType.PUBLIC_TO_ALL) .delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId)) - .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, VIEW)) + .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, EventType.VIEW)) .map(ResponseView::success); } @@ -103,7 +106,7 @@ public Mono> getPublishedApplication(@PathVariable public Mono> getPublishedMarketPlaceApplication(@PathVariable String applicationId) { return applicationApiService.getPublishedApplication(applicationId, ApplicationRequestType.PUBLIC_TO_MARKETPLACE) .delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId)) - .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, VIEW)) + .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, EventType.VIEW)) .map(ResponseView::success); } @@ -111,7 +114,7 @@ public Mono> getPublishedMarketPlaceApplication(@P public Mono> getAgencyProfileApplication(@PathVariable String applicationId) { return applicationApiService.getPublishedApplication(applicationId, ApplicationRequestType.AGENCY_PROFILE) .delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId)) - .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, VIEW)) + .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, EventType.VIEW)) .map(ResponseView::success); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java index 4df112274..b026a2544 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java @@ -289,12 +289,6 @@ public Boolean agencyProfile() { } } - public enum ApplicationRequestType { - PUBLIC_TO_ALL, - PUBLIC_TO_MARKETPLACE, - AGENCY_PROFILE, - } - public record UpdatePermissionRequest(String role) { } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java index 95f62de61..c682e8c70 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java @@ -6,6 +6,7 @@ import static org.lowcoder.sdk.util.StreamUtils.collectList; import java.time.Instant; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -316,12 +317,16 @@ public Flux getAllMarketplaceApplications(@Nulla .build(); // marketplace specific fields - Map marketplaceMeta = (Map) - ((Map)application.getEditingApplicationDSL().get("ui")).get("marketplaceMeta"); - marketplaceApplicationInfoView.setTitle((String)marketplaceMeta.get("title")); - marketplaceApplicationInfoView.setCategory((String)marketplaceMeta.get("category")); - marketplaceApplicationInfoView.setDescription((String)marketplaceMeta.get("description")); - marketplaceApplicationInfoView.setImage((String)marketplaceMeta.get("image")); + Map settings = new HashMap<>(); + if (application.getPublishedApplicationDSL() != null) + { + settings.putAll((Map)application.getPublishedApplicationDSL().getOrDefault("settings", new HashMap<>())); + } + + marketplaceApplicationInfoView.setTitle((String)settings.getOrDefault("title", application.getName())); + marketplaceApplicationInfoView.setCategory((String)settings.get("category")); + marketplaceApplicationInfoView.setDescription((String)settings.get("description")); + marketplaceApplicationInfoView.setImage((String)settings.get("icon")); return marketplaceApplicationInfoView; diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/application/ApplicationApiServiceTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/application/ApplicationApiServiceTest.java index 5bf57b461..0a53e58a7 100644 --- a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/application/ApplicationApiServiceTest.java +++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/application/ApplicationApiServiceTest.java @@ -8,7 +8,6 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.lowcoder.api.application.ApplicationEndpoints.ApplicationRequestType; import org.lowcoder.api.application.ApplicationEndpoints.CreateApplicationRequest; import org.lowcoder.api.application.view.ApplicationPermissionView; import org.lowcoder.api.application.view.ApplicationView; @@ -17,6 +16,7 @@ import org.lowcoder.api.home.FolderApiService; import org.lowcoder.api.permission.view.PermissionItemView; import org.lowcoder.domain.application.model.Application; +import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.application.model.ApplicationStatus; import org.lowcoder.domain.application.model.ApplicationType; import org.lowcoder.domain.application.service.ApplicationService;