Skip to content

Generic oauth and numerous fixes. #869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 12, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions deploy/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ RUN mkdir -p /etc/apt/keyrings \
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y bash gnupg curl lsb-release \
&& curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb bookworm main" | tee /etc/apt/sources.list.d/redis.list \
&& curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/4.4 main" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list \
&& curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/7.0 main" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list \
&& apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends -y \
mongodb-org \
redis \
Expand Down
11 changes: 11 additions & 0 deletions deploy/docker/docker-compose-multi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ services:
#
LOWCODER_API_KEY_SECRET: "5a41b090758b39b226603177ef48d73ae9839dd458ccb7e66f7e7cc028d5a50b"
LOWCODER_WORKSPACE_MODE: SAAS
# Lowcoder notification emails setup
LOWCODER_ADMIN_SMTP_HOST: smtp.gmail.com
LOWCODER_ADMIN_SMTP_PORT: 587
LOWCODER_ADMIN_SMTP_USERNAME:
LOWCODER_ADMIN_SMTP_PASSWORD:
LOWCODER_ADMIN_SMTP_AUTH: true
LOWCODER_ADMIN_SMTP_SSL_ENABLED: false
LOWCODER_ADMIN_SMTP_STARTTLS_ENABLED: true
LOWCODER_ADMIN_SMTP_STARTTLS_REQUIRED: true
# Email used as sender in lost password email
LOWCODER_EMAIL_NOTIFICATIONS_SENDER: info@localhost
restart: unless-stopped
depends_on:
- mongodb
Expand Down
13 changes: 13 additions & 0 deletions deploy/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
- "3443:3443"
# - "27017:27017"
environment:
# Public base url
LOWCODER_PUBLIC_URL: "http://localhost:3000/"
# enable services
LOWCODER_REDIS_ENABLED: "true"
LOWCODER_MONGODB_ENABLED: "true"
Expand Down Expand Up @@ -59,6 +61,17 @@ services:
LOWCODER_MAX_REQUEST_SIZE: 20m
LOWCODER_MAX_QUERY_TIMEOUT: 120
LOWCODER_WORKSPACE_MODE: SAAS
# Lowcoder notification emails setup
LOWCODER_ADMIN_SMTP_HOST: smtp.gmail.com
LOWCODER_ADMIN_SMTP_PORT: 587
LOWCODER_ADMIN_SMTP_USERNAME:
LOWCODER_ADMIN_SMTP_PASSWORD:
LOWCODER_ADMIN_SMTP_AUTH: true
LOWCODER_ADMIN_SMTP_SSL_ENABLED: false
LOWCODER_ADMIN_SMTP_STARTTLS_ENABLED: true
LOWCODER_ADMIN_SMTP_STARTTLS_REQUIRED: true
# Email used as sender in lost password email
LOWCODER_EMAIL_NOTIFICATIONS_SENDER: info@localhost
volumes:
- ./lowcoder-stacks:/lowcoder-stacks
- ./lowcoder-stacks/assets:/lowcoder/assets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public interface ApplicationRepository extends ReactiveMongoRepository<Applicati

Flux<Application> findByIdIn(Collection<String> ids);

Flux<Application> findByCreatedByAndIdIn(String userId, Collection<String> ids);

