Skip to content

Commit

Permalink
feature/add-task-ownership-handling (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
hardikSinghBehl authored Mar 18, 2024
1 parent bdc5b51 commit 755e469
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/behl/flare/entity/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ public static String name() {
private String description;
private TaskStatus status;
private Date dueDate;
private String createdBy;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.behl.flare.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

public class TaskOwnershipViolationException extends ResponseStatusException {

private static final long serialVersionUID = 3265020831437403636L;

private static final String DEFAULT_MESSAGE = "Access Denied: insufficient privileges to perform this action.";

public TaskOwnershipViolationException() {
super(HttpStatus.FORBIDDEN, DEFAULT_MESSAGE);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.behl.flare.filter;

import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -25,6 +27,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private static final String USER_ID_CLAIM = "user_id";

@Override
@SneakyThrows
Expand All @@ -37,9 +40,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
if (StringUtils.isNotEmpty(authorizationHeader) && authorizationHeader.startsWith(BEARER_PREFIX) ) {
final var token = authorizationHeader.replace(BEARER_PREFIX, StringUtils.EMPTY);
final var firebaseToken = firebaseAuth.verifyIdToken(token);
final var emailId = firebaseToken.getEmail();
final var userId = Optional.ofNullable(firebaseToken.getClaims().get(USER_ID_CLAIM)).orElseThrow(IllegalStateException::new);

final var authentication = new UsernamePasswordAuthenticationToken(emailId, null, null);
final var authentication = new UsernamePasswordAuthenticationToken(userId, null, null);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Expand Down
31 changes: 27 additions & 4 deletions src/main/java/com/behl/flare/service/TaskService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.behl.flare.entity.Task;
import com.behl.flare.entity.TaskStatus;
import com.behl.flare.exception.InvalidTaskIdException;
import com.behl.flare.exception.TaskOwnershipViolationException;
import com.behl.flare.utility.AuthenticatedUserIdProvider;
import com.behl.flare.utility.DateUtility;
import com.google.cloud.firestore.DocumentSnapshot;
import com.google.cloud.firestore.Firestore;
Expand All @@ -24,19 +26,26 @@ public class TaskService {

private final Firestore firestore;
private final DateUtility dateUtility;
private final AuthenticatedUserIdProvider authenticatedUserIdProvider;

public TaskResponseDto retrieve(@NonNull final String taskId) {
final var retrievedDocument = get(taskId);
final var task = retrievedDocument.toObject(Task.class);
verifyTaskOwnership(task);

return creatResponse(retrievedDocument, task);
}

@SneakyThrows
public List<TaskResponseDto> retrieve() {
return firestore.collection(Task.name()).get().get().getDocuments().stream().map(document -> {
final var task = document.toObject(Task.class);
return creatResponse(document, task);
}).toList();
final var userId = authenticatedUserIdProvider.getUserId();
return firestore.collection(Task.name()).whereEqualTo("createdBy", userId)
.get().get().getDocuments()
.stream()
.map(document -> {
final var task = document.toObject(Task.class);
return creatResponse(document, task);
}).toList();
}

public void create(@NonNull final TaskCreationRequestDto taskCreationRequest) {
Expand All @@ -45,13 +54,16 @@ public void create(@NonNull final TaskCreationRequestDto taskCreationRequest) {
task.setTitle(taskCreationRequest.getTitle());
task.setDescription(taskCreationRequest.getDescription());
task.setDueDate(dateUtility.convert(taskCreationRequest.getDueDate()));
task.setCreatedBy(authenticatedUserIdProvider.getUserId());

firestore.collection(Task.name()).document().set(task);
}

public void update(@NonNull final String taskId, @NonNull final TaskUpdationRequestDto taskUpdationRequest) {
final var retrievedDocument = get(taskId);
final var task = retrievedDocument.toObject(Task.class);
verifyTaskOwnership(task);

task.setDescription(taskUpdationRequest.getDescription());
task.setStatus(taskUpdationRequest.getStatus());
task.setDueDate(dateUtility.convert(taskUpdationRequest.getDueDate()));
Expand All @@ -61,8 +73,19 @@ public void update(@NonNull final String taskId, @NonNull final TaskUpdationRequ

public void delete(@NonNull final String taskId) {
final var document = get(taskId);
final var task = document.toObject(Task.class);
verifyTaskOwnership(task);

firestore.collection(Task.name()).document(document.getId()).delete();
}

private void verifyTaskOwnership(@NonNull final Task task) {
final var userId = authenticatedUserIdProvider.getUserId();
final var taskBelongsToUser = task.getCreatedBy().equals(userId);
if (Boolean.FALSE.equals(taskBelongsToUser)) {
throw new TaskOwnershipViolationException();
}
}

@SneakyThrows
private DocumentSnapshot get(@NonNull final String taskId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.behl.flare.utility;

import java.util.Optional;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

@Component
public class AuthenticatedUserIdProvider {

public String getUserId() {
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.map(Authentication::getPrincipal)
.filter(String.class::isInstance)
.map(String.class::cast)
.orElseThrow(IllegalStateException::new);
}

}

0 comments on commit 755e469

Please sign in to comment.