In case the user is not signed in, return a 401 so that the user can be redirected to login by the frontend.

This commit is contained in:
Trisha Anand 2020-05-13 18:00:03 +00:00
parent 963a3388c3
commit 9a8bf9dc89
10 changed files with 105 additions and 67 deletions

View File

@ -143,6 +143,7 @@ public class SecurityConfig {
user.setEmail("anonymousUser");
user.setCurrentOrganizationId("");
user.setOrganizationIds(new HashSet<>());
user.setIsAnonymous(true);
return user;
}
}

View File

@ -2,7 +2,7 @@ package com.appsmith.server.controllers;
import com.appsmith.server.constants.Url;
import com.appsmith.server.domains.Application;
import com.appsmith.server.dtos.OrganizationApplicationsDTO;
import com.appsmith.server.dtos.UserHomepageDTO;
import com.appsmith.server.dtos.ResponseDTO;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
@ -22,7 +22,6 @@ import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping(Url.APPLICATION_URL)
@ -65,7 +64,7 @@ public class ApplicationController extends BaseController<ApplicationService, Ap
}
@GetMapping("/new")
public Mono<ResponseDTO<List<OrganizationApplicationsDTO>>> getAllApplicationsMock() {
public Mono<ResponseDTO<UserHomepageDTO>> getAllApplicationsForHome() {
log.debug("Going to get all applications grouped by organization");
return service.getAllApplications()
.map(applications -> new ResponseDTO<>(HttpStatus.OK.value(), applications, null));

View File

@ -4,8 +4,10 @@ import com.appsmith.external.models.BaseDomain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@ -53,6 +55,9 @@ public class User extends BaseDomain implements UserDetails {
// During evaluation a union of the group permissions and user-specific permissions will take effect.
private Set<String> permissions = new HashSet<>();
@Transient
Boolean isAnonymous = false;
@Override
public Collection<GrantedAuthority> getAuthorities() {
return null;

View File

@ -0,0 +1,18 @@
package com.appsmith.server.dtos;
import com.appsmith.server.domains.User;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
@ToString
public class UserHomepageDTO {
User user;
List<OrganizationApplicationsDTO> organizationApplications;
}

View File

@ -26,6 +26,7 @@ public enum AppsmithError {
PAGE_DOESNT_BELONG_TO_APPLICATION(400, 4018, "Page {0} does not belong to the application {1}"),
NO_DSL_FOUND_IN_PAGE(400, 4020, "The page {0} doesn't have a DSL. This is an unexpected state"),
UNAUTHORIZED_DOMAIN(401, 4019, "Invalid email domain provided. Please sign in with a valid work email ID"),
USER_NOT_SIGNED_IN(401, 4020, "User is not logged in. Please sign in with the registered email ID or sign up" ),
INVALID_PASSWORD_RESET(400, 4020, "Unable to reset the password. Please initiate a request via 'forgot password' link to reset your password"),
LOGIN_INTERNAL_ERROR(401, 4021, "Internal error while trying to login"),
JSON_PROCESSING_ERROR(400, 4022, "Json processing error with error {0}"),

View File

@ -1,19 +1,9 @@
package com.appsmith.server.filters;
import com.appsmith.server.acl.AclService;
import com.appsmith.server.acl.OpaResponse;
import com.appsmith.server.dtos.ResponseDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.ErrorDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
@ -35,48 +25,51 @@ public class AclFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpMethod httpMethod = request.getMethod();
String url = request.getPath().value();
String[] urlParts = url.split("/");
// This is because all the urls are of the form /api/v1/{resource}. When we split by "/", the resource is always
// the 4th element in the result array
if (urlParts.length < 4) {
log.debug("Got request path {}. Not applying ACL filter", request.getPath());
return chain.filter(exchange);
}
return chain.filter(exchange);
String resource = urlParts[3];
Mono<OpaResponse> aclResponse = aclService.evaluateAcl(httpMethod, resource, url);
return aclResponse
.map(acl -> {
log.debug("Got ACL response: {}", acl);
return acl;
})
.flatMap(acl -> {
if (acl != null && acl.isSuccessful()) {
// Acl returned true. Continue with the filter chain
return chain.filter(exchange);
}
// The Acl response is false. Return unauthorized exception to the client
// We construct the error response JSON here because throwing an exception here doesn't get caught
// in the {@see GlobalExceptionHandler}.
AppsmithError error = AppsmithError.UNAUTHORIZED_ACCESS;
exchange.getResponse().setStatusCode(HttpStatus.resolve(error.getHttpErrorCode()));
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
try {
ResponseDTO<ErrorDTO> responseBody = new ResponseDTO<>(error.getHttpErrorCode(), new ErrorDTO(error.getAppErrorCode(),
error.getMessage()));
String responseStr = objectMapper.writeValueAsString(responseBody);
DataBuffer buffer = exchange.getResponse().bufferFactory().allocateBuffer().write(responseStr.getBytes());
return exchange.getResponse().writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
log.error("Exception caught while serializing JSON in AclFilter. Cause: ", e);
return exchange.getResponse().writeWith(Mono.empty());
}
});
// ServerHttpRequest request = exchange.getRequest();
// HttpMethod httpMethod = request.getMethod();
// String url = request.getPath().value();
// String[] urlParts = url.split("/");
//
// // This is because all the urls are of the form /api/v1/{resource}. When we split by "/", the resource is always
// // the 4th element in the result array
// if (urlParts.length < 4) {
// log.debug("Got request path {}. Not applying ACL filter", request.getPath());
// return chain.filter(exchange);
// }
//
// String resource = urlParts[3];
//
// Mono<OpaResponse> aclResponse = aclService.evaluateAcl(httpMethod, resource, url);
// return aclResponse
// .map(acl -> {
// log.debug("Got ACL response: {}", acl);
// return acl;
// })
// .flatMap(acl -> {
// if (acl != null && acl.isSuccessful()) {
// // Acl returned true. Continue with the filter chain
// return chain.filter(exchange);
// }
//
// // The Acl response is false. Return unauthorized exception to the client
// // We construct the error response JSON here because throwing an exception here doesn't get caught
// // in the {@see GlobalExceptionHandler}.
// AppsmithError error = AppsmithError.UNAUTHORIZED_ACCESS;
// exchange.getResponse().setStatusCode(HttpStatus.resolve(error.getHttpErrorCode()));
// exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
// try {
// ResponseDTO<ErrorDTO> responseBody = new ResponseDTO<>(error.getHttpErrorCode(), new ErrorDTO(error.getAppErrorCode(),
// error.getMessage()));
// String responseStr = objectMapper.writeValueAsString(responseBody);
// DataBuffer buffer = exchange.getResponse().bufferFactory().allocateBuffer().write(responseStr.getBytes());
// return exchange.getResponse().writeWith(Mono.just(buffer));
// } catch (JsonProcessingException e) {
// log.error("Exception caught while serializing JSON in AclFilter. Cause: ", e);
// return exchange.getResponse().writeWith(Mono.empty());
// }
// });
}
}

View File

@ -2,11 +2,9 @@ package com.appsmith.server.services;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Application;
import com.appsmith.server.dtos.OrganizationApplicationsDTO;
import com.appsmith.server.dtos.UserHomepageDTO;
import reactor.core.publisher.Mono;
import java.util.List;
public interface ApplicationService extends CrudService<Application, String> {
Mono<Application> findById(String id);
@ -23,5 +21,5 @@ public interface ApplicationService extends CrudService<Application, String> {
Mono<Application> archive(Application application);
Mono<List<OrganizationApplicationsDTO>> getAllApplications();
Mono<UserHomepageDTO> getAllApplications();
}

View File

@ -7,6 +7,8 @@ import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.UserHomepageDTO;
import com.appsmith.server.dtos.OrganizationApplicationsDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
@ -29,6 +31,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -167,13 +170,21 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
* @return List of OrganizationApplicationsDTO
*/
@Override
public Mono<List<OrganizationApplicationsDTO>> getAllApplications() {
public Mono<UserHomepageDTO> getAllApplications() {
return sessionUserService
Mono<User> userMono = sessionUserService
.getCurrentUser()
.map(user -> user.getOrganizationIds())
.flatMap(orgIds -> {
.flatMap(user -> {
if (user.getIsAnonymous()) {
return Mono.error(new AppsmithException(AppsmithError.USER_NOT_SIGNED_IN));
}
return Mono.just(user);
})
.cache();
return userMono
.flatMap(user -> {
Set<String> orgIds = user.getOrganizationIds();
/*
* For all the organization ids present in the user object, fetch all the organization objects
* and store in a map for fast access;
@ -181,6 +192,9 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
Mono<Map<String, Organization>> organizationsMapMono = organizationService.findByIdsIn(orgIds, READ_ORGANIZATIONS)
.collectMap(Organization::getId, Function.identity());
UserHomepageDTO userHomepageDTO = new UserHomepageDTO();
userHomepageDTO.setUser(user);
return repository
// Fetch all the applications which belong the organization ids present in the user
.findByMultipleOrganizationIds(orgIds, READ_APPLICATIONS)
@ -208,7 +222,8 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
organizationApplicationsDTOS.add(organizationApplicationsDTO);
}
return organizationApplicationsDTOS;
userHomepageDTO.setOrganizationApplications(organizationApplicationsDTOS);
return userHomepageDTO;
});
});
}

View File

@ -685,6 +685,9 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
public Mono<UserProfileDTO> getUserProfile() {
return sessionUserService.getCurrentUser()
.flatMap(user -> {
if (user.getIsAnonymous()) {
return Mono.error(new AppsmithException(AppsmithError.USER_NOT_SIGNED_IN));
}
String currentOrganizationId = user.getCurrentOrganizationId();
UserProfileDTO userProfile = new UserProfileDTO();
userProfile.setUser(user);

View File

@ -4,6 +4,7 @@ import com.appsmith.external.models.Policy;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.Page;
import com.appsmith.server.dtos.UserHomepageDTO;
import com.appsmith.server.dtos.OrganizationApplicationsDTO;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
@ -253,14 +254,18 @@ public class ApplicationServiceTest {
@Test
@WithUserDetails(value = "api_user")
public void getAllApplicationsForHome() {
Mono<List<OrganizationApplicationsDTO>> allApplications = applicationService.getAllApplications();
Mono<UserHomepageDTO> allApplications = applicationService.getAllApplications();
StepVerifier
.create(allApplications)
.assertNext(organizationApplicationsDTOS -> {
assertThat(organizationApplicationsDTOS).isNotEmpty();
.assertNext(userHomepageDTO -> {
assertThat(userHomepageDTO).isNotNull();
//In case of anonymous user, we should have errored out. Assert that the user is not anonymous.
assertThat(userHomepageDTO.getUser().getIsAnonymous()).isFalse();
OrganizationApplicationsDTO orgAppDto = organizationApplicationsDTOS.get(0);
List<OrganizationApplicationsDTO> organizationApplications = userHomepageDTO.getOrganizationApplications();
OrganizationApplicationsDTO orgAppDto = organizationApplications.get(0);
assertThat(orgAppDto.getOrganization().getUserPermissions().contains("read:organizations"));
Application application = orgAppDto.getApplications().get(0);