/**
* Filter public applications from list of supplied IDs
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ public interface ApplicationService {

@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
Mono<Set<String>> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection<String> applicationIds, boolean isAnonymous, Boolean isPrivateMarketplace);
Mono<Set<String>> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection<String> applicationIds, String userId, Boolean isPrivateMarketplace);

@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
Mono<Set<String>> getPublicApplicationIds(Collection<String> applicationIds);

@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
Mono<Set<String>> getPrivateApplicationIds(Collection<String> applicationIds);
Mono<Set<String>> getPrivateApplicationIds(Collection<String> applicationIds, String userId);

@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
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.organization.repository.OrganizationRepository;
import org.lowcoder.domain.organization.service.OrgMemberService;
import org.lowcoder.domain.permission.model.ResourceRole;
import org.lowcoder.domain.permission.service.ResourcePermissionService;
import org.lowcoder.domain.user.repository.UserRepository;
import org.lowcoder.domain.user.service.UserService;
import org.lowcoder.infra.annotation.NonEmptyMono;
import org.lowcoder.infra.mongo.MongoUpsertHelper;
import org.lowcoder.sdk.constants.FieldName;
Expand All @@ -37,6 +42,7 @@ public class ApplicationServiceImpl implements ApplicationService {
private final MongoUpsertHelper mongoUpsertHelper;
private final ResourcePermissionService resourcePermissionService;
private final ApplicationRepository repository;
private final UserRepository userRepository;

@Override
public Mono<Application> findById(String id) {
Expand Down Expand Up @@ -219,8 +225,8 @@ public Mono<Boolean> setApplicationAsAgencyProfile(String applicationId, boolean
@Override
@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
public Mono<Set<String>> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection<String> applicationIds, boolean isAnonymous, Boolean isPrivateMarketplace) {

public Mono<Set<String>> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection<String> applicationIds, String userId, Boolean isPrivateMarketplace) {
boolean isAnonymous = StringUtils.isBlank(userId);
switch(requestType)
{
case PUBLIC_TO_ALL:
Expand All @@ -230,7 +236,7 @@ public Mono<Set<String>> getFilteredPublicApplicationIds(ApplicationRequestType
}
else
{
return getPrivateApplicationIds(applicationIds);
return getPrivateApplicationIds(applicationIds, userId);
}
case PUBLIC_TO_MARKETPLACE:
return getPublicMarketplaceApplicationIds(applicationIds, isAnonymous, isPrivateMarketplace);
Expand Down Expand Up @@ -262,11 +268,16 @@ public Mono<Set<String>> getPublicApplicationIds(Collection<String> applicationI
@Override
@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
public Mono<Set<String>> getPrivateApplicationIds(Collection<String> applicationIds) {
public Mono<Set<String>> getPrivateApplicationIds(Collection<String> applicationIds, String userId) {

// TODO: in 2.4.0 we need to check whether the app was published or not
return repository.findByIdIn(applicationIds)
return repository.findByCreatedByAndIdIn(userId, applicationIds)
.map(HasIdAndAuditing::getId)
.collect(Collectors.toSet());

// return repository.findByIdIn(applicationIds)
// .map(HasIdAndAuditing::getId)
// .collect(Collectors.toSet());
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermission
// This is for PTM apps that are public but only available to logged-in users
@Override
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions
(Collection<String> resourceIds, ResourceAction resourceAction) {
(Collection<String> resourceIds, ResourceAction resourceAction, String userId) {

Set<String> applicationIds = newHashSet(resourceIds);
return Mono.zip(applicationService.getPrivateApplicationIds(applicationIds),
return Mono.zip(applicationService.getPrivateApplicationIds(applicationIds, userId),
templateSolutionService.getTemplateApplicationIds(applicationIds))
.map(tuple -> {
Set<String> publicAppIds = tuple.getT1();
Expand All @@ -75,7 +75,7 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserApplicatio
}

Set<String> applicationIds = newHashSet(resourceIds);
return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, Boolean.TRUE, config.getMarketplace().isPrivateMode())
return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, null, config.getMarketplace().isPrivateMode())
.defaultIfEmpty(new HashSet<>()),
templateSolutionService.getTemplateApplicationIds(applicationIds)
.defaultIfEmpty(new HashSet<>())
Expand All @@ -88,9 +88,9 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserApplicatio

@Override
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserApplicationPublicResourcePermissions(
Collection<String> resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) {
Collection<String> resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType, String userId) {
Set<String> applicationIds = newHashSet(resourceIds);
return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, Boolean.FALSE, config.getMarketplace().isPrivateMode()),
return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, userId, config.getMarketplace().isPrivateMode()),
templateSolutionService.getTemplateApplicationIds(applicationIds))
.map(tuple -> {
Set<String> publicAppIds = tuple.getT1();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermission
}

@Override
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions(Collection<String> resourceIds, ResourceAction resourceAction) {
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions(Collection<String> resourceIds, ResourceAction resourceAction, String userId) {
return Mono.just(Collections.emptyMap());
}

Expand All @@ -48,7 +48,7 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserApplicatio

@Override
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserApplicationPublicResourcePermissions(
Collection<String> resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) {
Collection<String> resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType, String userId) {
return Mono.just(Collections.emptyMap());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public Mono<UserPermissionOnResourceStatus> checkUserPermissionStatusOnResource(
return publicResourcePermissionMono;
}

Mono<UserPermissionOnResourceStatus> nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserPublicResourcePermissions(singletonList(resourceId), resourceAction)
Mono<UserPermissionOnResourceStatus> nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserPublicResourcePermissions(singletonList(resourceId), resourceAction, userId)
.map(it -> it.getOrDefault(resourceId, emptyList()))
.map(it -> {
if (!it.isEmpty()) {
Expand Down Expand Up @@ -141,13 +141,13 @@ protected abstract Mono<Map<String, List<ResourcePermission>>> getAnonymousUserP
ResourceAction resourceAction);

protected abstract Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions
(Collection<String> resourceIds, ResourceAction resourceAction);
(Collection<String> resourceIds, ResourceAction resourceAction, String userId);

protected abstract Mono<Map<String, List<ResourcePermission>>> getAnonymousUserApplicationPermissions(Collection<String> resourceIds,
ResourceAction resourceAction, ApplicationRequestType requestType);

protected abstract Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserApplicationPublicResourcePermissions
(Collection<String> resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType);
(Collection<String> resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType, String userId);


private Mono<Map<String, List<ResourcePermission>>> getAllMatchingPermissions0(String userId, String orgId, ResourceType resourceType,
Expand Down Expand Up @@ -229,7 +229,7 @@ public Mono<UserPermissionOnResourceStatus> checkUserPermissionStatusOnApplicati
return publicResourcePermissionMono;
}

Mono<UserPermissionOnResourceStatus> nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserApplicationPublicResourcePermissions(singletonList(resourceId), resourceAction, requestType)
Mono<UserPermissionOnResourceStatus> nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserApplicationPublicResourcePermissions(singletonList(resourceId), resourceAction, requestType, userId)
.map(it -> it.getOrDefault(resourceId, emptyList()))
.map(it -> {
if (!it.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package org.lowcoder.domain.user.model;

import static com.google.common.base.Suppliers.memoize;
import static org.lowcoder.infra.util.AssetUtils.toAssetPath;

import java.time.Instant;
import java.util.*;
import java.util.function.Supplier;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.*;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.StringUtils;
import org.lowcoder.domain.mongodb.AfterMongodbRead;
Expand All @@ -23,9 +20,12 @@
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
import java.util.*;
import java.util.function.Supplier;

import static com.google.common.base.Suppliers.memoize;
import static org.lowcoder.infra.util.AssetUtils.toAssetPath;


@Getter
Expand All @@ -42,8 +42,7 @@ public class User extends HasIdAndAuditing implements BeforeMongodbWrite, AfterM

private String name;

@Builder.Default
private String uiLanguage = UiConstants.DEFAULT_UI_LANGUAGE;
private String uiLanguage;

private String avatar;

Expand Down Expand Up @@ -79,8 +78,7 @@ public class User extends HasIdAndAuditing implements BeforeMongodbWrite, AfterM
/**
* Only used for mongodb (de)serialization
*/
@Builder.Default
private List<Object> apiKeys = new ArrayList<>();
private List<Object> apiKeys;

@Transient
@JsonIgnore
Expand Down Expand Up @@ -143,15 +141,25 @@ public void markAsDeleted() {

@Override
public void beforeMongodbWrite(MongodbInterceptorContext context) {
this.apiKeysList.forEach(apiKey -> apiKey.doEncrypt(s -> context.encryptionService().encryptString(s)));
apiKeys = JsonUtils.fromJsonSafely(JsonUtils.toJsonSafely(apiKeysList, SerializeConfig.JsonViews.Internal.class), new TypeReference<>() {
}, new ArrayList<>());
if (CollectionUtils.isNotEmpty(this.apiKeysList)) {
this.apiKeysList.forEach(apiKey -> apiKey.doEncrypt(s -> context.encryptionService().encryptString(s)));
apiKeys = JsonUtils.fromJsonSafely(JsonUtils.toJsonSafely(apiKeysList, SerializeConfig.JsonViews.Internal.class), new TypeReference<>() {
}, new ArrayList<>());
}
}

@Override
public void afterMongodbRead(MongodbInterceptorContext context) {
this.apiKeysList = JsonUtils.fromJsonSafely(JsonUtils.toJson(apiKeys), new TypeReference<>() {
}, new ArrayList<>());
this.apiKeysList.forEach(authConfig -> authConfig.doDecrypt(s -> context.encryptionService().decryptString(s)));
if (CollectionUtils.isNotEmpty(apiKeys))
{
this.apiKeysList = JsonUtils.fromJsonSafely(JsonUtils.toJson(apiKeys), new TypeReference<>() {
}, new ArrayList<>());
this.apiKeysList.forEach(authConfig -> authConfig.doDecrypt(s -> context.encryptionService().decryptString(s)));
}

/** set UI language to default one if it's null **/
if (StringUtils.isBlank(this.uiLanguage)) {
this.uiLanguage = UiConstants.DEFAULT_UI_LANGUAGE;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.lowcoder.sdk.auth;

import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;

/**
* This class is for Generic Auth Provider
*/
@Getter
@SuperBuilder
@Jacksonized
public class Oauth2GenericAuthConfig extends Oauth2SimpleAuthConfig {
private String sourceDescription;
private String sourceIcon;
private String sourceCategory;
private String issuerUri;
private String authorizationEndpoint;
private String tokenEndpoint;
private String userInfoEndpoint;
private String scope;
private Boolean userInfoIntrospection;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public String getAuthorizeUrl() {
case AuthTypeConstants.GITHUB -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.GITHUB_AUTHORIZE_URL);
case AuthTypeConstants.ORY -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.ORY_AUTHORIZE_URL);
case AuthTypeConstants.KEYCLOAK -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.KEYCLOAK_AUTHORIZE_URL);
case AuthTypeConstants.GENERIC -> ((Oauth2GenericAuthConfig)this).getAuthorizationEndpoint();
default -> null;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public class AuthTypeConstants {
public static final String GITHUB = "GITHUB";
public static final String ORY = "ORY";
public static final String KEYCLOAK = "KEYCLOAK";
public static final String GENERIC = "GENERIC";
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ public static class Workspace {

@Data
public static class Cookie {

private long maxAgeInSeconds = Duration.ofDays(30).toSeconds();
//Set cookie max age to 1 day
private long maxAgeInSeconds = Duration.ofDays(1).toSeconds();
}

@Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.lowcoder.sdk.auth.EmailAuthConfig;
import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig;
import org.lowcoder.sdk.auth.Oauth2OryAuthConfig;
import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig;
import org.lowcoder.sdk.auth.*;

import java.nio.charset.StandardCharsets;
import java.util.List;
Expand All @@ -41,6 +38,7 @@ public final class JsonUtils {
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2SimpleAuthConfig.class, GOOGLE));
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2OryAuthConfig.class, ORY));
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2KeycloakAuthConfig.class, KEYCLOAK));
OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2GenericAuthConfig.class, GENERIC));
}

public static final JsonNode EMPTY_JSON_NODE = createObjectNode();
Expand Down
Loading
Loading